Files
geMoldInsight/src/moldinsight/core/mold_machining.py
T
2026-05-29 18:10:08 +08:00

763 lines
27 KiB
Python

"""
模具加工碰撞检测与刀路优化模块
功能:
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