Files
geMoldInsight/src/core/mold_cam.py
T
2026-04-19 23:41:35 +08:00

647 lines
24 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
模具刀路设计与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