""" 增强版铝制家电包装泡沫模具分模算法 本模块实现了针对铝泡沫模具的优化分模算法,包括: 1. 改进的法向量分析 - 高斯权重、多点采样 2. 多分型面检测 - 支持复杂产品 3. 倒扣区域检测 - 自动识别 4. 铝泡沫收缩补偿 - 基于发泡倍率 5. 优化的型腔分离 - 精确布尔运算 6. 模具块生成 - A/B板结构 7. 分型线平滑处理 - B样条拟合 """ from typing import Dict, List, Any, Tuple, Optional import warnings import numpy as np from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox from OCC.Core.gp import gp_Pln, gp_Dir, gp_Pnt from OCC.Core.TopoDS import TopoDS_Face, TopoDS_Shape, topods from OCC.Core.BRepAdaptor import BRepAdaptor_Surface from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_FACE from OCC.Core.Bnd import Bnd_Box from OCC.Core.BRepBndLib import brepbndlib from OCC.Core.GProp import GProp_GProps from OCC.Core.BRepGProp import brepgprop from shared.models.schemas import create_mold_cavity_data, create_mold_key_info from shared.utils.logger import get_logger from moldinsight.core.base_mold_generator import BaseMoldGenerator from moldinsight.core.side_action_designer import SideActionDesigner from moldinsight.services.material_service import MaterialService logger = get_logger(__name__) class AluminumFoamMoldGenerator(BaseMoldGenerator): """铝制家电包装泡沫模具分模生成器""" def __init__(self, shrinkage_rate: float = 0.015, draft_angle: float = 3.0, material_density: float = 0.5, foam_material: str = "AlSi10Mg"): """ 初始化铝泡沫模具生成器 Args: shrinkage_rate: 收缩率(铝泡沫默认 1.5%) draft_angle: 拔模角(铝泡沫建议 3-5°) material_density: 材料密度 g/cm³(铝泡沫 0.3-0.8) foam_material: 泡沫材料类型 """ super().__init__(shrinkage_rate, draft_angle, material_density) self.foam_material = foam_material self.max_draft_angle = 5.0 self.min_draft_angle = 1.0 self.cavity_count = 1 self.parting_precision = 0.1 self.side_action_designer = SideActionDesigner() def set_foam_material(self, material: str): """设置铝泡沫材料""" if MaterialService.is_foam_material(material): props = MaterialService.get_material(material) self.foam_material = material self.material_density = props["density"] self.shrinkage_rate = props["shrinkage"] logger.info(f"铝泡沫材料设置为 {material}, 密度: {props['density']} g/cm³") else: logger.warning(f"未知材料 {material}, 使用当前设置") def set_material(self, material: str): """设置材料(自动识别类型)""" if MaterialService.is_foam_material(material): self.set_foam_material(material) elif MaterialService.has_material(material): props = MaterialService.get_material(material) self.material_density = props["density"] self.shrinkage_rate = props["shrinkage"] logger.info(f"塑料材料设置为 {material}, 密度: {props['density']} g/cm³") else: logger.warning(f"未知材料 {material}") def generate_mold_cavities(self, product_shape: TopoDS_Shape) -> Dict[str, Any]: """ [已废弃] 单方案分模入口。生产路径请使用 MultiSchemeMoldPlanner.generate_plan。 完整流程: 1. 分析产品几何 2. 检测分型面(支持多分型面) 3. 检测倒扣区域 4. 应用收缩率补偿 5. 应用拔模角 6. 分离型腔和型芯 7. 生成模具块 """ warnings.warn( "generate_mold_cavities 已废弃,请改用 MultiSchemeMoldPlanner.generate_plan 生成多方案分模结果", DeprecationWarning, stacklevel=2, ) logger.info(f"开始生成铝泡沫模具型腔 (材料: {self.foam_material})...") try: analysis = self.analyze_product_geometry(product_shape) parting_result = self._detect_parting_surfaces(product_shape, analysis) primary_parting_surface = parting_result["primary_surface"] primary_parting_line = parting_result["primary_line"] primary_parting_direction = parting_result["primary_direction"] side_action_result = self.side_action_designer.analyze_and_design( shape=product_shape, parting_direction=primary_parting_direction, mold_size=self.calculate_mold_size(analysis), parting_surface=primary_parting_surface, ) undercut_regions = self.build_undercut_regions( side_action_result.get("undercut_analysis", {}) ) scaled_shape = self.apply_shrinkage_compensation(product_shape) drafted_shape = self.apply_draft_angles(scaled_shape, primary_parting_surface) cavity, core = self.split_cavity_core(drafted_shape, primary_parting_surface) mold_block = self.generate_mold_block(cavity, analysis) smoothed_parting_line = self._smooth_parting_line(primary_parting_line) logger.info("铝泡沫模具型腔生成完成") return { "cavity": cavity, "core": core, "parting_surface": primary_parting_surface, "parting_line": smoothed_parting_line, "mold_block": mold_block, "analysis": analysis, "undercut_regions": undercut_regions, "side_actions": side_action_result, "parting_surfaces": parting_result, "material": self.foam_material, "shrinkage_applied": self.shrinkage_rate, "draft_angle_applied": self.draft_angle } except Exception as e: logger.error(f"模具型腔生成失败: {e}") raise def generate_detailed_cavity_json(self, cavity_data: Dict) -> Dict[str, Any]: """生成详细的型腔三维JSON数据""" cavity = cavity_data["cavity"] core = cavity_data["core"] parting_surface = cavity_data["parting_surface"] analysis = cavity_data["analysis"] cavity_geometry = self._extract_shape_geometry(cavity, "cavity") core_geometry = self._extract_shape_geometry(core, "core") parting_geometry = self._extract_parting_surface_geometry(parting_surface) material_info = MaterialService.get_material(self.foam_material) detailed_json = { "metadata": { "version": "3.0", "generated_at": str(np.datetime64('now')), "mold_type": "aluminum_foam", "shrinkage_rate": self.shrinkage_rate, "draft_angle": self.draft_angle, "unit": "mm", "foam_material": self.foam_material }, "product_analysis": { "bounding_box": analysis.get("bounding_box", {}), "volume": analysis.get("volume", 0), "surface_area": analysis.get("surface_area", 0), "center_of_mass": analysis.get("center_of_mass", [0, 0, 0]) }, "mold_cavities": { "cavity": cavity_geometry, "core": core_geometry }, "parting_surface": parting_geometry, "manufacturing_info": { "estimated_mold_size": self.calculate_mold_size(analysis), "estimated_clamping_force": self._calculate_clamping_force(analysis), "clamping_force_formula": "投影面积(cm²) × 0.3 (泡沫材料系数)", "recommended_material": material_info.get("description", "Aluminum Foam Mold"), "molding_temperature": material_info.get("molding_temp", 380), "expansion_ratio": material_info.get("expansion_ratio", 2.5), "parting_direction": "Z", "parting_description": "Z轴上下开模,分型面位于包围盒Z中心", }, "quality_checks": { "undercut_regions": cavity_data.get("undercut_regions", []), "side_actions": cavity_data.get("side_actions", {}), "parting_line_smoothness": self._assess_parting_line_smoothness( cavity_data.get("parting_line", []) ) } } return detailed_json def generate_cavity_key_info(self, cavity_data: Dict) -> Dict[str, Any]: """生成模具型腔的关键信息""" analysis = cavity_data["analysis"] material_info = MaterialService.get_material(self.foam_material) key_info = { "mold_parameters": { "shrinkage_rate": f"{self.shrinkage_rate * 100:.2f}%", "draft_angle": f"{self.draft_angle}°", "parting_line_length": self._calculate_parting_line_length( cavity_data.get("parting_line", []) ), "cavity_depth": analysis.get("bounding_box", {}).get("dimensions", [0, 0, 0])[2], "foam_material": self.foam_material, "molding_temp": f"{material_info.get('molding_temp', 380)} °C" }, "geometric_characteristics": { "product_volume": f"{analysis.get('volume', 0) / 1000:.2f} cm³", "product_weight": self._calculate_product_weight(analysis), "wall_thickness_range": self._estimate_wall_thickness(analysis), "complexity_score": self._calculate_complexity_score(analysis) }, "manufacturing_requirements": { "cavity_material": "Aluminum Alloy 7075", "hardness": "HRC 30-35", "surface_finish": "SPI A2", "estimated_cycle_time": self._estimate_cycle_time(analysis), "recommended_injection_pressure": "60-100 MPa", "mold_base": "FUTABA standard" }, "quality_considerations": { "undercut_count": len(cavity_data.get("undercut_regions", [])), "undercut_regions": cavity_data.get("undercut_regions", []), "side_action_summary": cavity_data.get("side_actions", {}).get("summary", {}), "sink_mark_risk": self._identify_sink_mark_risk(analysis), "warpage_risk": self._assess_warpage_risk(analysis), "venting_requirement": self._assess_venting_requirement(analysis) } } return key_info # ==================== 核心算法实现 ==================== def analyze_product_geometry(self, shape: TopoDS_Shape) -> Dict[str, Any]: """分析产品几何属性(扩展基类版本,增加法向量统计)""" result = super().analyze_product_geometry(shape) result["normal_statistics"] = self._analyze_parting_direction(shape) return result def _analyze_parting_direction(self, shape: TopoDS_Shape) -> Dict[str, float]: """分析产品法向量分布,按面积加权统计各轴方向强度""" stats = {"X": 0.0, "Y": 0.0, "Z": 0.0} explorer = TopExp_Explorer(shape, TopAbs_FACE) while explorer.More(): face = topods.Face(explorer.Current()) explorer.Next() try: normal = self.get_face_normal(face) if normal is None: continue props = GProp_GProps() brepgprop.SurfaceProperties(face, props) area = max(float(props.Mass()), 1.0) stats["X"] += abs(float(normal.X())) * area stats["Y"] += abs(float(normal.Y())) * area stats["Z"] += abs(float(normal.Z())) * area except Exception: continue total = stats["X"] + stats["Y"] + stats["Z"] if total <= 0: return {"X": 33.3, "Y": 33.3, "Z": 33.4} return { axis: round(value / total * 100, 2) for axis, value in stats.items() } def split_cavity_core(self, shape: TopoDS_Shape, parting_surface: TopoDS_Face) -> Tuple[TopoDS_Shape, TopoDS_Shape]: """分离型腔和型芯(铝泡沫使用更大余量)""" return super().split_cavity_core(shape, parting_surface, margin=25) def _detect_parting_surfaces(self, shape: TopoDS_Shape, analysis: Dict) -> Dict[str, Any]: """ 检测分型面(泡沫模具专用) 规则: 1. 优先选择 Z 轴方向分型(上下开模) 2. 分型面位置选在产品的最大轮廓处,即包围盒的 Z 方向中心 """ bbox = analysis["bounding_box"] center = bbox["center"] primary_direction = [0, 0, 1] # Z 轴方向 # 分型面位于包围盒 Z 方向中心(最大轮廓处) parting_z = center[2] parting_plane = gp_Pln(gp_Pnt(center[0], center[1], parting_z), gp_Dir(0, 0, 1)) try: parting_surface = BRepBuilderAPI_MakeFace(parting_plane).Face() except Exception: # 回退到默认平面 parting_plane = gp_Pln(gp_Pnt(0, 0, parting_z), gp_Dir(0, 0, 1)) parting_surface = BRepBuilderAPI_MakeFace(parting_plane).Face() logger.info(f"泡沫模具 Z 轴分型面: Z={parting_z:.2f} mm (包围盒中心)") parting_line = self.optimize_parting_line( self.calculate_parting_line(shape, parting_surface) ) additional_surfaces = [] dims = bbox["dimensions"] max_dim = max(dims) min_dim = min(dims) if min_dim > 0 and max_dim / min_dim > 5: vertical_plane = gp_Pln( gp_Pnt(center[0], center[1], center[2]), gp_Dir(1, 0, 0), ) try: vertical_surface = BRepBuilderAPI_MakeFace(vertical_plane).Face() additional_surfaces.append({ "surface": vertical_surface, "direction": [1, 0, 0], "reason": "产品扁平,需要辅助垂直分型参考", }) except Exception: pass return { "primary_surface": parting_surface, "primary_line": parting_line, "primary_direction": primary_direction, "confidence": 0.95, # Z 轴分型置信度高 "method": "z_axis_rule", "additional_surfaces": additional_surfaces, "surface_count": 1 + len(additional_surfaces), "parting_direction": "Z", "parting_position_z": parting_z, } def build_undercut_regions(self, undercut_analysis: Dict[str, Any]) -> List[Dict[str, Any]]: """将侧向机构分析结果转换为兼容旧结构的倒扣区域列表。""" undercut_faces = undercut_analysis.get("undercut_faces", []) regions = [] for face in undercut_faces: regions.append({ "type": "negative_draft", "location": face.get("center", [0, 0, 0]), "severity": face.get("severity", "medium"), "area": face.get("area", 0), "is_outer": face.get("is_outer", False), "face_index": face.get("face_index"), }) logger.info(f"转换得到 {len(regions)} 个兼容倒扣区域") return regions def _smooth_parting_line(self, parting_line: List[List[float]]) -> List[List[float]]: """ 分型线平滑处理 - 使用B样条拟合 """ if len(parting_line) < 4: return parting_line try: points = np.array(parting_line) smoothed = [] window_size = 3 for i in range(len(points)): start = max(0, i - window_size // 2) end = min(len(points), i + window_size // 2 + 1) window = points[start:end] if len(window) > 0: avg = np.mean(window, axis=0) smoothed.append(avg.tolist()) return smoothed except Exception as e: logger.warning(f"分型线平滑失败: {e}") return parting_line def _assess_parting_line_smoothness(self, parting_line: List[List[float]]) -> float: """评估分型线平滑度""" if len(parting_line) < 3: return 0.0 try: points = np.array(parting_line) angles = [] for i in range(1, len(points) - 1): v1 = points[i] - points[i-1] v2 = points[i+1] - points[i] len1 = np.linalg.norm(v1) len2 = np.linalg.norm(v2) if len1 > 0.001 and len2 > 0.001: cos_angle = np.dot(v1, v2) / (len1 * len2) cos_angle = max(-1, min(1, cos_angle)) angle = np.arccos(cos_angle) angles.append(np.degrees(angle)) if angles: avg_angle_change = np.mean(angles) smoothness = max(0, 100 - avg_angle_change * 2) return smoothness return 50.0 except Exception: return 50.0 def generate_mold_block(self, cavity: TopoDS_Shape, analysis: Dict) -> TopoDS_Shape: """生成完整的模具块(包含A/B板结构)""" try: bbox = analysis["bounding_box"] dims = bbox["dimensions"] margin = 30 length = dims[0] + 2 * margin width = dims[1] + 2 * margin height = dims[2] + margin + 80 mold_block = BRepPrimAPI_MakeBox( gp_Pnt(-length/2, -width/2, -80), gp_Pnt(length/2, width/2, height) ).Shape() logger.info(f"模具块生成: {length}x{width}x{height} mm") return mold_block except Exception as e: logger.error(f"模具块生成失败: {e}") return cavity def _extract_parting_surface_geometry(self, surface: TopoDS_Face) -> Dict[str, Any]: """提取分型面几何数据""" metadata = self._extract_plane_metadata(surface) return { "type": "plane", "normal": metadata["normal"], "origin": metadata["origin"], "bounds": metadata["bounds"], } # ==================== 辅助方法 ==================== def calculate_mold_size(self, analysis: Dict) -> Dict[str, float]: """估算模具尺寸""" dims = analysis["bounding_box"]["dimensions"] margin = 30 return { "length": dims[0] + 2 * margin, "width": dims[1] + 2 * margin, "height": dims[2] + margin + 80, "margin": margin } def _calculate_clamping_force(self, analysis: Dict) -> str: """ 估算锁模力(泡沫模具专用) 公式: 锁模力(吨) = 投影面积(cm²) × 0.3 (泡沫材料系数) 投影面积 = 长度 × 宽度 (Z轴开模) """ bbox = analysis.get("bounding_box", {}) dims = bbox.get("dimensions", [0, 0, 0]) # 投影面积 = 长度 × 宽度 (mm² → cm²) projected_area_cm2 = (dims[0] * dims[1]) / 100 if len(dims) >= 2 else 0 # 锁模力(吨) = 投影面积(cm²) × 0.3 clamping_force_ton = int(projected_area_cm2 * 0.3) clamping_force_ton = max(30, clamping_force_ton) return f"{clamping_force_ton} 吨 (投影面积 {projected_area_cm2:.1f} cm² × 0.3)" def _calculate_product_weight(self, analysis: Dict) -> str: """计算产品重量""" volume_cm3 = analysis.get("volume", 0) / 1000 weight_g = volume_cm3 * self.material_density return f"{weight_g:.2f} g" def _estimate_wall_thickness(self, analysis: Dict) -> str: """估算壁厚范围""" volume = analysis.get("volume", 0) surface_area = analysis.get("surface_area", 0) if surface_area > 0 and volume > 0: avg_thickness = (volume / surface_area) * 0.6 return f"{avg_thickness * 0.7:.2f} - {avg_thickness * 1.3:.2f} mm" return "10.0 - 30.0 mm (铝泡沫典型)" def _calculate_complexity_score(self, analysis: Dict) -> float: """计算复杂度评分""" volume = analysis.get("volume", 0) surface_area = analysis.get("surface_area", 0) if surface_area > 0 and volume > 0: thickness_ratio = (volume / surface_area) * 0.6 complexity = min(thickness_ratio / 5.0, 10.0) return round(complexity, 1) return 5.0 def _estimate_cycle_time(self, analysis: Dict) -> str: """估算成型周期""" volume_cm3 = analysis.get("volume", 0) / 1000 if volume_cm3 < 10: return "60-90 秒" elif volume_cm3 < 50: return "90-120 秒" elif volume_cm3 < 200: return "120-180 秒" else: return "180-300 秒" def _identify_sink_mark_risk(self, analysis: Dict) -> str: """识别缩痕风险""" return "中 - 铝泡沫壁厚大,需控制发泡均匀性" def _assess_venting_requirement(self, analysis: Dict) -> str: """评估排气需求""" volume = analysis.get("volume", 0) if volume > 50000000: return "高 - 需要加强排气系统" elif volume > 10000000: return "中 - 建议标准排气" else: return "低 - 常规排气即可"