Administrator
Administrator
发布于 2025-03-24 / 36 阅读
1
0

基于深度学习的模型倒角智能识别系统及其CAE应用(数据采集篇)

一、引言

本篇文章将描述为机器学习准备输入的过程,包括

  1. 如何选择有效特征
  2. 如何获取这些特征
  3. 如何将这些特征转换成符合神经网络输入的格式

二、倒角识别的影响要素分析

下面是识别倒角面可能用到的面特征,并展示了倒角面和非倒角面在这些特征上一般表现的特点。

  1. 面类型

    • 倒角面:通常为圆锥面()或平面(斜切)。
    • 非倒角面:多为钣金主体平面或复杂曲面(非过渡面)。
    • 判断逻辑:若面为圆柱面或平面,且满足其他条件,则可能为倒角。
  2. 相邻面数量

    • 倒角面:通常连接4个相邻面(过渡其中两个主平面)。
    • 非倒角面:可能连接更多面(如复杂结构或功能面)。
    • 判断逻辑:相邻面数量为4时需进一步验证。
  3. 相邻面类型

    • 倒角面:相邻面均为平面(钣金主体)。
    • 非倒角面:可能连接曲面或混合类型面。
    • 判断逻辑:若相邻面均为平面,则可能是倒角。
  4. 相邻面原始夹角

    • 倒角面:相邻面原始夹角接近直角(如85°~95°)。
    • 非倒角面:相邻面夹角可能为任意角度(非过渡需求)。
    • 判断逻辑:通过相邻面法向量计算原始夹角,若接近直角,则可能被倒角替代。
  5. 表面积对比

    • 倒角面:表面积较小(如圆角半径小或斜切宽度窄)。
    • 非倒角面:面积通常较大(如钣金主体面)。
    • 判断逻辑:倒角面面积显著小于相邻面。
  6. 与相邻面的连接角度

    • 圆角:倒角面与相邻面连接处相切(夹角≈180°)。
    • 斜切:倒角面与相邻面夹角对称(如45°斜切对应135°夹角)。
    • 判断逻辑:检查倒角面与相邻面的夹角是否符合过渡特征。
  7. 圆柱面半径(若适用)

    • 倒角面:圆柱面半径较小(符合工艺需求)。
    • 非倒角面:圆柱面可能为功能结构(如孔洞),半径较大。
    • 判断逻辑:设定半径阈值(如≤2mm),过滤小半径圆柱面。

至此,我们推理出了一些识别倒角可能用到的特征,那么,我们是否可以直接用编程的方法做判断,当符合上述倒角面特征时,即为倒角?
显然这种做法在面临严格遵循某规律的特点时是非常有效的。例如当时间等于上午7点时,执行闹钟响这个操作
但是当遇到一些没有严格遵守某规律的问题时,这种做法时非常低效切愚蠢的。例如,如何将一张揉皱了的纸展开?这种问题必然存在其合理的解,但我们无法制定规则,让第三者按照规则去执行。
screenshot-20250324-144409.png

  • 这就是上一篇开题是说的,利用机器学习,输入的是数据和从这些数据中预期得到的答案,系统输出的是规则。这些规则随后可应用于新的数据,并使计算机自主生成答案。

接下来我们需要通过SpaceClaim获取这些面的特征,这对于二次开发人员来讲真是小菜一碟。

三、二次开发特征提取方法

