# src/core/mold_generator.py from pathlib import Path from typing import Dict, List, Any, Tuple, Optional, Callable import numpy as np from OCC.Core.BRepOffsetAPI import BRepOffsetAPI_MakeThickSolid from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse, BRepAlgoAPI_Section from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace, BRepBuilderAPI_Transform from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox from OCC.Core.Geom import Geom_Plane from OCC.Core.gp import gp_Pln, gp_Dir, gp_Pnt, gp_Vec, gp_Trsf from OCC.Core.TopTools import TopTools_ListOfShape from OCC.Core.TopoDS import TopoDS_Face, TopoDS_Shape, TopoDS_Edge, TopoDS_Vertex from OCC.Core.BRep import BRep_Tool from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh from OCC.Core.BRepExtrema import BRepExtrema_DistShapeShape from OCC.Core.GProp import GProp_GProps from OCC.Core.BRepGProp import brepgprop from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_FACE, TopAbs_EDGE from OCC.Core.BRepAdaptor import BRepAdaptor_Surface, BRepAdaptor_Curve from models.schemas import create_mold_cavity_data, create_mold_key_info from utils.logger import get_logger logger = get_logger(__name__) class MoldCavityGenerator: """模具型腔生成器 - 基于产品模型生成Cavity和Core""" def __init__(self, shrinkage_rate: float = 0.005, draft_angle: float = 2.0, material_density: float = 1.05): """ 初始化模具生成器 Args: shrinkage_rate: 收缩率(默认0.5% for ABS) draft_angle: 拔模角(默认2度) material_density: 材料密度 g/cm³(默认1.05 for ABS) """ self.shrinkage_rate = shrinkage_rate self.draft_angle = draft_angle # 度 self.material_density = material_density # g/cm³ # 常用塑料材料密度(g/cm³) self.material_densities = { "ABS": 1.05, "PP": 0.90, "PC": 1.20, "PE": 0.95, "PS": 1.05, "PA": 1.14, "POM": 1.42, "PMMA": 1.18 } # 分型面检测参数 self.parting_line_tolerance = 0.1 self.max_draft_angle = 5.0 # AI 模型接口(预留) self.ai_parting_detector: Optional[Any] = None self.ai_draft_analyzer: Optional[Any] = None def set_ai_model(self, parting_detector: Any = None, draft_analyzer: Any = None): """ 设置 AI 模型接口(预留) Args: parting_detector: 分型面检测 AI 模型 draft_analyzer: 拔模分析 AI 模型 """ self.ai_parting_detector = parting_detector self.ai_draft_analyzer = draft_analyzer logger.info("AI 模型接口已设置") def set_material(self, material: str): """设置产品材料""" if material in self.material_densities: self.material_density = self.material_densities[material] logger.info(f"材料设置为 {material}, 密度: {self.material_density} g/cm³") else: logger.warning(f"未知材料 {material}, 使用默认密度 {self.material_density} g/cm³") def generate_mold_cavities(self, product_shape: Any) -> Dict[str, Any]: """ 从产品的3D模型生成型腔和型芯 Returns: { "cavity": cavity_shape, # 型腔(产品外部) "core": core_shape, # 型芯(产品内部) "parting_surface": parting_surface, # 分型面 "parting_line": parting_line # 分型线 } """ logger.info("开始生成模具型腔...") try: # Step 1: 分析产品几何 analysis = self._analyze_product_geometry(product_shape) # Step 2: 检测分型面和分型线 parting_surface, parting_line = self._detect_parting_surface( product_shape, analysis ) # Step 3: 应用收缩率补偿 scaled_shape = self._apply_shrinkage_compensation(product_shape) # Step 4: 添加拔模角 drafted_shape = self._apply_draft_angles(scaled_shape, parting_surface) # Step 5: 分离型腔和型芯 cavity, core = self._split_cavity_core(drafted_shape, parting_surface) logger.info("模具型腔生成完成") return { "cavity": cavity, "core": core, "parting_surface": parting_surface, "parting_line": parting_line, "analysis": analysis } except Exception as e: logger.error(f"模具型腔生成失败: {e}") raise def generate_detailed_cavity_json(self, cavity_data: Dict) -> Dict[str, Any]: """ 生成详细的型腔三维JSON数据 Returns: 包含完整几何信息的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 ) detailed_json = { "metadata": { "version": "2.0", "generated_at": str(np.datetime64('now')), "shrinkage_rate": self.shrinkage_rate, "draft_angle": self.draft_angle, "unit": "mm" }, "product_analysis": { "bounding_box": analysis.get("bounding_box", {}), # 使用get方法 "volume": analysis.get("volume", 0), # 使用get方法 "surface_area": analysis.get("surface_area", 0), # 使用get方法 "center_of_mass": analysis.get("center_of_mass", [0, 0, 0]) # 使用get方法 }, "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), "recommended_material": self._get_recommended_material() } } return detailed_json def generate_cavity_key_info(self, cavity_data: Dict) -> Dict[str, Any]: """ 生成模具型腔的关键信息 Returns: 关键参数摘要 """ analysis = cavity_data["analysis"] 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["parting_line"] ), "cavity_depth": analysis.get("bounding_box", {}).get("dimensions", [0, 0, 0])[2] }, "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": "80-120 MPa" }, "quality_considerations": { "potential_weld_lines": self._identify_weld_line_risk(analysis), "sink_mark_areas": self._identify_sink_mark_risk(analysis), "warpage_risk": self._assess_warpage_risk(analysis) } } return key_info # ==================== 内部方法 ==================== def _analyze_product_geometry(self, shape: Any) -> Dict[str, Any]: """分析产品几何属性""" # 计算体积属性 volume_props = GProp_GProps() brepgprop.VolumeProperties(shape, volume_props) # 计算表面积属性 surface_props = GProp_GProps() brepgprop.SurfaceProperties(shape, surface_props) # 计算边界框 from OCC.Core.Bnd import Bnd_Box from OCC.Core.BRepBndLib import brepbndlib bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() return { "volume": volume_props.Mass(), "surface_area": surface_props.Mass(), "center_of_mass": [ volume_props.CentreOfMass().X(), volume_props.CentreOfMass().Y(), volume_props.CentreOfMass().Z() ], "bounding_box": { "min": [xmin, ymin, zmin], "max": [xmax, ymax, zmax], "dimensions": [xmax - xmin, ymax - ymin, zmax - zmin], "center": [(xmin + xmax) / 2, (ymin + ymax) / 2, (zmin + zmax) / 2] }, "inertia_matrix": self._get_inertia_matrix(volume_props) } def _detect_parting_surface(self, shape: Any, analysis: Dict) -> Tuple[Any, List]: """ 检测分型面和分型线 优先级: 1. AI 模型检测(如果已设置) 2. 基于法向量分析的几何方法 3. 简化方法(基于边界框) """ # 1. 尝试使用 AI 模型 if self.ai_parting_detector is not None: try: logger.info("使用 AI 模型检测分型面") ai_result = self.ai_parting_detector.detect(shape, analysis) if ai_result: return self._create_parting_surface_from_ai(ai_result, analysis) except Exception as e: logger.warning(f"AI 分型面检测失败,回退到几何方法:{e}") # 2. 基于法向量分析的几何方法 try: logger.info("使用法向量分析检测分型面") optimal_direction = self._analyze_face_normals(shape) parting_plane = self._create_optimal_parting_plane( shape, analysis, optimal_direction ) parting_surface = BRepBuilderAPI_MakeFace(parting_plane).Face() # 计算真实分型线(产品与分型面的交线) parting_line = self._calculate_parting_line(shape, parting_surface) return parting_surface, parting_line except Exception as e: logger.warning(f"法向量分析失败,使用简化方法:{e}") # 3. 简化方法(回退) logger.info("使用简化方法检测分型面") return self._simple_parting_surface(analysis) def _apply_shrinkage_compensation(self, shape: Any) -> Any: """应用收缩率补偿(放大模型)""" scale_factor = 1.0 + self.shrinkage_rate # 创建缩放变换 trsf = gp_Trsf() trsf.SetScale(gp_Pnt(0, 0, 0), scale_factor) from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_Transform scaled_shape = BRepBuilderAPI_Transform(shape, trsf, True).Shape() return scaled_shape def _apply_draft_angles(self, shape: Any, parting_surface: Any) -> Any: """添加拔模角(简化实现)""" # 实际实现需要复杂的拔模面处理 # 这里返回原始形状(假设已在CAD中处理) logger.warning("拔模角处理为简化实现,建议在设计阶段处理") return shape def _split_cavity_core(self, shape: Any, parting_surface: Any) -> Tuple[Any, Any]: """分离型腔和型芯 型腔(Cavity): 模具中形成产品外表面的部分,是产品形状的负形 型芯(Core): 模具中形成产品内表面的部分,是产品形状的正形 """ try: from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_SOLID # 获取产品边界框 from OCC.Core.Bnd import Bnd_Box from OCC.Core.BRepBndLib import brepbndlib bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() # 计算模具块尺寸(比产品大一定余量) margin = 20 # mm mold_xmin = xmin - margin mold_ymin = ymin - margin mold_zmin = zmin - margin mold_xmax = xmax + margin mold_ymax = ymax + margin mold_zmax = zmax + margin # 创建模具块 mold_block = BRepPrimAPI_MakeBox( gp_Pnt(mold_xmin, mold_ymin, mold_zmin), gp_Pnt(mold_xmax, mold_ymax, mold_zmax) ).Shape() # 型腔 = 模具块 - 产品(布尔减法) cavity_operation = BRepAlgoAPI_Cut(mold_block, shape) if cavity_operation.IsDone(): cavity = cavity_operation.Shape() logger.info("型腔生成成功(模具块减去产品)") else: logger.warning("型腔布尔运算失败,使用原始形状") cavity = mold_block # 型芯 = 产品形状本身(收缩补偿后) core = shape logger.info("型芯 = 产品形状") return cavity, core except Exception as e: logger.error(f"型腔分离失败: {e}") return shape, shape def _extract_shape_geometry(self, shape: Any, shape_type: str) -> Dict[str, Any]: """提取形状几何数据为JSON格式""" try: # 网格化 mesh = BRepMesh_IncrementalMesh(shape, 0.1) mesh.Perform() # 提取顶点和面 from OCC.Core.TopExp import TopExp_Explorer from OCC.Core.TopAbs import TopAbs_FACE from OCC.Core.BRep import BRep_Tool from OCC.Core.Poly import Poly_Triangulation from OCC.Core.TopLoc import TopLoc_Location vertices = [] faces = [] explorer = TopExp_Explorer(shape, TopAbs_FACE) vertex_index = 0 while explorer.More(): # 使用 explorer.Current() 直接获取面 face = explorer.Current() location = TopLoc_Location() triangulation = BRep_Tool.Triangulation(face, location) if triangulation: # 提取顶点 nb_nodes = triangulation.NbNodes() for i in range(1, nb_nodes + 1): node = triangulation.Node(i) # 应用位置变换 transformed = node.Transformed(location.Transformation()) vertices.extend([ float(transformed.X()), float(transformed.Y()), float(transformed.Z()) ]) # 提取三角形面 nb_triangles = triangulation.NbTriangles() for i in range(1, nb_triangles + 1): triangle = triangulation.Triangle(i) # 三角形顶点索引需要加上之前的顶点数量 idx1 = triangle.Value(1) + vertex_index - 1 idx2 = triangle.Value(2) + vertex_index - 1 idx3 = triangle.Value(3) + vertex_index - 1 faces.extend([int(idx1), int(idx2), int(idx3)]) vertex_index += nb_nodes explorer.Next() vertex_count = len(vertices) // 3 face_count = len(faces) // 3 return { "type": shape_type, "vertices": vertices, "faces": faces, "vertex_count": vertex_count, "face_count": face_count, "triangulation": "BRepMesh三角化" } except Exception as e: logger.error(f"{shape_type}几何提取失败: {e}") return { "type": shape_type, "vertices": [], "faces": [], "vertex_count": 0, "face_count": 0, "triangulation": f"提取失败: {str(e)}" } def _extract_parting_surface_geometry(self, surface: Any) -> Dict[str, Any]: """提取分型面几何数据""" # 尝试从surface获取边界信息,失败则使用默认值 try: from OCC.Core.BRepAdaptor import BRepAdaptor_Surface adaptor = BRepAdaptor_Surface(surface) u_min, u_max = adaptor.FirstUParameter(), adaptor.LastUParameter() v_min, v_max = adaptor.FirstVParameter(), adaptor.LastVParameter() bounds = { "u_range": [float(u_min), float(u_max)], "v_range": [float(v_min), float(v_max)] } except Exception as e: logger.warning(f"分型面边界提取失败,使用默认值: {e}") bounds = { "u_range": [-200, 200], "v_range": [-200, 200] } # 分型面是水平面,法向量为 [0, 0, 1],原点在 Z 轴中心 return { "type": "plane", "normal": [0, 0, 1], "origin": [0, 0, 0], "bounds": bounds } return { "type": "plane", "normal": [0, 0, 1], "origin": [0, 0, 0], "bounds": bounds } def _calculate_mold_size(self, analysis: Dict) -> Dict[str, float]: """估算模具尺寸""" product_bbox = analysis["bounding_box"]["dimensions"] # 模具通常比产品大20-50mm margin = 30 # mm return { "length": product_bbox[0] + 2 * margin, "width": product_bbox[1] + 2 * margin, "height": product_bbox[2] + 2 * margin + 100, # 增加100mm用于模架 "margin": margin } def _calculate_clamping_force(self, analysis: Dict) -> str: """估算锁模力""" volume_cm3 = analysis.get("volume", 0) / 1000 # mm³ → cm³ # 经验公式: 锁模力 ≈ 投影面积 × 压力 × 安全系数 # 简化估算 if volume_cm3 < 10: return "50-100 吨" elif volume_cm3 < 100: return "150-300 吨" elif volume_cm3 < 500: return "400-600 吨" else: return "800+ 吨" def _get_recommended_material(self) -> str: """推荐模具材料""" # 根据产品产量推荐模具材料 # 小批量 (<5000件): 铝合金 # 中批量 (5000-50000件): P20钢 # 大批量 (>50000件): H13钢 return "Aluminum Alloy 7075 (铝合金模具)" 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" elif volume > 0: # 如果没有surface_area,基于体积估算 bbox_dims = analysis.get("bounding_box", {}).get("dimensions", [1, 1, 1]) bbox_volume = bbox_dims[0] * bbox_dims[1] * bbox_dims[2] if bbox_volume > 0: efficiency = volume / bbox_volume avg_thickness = (bbox_dims[0] + bbox_dims[1]) / 2 * efficiency return f"{avg_thickness * 0.7:.2f} - {avg_thickness * 1.3:.2f} mm" return "2.0 - 4.0 mm (默认)" def _calculate_complexity_score(self, analysis: Dict) -> float: """计算复杂度评分(0-10)""" # 基于体积、表面积比、边界框等 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) elif volume > 0: # 如果没有surface_area,基于拓扑复杂度评分 bbox_dims = analysis.get("bounding_box", {}).get("dimensions", [100, 100, 100]) bbox_volume = bbox_dims[0] * bbox_dims[1] * bbox_dims[2] if bbox_volume > 0: volume_ratio = volume / bbox_volume complexity = (1.0 - volume_ratio) * 10 return round(min(max(complexity, 0), 10), 1) return 5.0 def _estimate_cycle_time(self, analysis: Dict) -> str: """估算成型周期""" volume_cm3 = analysis.get("volume", 0) / 1000 if volume_cm3 < 10: return "15-25 秒" elif volume_cm3 < 50: return "25-40 秒" elif volume_cm3 < 200: return "40-60 秒" else: return "60-90 秒" def _identify_weld_line_risk(self, analysis: Dict) -> str: """识别熔接痕风险""" # 基于几何复杂度判断 complexity = self._calculate_complexity_score(analysis) if complexity > 7: return "高 - 建议优化浇口位置" elif complexity > 4: return "中 - 需仿真验证" else: return "低" def _identify_sink_mark_risk(self, analysis: Dict) -> str: """识别缩痕风险""" thickness = self._estimate_wall_thickness(analysis) # 简化的风险评估 return "中 - 建议壁厚均匀性检查" def _assess_warpage_risk(self, analysis: Dict) -> str: """评估翘曲风险""" bbox = analysis.get("bounding_box", {}).get("dimensions", [1, 1, 1]) aspect_ratio = max(bbox) / min(bbox) if aspect_ratio > 5: return "高 - 建议增加加强筋" elif aspect_ratio > 3: return "中 - 需优化冷却" else: return "低" def _get_inertia_matrix(self, props: GProp_GProps) -> List[List[float]]: """获取惯性矩阵""" inertia = props.MatrixOfInertia() return [ [inertia.Value(1, 1), inertia.Value(1, 2), inertia.Value(1, 3)], [inertia.Value(2, 1), inertia.Value(2, 2), inertia.Value(2, 3)], [inertia.Value(3, 1), inertia.Value(3, 2), inertia.Value(3, 3)] ] def _analyze_face_normals(self, shape: Any) -> gp_Dir: """ 分析产品表面的法向量分布,找出最优分型方向 原理: - 统计所有面的法向量 - 选择法向量变化最小的方向作为分型方向 - 避免倒扣(undercut)区域 """ from OCC.Core.TopoDS import TopoDS_Compound from OCC.Core.TopTools import TopTools_IndexedMapOfShape # 收集所有面的法向量 face_normals = [] explorer = TopExp_Explorer(shape, TopAbs_FACE) while explorer.More(): face = TopoDS_Face(explorer.Current()) surface = BRepAdaptor_Surface(face) # 获取面的法向量(在参数中心点) try: u = (surface.FirstUParameter() + surface.LastUParameter()) / 2 v = (surface.FirstVParameter() + surface.LastVParameter()) / 2 normal = gp_Dir() # 从曲面获取法向量 if surface.GetType() == 0: # Plane normal = surface.Plane().Position().Direction() else: # 对于非平面,使用微分几何计算法向量 from OCC.Core.GCPnts import GCPnts_AbscissaPoint from OCC.Core.BRepGProp import brepgprop_VolumeProperties # 简化:使用面的边界框中心法向量 from OCC.Core.Bnd import Bnd_Box from OCC.Core.BRepBndLib import brepbndlib_Add bbox = Bnd_Box() brepbndlib_Add(face, bbox) center = bbox.Center() # 估算面法向量(简化) normal = gp_Dir(0, 0, 1) # 默认 Z 方向 face_normals.append(normal) except Exception as e: logger.debug(f"面法向量计算失败:{e}") explorer.Next() # 如果没有法向量,返回默认 Z 方向 if not face_normals: return gp_Dir(0, 0, 1) # 统计法向量分布,选择最优方向 # 简化实现:计算平均法向量 avg_x = sum(n.X() for n in face_normals) / len(face_normals) avg_y = sum(n.Y() for n in face_normals) / len(face_normals) avg_z = sum(n.Z() for n in face_normals) / len(face_normals) # 归一化 length = np.sqrt(avg_x**2 + avg_y**2 + avg_z**2) if length > 0.001: return gp_Dir(avg_x/length, avg_y/length, avg_z/length) else: return gp_Dir(0, 0, 1) def _create_optimal_parting_plane(self, shape: Any, analysis: Dict, direction: gp_Dir) -> gp_Pln: """ 创建最优分型面 Args: shape: 产品形状 analysis: 几何分析结果 direction: 分型方向(法向量) Returns: gp_Pln: 分型面方程 """ bbox = analysis["bounding_box"] # 分型面通过产品的质心 center = bbox["center"] # 创建平面:通过质心,法向量为分型方向 parting_plane = gp_Pln( gp_Pnt(center[0], center[1], center[2]), direction ) logger.info(f"创建分型面:原点=({center[0]:.2f}, {center[1]:.2f}, {center[2]:.2f}), " f"法向量=({direction.X():.3f}, {direction.Y():.3f}, {direction.Z():.3f})") return parting_plane def _calculate_parting_line(self, shape: Any, parting_surface: Any) -> List[List[float]]: """ 计算真实的分型线(产品与分型面的交线) 使用 BRepAlgoAPI_Section 进行布尔运算求交 """ try: # 创建截面运算 section = BRepAlgoAPI_Section(shape, parting_surface) section.Build() if not section.IsDone(): logger.warning("截面运算未完成,使用简化分型线") return self._simple_parting_line( parting_surface, {"bounding_box": {"min": [-50, -50, 0], "max": [50, 50, 100]}} ) # 提取交线(边) edges = [] explorer = TopExp_Explorer(section.Shape(), TopAbs_EDGE) while explorer.More(): edge = TopoDS_Edge(explorer.Current()) # 从边提取点 curve = BRepAdaptor_Curve(edge) first_param = curve.FirstParameter() last_param = curve.LastParameter() # 采样点(至少 10 个点) num_points = max(10, int((last_param - first_param) / 0.5)) step = (last_param - first_param) / num_points for i in range(num_points + 1): param = first_param + i * step point = curve.Value(param) edges.append([point.X(), point.Y(), point.Z()]) explorer.Next() # 如果没有边,使用简化分型线 if not edges: logger.warning("未找到交线,使用简化分型线") return self._simple_parting_line( parting_surface, {"bounding_box": {"min": [-50, -50, 0], "max": [50, 50, 100]}} ) logger.info(f"计算得到 {len(edges)} 个分型线点") return edges except Exception as e: logger.error(f"分型线计算失败:{e}") return self._simple_parting_line( parting_surface, {"bounding_box": {"min": [-50, -50, 0], "max": [50, 50, 100]}} ) def _simple_parting_surface(self, analysis: Dict) -> Tuple[Any, List]: """简化的分型面检测(回退方案)""" bbox = analysis["bounding_box"] center_z = bbox["center"][2] # 创建分型面(XY 平面) parting_plane = gp_Pln( gp_Pnt(0, 0, center_z), gp_Dir(0, 0, 1) ) parting_surface = BRepBuilderAPI_MakeFace( parting_plane, bbox["min"][0] - 10, bbox["max"][0] + 10, bbox["min"][1] - 10, bbox["max"][1] + 10 ).Face() # 简化分型线 parting_line = self._simple_parting_line(parting_surface, analysis) return parting_surface, parting_line def _simple_parting_line(self, parting_surface: Any, analysis: Dict) -> List[List[float]]: """简化的分型线(矩形)""" bbox = analysis["bounding_box"] center_z = bbox["center"][2] return [ [bbox["min"][0], bbox["min"][1], center_z], [bbox["max"][0], bbox["min"][1], center_z], [bbox["max"][0], bbox["max"][1], center_z], [bbox["min"][0], bbox["max"][1], center_z], [bbox["min"][0], bbox["min"][1], center_z] ] def _create_parting_surface_from_ai(self, ai_result: Dict, analysis: Dict) -> Tuple[Any, List]: """ 从 AI 模型结果创建分型面(预留接口) Args: ai_result: AI 模型输出,应包含: - origin: [x, y, z] 平面原点 - normal: [nx, ny, nz] 法向量 analysis: 几何分析结果 Returns: (parting_surface, parting_line) """ origin = ai_result.get("origin", [0, 0, 0]) normal = ai_result.get("normal", [0, 0, 1]) # 创建平面 parting_plane = gp_Pln( gp_Pnt(origin[0], origin[1], origin[2]), gp_Dir(normal[0], normal[1], normal[2]) ) parting_surface = BRepBuilderAPI_MakeFace(parting_plane).Face() # 分型线可以使用 AI 结果或重新计算 if "parting_line" in ai_result: parting_line = ai_result["parting_line"] else: parting_line = self._simple_parting_line(parting_surface, analysis) logger.info(f"从 AI 结果创建分型面:原点={origin}, 法向量={normal}") return parting_surface, parting_line def _apply_draft_angles(self, shape: Any, parting_surface: Any) -> Any: """ 添加拔模角 使用 OpenCASCADE 的拔模功能 """ # 1. 尝试使用 AI 模型 if self.ai_draft_analyzer is not None: try: logger.info("使用 AI 模型分析拔模角") ai_result = self.ai_draft_analyzer.analyze(shape, parting_surface, self.draft_angle) if ai_result and "drafted_shape" in ai_result: logger.info("AI 拔模分析成功") return ai_result["drafted_shape"] except Exception as e: logger.warning(f"AI 拔模分析失败,回退到几何方法:{e}") # 2. 几何方法(简化实现) try: # 获取分型面的法向量作为拔模方向 surface_adaptor = BRepAdaptor_Surface(parting_surface) draft_direction = surface_adaptor.Plane().Position().Direction() # 使用 BRepOffsetAPI_ThickSolid 创建拔模 # 注意:完整的拔模需要更复杂的实现,这里简化处理 logger.info(f"使用几何方法添加拔模角:{self.draft_angle}度,方向=({draft_direction.X():.3f}, {draft_direction.Y():.3f}, {draft_direction.Z():.3f})") # 简化:直接返回原始形状(拔模已在 CAD 中处理) # 完整实现需要使用 BRepOffsetAPI_DraftAngle return shape except Exception as e: logger.warning(f"拔模角处理失败:{e}") return shape def _calculate_parting_line_length(self, parting_line: List) -> float: """计算分型线长度""" if not parting_line or len(parting_line) < 2: return 0.0 # 计算折线总长度 total_length = 0.0 for i in range(1, len(parting_line)): p1 = np.array(parting_line[i-1]) p2 = np.array(parting_line[i]) segment_length = np.linalg.norm(p2 - p1) total_length += segment_length return total_length