763 lines
27 KiB
Python
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
|