from typing import Dict, List, Any, Tuple, Optional import math import numpy as np from OCC.Core.BRepOffsetAPI import BRepOffsetAPI_DraftAngle from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Section, BRepAlgoAPI_Common, BRepAlgoAPI_Fuse from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace, BRepBuilderAPI_Transform from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeHalfSpace from OCC.Core.gp import gp_Pln, gp_Dir, gp_Pnt, gp_Trsf, gp_Ax2 from OCC.Core.TopoDS import TopoDS_Face, topods from OCC.Core.BRep import BRep_Tool from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh 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 OCC.Core.Bnd import Bnd_Box from OCC.Core.BRepBndLib import brepbndlib from OCC.Core.TopLoc import TopLoc_Location from models.schemas import create_mold_cavity_data, create_mold_key_info from utils.logger import get_logger logger = get_logger(__name__) class BaseMoldGenerator: """模具生成器基类 - 提供共用方法""" def __init__(self, shrinkage_rate: float = 0.005, draft_angle: float = 2.0, material_density: float = 1.05): self.shrinkage_rate = shrinkage_rate self.draft_angle = draft_angle self.material_density = material_density 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): self.ai_parting_detector = parting_detector self.ai_draft_analyzer = draft_analyzer logger.info("AI 模型接口已设置") 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) try: scaled_shape = BRepBuilderAPI_Transform(shape, trsf, True).Shape() logger.info(f"收缩率补偿: {self.shrinkage_rate*100:.2f}%, 缩放因子: {scale_factor:.4f}") return scaled_shape except Exception as e: logger.warning(f"收缩率补偿失败: {e}") return shape def _apply_draft_angles(self, shape: Any, parting_surface: Any) -> Any: try: draft_direction = self._get_draft_direction(parting_surface) if draft_direction is None: logger.warning("无法确定拔模方向,跳过拔模处理") return shape draft_angle_rad = math.radians(self.draft_angle) draftable_faces = self._find_draftable_faces(shape, draft_direction) if not draftable_faces: logger.info("未找到需要拔模的面,跳过拔模处理") return shape logger.info(f"应用拔模角: {self.draft_angle}°, {len(draftable_faces)} 个面") drafted_shape = self._execute_draft(shape, draftable_faces, draft_direction, draft_angle_rad) return drafted_shape except Exception as e: logger.warning(f"拔模角处理失败,返回原始形状: {e}") return shape def _get_draft_direction(self, parting_surface: Any) -> Optional[gp_Dir]: try: surface = BRepAdaptor_Surface(parting_surface) if surface.GetType() == 0: return surface.Plane().Position().Direction() return gp_Dir(0, 0, 1) except Exception: return gp_Dir(0, 0, 1) def _find_draftable_faces(self, shape: Any, draft_direction: gp_Dir) -> List[Any]: draftable = [] explorer = TopExp_Explorer(shape, TopAbs_FACE) while explorer.More(): face = topods.Face(explorer.Current()) normal = self._get_face_normal(face) if normal is not None: dot = abs(normal.Dot(draft_direction)) angle = math.degrees(math.acos(min(dot, 1.0))) if 5.0 < angle < 85.0: draftable.append(face) explorer.Next() return draftable def _get_face_normal(self, face: Any) -> Optional[gp_Dir]: try: surface = BRepAdaptor_Surface(face) u = (surface.FirstUParameter() + surface.LastUParameter()) / 2 v = (surface.FirstVParameter() + surface.LastVParameter()) / 2 if surface.GetType() == 0: return surface.Plane().Position().Direction() from OCC.Core.BRepLProp import BRepLProp_SLProps props = BRepLProp_SLProps(surface, 1, 0.001) props.SetParameters(u, v) if props.IsNormalDefined(): return props.Normal() return None except Exception: return None def _execute_draft(self, shape: Any, faces: List[Any], draft_direction: gp_Dir, draft_angle_rad: float) -> Any: try: draft = BRepOffsetAPI_DraftAngle(shape) for face in faces: try: normal = self._get_face_normal(face) if normal is None: continue dot = normal.Dot(draft_direction) if dot > 0: face_dir = draft_direction else: face_dir = gp_Dir(-draft_direction.X(), -draft_direction.Y(), -draft_direction.Z()) draft.Add(face, face_dir, draft_angle_rad, True) except Exception: continue draft.Build() if draft.IsDone(): logger.info(f"拔模角应用成功: {len(faces)} 个面, {self.draft_angle}°") return draft.Shape() else: logger.warning("BRepOffsetAPI_DraftAngle 构建失败,尝试逐面拔模") return self._draft_faces_sequentially(shape, faces, draft_direction, draft_angle_rad) except Exception as e: logger.warning(f"拔模执行失败: {e}") return shape def _draft_faces_sequentially(self, shape: Any, faces: List[Any], draft_direction: gp_Dir, draft_angle_rad: float) -> Any: current_shape = shape success_count = 0 for face in faces: try: draft = BRepOffsetAPI_DraftAngle(current_shape) normal = self._get_face_normal(face) if normal is None: continue dot = normal.Dot(draft_direction) if dot > 0: face_dir = draft_direction else: face_dir = gp_Dir(-draft_direction.X(), -draft_direction.Y(), -draft_direction.Z()) draft.Add(face, face_dir, draft_angle_rad, True) draft.Build() if draft.IsDone(): current_shape = draft.Shape() success_count += 1 except Exception: continue if success_count > 0: logger.info(f"逐面拔模完成: {success_count}/{len(faces)} 个面成功") else: logger.warning("逐面拔模全部失败,返回原始形状") return current_shape def _analyze_product_geometry(self, shape: Any) -> Dict[str, Any]: try: props = GProp_GProps() brepgprop.VolumeProperties(shape, props) volume = props.Mass() surface_props = GProp_GProps() brepgprop.SurfaceProperties(shape, surface_props) surface_area = surface_props.Mass() center = props.CentreOfMass() bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() inertia = props.MatrixOfInertia() return { "volume": volume, "surface_area": surface_area, "center_of_mass": [float(center.X()), float(center.Y()), float(center.Z())], "bounding_box": { "min": [float(xmin), float(ymin), float(zmin)], "max": [float(xmax), float(ymax), float(zmax)], "center": [float((xmin+xmax)/2), float((ymin+ymax)/2), float((zmin+zmax)/2)], "dimensions": [float(xmax-xmin), float(ymax-ymin), float(zmax-zmin)] }, "inertia_matrix": self._get_inertia_matrix(props) } except Exception as e: logger.error(f"产品几何分析失败: {e}") raise def _split_cavity_core(self, shape: Any, parting_surface: Any, margin: int = 20) -> Tuple[Any, Any]: """ 分离型腔和型芯 — 完全嵌入 + 突出贴合方式 流程: 1. 创建完整模具块(产品包围盒 + 全方向余量) 2. 型腔(凹模)= 模具块 - 产品 → 产品完全嵌入型腔块中 3. 型芯(凸模)= 产品形状突出体 → 从芯块面突出贴合 不再使用分型面中间切开产品的方式。 """ try: bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() mold_xmin = xmin - margin mold_ymin = ymin - margin mold_zmin = zmin - margin mold_xmax = xmax + margin mold_ymax = ymax + margin mold_zmax = zmax + margin cavity_block = BRepPrimAPI_MakeBox( gp_Pnt(mold_xmin, mold_ymin, mold_zmin), gp_Pnt(mold_xmax, mold_ymax, mold_zmax) ).Shape() cavity = self._subtract_product_from_plate(cavity_block, shape, "型腔") if cavity is None: logger.warning("型腔布尔减运算失败,使用原始模具块") cavity = cavity_block parting_normal = self._extract_parting_normal(parting_surface) core = self._build_protruding_core( shape, cavity_block, mold_xmin, mold_ymin, mold_zmin, mold_xmax, mold_ymax, mold_zmax, xmin, ymin, zmin, xmax, ymax, zmax, parting_normal ) logger.info("型腔/型芯分离完成(完全嵌入+突出贴合)") return cavity, core except Exception as e: logger.error(f"型腔分离失败: {e}") return self._split_cavity_core_fallback(shape, None) def _extract_parting_normal(self, parting_surface: Any) -> List[float]: """从分型面提取法向量方向""" try: surface = BRepAdaptor_Surface(parting_surface) if surface.GetType() == 0: plane = surface.Plane() normal = plane.Axis().Direction() return [float(normal.X()), float(normal.Y()), float(normal.Z())] except Exception: pass return [0.0, 0.0, 1.0] def _build_protruding_core( self, shape: Any, cavity_block: Any, mold_xmin: float, mold_ymin: float, mold_zmin: float, mold_xmax: float, mold_ymax: float, mold_zmax: float, xmin: float, ymin: float, zmin: float, xmax: float, ymax: float, zmax: float, parting_normal: List[float] = None, ) -> Any: """ 构建突出贴合式型芯。 型芯 = 底座平板 + 产品形状突出体。 底座平板沿分型方向位于产品下方,产品形状融合在底座之上, 与型腔的凹入形状完美贴合。 """ nx, ny, nz = (parting_normal or [0.0, 0.0, 1.0]) mold_min = [mold_xmin, mold_ymin, mold_zmin] mold_max = [mold_xmax, mold_ymax, mold_zmax] prod_min = [xmin, ymin, zmin] prod_max = [xmax, ymax, zmax] base_p1 = list(mold_min) base_p2 = list(mold_max) for i in range(3): prod_span = prod_max[i] - prod_min[i] overlap = max(prod_span * 0.03, 0.5) if parting_normal[i] > 0: base_p2[i] = prod_min[i] + overlap elif parting_normal[i] < 0: base_p1[i] = prod_max[i] - overlap base_plate = None try: base_plate = BRepPrimAPI_MakeBox( gp_Pnt(base_p1[0], base_p1[1], base_p1[2]), gp_Pnt(base_p2[0], base_p2[1], base_p2[2]) ).Shape() logger.info("型芯底座平板构建完成") except Exception as e: logger.warning(f"底座平板构建失败: {e}") return shape try: fuse_op = BRepAlgoAPI_Fuse(base_plate, shape) if fuse_op.IsDone(): core = fuse_op.Shape() logger.info("型芯(底座+产品突出体)融合成功") return core except Exception as e: logger.warning(f"底座与产品融合失败: {e}") try: cut_op = BRepAlgoAPI_Cut(cavity_block, shape) if cut_op.IsDone(): logger.info("型芯通过布尔减回退构建") return cut_op.Shape() except Exception: pass logger.info("型芯最终回退为产品形状") return shape def _get_parting_plane(self, parting_surface: Any, shape: Any) -> Optional[gp_Pln]: """从分型面提取平面方程""" try: surface = BRepAdaptor_Surface(parting_surface) if surface.GetType() == 0: return surface.Plane() bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() center_z = (zmin + zmax) / 2 return gp_Pln(gp_Pnt(0, 0, center_z), gp_Dir(0, 0, 1)) except Exception as e: logger.warning(f"分型面平面提取失败: {e}") return None def _split_mold_block_by_plane(self, mold_block: Any, parting_plane: gp_Pln) -> Tuple[Any, Any]: """ 用分型面将模具块切分为A板(上模)和B板(下模) 方法:使用半空间体与模具块的布尔交集运算 - A板 = 模具块 ∩ 分型面上方半空间 - B板 = 模具块 ∩ 分型面下方半空间 """ try: plane_origin = parting_plane.Location() plane_normal = parting_plane.Axis().Direction() ref_point_above = gp_Pnt( plane_origin.X() + plane_normal.X() * 10, plane_origin.Y() + plane_normal.Y() * 10, plane_origin.Z() + plane_normal.Z() * 10 ) ref_point_below = gp_Pnt( plane_origin.X() - plane_normal.X() * 10, plane_origin.Y() - plane_normal.Y() * 10, plane_origin.Z() - plane_normal.Z() * 10 ) half_space_above = BRepPrimAPI_MakeHalfSpace( BRepBuilderAPI_MakeFace(parting_plane).Face(), ref_point_above ).Shape() half_space_below = BRepPrimAPI_MakeHalfSpace( BRepBuilderAPI_MakeFace(parting_plane).Face(), ref_point_below ).Shape() a_plate_op = BRepAlgoAPI_Common(mold_block, half_space_above) a_plate = None if a_plate_op.IsDone(): a_plate = a_plate_op.Shape() logger.info("A板(上模)切分成功") else: logger.warning("A板切分失败") b_plate_op = BRepAlgoAPI_Common(mold_block, half_space_below) b_plate = None if b_plate_op.IsDone(): b_plate = b_plate_op.Shape() logger.info("B板(下模)切分成功") else: logger.warning("B板切分失败") return a_plate, b_plate except Exception as e: logger.error(f"A/B板分离失败: {e}") return None, None def _subtract_product_from_plate(self, plate: Any, product: Any, plate_name: str) -> Any: """从模板中减去产品形状,生成型腔或型芯""" try: cut_op = BRepAlgoAPI_Cut(plate, product) if cut_op.IsDone(): result = cut_op.Shape() logger.info(f"{plate_name}减去产品成功") return result else: logger.warning(f"{plate_name}布尔减运算失败") return plate except Exception as e: logger.warning(f"{plate_name}减产品失败: {e}") return plate def _split_cavity_core_fallback(self, shape: Any, mold_block: Optional[Any] = None) -> Tuple[Any, Any]: """ 分模回退方案:完全嵌入+突出贴合,不切分模具块。 1. 创建完整模具块 → 型腔 = 模具块 - 产品(产品完全嵌入) 2. 型芯 = 产品形状突出体(突出贴合) """ logger.warning("使用分模回退方案(完全嵌入+突出贴合)") try: bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() margin = 20 if mold_block is None: mold_block = BRepPrimAPI_MakeBox( gp_Pnt(xmin - margin, ymin - margin, zmin - margin), gp_Pnt(xmax + margin, ymax + margin, zmax + margin) ).Shape() cavity = self._subtract_product_from_plate(mold_block, shape, "型腔(回退)") if cavity is None: cavity = mold_block core = self._build_protruding_core( shape, mold_block, xmin - margin, ymin - margin, zmin - margin, xmax + margin, ymax + margin, zmax + margin, xmin, ymin, zmin, xmax, ymax, zmax ) logger.info("回退方案型腔/型芯分离完成") return cavity or mold_block, core except Exception as e: logger.error(f"分模回退方案失败: {e}") try: bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() margin = 20 cavity_block = BRepPrimAPI_MakeBox( gp_Pnt(xmin - margin, ymin - margin, zmin - margin), gp_Pnt(xmax + margin, ymax + margin, zmax + margin) ).Shape() cavity = self._subtract_product_from_plate(cavity_block, shape, "型腔(兜底)") return cavity or cavity_block, shape except Exception: return shape, shape def detect_insert_regions(self, shape: Any, analysis: Dict, depth_threshold: float = 30.0, aspect_threshold: float = 3.0) -> List[Dict[str, Any]]: """ 检测需要独立镶件的区域 镶件判定条件: 1. 深腔区域(深度超过阈值) 2. 细长特征(长径比超过阈值) 3. 易磨损区域(尖锐角落、薄壁) 4. 精密特征(高精度要求的局部区域) Args: shape: 产品形状 analysis: 几何分析结果 depth_threshold: 深腔深度阈值 mm aspect_threshold: 长径比阈值 Returns: 镶件区域列表 """ inserts = [] try: bbox = analysis.get("bounding_box", {}) dims = bbox.get("dimensions", [0, 0, 0]) center = bbox.get("center", [0, 0, 0]) if dims[2] > depth_threshold: inserts.append({ "type": "deep_cavity_insert", "location": center, "depth": dims[2], "reason": f"型腔深度 {dims[2]:.1f}mm 超过阈值 {depth_threshold}mm", "insert_type": "core_pin", "priority": "high" }) explorer = TopExp_Explorer(shape, TopAbs_FACE) face_idx = 0 while explorer.More(): face = topods.Face(explorer.Current()) face_idx += 1 try: surface = BRepAdaptor_Surface(face) face_props = GProp_GProps() brepgprop.SurfaceProperties(face, face_props) area = face_props.Mass() if area < 1.0 and area > 0.001: bbox_face = Bnd_Box() brepbndlib.Add(face, bbox_face) try: fxmin, fymin, fzmin, fxmax, fymax, fzmax = bbox_face.Get() f_dims = [fxmax - fxmin, fymax - fymin, fzmax - fzmin] max_dim = max(f_dims) min_dim = min(f_dims) if min_dim > 0.01 and max_dim / min_dim > aspect_threshold: face_center = [ float((fxmin + fxmax) / 2), float((fymin + fymax) / 2), float((fzmin + fzmax) / 2) ] inserts.append({ "type": "slender_feature_insert", "location": face_center, "aspect_ratio": max_dim / min_dim, "reason": f"细长特征,长径比 {max_dim/min_dim:.1f}", "insert_type": "core_pin", "priority": "medium", "face_index": face_idx }) except Exception: pass if surface.GetType() == 1: radius = surface.Cylinder().Radius() if radius < 3.0 and radius > 0.1: cyl_axis = surface.Cylinder().Position().Axis() cyl_loc = cyl_axis.Location() inserts.append({ "type": "small_hole_insert", "location": [float(cyl_loc.X()), float(cyl_loc.Y()), float(cyl_loc.Z())], "radius": float(radius), "reason": f"小孔特征,半径 {radius:.2f}mm", "insert_type": "core_pin", "priority": "high", "face_index": face_idx }) except Exception: pass explorer.Next() if not inserts: logger.info("未检测到需要镶件的区域") else: logger.info(f"检测到 {len(inserts)} 个镶件区域") except Exception as e: logger.warning(f"镶件检测失败: {e}") return inserts def _extract_shape_geometry(self, shape: Any, shape_type: str) -> Dict[str, Any]: try: mesh = BRepMesh_IncrementalMesh(shape, 0.1) mesh.Perform() vertices = [] faces = [] explorer = TopExp_Explorer(shape, TopAbs_FACE) vertex_index = 0 while explorer.More(): 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, } except Exception as e: logger.error(f"{shape_type}几何提取失败: {e}") return { "type": shape_type, "vertices": [], "faces": [], "vertex_count": 0, "face_count": 0, } def _extract_plane_metadata(self, surface: Any) -> Dict[str, Any]: """从分型面提取平面元数据(法向量、原点、边界)""" metadata = { "normal": [0.0, 0.0, 1.0], "origin": [0.0, 0.0, 0.0], "bounds": {"min": [0.0, 0.0, 0.0], "max": [0.0, 0.0, 0.0]}, } try: surface_adaptor = BRepAdaptor_Surface(surface) if surface_adaptor.GetType() == 0: plane = surface_adaptor.Plane() axis = plane.Axis() normal = axis.Direction() origin = plane.Location() metadata["normal"] = [float(normal.X()), float(normal.Y()), float(normal.Z())] metadata["origin"] = [float(origin.X()), float(origin.Y()), float(origin.Z())] bbox = Bnd_Box() brepbndlib.Add(surface, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() metadata["bounds"] = { "min": [float(xmin), float(ymin), float(zmin)], "max": [float(xmax), float(ymax), float(zmax)], } except Exception as e: logger.warning(f"提取平面元数据失败: {e}") return metadata 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 _assess_warpage_risk(self, analysis: Dict) -> str: bbox = analysis.get("bounding_box", {}).get("dimensions", [1, 1, 1]) aspect_ratio = max(bbox) / min(bbox) if min(bbox) > 0 else 1 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 _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 def _calculate_parting_line(self, shape: Any, parting_surface: Any) -> List[List[float]]: try: section = BRepAlgoAPI_Section(shape, parting_surface) section.Build() if not section.IsDone(): logger.warning("截面运算未完成,使用简化分型线") return self._simple_parting_line(shape) edges = [] explorer = TopExp_Explorer(section.Shape(), TopAbs_EDGE) while explorer.More(): edge = explorer.Current() curve = BRepAdaptor_Curve(edge) first_param = curve.FirstParameter() last_param = curve.LastParameter() 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(shape) logger.info(f"计算得到 {len(edges)} 个分型线点") return edges except Exception as e: logger.error(f"分型线计算失败: {e}") return self._simple_parting_line(shape) def _simple_parting_line(self, shape: Any) -> List[List[float]]: try: bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() center_z = (zmin + zmax) / 2 return [ [xmin, ymin, center_z], [xmax, ymin, center_z], [xmax, ymax, center_z], [xmin, ymax, center_z], [xmin, ymin, center_z] ] except Exception: return [[-50, -50, 0], [50, -50, 0], [50, 50, 0], [-50, 50, 0], [-50, -50, 0]] def extend_parting_surface(self, parting_surface: Any, shape: Any, extension: float = 30.0) -> Any: """ 将分型面延伸到模具块边界 分型面通常只覆盖产品轮廓,需要延伸到模具块边缘 才能正确分离A板和B板 Args: parting_surface: 原始分型面 shape: 产品形状 extension: 延伸距离 mm Returns: 延伸后的分型面 """ try: bbox = Bnd_Box() brepbndlib.Add(shape, bbox) xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get() surface = BRepAdaptor_Surface(parting_surface) if surface.GetType() != 0: logger.info("分型面非平面,延伸操作跳过") return parting_surface plane = surface.Plane() origin = plane.Location() normal = plane.Axis().Direction() extended_xmin = xmin - extension extended_ymin = ymin - extension extended_xmax = xmax + extension extended_ymax = ymax + extension extended_plane = gp_Pln(origin, normal) extended_surface = BRepBuilderAPI_MakeFace( extended_plane, extended_xmin, extended_xmax, extended_ymin, extended_ymax ).Face() logger.info(f"分型面延伸完成: 延伸距离={extension}mm") return extended_surface except Exception as e: logger.warning(f"分型面延伸失败: {e}") return parting_surface def optimize_parting_line(self, parting_line: List[List[float]], smooth_window: int = 5, min_segment_length: float = 0.5, angle_threshold: float = 150.0) -> List[List[float]]: """ 优化分型线 优化内容: 1. 平滑处理 - 消除噪声点 2. 去除短线段 - 合并过短的线段 3. 尖角处理 - 在尖角处添加过渡圆弧 4. 点密度均匀化 - 重采样使点间距均匀 Args: parting_line: 原始分型线点列表 smooth_window: 平滑窗口大小 min_segment_length: 最小线段长度 angle_threshold: 尖角判定角度(度) Returns: 优化后的分型线 """ if len(parting_line) < 3: return parting_line try: smoothed = self._smooth_parting_line(parting_line, smooth_window) filtered = self._filter_short_segments(smoothed, min_segment_length) optimized = self._round_sharp_corners(filtered, angle_threshold) resampled = self._resample_parting_line(optimized, target_spacing=2.0) logger.info(f"分型线优化: {len(parting_line)} → {len(resampled)} 点") return resampled except Exception as e: logger.warning(f"分型线优化失败: {e}") return parting_line def _smooth_parting_line(self, points: List[List[float]], window: int = 5) -> List[List[float]]: """移动平均平滑""" if len(points) < window: return points arr = np.array(points, dtype=np.float64) smoothed = [] for i in range(len(arr)): start = max(0, i - window // 2) end = min(len(arr), i + window // 2 + 1) avg = np.mean(arr[start:end], axis=0) smoothed.append(avg.tolist()) return smoothed def _filter_short_segments(self, points: List[List[float]], min_length: float) -> List[List[float]]: """去除过短线段""" if not points: return points filtered = [points[0]] for i in range(1, len(points)): dist = np.linalg.norm(np.array(points[i]) - np.array(filtered[-1])) if dist >= min_length: filtered.append(points[i]) return filtered def _round_sharp_corners(self, points: List[List[float]], angle_threshold: float) -> List[List[float]]: """在尖角处添加过渡点""" if len(points) < 3: return points result = [points[0]] for i in range(1, len(points) - 1): v1 = np.array(points[i]) - np.array(points[i - 1]) v2 = np.array(points[i + 1]) - np.array(points[i]) len1 = np.linalg.norm(v1) len2 = np.linalg.norm(v2) if len1 > 0.001 and len2 > 0.001: cos_angle = np.clip(np.dot(v1, v2) / (len1 * len2), -1, 1) angle = math.degrees(math.acos(cos_angle)) if angle < angle_threshold: mid1 = (np.array(points[i - 1]) + np.array(points[i])) / 2 mid2 = (np.array(points[i]) + np.array(points[i + 1])) / 2 result.append(mid1.tolist()) result.append(mid2.tolist()) else: result.append(points[i]) else: result.append(points[i]) result.append(points[-1]) return result def _resample_parting_line(self, points: List[List[float]], target_spacing: float) -> List[List[float]]: """重采样使点间距均匀""" if len(points) < 2: return points arr = np.array(points, dtype=np.float64) cumulative_dist = [0.0] for i in range(1, len(arr)): dist = np.linalg.norm(arr[i] - arr[i - 1]) cumulative_dist.append(cumulative_dist[-1] + dist) total_length = cumulative_dist[-1] if total_length < target_spacing: return points num_points = max(3, int(total_length / target_spacing)) new_distances = np.linspace(0, total_length, num_points) resampled = [] for d in new_distances: idx = np.searchsorted(cumulative_dist, d) - 1 idx = max(0, min(idx, len(arr) - 2)) seg_start = cumulative_dist[idx] seg_end = cumulative_dist[idx + 1] seg_length = seg_end - seg_start if seg_length > 0: t = (d - seg_start) / seg_length else: t = 0 point = arr[idx] + t * (arr[idx + 1] - arr[idx]) resampled.append(point.tolist()) return resampled