后端模块拆分
This commit is contained in:
@@ -0,0 +1,762 @@
|
||||
"""
|
||||
模具加工碰撞检测与刀路优化模块
|
||||
|
||||
功能:
|
||||
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
|
||||
Reference in New Issue
Block a user