一、引言
本篇文章将描述为机器学习准备输入的过程,包括
- 如何选择有效特征
- 如何获取这些特征
- 如何将这些特征转换成符合神经网络输入的格式
二、倒角识别的影响要素分析
下面是识别倒角面可能用到的面特征,并展示了倒角面和非倒角面在这些特征上一般表现的特点。
-
面类型
- 倒角面:通常为圆锥面()或平面(斜切)。
- 非倒角面:多为钣金主体平面或复杂曲面(非过渡面)。
- 判断逻辑:若面为圆柱面或平面,且满足其他条件,则可能为倒角。
-
相邻面数量
- 倒角面:通常连接4个相邻面(过渡其中两个主平面)。
- 非倒角面:可能连接更多面(如复杂结构或功能面)。
- 判断逻辑:相邻面数量为4时需进一步验证。
-
相邻面类型
- 倒角面:相邻面均为平面(钣金主体)。
- 非倒角面:可能连接曲面或混合类型面。
- 判断逻辑:若相邻面均为平面,则可能是倒角。
-
相邻面原始夹角
- 倒角面:相邻面原始夹角接近直角(如85°~95°)。
- 非倒角面:相邻面夹角可能为任意角度(非过渡需求)。
- 判断逻辑:通过相邻面法向量计算原始夹角,若接近直角,则可能被倒角替代。
-
表面积对比
- 倒角面:表面积较小(如圆角半径小或斜切宽度窄)。
- 非倒角面:面积通常较大(如钣金主体面)。
- 判断逻辑:倒角面面积显著小于相邻面。
-
与相邻面的连接角度
- 圆角:倒角面与相邻面连接处相切(夹角≈180°)。
- 斜切:倒角面与相邻面夹角对称(如45°斜切对应135°夹角)。
- 判断逻辑:检查倒角面与相邻面的夹角是否符合过渡特征。
-
圆柱面半径(若适用)
- 倒角面:圆柱面半径较小(符合工艺需求)。
- 非倒角面:圆柱面可能为功能结构(如孔洞),半径较大。
- 判断逻辑:设定半径阈值(如≤2mm),过滤小半径圆柱面。
至此,我们推理出了一些识别倒角可能用到的特征,那么,我们是否可以直接用编程的方法做判断,当符合上述倒角面特征时,即为倒角?
显然这种做法在面临严格遵循某规律的特点时是非常有效的。例如当时间等于上午7点时,执行闹钟响这个操作。
但是当遇到一些没有严格遵守某规律的问题时,这种做法时非常低效切愚蠢的。例如,如何将一张揉皱了的纸展开?这种问题必然存在其合理的解,但我们无法制定规则,让第三者按照规则去执行。
- 这就是上一篇开题是说的,利用机器学习,输入的是数据和从这些数据中预期得到的答案,系统输出的是规则。这些规则随后可应用于新的数据,并使计算机自主生成答案。
接下来我们需要通过SpaceClaim获取这些面的特征,这对于二次开发人员来讲真是小菜一碟。
三、二次开发特征提取方法
为了方便验证,这里直接展示提取信息的代码
-
获取面积
area = face.Area
-
获取面类型
type = face.Shape.Geometry.GetType().ToString().Split('.')[-1]
-
体积占比
该参数是新增参数,可能对倒角识别有用
# 计算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 -
圆柱半径(仅对圆柱有效)
R = round(face.Shape.Geometry.Radius, 4) if "Cylinder" in geo_type else 0.0
-
相邻面数量
adj_faces = face.AdjacentFaces
adj_count = len(adj_faces) -
与相邻面之间的夹角
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全连接神经网络,所以输入需要设置为一个列向量。长度需要固定,并且不能有类似“平面”、“圆柱面”、“圆锥面”之类的字符。部分参数也需要进行归一化处理。
-
面积的处理
利用体上最大面的面积对该体上所有的面的面积进行归一化处理。
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)
-
面类型的处理
将面类型面类型转换为独热编码向量,并将整个向量拆开添加到面特征中。
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)
-
相邻面及其参数的处理
相邻面数量并非固定值,而倒角特征并非存在于所有相邻面中,所以案例中只获取前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获取面特征的方法,并展示了训练数据与预测数据采集的整个过程。接下来,需要创建神经网络模型,并用采集到的数据进行训练。