为了方便验证,这里直接展示提取信息的代码

  1. 获取面积

    area = face.Area

  2. 获取面类型

    type = face.Shape.Geometry.GetType().ToString().Split('.')[-1]

  3. 体积占比

    该参数是新增参数,可能对倒角识别有用

    # 计算body体积
    def calculate_V(bodyBox):
        x, y, z = bodyBox.Size
        x, y, z = abs(x), abs(y), abs(z)
        return x * y * z
    

    body_box = selected_body.Shape.GetBoundingBox(Matrix.CreateMapping(Frame.World))
    body_vol = calculate_V(body_box)
    face_box = face.Shape.GetBoundingBox(Matrix.CreateMapping(Frame.World))
    face_vol_ratio = calculate_V(face_box) / body_vol * 1e6

  4. 圆柱半径(仅对圆柱有效)

    R = round(face.Shape.Geometry.Radius, 4) if "Cylinder" in geo_type else 0.0

  5. 相邻面数量

    adj_faces = face.AdjacentFaces
    adj_count = len(adj_faces)

  6. 与相邻面之间的夹角

    def calculate_angle(vec1, vec2):
        """计算两个向量之间的夹角(角度制)"""
        dot = sum(a * b for a, b in zip(vec1, vec2))
        mag1 = sum(a ** 2 for a in vec1) ** 0.5
        mag2 = sum(a ** 2 for a in vec2) ** 0.5
        try:
            out = math.degrees(math.acos(dot / (mag1 * mag2))) if mag1 * mag2 != 0 else 0.0
        except:
            out = 9999
        return out
    def parse_normal(normal_str):
        """解析法向量字符串为数值列表"""
        return [float(x) for x in normal_str[2:-1].split(',')]
    

    face_normal = parse_normal(face.GetFaceNormal(0.5, 0.5).ToString().Split(':')[-1])
    adj_normal = parse_normal(adj_face.GetFaceNormal(0.5, 0.5).ToString().Split(':')[-1])
    ang = round(calculate_angle(face_normal, adj_normal), 2)

四、神经网络输入矩阵设计

接下来需要对这些特征进行预处理,使之成为包含面的所有特征的向量。

神经网络实现了向量在不同空间中的转换,即将输入向量经过一定的矩阵运算,转换到了另一个空间,而这个空间叫做解空间。其中的矩阵运算描述的就是权重W组成的坐标转换矩阵,当然还包括用于增强非线性的激活函数。

  • 本案例中使用的是FCNN全连接神经网络,所以输入需要设置为一个列向量。长度需要固定,并且不能有类似“平面”、“圆柱面”、“圆锥面”之类的字符。部分参数也需要进行归一化处理。

    1. 面积的处理

      利用体上最大面的面积对该体上所有的面的面积进行归一化处理

      all_face_areas = [face.Area for face in selected_body.Faces]
      max_area = max(all_face_areas) if all_face_areas else 1.0  # 防零除
      normalized_area = round(face.Area / max_area, 4)
      
    2. 面类型的处理

      将面类型面类型转换为独热编码向量,并将整个向量拆开添加到面特征中。

      def get_face_type_encoding(face_type):
          """
          编码规则:
          - Plane(平面):    [1, 0, 0, 0]
          - Cylinder(圆柱): [0, 1, 0, 0]
          - Torus(圆环):   [0, 0, 1, 0]
          - 其他类型:        [0, 0, 0, 1]
          参数:
          face_type : str - 来自face.Shape.Geometry.GetType()的类型字符串
          返回:
          list - 4维独热编码向量
          """
          type_lower = face_type.strip().lower()
          encoding_map = {
              "plane": [1, 0, 0, 0],
              "cylinder": [0, 1, 0, 0],
              "torus": [0, 0, 1, 0]
          }
          # 检查已知类型
          for key in encoding_map:
              if key in type_lower:
                  return encoding_map[key]
          # 未知类型归为其他
          return [0, 0, 0, 1]
      
      geo_type = face.Shape.Geometry.GetType().ToString().Split('.')[-1]
      get_face_type_encoding(geo_type)
      
      
    3. 相邻面及其参数的处理

      相邻面数量并非固定值,而倒角特征并非存在于所有相邻面中,所以案例中只获取前5个面积最大的相邻面的特征

      • 获取相邻面的数量:
      adj_faces = face.AdjacentFaces
      adj_count = len(adj_faces)
      
      • 按照面积降序,选择前五个相邻面
      sorted_adj = sorted(
          [(af, af.Area) for af in adj_faces],
          key=lambda x: x[1],
          reverse=True
      )[:5]
      
      • 获取每个相邻面的特征,获取方法省略
      • 当相邻面不满足5个时,填充空值
      face_features.extend([0] * 4 + [0.0, 0.0])
      

