识别图元(Queue)

博客给出一段C++代码实现图元标记功能。代码先初始化围墙和偏移量,利用队列扫描所有像素,当遇到新图元时进行标记,并寻找其余图元,通过检查相邻像素并标记,将未探索像素入队,体现了数据结构和算法的应用。

void Label()
{
 //初始化围墙
 for(int i=0;i<=m+1;i++)
 {
  pixel[0][i]=pixel[m+1][i]=0;
  pixel[i][0]=pixel[i][m+1]=0;
 }

 // 初始化offset
 Position offset[4];
 offset[0].row=0; offset[0].col=1; // 右
 offset[1].row=1; offset[1].col=0; //下
 offset[2].row=0; offset[2].col=-1; //左
 offset[3].row=-1; offset[3].col=0; //上

 int NumofNbrs=4;
 LinkedQueue<Position> Q;
 int id=1;
 Position here, nbr;

 //扫描所有象素
 for(int r=1; r<=m; r++)
  for(int c=1;c<=m; c++)
  {
   if(pixel[r][c]==1)// 新图元
   {
    pixel[r][c]==++id;
    here.row=r;
    here.col=c;
   }

   // 寻找其余图元
   do  
   {
    for(int i=0; i<NumofNbrs; i++)
    {
     //检查当前象素的所有相邻象素
     nbr.row=here.row+offset[i].row;
     nbr.col=here.row+offset[i].col;
     if(pixel[nbr.row][nbr.col]==1)
     {
      pixel[nbr.row][nbr.col]=id;
      Q.Add(nbr);
     }
    }
    // 还有未探索的象素吗?
    if(Q.IsEmpty())
     break;
    Q.Delete(here);
   }while(1);
  }
}

Ref:<<数据结构,算法与应用>>P204~206

