""" 模具刀路设计与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