五、数据采集流程及代码实现解析

5.1 训练数据提取流程

5.1.1 特征获取

  • 获取体的面信息,作为输入参数
#获取体的面信息,作为输入参数
# Python Script, API Version = V22
BodyList = GetRootPart().Components[0].GetAllBodies()
datalist = []
for body in BodyList:
    print body.GetName()
    bodydata = GetBodyFaceData(body)
    datalist.append(bodydata)
    save_to_csv(bodydata, r'Data\untrain\faceDatas_'+body.GetName()+'.csv')

# save_to_csv函数
def save_to_csv(data, filename):
    # Check if data[0] is iterable (e.g., a list or tuple)
    if not isinstance(data[0], (list, tuple)):
        # If it's a single element, wrap it into a list
        data = [[d] for d in data]

    # Open the file in write mode (text mode)
    with open(filename, 'wb') as csvfile:
        # Use column indices as headers
        fieldnames = [str(i) for i in range(len(data[0]))]
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()

        for row in data:
            row_dict = {str(i): str(value) for i, value in enumerate(row)}
            writer.writerow(row_dict)

其中GetBodyFaceData函数为获取面特征的核心函数

def GetBodyFaceData(selected_body):
    features_list = []
    
    # 预计算所有面的面积用于归一化
    all_face_areas = [face.Area for face in selected_body.Faces]
    max_area = max(all_face_areas) if all_face_areas else 1.0  # 防零除

    # 获取体包围盒体积
    body_box = selected_body.Shape.GetBoundingBox(Matrix.CreateMapping(Frame.World))
    body_vol = calculate_V(body_box)
    num = 0;
    for face in selected_body.Faces:
        face_features = []

        # ---------------------------
        # 基础特征(6维)
        # ---------------------------
        # 1. 归一化面积(原始面积/最大面积)
        normalized_area = round(face.Area / max_area, 4)
        face_features.append(normalized_area)

        # 2. 面类型编码(4维)
        geo_type = face.Shape.Geometry.GetType().ToString().Split('.')[-1]
        face_features.extend(get_face_type_encoding(geo_type))

        # 3. 体积占比(1维)
        face_box = face.Shape.GetBoundingBox(Matrix.CreateMapping(Frame.World))
        face_vol_ratio = calculate_V(face_box) / body_vol * 1e6
        face_features.append(round(face_vol_ratio, 4))

        # 4. 圆柱半径(1维)
        face_features.append(round(face.Shape.Geometry.Radius, 4) if "Cylinder" in geo_type else 0.0)

        # ---------------------------
        # 相邻面特征处理(5×6=30维)
        # ---------------------------
        adj_faces = face.AdjacentFaces
        adj_count = len(adj_faces)
        face_features.append(adj_count)

        # 按面积降序选择前5个相邻面
        sorted_adj = sorted(
            [(af, af.Area) for af in adj_faces],
            key=lambda x: x[1],
            reverse=True
        )[:5]

        # 处理每个相邻面特征(最多5个)
        for i in range(5):
            if i < len(sorted_adj):
                adj_face, adj_area = sorted_adj[i]

                # 相邻面类型(4维)
                adj_type = adj_face.Shape.Geometry.GetType().ToString().Split('.')[-1]
                face_features.extend(get_face_type_encoding(adj_type))

                # 归一化相邻面面积(1维)
                face_features.append(round(adj_area / max_area, 4))

                # 夹角计算(1维)
                face_normal = parse_normal(face.GetFaceNormal(0.5, 0.5).ToString().Split(':')[-1])
                adj_normal = parse_normal(adj_face.GetFaceNormal(0.5, 0.5).ToString().Split(':')[-1])
                face_features.append(round(calculate_angle(face_normal, adj_normal), 2))
            else:
                # 填充空值(4+1+1=6维)
                face_features.extend([0] * 4 + [0.0, 0.0])

        # ---------------------------
        # 原始夹角特征(1维)
        # ---------------------------
        original_angle = 0.0
        if adj_count >= 2:
            # 使用原始相邻面顺序的前两个面计算
            adj1 = adj_faces[0]
            adj2 = adj_faces[1]
            adj1_normal = parse_normal(adj1.GetFaceNormal(0.5, 0.5).ToString().Split(':')[-1])
            adj2_normal = parse_normal(adj2.GetFaceNormal(0.5, 0.5).ToString().Split(':')[-1])
            original_angle = round(calculate_angle(adj1_normal, adj2_normal), 2)
        face_features.append(original_angle)

        features_list.append(face_features)

    return features_list