#!/bin/env python # 必须写在第一行 # -*- coding: utf-8 -*- ################################################# # Author: songwenhua # Function:客户阻抗线连续性检查 # Date: 2025-11-05 # v1.00 songwenhua 用户需求号: 2482 任务ID:2100 # /home/incam/Desktop/scripts/shendan/check_Y_zk_line # 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开头的阻抗层 pattern_tmp = re.compile(r'^((\d+|[tb])zk)-(\d+\.?\d*)-(%s)-?(\d+\.?\d*)?-?(\d+\.?\d*)?-?(\d+\.?\d*)?$' % t) 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_single_layer(self, step, backup_layer): """ 仅从单个 backup_layer 提取端点(仅本层内统计) """ self.ico.ClearLayer() self.ico.DispWork(backup_layer) features = self.ico.GetFeatureFullInfo(step, layer=backup_layer) if not features: return [] layer_endpoints = [] for feat in features: ftype = feat['type'] if ftype in ('line', 'arc'): xs, ys = feat['x0'], feat['y0'] xe, ye = feat['x1'], feat['y1'] layer_endpoints.append((round(xs, 6), round(ys, 6))) layer_endpoints.append((round(xe, 6), round(ye, 6))) point_count = defaultdict(int) for pt in layer_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"{backup_layer}: 找到 {len(unique_ends)} 个唯一端点") return unique_ends def selectun_separate(self, backup_layer, zkLay, target_temp_layer): """ 在原始信号层上选出与 backup_layer 接触的所有非 line/arc 图形, 复制到指定的 target_temp_layer 并合并成 surface。 """ try: zk2SigLay = self.getinfomation_dict[zkLay]['sigLayerName'] except KeyError as e: return False self.ico.ClearAll() self.incam.COM("adv_filter_reset") self.incam.COM(f"display_layer, name={zk2SigLay}, display=yes") self.incam.COM(f"work_layer, name={zk2SigLay}") self.incam.COM("set_filter_type,filter_name=,lines=no,pads=yes,surfaces=yes,arcs=no,text=yes") self.incam.COM("set_filter_polarity,filter_name=,positive=yes,negative=yes") self.incam.COM(f"sel_ref_feat,layers={backup_layer},use=filter,mode=touch,pads_as=shape," "f_types=line;pad;surface;arc;text,polarity=positive;negative,include_syms=,exclude_syms=") self.incam.COM("get_select_count") count = int(self.incam.COMANS) if count == 0: return False # 复制到专属临时层 self.incam.COM(f"sel_copy_other, dest=layer_name, target_layer={target_temp_layer}, " "invert=no, dx=0, dy=0, size=0, x_anchor=0, y_anchor=0") # 切换到目标临时层并合并 self.ico.ClearAll() self.ico.DispWork(target_temp_layer) self.ico.TrySelContResize([2.54, 5, 10]) return True 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) # 坐标 -> 相邻坐标 edge_map = {} # (start, end) -> feature_index 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) edge_map[(start, end)] = idx edge_map[(end, start)] = idx visited_coords = set() short_polyline_seeds = [] # 存储每根短 polyline 的一个起点用于删除 # BFS 遍历每个连通组件 for coord in graph: if coord in visited_coords: continue queue = deque([coord]) current_visited = set() current_feature_indices = set() while queue: curr = queue.popleft() if curr in current_visited: continue current_visited.add(curr) for neighbor in graph[curr]: edge_key = (curr, neighbor) if edge_key in edge_map: feat_idx = edge_map[edge_key] if feat_idx not in current_feature_indices: current_feature_indices.add(feat_idx) if neighbor not in current_visited: queue.append(neighbor) visited_coords.update(current_visited) if not current_feature_indices: continue # 计算当前 polyline 总长度 total_length = sum(valid_features[i]['length'] for i in current_feature_indices) if total_length < min_length: # 找一个端点作为选择依据(度为1的点) endpoints = [pt for pt in current_visited if len(graph[pt]) == 1] seed = endpoints[0] if endpoints else next(iter(current_visited)) 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("get_select_count") current_count = int(self.incam.COMANS) # 一次性删除所有选中的图形 if current_count > 0: self.incam.COM("sel_delete") def delete_surfaces_covering_points(self, points, layer_new): """ 删除临时层中包含给定点的 surface :param points: list of (x, y) """ self.ico.ClearLayer() self.ico.DispWork(layer_new) self.incam.COM("sel_clear_feat") # 初始清空 tol = 0.05 # 匹配点的 filter 容差 selected_count = 0 for x, y in points: #用 FilterAreaXY 查找落在点附近的图形 # self.incam.COM("sel_clear_feat") # 临时清空 # x1, y1 = x - tol, y - tol # x2, y2 = x + tol, y + tol # count_in_area = self.ico.FilterAreaXY( # isInside='yes', # intersect='yes', # x1=x1, # y1=y1, # x2=x2, # y2=y2 # ) self.incam.COM(f"sel_single_feat, operation=select, x={x}, y={y}, tol=0.1") # self.incam.COM(f"sel_single_feat,operation=select,x={x},y={y},tol=35.775,cyclic=yes,clear_prev=no") self.incam.COM("get_select_count") current_count = int(self.incam.COMANS) # 一次性删除所有选中的图形 if current_count > 0: self.incam.COM("sel_delete") # print(f"成功删除了 {current_count} 个覆盖末端点的 surface") # else: # print("未找到覆盖末端点的 surface") @staticmethod def extract_start_points(data): """ 支持输入为 str 或 list[str],统一处理并提取每个图形的 #OB 起点 """ if isinstance(data, list): # 如果是列表,用换行符拼接成字符串 content = "\n".join(data) elif isinstance(data, str): content = data else: raise TypeError("Input must be str or list of str") start_points = [] # 按 '#数字 #S' 分割出各个图形块 blocks = re.split(r'#(\d+)\s+#S', content)[1:] # 每两项:编号 + 内容 for i in range(0, len(blocks), 2): block_content = blocks[i+1] match = re.search(r'#OB\s+([-\d.]+)\s+([-\d.]+)', block_content) if match: x = float(match.group(1)) y = float(match.group(2)) start_points.append((x, y)) return start_points def get_coords(self, feature): """ 从任意图元中快速提取一个坐标点 (x, y) 特别处理 surface 的 orig 字段 """ # 处理 surface 的 orig if feature.get('type') == 'surface' and isinstance(feature.get('orig'), list): pattern = r'#O[BS]\s+([-\d.]+)\s+([-\d.]+)' for line in feature['orig']: match = re.search(pattern, line) if match: x = float(match.group(1)) y = float(match.group(2)) return round(x, 3), round(y, 3) # 返回第一个有效坐标即可 return None def run(self): """ 主执行函数:客户阻抗线连续性检查(分层处理 + 四步清理) 改进版:不合并到 tmp_total,逐层独立判断 temp_layer_n 是否为空 并且:仅将实际处理过的层纳入结果提示 """ job = self.JOB step = 'edit' unthrough_lay = [] # 存在与 ball pad 导通风险的层 backup_layers = [] temp_layers = [] self.ico.ClearAll() self.zkLineIndex = defaultdict(lambda: defaultdict(list)) ######## 1. 创建备份层 ################## for idx, zklay in enumerate(self.zkLayerList): zkLay_bak = f'{zklay}_bak' layer_n = f'tmp_{zklay}' temp_layers.append(layer_n) self.zkLineIndex[zklay]['zkLaybak'] = zkLay_bak self.zkLineIndex[zklay]['tempLayer'] = layer_n self.ico.DelLayer(layer_list=[zkLay_bak, layer_n]) 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) ######## 2. 分层处理:复制非line/arc图形到独立临时层,并删末端覆盖surface ######## for backup_layer in backup_layers: original_zklay = backup_layer.replace('_bak', '') temp_layer_n = self.zkLineIndex[original_zklay]['tempLayer'] print(f"处理 {backup_layer} => 使用临时层 {temp_layer_n}") has_copied = self.selectun_separate(backup_layer, original_zklay, temp_layer_n) if not has_copied: continue unique_ends = self.get_unique_endpoints_single_layer(step, backup_layer) if not unique_ends: print(f"{backup_layer}: 无唯一端点,跳过 surface 删除") else: self.delete_surfaces_covering_points(unique_ends, temp_layer_n) ######## 3. 检查是否连接到背面 Ball Pad(逐层判断)######## for backup_layer in backup_layers: original_zklay = backup_layer.replace('_bak', '') temp_layer_n = self.zkLineIndex[original_zklay]['tempLayer'] self.ico.ClearAll() self.ico.DispWork(backup_layer) self.ico.DispLayer(temp_layer_n) # 设置过滤器:只选 line self.incam.COM("adv_filter_reset") self.incam.COM("set_filter_type, lines=yes, pads=no, surfaces=no, arcs=no, text=no") self.incam.COM("set_filter_polarity, positive=yes, negative=yes") # 选择与 final_tmp_layer 接触的 line self.incam.COM(f"sel_ref_feat,layers={temp_layer_n},use=filter,mode=touch,pads_as=shape," "f_types=line;pad;surface;arc;text,polarity=positive;negative,include_syms=,exclude_syms=") features = self.ico.GetFeatureFullInfo(step, layer=backup_layer, mode='select') if not features: continue connection_to_ballpad = False problem_coords = [] test_points = [(round(feat['x0'], 6), round(feat['y0'], 6)) for feat in features if feat['type'] in ('line', 'arc')] test_points = list(set(test_points)) for xs, ys in test_points: if self.selectBallPad(original_zklay, xs, ys): connection_to_ballpad = True problem_coords.append((xs, ys)) if connection_to_ballpad: unthrough_lay.append({ 'layer': backup_layer, 'coords': problem_coords }) ######## 统一弹窗提示 ###### if unthrough_lay: # 提取所有出问题的原始层名(去掉 '_bak' 后缀) problem_layers = [item['layer'].replace('_bak', '') for item in unthrough_lay] layer_list_str = ', '.join(problem_layers) messageBox.showDialog( title='警告', text=f'{layer_list_str}层存在阻抗线不连续,此阻抗线与背面 Ball Pad 导通:\n\n' '1. 人为确认是否为分流设计;\n' '2. 如为分流设计,需与客户确认是否设计异常,与客户EQ此组阻抗通过测量科邦控阻值,如客户不同意,内部策划列难点评估测科邦', bitmap='warning', buttons=['OK'], defaultButton='OK' ) sys.exit(0) # sys.exit(0) ######### 4. 清理阶段:三大删除操作(仅对非空 temp_layer_n 执行)######## self.ico.ClearAll() processed_empty = [] # 被处理过且清空的层 processed_valid = [] # 被处理过但仍保留线路的层 skipped_layers = [] # temp_layer_n 为空,跳过的层 for backup_layer in backup_layers: original_zklay = backup_layer.replace('_bak', '') temp_layer_n = self.zkLineIndex[original_zklay]['tempLayer'] current_step = 'edit' # --- 判断是否跳过 --- temp_info = self.ico.GetFeatureFullInfo(current_step, temp_layer_n) if not temp_info or len(temp_info) == 0: print(f"跳过处理:{backup_layer} 对应 {temp_layer_n} 为空") skipped_layers.append(original_zklay) continue # 完全跳过,不执行任何操作,也不做判断 ############################################################# # 从现在开始:只有 temp_layer_n 非空的层才会进入下面流程 ############################################################# self.ico.ClearAll() self.incam.COM("sel_clear_feat") self.ico.DispWork(backup_layer) self.ico.DispLayer(temp_layer_n) # --- 步骤1: 删除与 temp_layer_n 接触的所有整根 polyline --- print(f"[{backup_layer}] 步骤1: 删除与 {temp_layer_n} 接触的整根 polyline...") self.ico.ResetFilter() self.incam.COM(f"sel_ref_feat,layers={temp_layer_n},use=filter,mode=touch,pads_as=shape," "f_types=line;pad;surface;arc;text,polarity=positive;negative") features_touch = self.ico.GetFeatureFullInfo(current_step, layer=backup_layer, mode='select') touch_start_points = set() for feat in features_touch: if feat['type'] in ('line', 'arc'): xs, ys = round(feat['x0'], 6), round(feat['y0'], 6) touch_start_points.add((xs, ys)) if touch_start_points: self.incam.COM("sel_clear_feat") # print("11111111111") for x, y in touch_start_points: self.incam.COM(f"sel_polyline_feat,operation=select,x={x:.3f},y={y:.3f},tol=1") self.incam.COM("get_select_count") selected_count = int(self.incam.COMANS) if selected_count > 0: self.incam.COM("sel_delete") print(f"{backup_layer}: 删除了 {selected_count} 根与 {temp_layer_n} 接触的 polyline") # sys.exit(0) # ''' # --- 步骤2: 删除长度 < 8mm 的短 polyline --- print(f"[{backup_layer}] 步骤2: 删除长度 < 8mm 的短 polyline...") self.cleanup_short_polylines(step=current_step, backup_layer=backup_layer, min_length=8.0) # --- 步骤3: 删除两端都不连接 ball pad 的孤立段 --- print(f"[{backup_layer}] 步骤3: 删除孤立段(不连 ball pad)...") remaining_features = self.ico.GetFeatureFullInfo(step=current_step, layer=backup_layer) delete_seeds = [] for feat in remaining_features: if feat['type'] not in ('line', 'arc'): continue xs, ys = round(feat['x0'], 6), round(feat['y0'], 6) xe, ye = round(feat['x1'], 6), round(feat['y1'], 6) start_ok = self.selectBallPad(original_zklay, xs, ys) end_ok = self.selectBallPad(original_zklay, xe, ye) if not (start_ok or end_ok): delete_seeds.append((xs, ys)) if delete_seeds: self.incam.COM("sel_clear_feat") for x, y in delete_seeds: self.incam.COM(f"sel_polyline_feat,operation=select,x={x:.3f},y={y:.3f},tol=1") self.incam.COM("get_select_count") current_count = int(self.incam.COMANS) if current_count > 0: self.incam.COM("sel_delete") print(f"{backup_layer}: 删除了 {len(delete_seeds)} 根孤立段") # --- 步骤4: 仅当该层被实际处理过时,才判断其清理后状态 --- remaining_after = self.ico.GetFeaturesPro(job=job, step=current_step, layer=backup_layer) has_valid_line = any(f['type'] in ('line', 'arc') for f in remaining_after) if has_valid_line: processed_valid.append(original_zklay) else: processed_empty.append(original_zklay) # 注意:这里不在 continue 路径上 -> 只有处理过的层才走到这里 ######## 5. 输出分类提示信息(仅包含实际处理过的层)######## msg_parts = [] if processed_empty: msg_parts.append( f'{", ".join(processed_empty)} 存在阻抗线不连续,同组无可测阻抗线,与客户EQ此组阻抗通过测量科邦卡控阻值,如客户不同意,内部策划列难点评估测科邦' ) if processed_valid: msg_parts.append( f'{", ".join(processed_valid)} 存在阻抗线不连续,同组存在可测阻抗线,EQ客户Y型阻抗线follow同组可测阻抗线进行调整' ) if not msg_parts: messageBox.showMessage( message="本次未处理任何阻抗层(所有 tmp_zklay 均为空)", bitmap='warning' ) else: messageBox.showDialog( title='结果', text='\n\n'.join(msg_parts), bitmap='warning', buttons=['OK'], defaultButton='OK' ) sys.exit() # ''' if __name__ == "__main__": app = QApplication(sys.argv) analyzer = chk_Conti_zkline() 从开发的角度说明是如何实现这个代码功能的,使用什么功能判断、什么功能实现。一定要有逻辑
最新发布
11-26
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值