#!/bin/env python # 必须写在第一行
# -*- coding: utf-8 -*-
#################################################
# Author: songwenhua
# Function:客户阻抗线连续性检查
# Date: 2025-11-05
# v1.00 songwenhua 用户需求号: 2482 任务ID:2100
# LOAD_MODE__
import os
import re
import sys
from py39COM import Gateway, InCAM
from py39Tools import TableWidget
from ICO import ICO
from ICNET import ICNET
from XMLParse import XMLParsePlus
from messageBox import messageBox
from EqHelper import EqHelper
from PyQt5.QtWidgets import QWidget, QApplication, QComboBox
from collections import defaultdict, deque
from functools import partial
import math
# from img import apprcc_rc
from pprint import pprint
import time
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() # 获取阻抗线层别
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' or ftype == 'arc':
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'affected_layer, name={backup_layer}, mode=single, affected=yes')
self.incam.COM(f'display_layer, name={zk2SigLay}, display=yes')
# if self.ico.IsLayerExist('01'):
# self.ico.DelLayer('01') #如果已经存在01层就重新创建
#创建01层
# self.incam.COM(f"matrix_insert_layer,job={self.JOB},matrix=matrix,subsystem=1-Up-Edit")
# self.incam.COM("display_layer,name=01,hide_others=yes")
self.ico.CreateLay(layer_list=['01'])
#######处理每根阻抗线,复制并合并 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_all_feat, operation=select, x={x}, y={y}, tol=0.3, use_ffilter=no")
self.incam.COM(f"sel_all_feat")
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_all_feat, 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_single_feat, 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 = []
if self.ico.IsLayerExist('01'):
self.ico.DelLayer('01') #如果已经存在01层就删除
print("2222222222222222222222222222222222222222222222")
############# 遍历每个 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'{layer_list_str}层存在阻抗线不连续,此阻抗线与背面ball pad导通\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;然后将备份层阻抗线touch到的原线路层中非line非arc图形选中复制到01层,再分别将每根阻抗线touch到的图形在01层中合成surface
接着获取对应阻抗备份层所有线路的端点坐标(只要不重复的坐标点,因为有许多根线相连,不重复的坐标点是整根线真正的端点),并删除在01层中cover端点坐标的sruface物体;然后判断原线路层与01层touch的阻抗线是否与背面的pad导通(selectBallPad函数已经能够实现),导通的话,messagebox提示并结束整个代码;
没有导通的话,第一:就在备份层阻抗线路删除与01层touch的整根线路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提示并结束整个代码
如果有必要也可改动selectun函数与get_unique_endpoints函数代码
最新发布