5.1.2 标签获取

  • 获取处理前的面对象
# 获取处理前的面对象
BodyList = GetRootPart().Components[0].GetAllBodies()
datalist = []
for body in BodyList:
    facelist = body.Faces
    datalist.append(facelist)
  • 手工清理倒角

  • 获取处理后的面对象,并保存到CSV中。

# 获取处理后的面对象
resultDatas = []
for facelist in datalist:
   bodyresult = []
   faceitem = None
   for face0 in facelist:
       if(face0.IsDeleted):
           bodyresult.append(1)
       else:
           bodyresult.append(0)
           faceitem = face0
   bodyname = faceitem.GetAncestor[IDesignBody]().GetName()
   # 获取训练输入
   save_to_csv(bodyresult,r'Data\untrain\faceDatas_'+bodyname+'_resualt.csv')
   resultDatas.append(bodyresult)

特征说明(每行51维):

特征位置特征名称维度说明
0面积1面表面积(mm²)
1-4面类型编码4平面/圆柱/圆环/其他
5体积占比1面包围盒体积占比(ppm)
6圆柱半径1圆柱面半径(mm),其他为0
7相邻面数量1直接相邻的面数量
8-15相邻面1特征8类型(4)+面积(1)+夹角(1)
16-23相邻面2特征8类型(4)+面积(1)+夹角(1)
24-32相邻面3特征8类型(4)+面积(1)+夹角(1)
33-41相邻面4特征8类型(4)+面积(1)+夹角(1)
42-50相邻面5特征8类型(4)+面积(1)+夹角(1)
51原始相邻面夹角1相邻面之间的原始角度

输出示例

# 假设一个圆柱倒角面:
[
    15.32,         # 面积
    0,1,0,0,       # 圆柱类型
    0.15,          # 体积占比
    1.5,           # 圆柱半径
    2,             # 相邻面数
    1,0,0,0,       # 相邻面1类型(平面)
    250.0,         # 相邻面1面积
    175.3,         # 与当前面夹角
    1,0,0,0,       # 相邻面2类型(平面)
    300.0,         # 相邻面2面积
    ...
    172.8,         # 与当前面夹角
    89.7           # 原始相邻面夹角
]

5.2、预测数据提取流程

5.2.1 预测数据采集

 #获取体的面信息,作为输入参数
 # Python Script, API Version = V22
 BodyList = GetRootPart().Components[0].Components[0].Components[1].GetAllBodies()
 datalist = []
 for body in BodyList:
     print body.GetName()
     bodydata = GetBodyFaceData(body)
     datalist.append(bodydata)
     # 获取预测输入
     save_to_csv(bodydata,r'Data\processed\faceDatas_'+body.GetName()+'.csv')

上述代码与特征获取代码一致,区别在于数据保存位置在带预测文件夹中。

在项目中需要管理好项目文件

六、总结

本篇详细介绍了通过SCDM的二次开发接口用python获取面特征的方法,并展示了训练数据与预测数据采集的整个过程。接下来,需要创建神经网络模型,并用采集到的数据进行训练。


评论