x
This commit is contained in:
@@ -212,6 +212,27 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="sideActionAiAdvice" class="result-grid">
|
<div v-if="sideActionAiAdvice" class="result-grid">
|
||||||
|
<div class="result-card">
|
||||||
|
<h4>规则检测摘要</h4>
|
||||||
|
<div class="info-list">
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">检测状态</span>
|
||||||
|
<span class="info-value">{{ ruleActionSummary.total_mechanism_count > 0 ? '检测到倒扣' : '未检测到倒扣' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">滑块数量</span>
|
||||||
|
<span class="info-value">{{ ruleActionSummary.total_slider_count || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">斜顶数量</span>
|
||||||
|
<span class="info-value">{{ ruleActionSummary.total_lifter_count || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">复杂度</span>
|
||||||
|
<span class="info-value">{{ ruleActionSummary.complexity || '未知' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="result-card result-card-highlight">
|
<div class="result-card result-card-highlight">
|
||||||
<h4>AI 结论</h4>
|
<h4>AI 结论</h4>
|
||||||
<div class="info-list">
|
<div class="info-list">
|
||||||
@@ -815,6 +836,16 @@ const sideActionAiAdvice = computed(() => {
|
|||||||
) || fallbackSideActionAiAdvice.value
|
) || 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<DesignReport | null>(() =>
|
const designReport = computed<DesignReport | null>(() =>
|
||||||
parseEmbeddedDesignReport(state.task?.llm_report || '')
|
parseEmbeddedDesignReport(state.task?.llm_report || '')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,42 +24,49 @@ router = APIRouter()
|
|||||||
cad_exporter = CADExporter()
|
cad_exporter = CADExporter()
|
||||||
storage_service = StorageIntegrationService()
|
storage_service = StorageIntegrationService()
|
||||||
|
|
||||||
_api_routes_cache = {}
|
_cached_instances = {}
|
||||||
|
|
||||||
|
|
||||||
def _get_cached(key):
|
def _get_cached_import(key: str):
|
||||||
global _api_routes_cache
|
"""惰性导入核心模块,避免路由器模块级加载时的循环依赖。"""
|
||||||
if key not in _api_routes_cache:
|
if key in _cached_instances:
|
||||||
try:
|
return _cached_instances[key]
|
||||||
from api import routes
|
try:
|
||||||
except Exception as e:
|
if key == "side_action_designer":
|
||||||
logger.warning(f"api.routes 模块加载失败: {e}")
|
from moldinsight.core.side_action_designer import SideActionDesigner
|
||||||
_api_routes_cache["__error__"] = str(e)
|
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
|
return None
|
||||||
_api_routes_cache.clear()
|
_cached_instances[key] = instance
|
||||||
_api_routes_cache.update({
|
return instance
|
||||||
"tasks": routes.tasks,
|
except Exception as e:
|
||||||
"cavity_layout_optimizer": routes.cavity_layout_optimizer,
|
logger.warning(f"核心模块 {key} 加载失败: {e}")
|
||||||
"mold_system_designer": routes.mold_system_designer,
|
return None
|
||||||
"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)
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_task_data(task_id: str) -> dict:
|
async def _get_task_data(task_id: str) -> dict:
|
||||||
task = await redis_task_manager.get_task(task_id)
|
return 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
|
|
||||||
|
|
||||||
|
|
||||||
async def _ensure_task_access(
|
async def _ensure_task_access(
|
||||||
@@ -193,7 +200,7 @@ async def optimize_cavity_layout(
|
|||||||
layout_type = body.get("layout_type", "auto")
|
layout_type = body.get("layout_type", "auto")
|
||||||
if cavity_count < 1 or cavity_count > 64:
|
if cavity_count < 1 or cavity_count > 64:
|
||||||
raise HTTPException(400, "型腔数量必须在 1-64 之间")
|
raise HTTPException(400, "型腔数量必须在 1-64 之间")
|
||||||
optimizer = _get_cached("cavity_layout_optimizer")
|
optimizer = _get_cached_import("cavity_layout_optimizer")
|
||||||
if not optimizer:
|
if not optimizer:
|
||||||
raise HTTPException(503, "服务不可用:核心模块未加载")
|
raise HTTPException(503, "服务不可用:核心模块未加载")
|
||||||
result = optimizer.optimize_layout(
|
result = optimizer.optimize_layout(
|
||||||
@@ -260,7 +267,7 @@ async def design_complete_mold_system(
|
|||||||
gate_type = body.get("gate_type", "auto")
|
gate_type = body.get("gate_type", "auto")
|
||||||
cycle_time_target = body.get("cycle_time_target")
|
cycle_time_target = body.get("cycle_time_target")
|
||||||
layout_positions = body.get("layout_positions")
|
layout_positions = body.get("layout_positions")
|
||||||
ds = _get_cached("mold_system_designer")
|
ds = _get_cached_import("mold_system_designer")
|
||||||
if not ds:
|
if not ds:
|
||||||
raise HTTPException(503, "服务不可用:核心模块未加载")
|
raise HTTPException(503, "服务不可用:核心模块未加载")
|
||||||
result = ds.design_complete_system(
|
result = ds.design_complete_system(
|
||||||
@@ -307,7 +314,7 @@ async def detect_undercuts(
|
|||||||
task_data = await _get_task_data(task_id)
|
task_data = await _get_task_data(task_id)
|
||||||
if not task_data:
|
if not task_data:
|
||||||
raise HTTPException(404, "任务不存在")
|
raise HTTPException(404, "任务不存在")
|
||||||
sd = _get_cached("side_action_designer")
|
sd = _get_cached_import("side_action_designer")
|
||||||
if not sd:
|
if not sd:
|
||||||
raise HTTPException(503, "服务不可用:核心模块未加载")
|
raise HTTPException(503, "服务不可用:核心模块未加载")
|
||||||
result = sd.analyze_and_design(
|
result = sd.analyze_and_design(
|
||||||
@@ -327,7 +334,7 @@ async def design_mold_cam(
|
|||||||
mold_steel = body.get("mold_steel", "P20")
|
mold_steel = body.get("mold_steel", "P20")
|
||||||
surface_quality = body.get("surface_quality", "standard")
|
surface_quality = body.get("surface_quality", "standard")
|
||||||
controller = body.get("controller", "fanuc")
|
controller = body.get("controller", "fanuc")
|
||||||
cam = _get_cached("mold_cam_designer")
|
cam = _get_cached_import("mold_cam_designer")
|
||||||
if not cam:
|
if not cam:
|
||||||
raise HTTPException(503, "服务不可用:核心模块未加载")
|
raise HTTPException(503, "服务不可用:核心模块未加载")
|
||||||
result = cam.design_mold_cam(
|
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})
|
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]})
|
stock_bbox = body.get("stock_bbox", {"min": [-50, -50, -25], "max": [50, 50, 25]})
|
||||||
clamp_positions = body.get("clamp_positions")
|
clamp_positions = body.get("clamp_positions")
|
||||||
cd = _get_cached("collision_detector")
|
cd = _get_cached_import("collision_detector")
|
||||||
if not cd:
|
if not cd:
|
||||||
raise HTTPException(503, "服务不可用:核心模块未加载")
|
raise HTTPException(503, "服务不可用:核心模块未加载")
|
||||||
result = cd.check_toolpath_safety(toolpath_points, tool, stock_bbox, clamp_positions)
|
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]])
|
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})
|
cutting_params = body.get("cutting_params", {"feed_rate_mm_min": 500})
|
||||||
stock_bbox = body.get("stock_bbox")
|
stock_bbox = body.get("stock_bbox")
|
||||||
to = _get_cached("toolpath_optimizer")
|
to = _get_cached_import("toolpath_optimizer")
|
||||||
if not to:
|
if not to:
|
||||||
raise HTTPException(503, "服务不可用:核心模块未加载")
|
raise HTTPException(503, "服务不可用:核心模块未加载")
|
||||||
result = to.optimize_toolpath(toolpath_points, cutting_params, stock_bbox)
|
result = to.optimize_toolpath(toolpath_points, cutting_params, stock_bbox)
|
||||||
@@ -382,7 +389,7 @@ async def design_edm_electrodes(
|
|||||||
material = body.get("material", "copper")
|
material = body.get("material", "copper")
|
||||||
spark_gap = body.get("spark_gap", 0.05)
|
spark_gap = body.get("spark_gap", 0.05)
|
||||||
overburn = body.get("overburn", 0.1)
|
overburn = body.get("overburn", 0.1)
|
||||||
ed = _get_cached("edm_designer")
|
ed = _get_cached_import("edm_designer")
|
||||||
if not ed:
|
if not ed:
|
||||||
raise HTTPException(503, "服务不可用:核心模块未加载")
|
raise HTTPException(503, "服务不可用:核心模块未加载")
|
||||||
result = ed.design_electrodes(undercut_regions, cavity_bbox, material, spark_gap, overburn)
|
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}]}])
|
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]})
|
stock_bbox = body.get("stock_bbox", {"dimensions": [100, 100, 50], "min": [-50, -50, -25], "max": [50, 50, 25]})
|
||||||
resolution = body.get("resolution", 2.0)
|
resolution = body.get("resolution", 2.0)
|
||||||
ms = _get_cached("machining_simulator")
|
ms = _get_cached_import("machining_simulator")
|
||||||
if not ms:
|
if not ms:
|
||||||
raise HTTPException(503, "服务不可用:核心模块未加载")
|
raise HTTPException(503, "服务不可用:核心模块未加载")
|
||||||
result = ms.simulate_machining(operations, stock_bbox, resolution)
|
result = ms.simulate_machining(operations, stock_bbox, resolution)
|
||||||
|
|||||||
@@ -140,8 +140,11 @@ class MoldCavityGenerator(BaseMoldGenerator):
|
|||||||
},
|
},
|
||||||
"parting_surface": parting_geometry,
|
"parting_surface": parting_geometry,
|
||||||
"quality_checks": {
|
"quality_checks": {
|
||||||
"undercut_regions": cavity_data.get("undercut_regions", []),
|
"undercut_summary": {
|
||||||
"side_actions": cavity_data.get("side_actions", {}),
|
"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": {
|
"manufacturing_info": {
|
||||||
"estimated_mold_size": self._calculate_mold_size(analysis),
|
"estimated_mold_size": self._calculate_mold_size(analysis),
|
||||||
|
|||||||
@@ -388,8 +388,24 @@ class LifterMechanismDesigner:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _calculate_lifter_angle(self, region: Dict) -> float:
|
def _calculate_lifter_angle(self, region: Dict) -> float:
|
||||||
"""计算斜顶角度(通常5-15度)"""
|
"""基于倒扣深度和估算顶出行程计算斜顶角度(通常 5-15 度)。
|
||||||
return 8.0
|
|
||||||
|
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:
|
def _calculate_lifter_stroke(self, region: Dict) -> float:
|
||||||
"""计算斜顶行程"""
|
"""计算斜顶行程"""
|
||||||
|
|||||||
@@ -272,13 +272,13 @@ class CalculationService:
|
|||||||
if quality_checks:
|
if quality_checks:
|
||||||
detailed_cavity_json["quality_checks"] = quality_checks
|
detailed_cavity_json["quality_checks"] = quality_checks
|
||||||
|
|
||||||
undercut_regions = quality_checks.get("undercut_regions")
|
undercut_summary = quality_checks.get("undercut_summary")
|
||||||
if undercut_regions:
|
if undercut_summary:
|
||||||
detailed_cavity_json["undercut_regions"] = undercut_regions
|
detailed_cavity_json["undercut_regions"] = [] # 详情在 scheme 级别
|
||||||
|
|
||||||
side_actions = quality_checks.get("side_actions")
|
side_action_summary = quality_checks.get("side_action_summary")
|
||||||
if side_actions:
|
if side_action_summary:
|
||||||
detailed_cavity_json["side_actions"] = side_actions
|
detailed_cavity_json["side_actions"] = {"summary": side_action_summary}
|
||||||
|
|
||||||
# 添加型腔关键信息
|
# 添加型腔关键信息
|
||||||
detailed_cavity_json["mold_cavities"]["cavity_key_info"] = {
|
detailed_cavity_json["mold_cavities"]["cavity_key_info"] = {
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ class LLMService:
|
|||||||
volume=f"{analysis_result.get('geometry_data', {}).get('volume', 0):.1f} mm³",
|
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²",
|
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),
|
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),
|
quality_metrics=json.dumps(analysis_result.get("quality_metrics", {}), ensure_ascii=False, indent=2),
|
||||||
schemes=schemes_text or "无分模方案数据",
|
schemes=schemes_text or "无分模方案数据",
|
||||||
mold_material=mfg.get("mold_material", "自动选择"),
|
mold_material=mfg.get("mold_material", "自动选择"),
|
||||||
@@ -350,13 +350,12 @@ class LLMService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _build_side_action_prompt(self, analysis_result, detailed_cavity_json) -> str:
|
def _build_side_action_prompt(self, analysis_result, detailed_cavity_json) -> str:
|
||||||
features = json.dumps(
|
features = self._prioritize_features_for_side_action(
|
||||||
analysis_result.get("detected_features", []),
|
analysis_result.get("detected_features", [])
|
||||||
ensure_ascii=False,
|
|
||||||
indent=2,
|
|
||||||
)
|
)
|
||||||
if len(features) > 2500:
|
trimmed = json.dumps(features, ensure_ascii=False, indent=2)
|
||||||
features = features[:2500] + "\n... (已截断)"
|
if len(trimmed) > 2500:
|
||||||
|
trimmed = trimmed[:2500] + "\n... (已截断)"
|
||||||
|
|
||||||
best_scheme = {}
|
best_scheme = {}
|
||||||
if detailed_cavity_json:
|
if detailed_cavity_json:
|
||||||
@@ -449,6 +448,27 @@ class LLMService:
|
|||||||
content = resp.json()["choices"][0]["message"]["content"]
|
content = resp.json()["choices"][0]["message"]["content"]
|
||||||
return content.strip() if content else None
|
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
|
@staticmethod
|
||||||
def _parse_json_response(raw):
|
def _parse_json_response(raw):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ 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")
|
# OCC 非线程安全,max_workers=1 保证所有 OCC 操作序列化执行,避免偶发崩溃
|
||||||
|
self._occ_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="occ")
|
||||||
|
|
||||||
# ─── 对外入口 ───
|
# ─── 对外入口 ───
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user