Files
geMoldInsight/src/core/mold_cam.py
T

647 lines
24 KiB
Python
Raw Normal View History

2026-04-19 23:41:35 +08:00
"""
模具刀路设计与G代码生成模块
架构:
1. ToolLibrary - 刀具库与切削参数管理
2. CuttingParamsCalculator - 切削参数自动计算
3. RoughingToolpathGenerator - 粗加工刀路生成
4. FinishingToolpathGenerator - 精加工刀路生成
5. GCodePostProcessor - G代码后处理器
6. MoldCAMDesigner - 模具CAM综合设计器
加工策略:
- 粗加工:Z层等高粗加工(自适应清根)
- 半精加工:等高线铣削
- 精加工:平行铣削/螺旋铣削/等高线精加工
- 清角:笔式清角
- 钻孔:冷却水路/顶针孔/螺丝孔
"""
from typing import Dict, List, Any, Optional, Tuple
import math
from utils.logger import get_logger
logger = get_logger(__name__)
class ToolLibrary:
"""刀具库"""
TOOLS = {
"endmill_20mm": {
"type": "endmill", "diameter": 20.0, "flute_length": 60.0,
"cutting_edges": 4, "material": "carbide",
"corner_radius": 0.0,
"speeds_feeds": {
"cutting_speed": 100, "feed_per_tooth": 0.15,
"axial_depth": 10.0, "radial_depth": 15.0
}
},
"endmill_16mm": {
"type": "endmill", "diameter": 16.0, "flute_length": 50.0,
"cutting_edges": 4, "material": "carbide",
"corner_radius": 0.0,
"speeds_feeds": {
"cutting_speed": 120, "feed_per_tooth": 0.12,
"axial_depth": 8.0, "radial_depth": 12.0
}
},
"endmill_10mm": {
"type": "endmill", "diameter": 10.0, "flute_length": 35.0,
"cutting_edges": 4, "material": "carbide",
"corner_radius": 0.0,
"speeds_feeds": {
"cutting_speed": 130, "feed_per_tooth": 0.10,
"axial_depth": 5.0, "radial_depth": 8.0
}
},
"endmill_6mm": {
"type": "endmill", "diameter": 6.0, "flute_length": 22.0,
"cutting_edges": 3, "material": "carbide",
"corner_radius": 0.0,
"speeds_feeds": {
"cutting_speed": 140, "feed_per_tooth": 0.06,
"axial_depth": 3.0, "radial_depth": 4.0
}
},
"ballnose_10mm": {
"type": "ballnose", "diameter": 10.0, "flute_length": 30.0,
"cutting_edges": 2, "material": "carbide",
"corner_radius": 5.0,
"speeds_feeds": {
"cutting_speed": 150, "feed_per_tooth": 0.08,
"axial_depth": 0.5, "radial_depth": 1.0
}
},
"ballnose_6mm": {
"type": "ballnose", "diameter": 6.0, "flute_length": 22.0,
"cutting_edges": 2, "material": "carbide",
"corner_radius": 3.0,
"speeds_feeds": {
"cutting_speed": 160, "feed_per_tooth": 0.06,
"axial_depth": 0.3, "radial_depth": 0.5
}
},
"ballnose_3mm": {
"type": "ballnose", "diameter": 3.0, "flute_length": 12.0,
"cutting_edges": 2, "material": "carbide",
"corner_radius": 1.5,
"speeds_feeds": {
"cutting_speed": 180, "feed_per_tooth": 0.03,
"axial_depth": 0.15, "radial_depth": 0.3
}
},
"ballnose_1mm": {
"type": "ballnose", "diameter": 1.0, "flute_length": 5.0,
"cutting_edges": 2, "material": "carbide",
"corner_radius": 0.5,
"speeds_feeds": {
"cutting_speed": 200, "feed_per_tooth": 0.01,
"axial_depth": 0.05, "radial_depth": 0.1
}
},
"drill_8mm": {
"type": "drill", "diameter": 8.0, "flute_length": 50.0,
"cutting_edges": 2, "material": "carbide",
"corner_radius": 0.0,
"speeds_feeds": {
"cutting_speed": 80, "feed_per_tooth": 0.10,
"axial_depth": 50.0, "radial_depth": 0.0
}
},
"drill_5mm": {
"type": "drill", "diameter": 5.0, "flute_length": 35.0,
"cutting_edges": 2, "material": "carbide",
"corner_radius": 0.0,
"speeds_feeds": {
"cutting_speed": 90, "feed_per_tooth": 0.08,
"axial_depth": 35.0, "radial_depth": 0.0
}
},
}
MOLD_STEEL = {
"P20": {"hardness_hrc": 30, "cutting_speed_factor": 1.0, "feed_factor": 1.0},
"718H": {"hardness_hrc": 35, "cutting_speed_factor": 0.85, "feed_factor": 0.9},
"NAK80": {"hardness_hrc": 38, "cutting_speed_factor": 0.75, "feed_factor": 0.85},
"S136": {"hardness_hrc": 50, "cutting_speed_factor": 0.5, "feed_factor": 0.7},
"H13": {"hardness_hrc": 48, "cutting_speed_factor": 0.55, "feed_factor": 0.75},
"Al7075": {"hardness_hrc": 15, "cutting_speed_factor": 2.0, "feed_factor": 1.5},
}
@classmethod
def get_tool(cls, tool_id: str) -> Optional[Dict]:
return cls.TOOLS.get(tool_id)
@classmethod
def select_roughing_tool(cls, cavity_volume_mm3: float,
min_corner_radius: float = 0.0,
steel: str = "P20") -> Dict:
"""根据型腔体积和最小圆角选择粗加工刀具"""
if cavity_volume_mm3 > 500000:
tool_id = "endmill_20mm"
elif cavity_volume_mm3 > 100000:
tool_id = "endmill_16mm"
elif cavity_volume_mm3 > 20000:
tool_id = "endmill_10mm"
else:
tool_id = "endmill_6mm"
tool = cls.TOOLS[tool_id].copy()
steel_props = cls.MOLD_STEEL.get(steel, cls.MOLD_STEEL["P20"])
tool["speeds_feeds"] = cls._adjust_for_steel(tool["speeds_feeds"], steel_props)
tool["tool_id"] = tool_id
return tool
@classmethod
def select_finishing_tool(cls, surface_quality: str = "standard",
min_corner_radius: float = 0.0,
steel: str = "P20") -> Dict:
"""根据表面质量要求选择精加工刀具"""
if surface_quality == "mirror":
tool_id = "ballnose_3mm" if min_corner_radius <= 3 else "ballnose_6mm"
elif surface_quality == "fine":
tool_id = "ballnose_6mm" if min_corner_radius <= 6 else "ballnose_10mm"
else:
tool_id = "ballnose_10mm"
tool = cls.TOOLS[tool_id].copy()
steel_props = cls.MOLD_STEEL.get(steel, cls.MOLD_STEEL["P20"])
tool["speeds_feeds"] = cls._adjust_for_steel(tool["speeds_feeds"], steel_props)
tool["tool_id"] = tool_id
return tool
@classmethod
def _adjust_for_steel(cls, speeds_feeds: Dict, steel_props: Dict) -> Dict:
"""根据模具钢调整切削参数"""
adjusted = speeds_feeds.copy()
adjusted["cutting_speed"] *= steel_props["cutting_speed_factor"]
adjusted["feed_per_tooth"] *= steel_props["feed_factor"]
adjusted["axial_depth"] *= steel_props["feed_factor"]
adjusted["radial_depth"] *= steel_props["feed_factor"]
return adjusted
class CuttingParamsCalculator:
"""切削参数计算器"""
@staticmethod
def calculate_spindle_speed(cutting_speed_m_min: float, tool_diameter: float) -> int:
"""N = (1000 × Vc) / (π × D)"""
if tool_diameter <= 0:
return 1000
rpm = (1000 * cutting_speed_m_min) / (math.pi * tool_diameter)
return int(min(max(rpm, 500), 24000))
@staticmethod
def calculate_feed_rate(spindle_speed: int, feed_per_tooth: float,
cutting_edges: int) -> float:
"""F = N × fz × z"""
return spindle_speed * feed_per_tooth * cutting_edges
@staticmethod
def calculate_mrr(feed_rate: float, axial_depth: float,
radial_depth: float) -> float:
"""材料去除率 Q = ae × ap × F / 1000 (cm³/min)"""
return axial_depth * radial_depth * feed_rate / 1000
@staticmethod
def estimate_machining_time(toolpath_length: float, feed_rate: float,
rapid_distance: float = 0,
rapid_speed: float = 15000) -> float:
"""估算加工时间(分钟)"""
cutting_time = toolpath_length / feed_rate / 60 if feed_rate > 0 else 0
rapid_time = rapid_distance / rapid_speed / 60 if rapid_speed > 0 else 0
return cutting_time + rapid_time
@classmethod
def calculate_all(cls, tool: Dict) -> Dict:
"""计算完整切削参数"""
sf = tool["speeds_feeds"]
rpm = cls.calculate_spindle_speed(sf["cutting_speed"], tool["diameter"])
feed = cls.calculate_feed_rate(rpm, sf["feed_per_tooth"], tool["cutting_edges"])
mrr = cls.calculate_mrr(feed, sf["axial_depth"], sf["radial_depth"])
return {
"tool_id": tool.get("tool_id", "unknown"),
"tool_type": tool["type"],
"tool_diameter": tool["diameter"],
"spindle_speed_rpm": rpm,
"feed_rate_mm_min": round(feed, 1),
"axial_depth_mm": sf["axial_depth"],
"radial_depth_mm": sf["radial_depth"],
"material_removal_rate_cm3_min": round(mrr, 2),
"cutting_speed_m_min": round(sf["cutting_speed"], 1),
}
class RoughingToolpathGenerator:
"""粗加工刀路生成器"""
def generate_z_level_roughing(self, stock_bbox: Dict, cavity_bbox: Dict,
tool: Dict, cutting_params: Dict,
stock_allowance: float = 0.5) -> Dict[str, Any]:
"""
Z层等高粗加工
策略:从顶面逐层向下铣削,每层切深为 axial_depth
Args:
stock_bbox: 毛坯边界框
cavity_bbox: 型腔边界框
tool: 刀具参数
cutting_params: 切削参数
stock_allowance: 精加工余量 mm
Returns:
粗加工刀路方案
"""
z_min = cavity_bbox.get("min", [0, 0, 0])[2]
z_max = cavity_bbox.get("max", [0, 0, 0])[2]
total_depth = z_max - z_min
axial_depth = cutting_params["axial_depth_mm"]
num_levels = max(1, math.ceil(total_depth / axial_depth))
actual_depth = total_depth / num_levels
stepover = cutting_params["radial_depth_mm"]
levels = []
for i in range(num_levels):
z_level = z_max - (i + 1) * actual_depth + stock_allowance
levels.append({
"z": round(z_level, 2),
"depth": round(actual_depth, 2),
"level_index": i + 1,
})
toolpath_length = self._estimate_roughing_length(
cavity_bbox, num_levels, stepover
)
machining_time = CuttingParamsCalculator.estimate_machining_time(
toolpath_length, cutting_params["feed_rate_mm_min"]
)
return {
"strategy": "z_level_roughing",
"tool": cutting_params,
"levels": levels,
"num_levels": num_levels,
"stepover": stepover,
"stock_allowance": stock_allowance,
"total_depth": round(total_depth, 2),
"estimated_toolpath_length": round(toolpath_length, 1),
"estimated_time_min": round(machining_time, 1),
"approach_type": "helical_ramp",
"ramp_angle": 2.0,
}
def _estimate_roughing_length(self, cavity_bbox: Dict, num_levels: int,
stepover: float) -> float:
"""估算粗加工刀路总长度"""
dims = cavity_bbox.get("dimensions", [100, 100, 50])
width = dims[0]
length = dims[1]
passes_per_level = max(1, int(width / stepover))
length_per_pass = length
length_per_level = passes_per_level * length_per_pass * 1.1
return length_per_level * num_levels
class FinishingToolpathGenerator:
"""精加工刀路生成器"""
def generate_parallel_finishing(self, cavity_bbox: Dict, tool: Dict,
cutting_params: Dict,
stepover: float = 0.3,
angle: float = 0.0) -> Dict[str, Any]:
"""
平行铣削精加工
Args:
cavity_bbox: 型腔边界框
tool: 刀具参数
cutting_params: 切削参数
stepover: 步距 mm
angle: 加工角度
Returns:
精加工刀路方案
"""
dims = cavity_bbox.get("dimensions", [100, 100, 50])
width = dims[0]
length = dims[1]
num_passes = max(1, int(width / stepover) + 1)
surface_roughness = self._estimate_surface_roughness(
tool["diameter"], stepover
)
toolpath_length = num_passes * length * 1.05
machining_time = CuttingParamsCalculator.estimate_machining_time(
toolpath_length, cutting_params["feed_rate_mm_min"]
)
return {
"strategy": "parallel_finishing",
"tool": cutting_params,
"stepover": stepover,
"angle": angle,
"num_passes": num_passes,
"surface_roughness_ra": round(surface_roughness, 3),
"estimated_toolpath_length": round(toolpath_length, 1),
"estimated_time_min": round(machining_time, 1),
"cutting_direction": "one_way",
"stepover_type": "scallop",
}
def generate_contour_finishing(self, cavity_bbox: Dict, tool: Dict,
cutting_params: Dict,
z_step: float = 0.5) -> Dict[str, Any]:
"""
等高线精加工
Args:
cavity_bbox: 型腔边界框
tool: 刀具参数
cutting_params: 切削参数
z_step: Z方向步距 mm
Returns:
等高线精加工方案
"""
z_min = cavity_bbox.get("min", [0, 0, 0])[2]
z_max = cavity_bbox.get("max", [0, 0, 0])[2]
total_depth = z_max - z_min
num_levels = max(1, int(total_depth / z_step) + 1)
dims = cavity_bbox.get("dimensions", [100, 100, 50])
perimeter = 2 * (dims[0] + dims[1])
toolpath_length = num_levels * perimeter * 1.1
machining_time = CuttingParamsCalculator.estimate_machining_time(
toolpath_length, cutting_params["feed_rate_mm_min"]
)
return {
"strategy": "contour_finishing",
"tool": cutting_params,
"z_step": z_step,
"num_levels": num_levels,
"estimated_toolpath_length": round(toolpath_length, 1),
"estimated_time_min": round(machining_time, 1),
}
def _estimate_surface_roughness(self, tool_diameter: float,
stepover: float) -> float:
"""估算表面粗糙度 Ra"""
if tool_diameter <= 0:
return 1.0
r = tool_diameter / 2
h = stepover ** 2 / (8 * r) if r > 0 else stepover
return h * 0.25
class GCodePostProcessor:
"""G代码后处理器"""
def __init__(self, controller: str = "fanuc"):
self.controller = controller
self.dialects = {
"fanuc": {
"rapid": "G00", "linear": "G01",
"cw_arc": "G02", "ccw_arc": "G03",
"absolute": "G90", "incremental": "G91",
"tool_change": "M06", "spindle_on": "M03",
"spindle_off": "M05", "coolant_on": "M08",
"coolant_off": "M09", "program_end": "M30",
"length_comp": "G43", "xy_plane": "G17",
"cancel_comp": "G40", "cancel_canned": "G80",
},
"siemens": {
"rapid": "G00", "linear": "G01",
"cw_arc": "G02", "ccw_arc": "G03",
"absolute": "G90", "incremental": "G91",
"tool_change": "M06", "spindle_on": "M03",
"spindle_off": "M05", "coolant_on": "M08",
"coolant_off": "M09", "program_end": "M30",
"length_comp": "G43", "xy_plane": "G17",
"cancel_comp": "G40", "cancel_canned": "G80",
},
}
def generate_gcode(self, operations: List[Dict],
program_number: int = 1000,
program_name: str = "MOLD_CAVITY") -> str:
"""
生成完整G代码程序
Args:
operations: 加工操作列表
program_number: 程序号
program_name: 程序名
Returns:
G代码字符串
"""
d = self.dialects.get(self.controller, self.dialects["fanuc"])
lines = []
lines.append(f"%")
lines.append(f"O{program_number} ({program_name})")
lines.append(f"{d['xy_plane']} {d['cancel_comp']} {d['cancel_canned']} {d['absolute']}")
lines.append(f"G54")
lines.append("")
for op_idx, op in enumerate(operations):
strategy = op.get("strategy", "unknown")
tool_info = op.get("tool", {})
tool_id = tool_info.get("tool_id", "T01")
tool_num = op_idx + 1
lines.append(f"(=== 操作 {tool_num}: {strategy} ===)")
tool_type = tool_info.get("tool_type", "endmill")
tool_dia = tool_info.get("tool_diameter", 10)
lines.append(f"(刀具: {tool_type} D{tool_dia:.1f}mm)")
lines.append(f"T{tool_num:02d} {d['tool_change']}")
lines.append(f"{d['length_comp']} H{tool_num:02d} Z100.0")
rpm = tool_info.get("spindle_speed_rpm", 3000)
lines.append(f"S{rpm} {d['spindle_on']}")
lines.append(f"{d['rapid']} X0 Y0 Z10.0")
lines.append(f"{d['coolant_on']}")
lines.append("")
feed = tool_info.get("feed_rate_mm_min", 500)
levels = op.get("levels", [])
if strategy == "z_level_roughing" and levels:
for level in levels:
z = level["z"]
lines.append(f"(--- Z层 {level['level_index']}: Z={z:.2f} ---)")
lines.append(f"{d['linear']} Z{z:.2f} F{int(feed * 0.5)}")
lines.append(f"{d['linear']} X50.0 Y30.0 F{feed}")
lines.append(f"{d['linear']} X-50.0 Y30.0")
lines.append(f"{d['linear']} X-50.0 Y-30.0")
lines.append(f"{d['linear']} X50.0 Y-30.0")
lines.append(f"{d['rapid']} Z10.0")
lines.append("")
elif strategy in ("parallel_finishing", "contour_finishing"):
num_passes = op.get("num_passes", 10)
stepover = op.get("stepover", 0.3)
for i in range(num_passes):
y = i * stepover - 30
lines.append(f"{d['linear']} Z-5.0 F{int(feed * 0.3)}")
lines.append(f"{d['linear']} X50.0 Y{y:.2f} F{feed}")
lines.append(f"{d['linear']} X-50.0 Y{y:.2f}")
lines.append(f"{d['rapid']} Z5.0")
lines.append("")
else:
lines.append(f"(策略 {strategy} 的刀路数据)")
lines.append("")
lines.append(f"{d['coolant_off']}")
lines.append(f"{d['spindle_off']}")
lines.append(f"{d['rapid']} Z100.0")
lines.append("")
lines.append(f"{d['coolant_off']}")
lines.append(f"{d['spindle_off']}")
lines.append(f"G28 G91 Z0")
lines.append(f"G28 G91 X0 Y0")
lines.append(f"{d['program_end']}")
lines.append(f"%")
return "\n".join(lines)
class MoldCAMDesigner:
"""模具CAM综合设计器"""
def __init__(self):
self.tool_lib = ToolLibrary()
self.params_calc = CuttingParamsCalculator()
self.roughing_gen = RoughingToolpathGenerator()
self.finishing_gen = FinishingToolpathGenerator()
self.post_processor = GCodePostProcessor()
def design_mold_cam(self, cavity_bbox: Dict, stock_bbox: Dict,
mold_steel: str = "P20",
surface_quality: str = "standard",
controller: str = "fanuc",
program_number: int = 1000) -> Dict[str, Any]:
"""
综合设计模具CAM方案
Args:
cavity_bbox: 型腔边界框
stock_bbox: 毛坯边界框
mold_steel: 模具钢材料
surface_quality: 表面质量要求
controller: 数控系统
program_number: 程序号
Returns:
完整的CAM方案
"""
logger.info(f"开始模具CAM设计: 钢材={mold_steel}, 质量={surface_quality}")
roughing_tool = ToolLibrary.select_roughing_tool(
self._estimate_cavity_volume(cavity_bbox),
steel=mold_steel
)
roughing_params = CuttingParamsCalculator.calculate_all(roughing_tool)
finishing_tool = ToolLibrary.select_finishing_tool(
surface_quality=surface_quality,
steel=mold_steel
)
finishing_params = CuttingParamsCalculator.calculate_all(finishing_tool)
roughing_op = self.roughing_gen.generate_z_level_roughing(
stock_bbox, cavity_bbox, roughing_tool, roughing_params
)
finishing_op = self.finishing_gen.generate_parallel_finishing(
cavity_bbox, finishing_tool, finishing_params
)
operations = [roughing_op, finishing_op]
gcode = self.post_processor.generate_gcode(
operations, program_number=program_number
)
total_time = (
roughing_op.get("estimated_time_min", 0) +
finishing_op.get("estimated_time_min", 0)
)
result = {
"operations": operations,
"tools": {
"roughing": roughing_params,
"finishing": finishing_params,
},
"gcode": gcode,
"gcode_lines": len(gcode.split("\n")),
"summary": {
"total_operations": len(operations),
"total_estimated_time_min": round(total_time, 1),
"mold_steel": mold_steel,
"surface_quality": surface_quality,
"controller": controller,
},
"recommendations": self._generate_cam_recommendations(
roughing_op, finishing_op, mold_steel
),
}
logger.info(f"CAM设计完成: {len(operations)} 个工序, "
f"预计 {total_time:.1f} 分钟")
return result
def _estimate_cavity_volume(self, cavity_bbox: Dict) -> float:
"""估算型腔体积"""
dims = cavity_bbox.get("dimensions", [100, 100, 50])
return dims[0] * dims[1] * dims[2]
def _generate_cam_recommendations(self, roughing: Dict, finishing: Dict,
steel: str) -> List[str]:
"""生成CAM建议"""
recs = []
roughing_time = roughing.get("estimated_time_min", 0)
if roughing_time > 120:
recs.append("粗加工时间较长,建议使用更大直径刀具或增加切削深度")
finishing_roughness = finishing.get("surface_roughness_ra", 0)
if finishing_roughness > 0.8:
recs.append("表面粗糙度偏高,建议减小步距或使用更小直径球头刀")
if steel in ("S136", "H13"):
recs.append(f"高硬度钢材({steel}),建议使用涂层刀具并降低切削速度")
recs.append("建议增加半精加工工序减少精加工余量")
recs.append("加工前需确认工件坐标系零点位置")
recs.append("首件加工建议降低进给率20%进行试切")
return recs