import klayout.pya as pya
from collections import defaultdict
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.enum.text import PP_ALIGN
from pptx.dml.color import RGBColor
from pptx.enum.shapes import MSO_SHAPE
import os
import re
def extract_coordinates(poly_str):
# 使用正则表达式提取括号内的所有坐标
match = re.search(r'\(([\d\.,;]+)\)', poly_str)
if not match:
raise ValueError("无效的坐标字符串格式")
# 分割坐标点并转换为浮点数列表
points = []
for pair in match.group(1).split(';'):
if ',' in pair:
x, y = map(float, pair.split(','))
points.append((x, y))
if not points:
raise ValueError("未找到有效坐标点")
# 找到左下角坐标 (x最小, y最小)
ll = min(points, key=lambda p: (p[0], p[1]))
# 找到右上角坐标 (x最大, y最大)
ur = max(points, key=lambda p: (p[0], p[1]))
print(f"左下角坐标: ({ll[0]}, {ll[1]})")
print(f"右上角坐标: ({ur[0]}, {ur[1]})")
return ll[0], ll[1], ur[0], ur[1]
# 加载DRC报告数据库
report_db = pya.ReportDatabase().load("NEPTUNEZ_FIN.out")
# 存储所有类别的数据结构
all_categories = []
category_counter = defaultdict(int) # 用于统计每个类别的总项目数
# 遍历所有cell和item
for item_cell in report_db.each_cell():
layer_name = item_cell.name()
cell_category_data = defaultdict(lambda: {"description": "", "items": []})
for item_id in item_cell.each_item():
category = report_db.category_by_id(item_id.category_id())
cat_name = category.name()
# 新增:更新全局类别计数器
category_counter[cat_name] += 1 # 每遇到一个项目,对应类别计数+1
# 只保存每个类别的前三个项目
if len(cell_category_data[cat_name]["items"]) >= 3:
continue
# 保存类别描述
if not cell_category_data[cat_name]["description"]:
cell_category_data[cat_name]["description"] = category.description
# 收集当前项目的所有值
item_values = [str(item_value) for item_value in item_id.each_value()]
cell_category_data[cat_name]["items"].append(item_values)
# 收集这个cell的所有类别数据
for cat_name, data in cell_category_data.items():
if data["items"]:
category_entry = {
"layerName": layer_name,
"ruleName": cat_name,
"description": data["description"],
"items": list(data["items"]) # 保存违规项目的详细信息
}
all_categories.append(category_entry)
# 创建PPT演示文稿
prs = Presentation()
# 为每个违规类别创建详细页
for category in all_categories:
slide = prs.slides.add_slide(prs.slide_layouts[6])
# === 规则描述处理 ===
full_description = category["description"]
rule_name=category["ruleName"]
total_errors = category_counter[rule_name]
# 规则描述超过50字符时显示省略号
if len(full_description) > 150:
display_description = full_description[:150] + "..."
else:
display_description = full_description
# === 将完整描述添加到备注 ===
notes_slide = slide.notes_slide
notes_text_frame = notes_slide.notes_text_frame
notes_text_frame.text = f"规则完整描述:\n{full_description}"
# === 左侧区域:规则描述 ===
left_start = Inches(0.2)
left_width = Inches(2.8) # 左侧区域宽度
# 添加标题(图层+规则名)
title_box = slide.shapes.add_textbox(left_start, Inches(0.2), left_width, Inches(1))
title_frame = title_box.text_frame
title_para = title_frame.add_paragraph()
title_para.text = f"{category['layerName']} - {category['ruleName']}"
title_para.font.size = Pt(24)
title_para.font.bold = True
title_para.alignment = PP_ALIGN.LEFT
# 添加规则描述区域
desc_box = slide.shapes.add_textbox(left_start, Inches(0.8), left_width, Inches(2.0)) # 高度减小
desc_frame = desc_box.text_frame
desc_frame.word_wrap = True # 允许文本换行
# 添加描述文本
desc_para = desc_frame.add_paragraph()
desc_para.text = "规则描述:"
desc_para.font.size = Pt(14)
desc_para.font.bold = True
desc_content = desc_frame.add_paragraph()
desc_content.text = display_description # 使用处理后的描述
desc_content.font.size = Pt(10)
desc_content.font.color.rgb = RGBColor(70, 70, 70)
# ===== Zensemi Comment文本框 =====
risk_box_y = Inches(0.8) + Inches(2.0) + Inches(0.2) # 规则描述下方+1英寸间距
risk_box = slide.shapes.add_textbox(left_start, risk_box_y, left_width, Inches(0.8))
risk_frame = risk_box.text_frame
risk_frame.word_wrap = True
# 标题
risk_title = risk_frame.add_paragraph()
risk_title.text = "Zensemi comment:"
risk_title.font.size = Pt(14)
risk_title.font.bold = True
risk_title.font.color.rgb = RGBColor(200, 0, 0) # 红色强调
# ===== 评论(CTM Comment)文本框 =====
comment_box_y = risk_box_y + Inches(0.8) + Inches(0.5) # 风险框下方+0.5英寸间距
comment_box = slide.shapes.add_textbox(left_start, comment_box_y, left_width, Inches(1.5))
comment_frame = comment_box.text_frame
comment_frame.word_wrap = True
# 标题
comment_title = comment_frame.add_paragraph()
comment_title.text = "CTM comment(Please input \u2713):"
comment_title.font.size = Pt(14)
comment_title.font.bold = True
comment_title.font.color.rgb = RGBColor(0, 100, 200) # 蓝色强调
# 内容(占位符)
comment_content = comment_frame.add_paragraph()
comment_content.text = "#Waive:"
comment_content.font.size = Pt(12)
comment_content.font.color.rgb = RGBColor(100, 100, 100)
# 内容(占位符)
comment_content = comment_frame.add_paragraph()
comment_content.text = "#Update GDS:"
comment_content.font.size = Pt(12)
comment_content.font.color.rgb = RGBColor(100, 100, 100)
# ===== remark文本框 =====
remark_box_y = comment_box_y + Inches(0.8) + Inches(0.5) # 风险框下方+0.5英寸间距
remark_box = slide.shapes.add_textbox(left_start, remark_box_y, left_width, Inches(1.5))
remark_frame = remark_box.text_frame
remark_frame.word_wrap = True
remark_content = remark_frame.add_paragraph()
remark_content.text="Remark:"
remark_content.font.size = Pt(14)
remark_content.font.bold = True
remark_content.font.color.rgb = RGBColor(127, 255, 0) # 蓝色强调
# === 右侧区域:违规表格 ===
right_start = left_start + left_width + Inches(0.3) # 左侧区域右侧+0.3英寸间距
right_width = Inches(5.0) # 右侧区域宽度
# 表格标题(右侧区域标题)
table_title = slide.shapes.add_textbox(right_start, Inches(0.1), right_width, Inches(0.2))
title_frame = table_title.text_frame
title_para = title_frame.add_paragraph()
title_para.font.size = Pt(14)
title_para.font.bold = True
title_para.alignment = PP_ALIGN.CENTER
# 添加违规项目表格
if category["items"]:
num_items = len(category["items"])
rows = num_items + 1 # 表头+数据行
cols = 4 # 4列: #, Violation Info, Screenshot1, Screenshot2
# 创建表格(位置和尺寸)
table_shape = slide.shapes.add_table(
rows, cols,
right_start, Inches(1.0), # 移动到右侧位置
right_width, Inches(1.0 * rows)
)
table = table_shape.table
# 设置表头样式
headers = ["#", f"Error[{total_errors}]", "ALL", "POLY"]
for col in range(cols):
cell = table.cell(0, col)
cell.text = headers[col]
cell.fill.solid()
cell.fill.fore_color.rgb = RGBColor(59, 89, 152)
cell.text_frame.paragraphs[0].font.color.rgb = RGBColor(255, 255, 255)
cell.text_frame.paragraphs[0].font.bold = True
# 设置列宽比例
if col == 0: # 序号列
table.columns[col].width = Inches(0.3)
elif col == 1: # 违规信息列
table.columns[col].width = Inches(1.6)
else: # 截图列
table.columns[col].width = Inches(2.4) # 调整为1.0英寸以适应右侧宽度
# 填充数据行
for row_idx in range(1, rows):
# 序号列
table.cell(row_idx, 0).text = str(row_idx)
table.cell(row_idx, 0).text_frame.paragraphs[0].font.bold = True
table.cell(row_idx, 0).text_frame.paragraphs[0].font.size = Pt(12)
# 违规信息列 - 显示category["items"]中的详细信息
violation_info = category["items"][row_idx - 1]
extract_coordinates(''.join(violation_info))
info_cell = table.cell(row_idx, 1)
info_cell.text = "\n".join(violation_info) # 将信息分行显示
info_cell.text_frame.paragraphs[0].font.size = Pt(11)
info_cell.text_frame.word_wrap = True # 允许文本换行
# 调整行高以容纳文本
table.rows[row_idx].height = Inches(2.0)
# 截图列
for col_idx in [2, 3]:
cell = table.cell(row_idx, col_idx)
# 清空原有文本
cell.text = ""
# 计算单元格位置
cell_left = table_shape.left + sum([table.columns[c].width for c in range(col_idx)])
cell_top = table_shape.top + sum([table.rows[r].height for r in range(row_idx)])
# 图片尺寸(稍微小于单元格)
pic_width = table.columns[col_idx].width - Inches(0.1)
pic_height = table.rows[row_idx].height - Inches(0.1)
# 添加图片占位符
placeholder = slide.shapes.add_shape(
MSO_SHAPE.ROUNDED_RECTANGLE,
cell_left + Inches(0.05),
cell_top + Inches(0.05),
pic_width,
pic_height
)
placeholder.fill.solid()
placeholder.fill.fore_color.rgb = RGBColor(230, 230, 230)
# 添加截图(如果有)
screenshot_path = f"screenshots/{category['ruleName']}_{row_idx}_{col_idx - 1}.png"
if os.path.exists(screenshot_path):
slide.shapes.add_picture(
screenshot_path,
cell_left + Inches(0.05),
cell_top + Inches(0.05),
width=pic_width,
height=pic_height
)
# 保存PPT文件
prs.save("DRC_Violation_Report_With_Risk_Comment.pptx")
print("PPT报告生成完成,左侧新增了Risk和Comment文本框,完整规则描述已添加到备注")
帮我缩短一下表头的行高
最新发布