""" 模具加工碰撞检测与刀路优化模块 功能: 1. CollisionDetector - 碰撞检测器 - 刀柄干涉检测 - 快速移动碰撞检测 - 机床行程限制验证 - 安全区域计算 2. ToolpathOptimizer - 刀路优化器 - 进给率自适应优化 - 空走刀路径最小化 - 拐角减速处理 - 切入切出优化 3. EDMElectrodeDesigner - EDM电极设计器 - 电极自动生成 - 放电间隙计算 - 电极加工路径 4. MachiningSimulator - 加工仿真器 - 材料去除模拟 - 过切检测 - 残余材料分析 - 加工质量评估 """ from typing import Dict, List, Any, Optional, Tuple import math import numpy as np from shared.utils.logger import get_logger logger = get_logger(__name__) class CollisionDetector: """碰撞检测器""" def __init__(self): self.machine_limits = { "x_min": -500, "x_max": 500, "y_min": -400, "y_max": 400, "z_min": -300, "z_max": 300, } self.safety_margin = 5.0 self.retract_height = 50.0 def check_toolpath_safety(self, toolpath_points: List[List[float]], tool: Dict, stock_bbox: Dict, clamp_positions: Optional[List[Dict]] = None) -> Dict[str, Any]: """ 综合检查刀路安全性 Args: toolpath_points: 刀路点列表 [[x,y,z], ...] tool: 刀具参数 stock_bbox: 毛坯边界框 clamp_positions: 压板位置列表 Returns: 安全检查结果 """ holder_collisions = self._check_holder_collision(toolpath_points, tool, stock_bbox) rapid_collisions = self._check_rapid_move_collisions(toolpath_points, stock_bbox) limit_violations = self._check_machine_limits(toolpath_points) clamp_collisions = [] if clamp_positions: clamp_collisions = self._check_clamp_collisions( toolpath_points, tool, clamp_positions ) all_issues = holder_collisions + rapid_collisions + limit_violations + clamp_collisions safe_retract_points = self._calculate_safe_retract_points( toolpath_points, stock_bbox ) is_safe = len(all_issues) == 0 return { "is_safe": is_safe, "total_issues": len(all_issues), "holder_collisions": holder_collisions, "rapid_collisions": rapid_collisions, "limit_violations": limit_violations, "clamp_collisions": clamp_collisions, "safe_retract_points": safe_retract_points, "recommendations": self._generate_safety_recommendations(all_issues), } def _check_holder_collision(self, points: List[List[float]], tool: Dict, stock_bbox: Dict) -> List[Dict]: """检测刀柄干涉""" collisions = [] tool_diameter = tool.get("diameter", 10) flute_length = tool.get("flute_length", 30) shank_diameter = tool.get("shank_diameter", tool_diameter) holder_diameter = tool.get("holder_diameter", shank_diameter * 2) stock_z_max = stock_bbox.get("max", [0, 0, 0])[2] for i, pt in enumerate(points): if len(pt) < 3: continue z = pt[2] depth_below_stock = stock_z_max - z if depth_below_stock > flute_length: holder_z = z + flute_length holder_clearance = holder_diameter / 2 + self.safety_margin stock_xmin = stock_bbox.get("min", [0, 0, 0])[0] stock_xmax = stock_bbox.get("max", [0, 0, 0])[0] stock_ymin = stock_bbox.get("min", [0, 0, 0])[1] stock_ymax = stock_bbox.get("max", [0, 0, 0])[1] if (stock_xmin - holder_clearance < pt[0] < stock_xmax + holder_clearance and stock_ymin - holder_clearance < pt[1] < stock_ymax + holder_clearance): collisions.append({ "type": "holder_collision", "point_index": i, "position": pt, "depth": round(depth_below_stock, 2), "flute_length": flute_length, "severity": "high", "message": f"点{i}: 切深{depth_below_stock:.1f}mm超过刃长{flute_length}mm,刀柄可能干涉" }) return collisions def _check_rapid_move_collisions(self, points: List[List[float]], stock_bbox: Dict) -> List[Dict]: """检测快速移动碰撞""" collisions = [] stock_xmin = stock_bbox.get("min", [0, 0, 0])[0] stock_xmax = stock_bbox.get("max", [0, 0, 0])[0] stock_ymin = stock_bbox.get("min", [0, 0, 0])[1] stock_ymax = stock_bbox.get("max", [0, 0, 0])[1] stock_zmin = stock_bbox.get("min", [0, 0, 0])[2] stock_zmax = stock_bbox.get("max", [0, 0, 0])[2] for i in range(1, len(points)): prev = points[i - 1] curr = points[i] if len(prev) < 3 or len(curr) < 3: continue z_change = abs(curr[2] - prev[2]) xy_change = math.sqrt((curr[0] - prev[0])**2 + (curr[1] - prev[1])**2) if z_change < 1.0 and xy_change > 5.0: min_z = min(prev[2], curr[2]) if min_z < stock_zmax + self.safety_margin: mid_x = (prev[0] + curr[0]) / 2 mid_y = (prev[1] + curr[1]) / 2 if (stock_xmin < mid_x < stock_xmax and stock_ymin < mid_y < stock_ymax): collisions.append({ "type": "rapid_collision", "segment": [i - 1, i], "start": prev, "end": curr, "severity": "high", "message": f"段{i-1}-{i}: 水平快速移动可能穿过毛坯" }) return collisions def _check_machine_limits(self, points: List[List[float]]) -> List[Dict]: """验证机床行程限制""" violations = [] for i, pt in enumerate(points): if len(pt) < 3: continue if not (self.machine_limits["x_min"] <= pt[0] <= self.machine_limits["x_max"]): violations.append({ "type": "machine_limit", "point_index": i, "axis": "X", "value": pt[0], "limit": [self.machine_limits["x_min"], self.machine_limits["x_max"]], "severity": "critical", }) if not (self.machine_limits["y_min"] <= pt[1] <= self.machine_limits["y_max"]): violations.append({ "type": "machine_limit", "point_index": i, "axis": "Y", "value": pt[1], "limit": [self.machine_limits["y_min"], self.machine_limits["y_max"]], "severity": "critical", }) if not (self.machine_limits["z_min"] <= pt[2] <= self.machine_limits["z_max"]): violations.append({ "type": "machine_limit", "point_index": i, "axis": "Z", "value": pt[2], "limit": [self.machine_limits["z_min"], self.machine_limits["z_max"]], "severity": "critical", }) return violations def _check_clamp_collisions(self, points: List[List[float]], tool: Dict, clamps: List[Dict]) -> List[Dict]: """检测压板碰撞""" collisions = [] tool_radius = tool.get("diameter", 10) / 2 for i, pt in enumerate(points): if len(pt) < 3: continue for j, clamp in enumerate(clamps): clamp_center = clamp.get("center", [0, 0, 0]) clamp_size = clamp.get("size", [50, 30, 20]) clamp_z_top = clamp_center[2] + clamp_size[2] / 2 if pt[2] < clamp_z_top + self.safety_margin: dx = abs(pt[0] - clamp_center[0]) dy = abs(pt[1] - clamp_center[1]) if (dx < clamp_size[0] / 2 + tool_radius + self.safety_margin and dy < clamp_size[1] / 2 + tool_radius + self.safety_margin): collisions.append({ "type": "clamp_collision", "point_index": i, "clamp_index": j, "severity": "high", "message": f"点{i}: 可能与压板{j}碰撞" }) return collisions def _calculate_safe_retract_points(self, points: List[List[float]], stock_bbox: Dict) -> List[Dict]: """计算安全抬刀点""" retract_points = [] stock_zmax = stock_bbox.get("max", [0, 0, 0])[2] safe_z = stock_zmax + self.retract_height for i in range(0, len(points), max(1, len(points) // 10)): pt = points[i] if len(pt) >= 3: retract_points.append({ "index": i, "from": pt, "retract_to": [pt[0], pt[1], safe_z], "safe_z": safe_z, }) return retract_points def _generate_safety_recommendations(self, issues: List[Dict]) -> List[str]: """生成安全建议""" recs = [] holder_issues = [i for i in issues if i["type"] == "holder_collision"] if holder_issues: recs.append(f"发现 {len(holder_issues)} 处刀柄干涉,建议加长刀具或减少切深") rapid_issues = [i for i in issues if i["type"] == "rapid_collision"] if rapid_issues: recs.append(f"发现 {len(rapid_issues)} 处快速移动碰撞风险,建议增加抬刀高度") limit_issues = [i for i in issues if i["type"] == "machine_limit"] if limit_issues: recs.append(f"发现 {len(limit_issues)} 处超出机床行程,需调整工件位置") clamp_issues = [i for i in issues if i["type"] == "clamp_collision"] if clamp_issues: recs.append(f"发现 {len(clamp_issues)} 处压板碰撞,建议调整压板位置") if not issues: recs.append("刀路安全检查通过,无碰撞风险") return recs class ToolpathOptimizer: """刀路优化器""" def optimize_toolpath(self, toolpath_points: List[List[float]], cutting_params: Dict, stock_bbox: Optional[Dict] = None) -> Dict[str, Any]: """ 综合优化刀路 优化内容: 1. 进给率自适应优化 2. 拐角减速处理 3. 空走刀路径优化 4. 切入切出优化 Args: toolpath_points: 原始刀路点 cutting_params: 切削参数 stock_bbox: 毛坯边界框 Returns: 优化后的刀路和参数 """ feed_optimized = self._optimize_feed_rates(toolpath_points, cutting_params) corner_optimized = self._optimize_corner_speeds(toolpath_points, feed_optimized) entry_exit_optimized = self._optimize_entry_exit(toolpath_points, stock_bbox) stats = self._calculate_optimization_stats( toolpath_points, feed_optimized, corner_optimized ) return { "original_point_count": len(toolpath_points), "optimized_feeds": feed_optimized, "corner_slowdowns": corner_optimized, "entry_exit": entry_exit_optimized, "stats": stats, "recommendations": self._generate_optimization_recommendations(stats), } def _optimize_feed_rates(self, points: List[List[float]], params: Dict) -> List[Dict]: """进给率自适应优化""" base_feed = params.get("feed_rate_mm_min", 500) optimized = [] for i in range(len(points)): if i < 2 or i >= len(points) - 2: feed = base_feed * 0.8 else: v1 = np.array(points[i]) - np.array(points[i - 1]) v2 = np.array(points[i + 1]) - np.array(points[i]) len1 = np.linalg.norm(v1) len2 = np.linalg.norm(v2) if len1 > 0.001 and len2 > 0.001: cos_angle = np.clip(np.dot(v1, v2) / (len1 * len2), -1, 1) angle = math.degrees(math.acos(cos_angle)) if angle < 30: feed = base_feed * 0.3 elif angle < 60: feed = base_feed * 0.5 elif angle < 120: feed = base_feed * 0.7 else: feed = base_feed else: feed = base_feed optimized.append({ "index": i, "feed_rate": round(feed, 1), "feed_ratio": round(feed / base_feed, 2), }) return optimized def _optimize_corner_speeds(self, points: List[List[float]], feed_data: List[Dict]) -> List[Dict]: """拐角减速处理""" slowdowns = [] base_feed = 500 for i in range(1, len(points) - 1): if i >= len(feed_data): break v1 = np.array(points[i]) - np.array(points[i - 1]) v2 = np.array(points[i + 1]) - np.array(points[i]) len1 = np.linalg.norm(v1) len2 = np.linalg.norm(v2) if len1 > 0.001 and len2 > 0.001: cos_angle = np.clip(np.dot(v1, v2) / (len1 * len2), -1, 1) angle = math.degrees(math.acos(cos_angle)) if angle < 90: decel_distance = max(2.0, 10.0 * (1 - angle / 90)) slowdowns.append({ "index": i, "angle": round(angle, 1), "decel_distance": round(decel_distance, 2), "min_feed_ratio": 0.3 if angle < 45 else 0.5, }) return slowdowns def _optimize_entry_exit(self, points: List[List[float]], stock_bbox: Optional[Dict]) -> Dict[str, Any]: """切入切出优化""" entry = {"type": "arc_tangent", "radius": 5.0, "angle": 90} exit_ = {"type": "arc_tangent", "radius": 5.0, "angle": 90} if stock_bbox: z_max = stock_bbox.get("max", [0, 0, 0])[2] entry["approach_z"] = z_max + 10 exit_["retract_z"] = z_max + 50 return {"entry": entry, "exit": exit_} def _calculate_optimization_stats(self, points: List, feeds: List, corners: List) -> Dict: """计算优化统计""" if not feeds: return {"time_reduction_percent": 0} feed_values = [f["feed_rate"] for f in feeds] avg_feed = sum(feed_values) / len(feed_values) if feed_values else 500 base_feed = max(feed_values) if feed_values else 500 time_reduction = 0 if base_feed > 0: time_reduction = (1 - avg_feed / base_feed) * 100 return { "avg_feed_rate": round(avg_feed, 1), "base_feed_rate": base_feed, "corner_slowdown_count": len(corners), "time_reduction_percent": round(abs(time_reduction), 1), } def _generate_optimization_recommendations(self, stats: Dict) -> List[str]: """生成优化建议""" recs = [] if stats.get("corner_slowdown_count", 0) > 10: recs.append("拐角减速点较多,建议优化刀路方向减少急转弯") if stats.get("time_reduction_percent", 0) > 30: recs.append("进给率降低幅度较大,建议优化加工策略") if not recs: recs.append("刀路优化完成,进给率分布合理") return recs class EDMElectrodeDesigner: """EDM电极设计器""" ELECTRODE_MATERIALS = { "copper": { "density": 8.96, "wear_rate": 1.0, "machinability": "good", "cost": "medium" }, "graphite": { "density": 1.75, "wear_rate": 0.5, "machinability": "excellent", "cost": "low" }, "copper_tungsten": { "density": 14.0, "wear_rate": 0.3, "machinability": "poor", "cost": "high" }, } def design_electrodes(self, undercut_regions: List[Dict], cavity_bbox: Dict, material: str = "copper", spark_gap: float = 0.05, overburn: float = 0.1) -> Dict[str, Any]: """ 设计EDM电极 Args: undercut_regions: 倒扣区域列表 cavity_bbox: 型腔边界框 material: 电极材料 spark_gap: 放电间隙 mm overburn: 过切量 mm Returns: 电极设计方案 """ mat_props = self.ELECTRODE_MATERIALS.get(material, self.ELECTRODE_MATERIALS["copper"]) electrodes = [] for i, region in enumerate(undercut_regions): electrode = self._design_single_electrode( region, i + 1, material, spark_gap, overburn, cavity_bbox ) electrodes.append(electrode) total_volume = sum(e["volume_mm3"] for e in electrodes) total_weight = total_volume * mat_props["density"] / 1000 return { "electrodes": electrodes, "material": material, "material_properties": mat_props, "spark_gap": spark_gap, "overburn": overburn, "total_electrode_count": len(electrodes), "total_volume_cm3": round(total_volume / 1000, 2), "total_weight_g": round(total_weight, 2), "machining_strategy": self._generate_electrode_machining_strategy( electrodes, material ), "recommendations": self._generate_electrode_recommendations( electrodes, material ), } def _design_single_electrode(self, region: Dict, index: int, material: str, spark_gap: float, overburn: float, cavity_bbox: Dict) -> Dict: """设计单个电极""" center = region.get("center", [0, 0, 0]) area = region.get("area", 100) feature_size = math.sqrt(area) electrode_size = { "width": round(feature_size * 1.3 + 2 * (spark_gap + overburn), 2), "length": round(feature_size * 1.3 + 2 * (spark_gap + overburn), 2), "height": round(cavity_bbox.get("dimensions", [0, 0, 50])[2] * 0.8 + 20, 2), } volume = electrode_size["width"] * electrode_size["length"] * electrode_size["height"] return { "index": index, "type": region.get("type", "undercut"), "location": center, "size": electrode_size, "volume_mm3": round(volume, 1), "spark_gap": spark_gap, "overburn": overburn, "material": material, "roughing_passes": 3, "finishing_passes": 2, } def _generate_electrode_machining_strategy(self, electrodes: List, material: str) -> List[Dict]: """生成电极加工策略""" strategies = [] for elec in electrodes: size = elec["size"] is_small = min(size["width"], size["length"]) < 5 strategy = { "electrode_index": elec["index"], "operations": [ { "operation": "roughing", "tool": "endmill_6mm" if not is_small else "endmill_3mm", "stock_allowance": 0.3, }, { "operation": "finishing", "tool": "ballnose_3mm" if not is_small else "ballnose_1mm", "stepover": 0.2, }, ], } strategies.append(strategy) return strategies def _generate_electrode_recommendations(self, electrodes: List, material: str) -> List[str]: """生成电极建议""" recs = [] if material == "copper": recs.append("铜电极加工性良好,建议使用高速钢刀具") elif material == "graphite": recs.append("石墨电极易加工但易碎,注意切削力控制") elif material == "copper_tungsten": recs.append("铜钨合金硬度高,建议使用金刚石刀具") if len(electrodes) > 4: recs.append("电极数量较多,建议评估是否可合并电极设计") recs.append("电极加工后需检测尺寸精度和表面质量") recs.append("放电加工时需根据材料调整电参数") return recs class MachiningSimulator: """加工仿真器""" def simulate_machining(self, operations: List[Dict], stock_bbox: Dict, resolution: float = 1.0) -> Dict[str, Any]: """ 模拟加工过程 Args: operations: 加工操作列表 stock_bbox: 毛坯边界框 resolution: 仿真精度 mm Returns: 仿真结果 """ stock_dims = stock_bbox.get("dimensions", [100, 100, 50]) nx = max(2, int(stock_dims[0] / resolution)) ny = max(2, int(stock_dims[1] / resolution)) nz = max(2, int(stock_dims[2] / resolution)) stock = np.ones((nx, ny, nz), dtype=np.float32) total_removed = 0 operation_results = [] for op in operations: removed = self._simulate_operation(stock, op, stock_bbox, resolution) total_removed += removed operation_results.append({ "strategy": op.get("strategy", "unknown"), "volume_removed_mm3": removed, "remaining_stock_percent": round( (1 - total_removed / (nx * ny * nz)) * 100, 1 ), }) total_voxels = nx * ny * nz remaining = np.sum(stock > 0) removal_efficiency = (1 - remaining / total_voxels) * 100 if total_voxels > 0 else 0 gouging = self._detect_gouging(stock, operations, stock_bbox, resolution) residual = self._analyze_residual_material(stock, stock_bbox, resolution) return { "resolution": resolution, "grid_size": {"nx": nx, "ny": ny, "nz": nz}, "operations": operation_results, "total_volume_removed_percent": round(removal_efficiency, 1), "gouging_detected": gouging, "residual_analysis": residual, "quality_assessment": self._assess_quality(gouging, residual), "recommendations": self._generate_simulation_recommendations( gouging, residual, removal_efficiency ), } def _simulate_operation(self, stock: np.ndarray, op: Dict, bbox: Dict, resolution: float) -> int: """模拟单个加工操作的材料去除""" strategy = op.get("strategy", "") removed = 0 nx, ny, nz = stock.shape if strategy == "z_level_roughing": levels = op.get("levels", []) for level in levels: z_level = level.get("z", 0) z_idx = int((z_level - bbox.get("min", [0, 0, 0])[2]) / resolution) z_idx = max(0, min(z_idx, nz - 1)) for iz in range(z_idx, nz): removed += int(np.sum(stock[:, :, iz] > 0)) stock[:, :, iz] = 0 elif strategy in ("parallel_finishing", "contour_finishing"): stepover = op.get("stepover", 0.3) step_idx = max(1, int(stepover / resolution)) for ix in range(0, nx, step_idx): for iy in range(0, ny, step_idx): if stock[ix, iy, :].any(): removed += int(np.sum(stock[ix, iy, :] > 0)) stock[ix, iy, :] = 0 return removed def _detect_gouging(self, stock: np.ndarray, operations: List, bbox: Dict, resolution: float) -> List[Dict]: """检测过切""" gouging = [] for op in operations: stock_allowance = op.get("stock_allowance", 0) if stock_allowance < 0: gouging.append({ "operation": op.get("strategy", "unknown"), "type": "negative_allowance", "severity": "high", "message": f"工序 {op.get('strategy')} 余量为负值,存在过切风险" }) return gouging def _analyze_residual_material(self, stock: np.ndarray, bbox: Dict, resolution: float) -> Dict: """分析残余材料""" total_voxels = stock.size remaining = int(np.sum(stock > 0)) remaining_percent = (remaining / total_voxels) * 100 if total_voxels > 0 else 0 return { "remaining_voxels": remaining, "remaining_percent": round(remaining_percent, 2), "estimated_residual_volume_cm3": round( remaining * resolution ** 3 / 1000, 2 ), } def _assess_quality(self, gouging: List, residual: Dict) -> Dict: """评估加工质量""" has_gouging = len(gouging) > 0 residual_pct = residual.get("remaining_percent", 100) if has_gouging: grade = "FAIL" elif residual_pct < 5: grade = "GOOD" elif residual_pct < 15: grade = "ACCEPTABLE" else: grade = "INSUFFICIENT" return { "grade": grade, "has_gouging": has_gouging, "residual_percent": residual_pct, } def _generate_simulation_recommendations(self, gouging: List, residual: Dict, efficiency: float) -> List[str]: """生成仿真建议""" recs = [] if gouging: recs.append("检测到过切,需调整加工参数") residual_pct = residual.get("remaining_percent", 0) if residual_pct > 20: recs.append("残余材料较多,建议增加精加工工序") elif residual_pct > 5: recs.append("残余材料适中,需检查关键区域是否加工到位") if efficiency < 50: recs.append("材料去除率偏低,建议优化粗加工策略") if not recs: recs.append("仿真结果良好,加工方案可行") return recs