class chk_Conti_zkline:
def __init__(self):
# self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.JOB = os.environ.get('JOB', None)
self.STEP = os.environ.get('STEP', None)
# self.chklist = os.environ.get('chklist', None)
# --启动pycharm.sh时,里面有export INCAM_DEBUG=yes设置此环境变量
INCAM_DEBUG = os.getenv('INCAM_DEBUG', None)
# 接口定义
if INCAM_DEBUG == 'yes':
# 通过genesis gateway命令连结pid进行会话,不用在genesis环境下运行,直接用gateway的方式,可在pycharm环境下直接debug
self.incam = Gateway()
# 方法genesis_connect通过查询log-genesis文件获取的料号名
self.JOB = self.incam.job_name
self.STEP = self.incam.step_name
self.pid = self.incam.pid
else:
self.incam = InCAM()
self.pid = os.getpid()
self.ico = ICO(incam=self.incam)
self.icNet = ICNET(incam=self.incam)
self.jobName = self.ico.SimplifyJobName(jobName=self.JOB)
self.dbSite = self.ico.GetDBSite(JOB=self.JOB)
self.SITE = self.ico.GetSite(JOB=self.JOB)
self.getinfomation_dict = defaultdict(defaultdict)
self.ballPadIndexList = defaultdict(list)
self.layerMatrix = self.ico.GetLayerMatrix()
self.zkLineIndex = defaultdict(defaultdict)
self.step_list = self.ico.GetStepList()
layer_list = self.ico.GetLayerList()
self.zk_siglay = {}
self.zkLayerList = self.getZKLayer() # 获取阻抗线层别
for zklay in self.zkLayerList:
zkLay_bak = f'{zklay}_bak'
self.zkLineIndex[zklay]['zkLaytmp'] = zkLay_bak
self.zkLineIndex[zklay]['index'] = []
self.ico.DelLayer(layer_list=[zkLay_bak]) # 创建前先删除 # 删除辅助层
self.ico.DispWork(zklay)
self.incam.COM(f'sel_copy_other, dest = layer_name, target_layer ={zkLay_bak},invert = no,dx = 0 ,dy = 0,size= 0,x_anchor= 0,y_anchor = 0') # 复制
self.ico.DispWork(zkLay_bak)
# self.incam.COM(f'sel_change_sym, symbol = r3, reset_angle = no') # 缩小线宽为r3(前提是线比r3大)
# self.incam.COM("sel_cont_resize,accuracy=0,break_to_islands=yes,island_size=0,hole_size=0,drill_filter=no,corner_ctl=no") # ctrl+F
FeatureFullInfo = self.ico.GetFeatureFullInfo(step=self.STEP, layer=zkLay_bak)
for info in FeatureFullInfo:
info['orig'] = [re.sub(r"\s+", " ", i) for i in info['orig']]
for i in info['orig']:
if re.search(r'.*#OB.*', i):
index_info_list = i.split(' ')
self.zkLineIndex[zklay]['index'].append({'x': index_info_list[-3], 'y': index_info_list[-2], 'id': info['id']})
# if re.search(r'.*#OC.*',i):
# index_info_list = i.split(' ')
# self.zkLineIndex[zklay]['index'].append({'x': index_info_list[-3], 'y': index_info_list[-2],'id': info['id']})
# break # 识别到第一个#OC就break
self.ico.DelLayer(layer_list=[f'{zkLay_bak}+++']) # 创建前先删除 # 删除辅助层
self.ico.ClearAll()
self.run()
def getZKLayer(self):
"""
获取阻抗线层别
:return: 阻抗线层别
"""
zkLayerList = list()
layerList = self.layerMatrix['allLay'] # self.ico.GetLayerList()
for lay in layerList:
for t in ('s', 'ss', 'gsg', 'gssg'):
pattern_tmp = re.compile(r'^((?:un-)?(\d+|[tb])zk)-(\d+\.?\d*)-(%s)-?(\d+\.?\d*)?-?(\d+\.?\d*)?-?(\d+\.?\d*)?$' % t)#存在un开头的阻抗层
matchObj = re.match(pattern_tmp, lay)
if matchObj:
matchList = matchObj.groups()
zkLayerList.append(lay)
self.getinfomation_dict[lay]['layer'] = matchList[0]
print(zkLayerList)
if len(zkLayerList) > 0:
SignalLayer_list = self.layerMatrix['sigAllLay']
for i in range(len(SignalLayer_list)):
if i == 0:
self.zk_siglay['tzk'] = SignalLayer_list[0]
elif i == (len(SignalLayer_list) - 1):
self.zk_siglay['bzk'] = SignalLayer_list[-1]
else:
self.zk_siglay[str(i + 1) + 'zk'] = SignalLayer_list[i]
self.zk_siglay['un-' + str(i + 1) + 'zk'] = SignalLayer_list[i]#还要加一个un-开头的对应线路层!
print("zk_siglay : %s" % self.zk_siglay)
for lay in self.getinfomation_dict.keys():
for key in self.zk_siglay:
if self.getinfomation_dict[lay]['layer'] == key:
self.getinfomation_dict[lay]['sigLayerNum'] = str(SignalLayer_list.index(self.zk_siglay[key]) + 1)
self.getinfomation_dict[lay]['sigLayerName'] = str(self.zk_siglay[key])
break
else:
messageBox.showMessage(message='没有识别到阻抗线层别,请规范命名(eg:tzk-gssg-100)', bitmap='critical')
exit()
return zkLayerList
def get_unique_endpoints(self, job, step, backup_layer):
"""
提取 backup_layer 层上所有 line/arc 的端点,
返回仅出现一次的“末端”点(即非连接点)
"""
features = self.ico.GetFeaturesPro(job=job, step=step, layer=backup_layer)
if not features:
print(f"[WARN] No features found on layer '{backup_layer}'")
return []
endpoints = []
for feat in features:
ftype = feat['type']
if ftype == 'line':
xs, ys = feat['xs'], feat['ys']
xe, ye = feat['xe'], feat['ye']
endpoints.append((round(xs, 3), round(ys, 3)))
endpoints.append((round(xe, 3), round(ye, 3)))
elif ftype == 'arc':
x_start, y_start = feat['xs'], feat['ys']
x_end, y_end = feat['xe'], feat['ye']
endpoints.append((round(x_start, 6), round(y_start, 6)))
endpoints.append((round(x_end, 6), round(y_end, 6)))
# 统计每个点出现次数
point_count = defaultdict(int)
for pt in endpoints:
point_count[pt] += 1
# 只保留只出现一次的点(真正的末端)
unique_ends = [pt for pt, cnt in point_count.items() if cnt == 1]
unique_ends.sort(key=lambda p: (p[0], p[1])) # 按坐标排序便于调试
print(f"[INFO] Found {len(unique_ends)} unique endpoints on '{backup_layer}'.")
return unique_ends
def selectun(self, backup_layer, zkLay):
"""
完整流程:
1. 复制非 line/arc 图形到 '01' 层
2. 局部合并为独立 surface
3. 提取 backup_layer 的唯一端点
4. 删除覆盖这些端点的 surface
5. 返回状态信息供 run() 使用
"""
zk2SigLay = self.getinfomation_dict[zkLay]['sigLayerName']
zkLineIndex = self.zkLineIndex[zkLay]['index']
step = 'edit'
######初始化环境#######
self.ico.ClearAll()
self.ico.ResetFilter()
self.incam.COM(f'afffected_layer, name={backup_layer}, mode=single, affected=yes')
self.incam.COM(f'display_layer, name={zk2SigLay}, display=yes')
#######处理每根阻抗线,复制并合并 surface 到 '01'层#######
for idx, info in enumerate(zkLineIndex):
x, y, tmpID = info['x'], info['y'], info['id']
#跨网络选择
self.incam.COM("sel_clear_feat")
self.incam.COM("clear_highlight")
self.incam.COM(f'sel_board_net_feat, operation=select, x={x}, y={y}, tol=1, use_ffilter=no') #坐标点选
features = self.ico.GetFeatureFullInfo(step=step, layer=zk2SigLay, mode='select')
non_line_arcs = [f for f in features if f['type'] not in ('line', 'arc')]
if not non_line_arcs:
print(f"{backup_layer}层中没有非line非弧图形")
continue
# 复制到 '01' 层
self.incam.COM(
"sel_copy_other, dest=layer_name, target_layer=01, invert=no, "
"dx=0, dy=0, size=0, x_anchor=0, y_anchor=0"
)
# 切换到 '01' 层进行局部合并
self.ico.DispWork(layer='01')
self.incam.COM("sel_clear_feat")
self.incam.COM(f"sel_feature, operation=select, x={x}, y={y}, tol=0.3, use_ffilter=no")
self.incam.COM('get_select_count')
selected_count = int(self.incam.COMANS)
if selected_count > 0:
self.incam.COM(
"sel_cont_resize, accuracy=0, break_to_islands=yes, "
"island_size=0, hole_size=0, drill_filter=no, corner_ctl=no"
)
#######获取备份层中的唯一端点######
print(f"在{backup_layer}层查找是否有端点坐标")
unique_endpoints = self.get_unique_endpoints(job=self.JOB, step=step, backup_layer=backup_layer)
if not unique_endpoints:
print(f"在{backup_layer}层没有找到端点坐标")
# # 仍需检查 01 层状态
# features_01 = self.ico.GetFeaturesPro(job=self.JOB, step=step, layer='01')
# return {
# 'endpoints_count': 0,
# 'surfaces_deleted': 0,
# 'remaining_on_01': len(features_01) > 0
# }
#############删除 '01' 层中覆盖这些端点的 surface############
print(f"[PHASE 3] Checking {len(unique_endpoints)} endpoints against surfaces on '01'...")
self.ico.DispWork(layer='01')
self.incam.COM("sel_clear_feat")
deleted_surface_count = 0
processed_centers = set()
for i, (ux, uy) in enumerate(unique_endpoints):
print(f" [CHECK] Endpoint {i+1}: ({ux:.4f}, {uy:.4f})")
self.incam.COM("sel_clear_feat")
self.incam.COM(f"sel_feature, operation=select, x={ux}, y={uy}, tol=0.01, use_ffilter=no") #在 '01' 层尝试以极小容差(tol=0.01)选择该点附近图形。检查是否有 surface 类型被选中。
self.incam.COM('get_select_count')
selected_features = int(self.incam.COMANS)
if not selected_features:
continue
for feat in selected_features:
if feat['type'] == 'surface':
center_key = (round(feat['x'], 3), round(feat['y'], 3))
if center_key not in processed_centers:
print(f" --> Deleting surface at center ({feat['x']:.3f}, {feat['y']:.3f})")
processed_centers.add(center_key)
deleted_surface_count += 1
#########执行删除#############
if deleted_surface_count > 0:
self.incam.COM("sel_clear_feat")
self.incam.COM('get_select_count')
all_surfs = int(self.incam.COMANS)
for surf in all_surfs:
if surf['type'] == 'surface':
key = (round(surf['x'], 3), round(surf['y'], 3))
if key in processed_centers:
self.incam.COM(f"sel_feature, operation=select, x={surf['x']}, y={surf['y']}, tol=0.1")
self.incam.COM("sel_delete")
# === 最终判断 '01' 层是否还有内容 ===
features_01 = self.ico.GetFeaturesPro(job=self.JOB, step=step, layer='01')
has_remaining = len(features_01) > 0
return has_remaining, features_01
# return {
# 'endpoints_count': len(unique_endpoints),
# 'surfaces_deleted': deleted_surface_count,
# 'remaining_on_01': has_remaining
# }
def selectBallPad(self, zkLay, x, y):
"""
判断指定坐标点是否与背面ball pad导通
:param zkLay: 阻抗层名称
:param x: 检查点的x坐标
:param y: 检查点的y坐标
:return: bool 是否导通
"""
# 阻抗层对应的线路层
zk2SigLay = self.getinfomation_dict[zkLay]['sigLayerName']#self.getinfomation_dict得到的是原始阻抗层,而不是_bak备份阻抗层
sigBot = self.layerMatrix['sigAllLay'][-1] #线路层最后一层,背面
# 设置工作环境
self.ico.ClearAll()
self.ico.ResetFilter()
self.incam.COM(f'affected_layer, name={sigBot}, mode=single, affected=yes')
self.incam.COM(f'display_layer, name={zk2SigLay}, display=yes')
self.incam.COM(f'work_layer, name={zk2SigLay}')
# 执行选择
self.incam.COM("sel_clear_feat") #清除之前的选择
self.incam.COM("clear_highlight")
self.incam.COM(f'sel_board_net_feat, operation=select, x={x}, y={y}, tol=1, use_ffilter=no')
# 获取选中特征
featureFullInfo_sigBot = self.ico.GetFeatureFullInfo(step=self.STEP, layer=sigBot, mode='select')
# 检查背面ball pad
# 对于背面sig选到的物体,做个筛选,如果是pad且是.smd属性,说明选到了ball pad
selectPadList = [info for info in featureFullInfo_sigBot if info['type'] == 'pad' and (info['attr'] == '.smd' or info['attr'] == '.smd,.bga' or info['attr'] == '.smd,.lga' or
info['attr'] == '.smd,.smt_pad' or
info['attr'] == '.smd,.test_pad')] # 20241209 新增ball pad属性是.smd.bga和.smd.lga
return len(selectPadList) > 0 # 如果找到ball pad则返回True
def cleanup_short_polylines(self, step, backup_layer, min_length=8.0):
"""
删除总长度 < min_length 的整根 polyline(由多个 line 与arc 组成)
利用 feature['length'] 避免重复计算几何距离
"""
features = self.ico.GetFeatures(step, backup_layer)
# 提取所有 line 和 arc
valid_features = [f for f in features if f['type'] in ('line', 'arc')]
if not valid_features:
return
# #######构建拓扑图 端点->线段####
graph = defaultdict(list) # 坐标 -> 相邻坐标
coord_to_feats = defaultdict(list) # 坐标 -> (index, feature)
for idx, feat in enumerate(valid_features):
xs = round(feat['xs'], 3)
ys = round(feat['ys'], 3)
xe = round(feat['xe'], 3)
ye = round(feat['ye'], 3)
start = (xs, ys)
end = (xe, ye)
graph[start].append(end)
graph[end].append(start)
coord_to_feats[start].append((idx, feat))
coord_to_feats[end].append((idx, feat))
visited_coords = set()
short_polyline_seeds = [] # 存储每根短 polyline 的一个起点用于删除
##########BFS 遍历每个连通组件###########
for coord in list(graph.keys()):
if coord in visited_coords:
continue
queue = deque([coord])
current_visited_coords = set()
current_feat_indices = set()
while queue:
curr = queue.popleft()
if curr in current_visited_coords:
continue
current_visited_coords.add(curr)
for neighbor in graph[curr]:
# 查找连接 curr和neighbor 的 feature
found = False
for feat_idx, feat in coord_to_feats[curr]:
if feat_idx in current_feat_indices:
continue
# 判断是否连接 curr 和 neighbor
s = (round(feat['xs'],3), round(feat['ys'],3))
e = (round(feat['xe'],3), round(feat['ye'],3))
connected = (curr == s and neighbor == e) or (curr == e and neighbor == s)
if connected:
current_feat_indices.add(feat_idx)
found = True
break
if not found:
continue # 没有可用边
if neighbor not in current_visited_coords:
queue.append(neighbor)
# 标记这些点已被全局访问
visited_coords.update(current_visited_coords)
if not current_feat_indices:
continue
################计算当前 polyline 总长度###########
total_length = sum(valid_features[i]['length'] for i in current_feat_indices)
if total_length < min_length:
# 找一个端点作为选择依据(度为1的点)
endpoints = [pt for pt in current_visited_coords if len(graph[pt]) == 1]
seed = endpoints[0] if endpoints else next(iter(current_visited_coords))
short_polyline_seeds.append(seed)
##########删除:选中所有短 polyline#########
if short_polyline_seeds:
self.incam.COM("sel_clear_feat")
for x, y in short_polyline_seeds:
self.incam.COM(f"sel_polyline_feat,operation=select,x={x:.3f},y={y:.3f},tol=1")
self.incam.COM("sel_delete")
def run(self):
"""
处理Y形阻抗线检测与清理的主执行函数
修改重点:
- 每个 backup_layer 单独处理:检测 -> 标记 -> 删除 -> 刷新
- 每次删除后重新获取 features,确保状态最新
- 避免 Entity does not exist 错误
"""
job = self.JOB
step = 'edit' #在edit层
backup_layers = []
self.ico.ClearAll()
self.zkLineIndex = defaultdict(lambda: defaultdict(list))
######## 创建备份层 ##################
for zklay in self.zkLayerList:
zkLay_bak = f'{zklay}_bak'
self.zkLineIndex[zklay]['zkLaybak'] = zkLay_bak
self.ico.DelLayer(layer_list=[zkLay_bak])
self.ico.DispWork(zklay)
self.incam.COM(f'sel_copy_other,dest=layer_name,target_layer={zkLay_bak},invert=no,dx=0,dy=0,size=0,x_anchor=0,y_anchor=0')
backup_layers.append(zkLay_bak)
self.ico.ClearLayer()
unthrough_lay = []
############# 遍历每个 step ##############
for backup_layer in backup_layers:
original_layer = backup_layer.replace('_bak', '')
# self.ico.DispWork(backup_layer)
# #############获取当前特征############
features = self.ico.GetFeaturesPro(job, step, backup_layer)
if not features:
continue
# self.incam.COM("sel_clear_feat") # 清空之前选择
has_remaining, features_01 = self.selectun(backup_layer=backup_layer, zkLay=original_layer)
if not has_remaining:
continue
#############待删除坐标的集合(仅针对当前 layer)############
seeds_to_delete = set()
##############判断原线路层中与01层touch的阻抗线是否与背面的ball pad导通###########
for feat in features_01:
layer_through = False
if feat['type'] not in ('line', 'arc'):
continue
xs, ys = round(feat['xs'], 3), round(feat['ys'], 3)
xe, ye = round(feat['xe'], 3), round(feat['ye'], 3)
start_1 = self.selectBallPad(zkLay=original_layer, x=xs, y=ys)
end_1 = self.selectBallPad(zkLay=original_layer, x=xe, y=ye)
if (start_1 or end_1):
layer_through = True
break
if layer_through:
connection_to_ballpad = True
# problem_x, problem_y = x, y
break # 找到第一个即可跳出
else:
seeds_to_delete.add((x, y)) #找到备份层中与01层touch的阻抗线
if connection_to_ballpad:
# 记录 layer 和坐标
unthrough_lay.append({'layer': backup_layer})
########统一判断并提示################
if unthrough_lay:
lines = []
for pt in unthrough_lay:
lines.append(f"{pt['layer']}") # ({pt['x']:.3f}, {pt['y']:.3f})
layer_list_str = ";".join(lines)
messageBox.showDialog(
title='提示',
text=f'以下层存在Y形阻抗线且与背面ball pad导通:\n\n{layer_list_str}\n\n'
'1、人为确认是否为分流设计\n'
'2、如为分流设计,需与客户确认是否设计异常,与客户EQ此组对抗通过测量科邦卡控阻值,\n'
' 如客户不同意,内部策划列难点评估测科邦',
bitmap='warning',
buttons=['OK'],
defaultButton='OK'
)
self.ico.ClearLayer()
#############找到孤立不导通线段############
for feat in features:
if feat['type'] not in ('line', 'arc'):
continue
xs, ys = round(feat['xs'], 3), round(feat['ys'], 3)
xe, ye = round(feat['xe'], 3), round(feat['ye'], 3)
start_ok = self.selectBallPad(zkLay=original_layer, x=xs, y=ys)
end_ok = self.selectBallPad(zkLay=original_layer, x=xe, y=ye)
if not (start_ok or end_ok):
seeds_to_delete.add((xs, ys))
############执行删除操作 ############
self.incam.COM("sel_clear_feat")
# 1. 先清理 <8mm的polyline
self.cleanup_short_polylines(job=job, step='edit', backup_layer=backup_layer)
# 2. 再删除01层与原线路层以及不导通线对应的 polyline
current_features = self.ico.GetFeatures(step, backup_layer) # 刷新!重新获取每个备份层的特征,防止 entity not exist
valid_coords = {(round(f['xs'],3), round(f['ys'],3)) for f in current_features if f['type'] in ('line','arc')}
valid_coords.update({(round(f['xe'],3), round(f['ye'],3)) for f in current_features if f['type'] in ('line','arc')})
for x, y in seeds_to_delete:
if (round(x,3), round(y,3)) not in valid_coords:
continue # 已被前面的 cleanup 删除,跳过
try:
self.incam.COM(f"sel_polyline_feat,operation=select,x={x:.3f},y={y:.3f},tol=1")
except:
pass # 忽略异常
self.incam.COM("sel_delete")
self.ico.ClearLayer()
############判断是否为空层############
features_after = self.ico.GetFeaturesPro(job, step, backup_layer)
has_valid = any(f['type'] in ('line', 'arc') for f in features_after)
msg = (
f'{backup_layer}存在Y形阻抗线,同组无可测阻抗线,与客户EQ此组阻抗通过测量科邦卡控阻值,如客户不同意,内部策划列难点评估测科邦' if not has_valid
else f'{backup_layer}存在Y形阻抗线,同组存在可测阻抗线,EQ客户Y型阻抗线follow同组可测阻抗线进行调整'
)
messageBox.showMessage(
title='提示',
text=msg,
bitmap='warning',
buttons=['OK'],
defaultButton='OK'
)
self.ico.ClearLayer()
sys.exit()
if __name__ == "__main__":
app = QApplication(sys.argv)
analyzer = chk_Conti_zkline()
#在run函数中我想要实现:备份所有阻抗层,备份为_bak;对所有备份层的进行线简化,然后对所有line和arc的端点取坐标(通过GetFeaturesPro函数找到‘line’和’arc’属性即可得到),
# 接着判断是否存在大于等于3个相同的坐标(重合坐标,同一个坐标点出现超过3次)。没有的话,messagebox提示并结束整个代码;
# 如果是的话,接着判断原稿与01层的线是否与背面的pad导通(selectBallPad函数已经能够实现),导通的话,messagebox提示并结束整个代码;
# 没有导通的话,第一:就在备份层阻抗线路删除与01层接触的整根线路polyline(注意polyline是一根很长线,由很多短的line组成)self.incam.COM(f"sel_polyline_feat,operation=select,x={feature[‘xs’]},y={feature[‘ys’]}")命令能够通过一个坐标点选中一根polyline
# 第二:删除整根线长小于8的polyline;第三:删除不与背面ball pad导通的polyline;
# 第四:判断经过删除的备份层是否为空,是与不是都输出messagebox提示并结束整个代码