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
import pandas as pd
from PIL import Image, ImageDraw # 新增PIL库用于图像处理
# 算坐标,左下右上
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]))
return ll[0], ll[1], ur[0], ur[1]
def saveImageAll(lx, ly, rx, ry, output_image_name, lv):
# 设置导出区域(单位为微米)- 添加缓冲区
buffer = 1000 # 微米单位的缓冲区大小
# 3. 更新视图
lv.update_content()
# 设置导出区域(单位为微米)
export_region = pya.DBox(lx - buffer, ly - buffer, rx + buffer, ry + buffer) # 左下x, 左下y, 右上x, 右上y
for layer in lv.each_layer():
layer.visible = True
# layer.frame_color = 0x00FFFF
# 3. 更新视图
lv.update_content()
# 设置视图区域
lv.zoom_box(export_region)
# 导出图片
width = 2000
height = 1100
linewidth = 1
oversampling = -2
resolution = 0.0
# 使用正确的参数格式调用save_image_with_options
lv.save_image_with_options(output_image_name, width, height, linewidth, oversampling, resolution, export_region,
False)
'''===== 新增部分:添加黄色矩形边框 ====='''
try:
# 打开导出的图片
img = Image.open(output_image_name)
draw = ImageDraw.Draw(img)
# 计算图片坐标比例
img_width, img_height = img.size
region_width = (rx + buffer) - (lx - buffer)
region_height = (ry + buffer) - (ly - buffer)
# 将布局坐标转换为图片坐标
def layout_to_image_coords(layout_x, layout_y):
"""将布局坐标转换为图片像素坐标"""
x_percent = (layout_x - (lx - buffer)) / region_width
y_percent = 1.0 - ((layout_y - (ly - buffer)) / region_height) # 反转Y轴
img_x = int(x_percent * img_width)
img_y = int(y_percent * img_height)
return img_x, img_y
# 计算矩形边界在图片上的坐标
top_left = layout_to_image_coords(lx, ry) # 左上角 (lx, ry)
bottom_right = layout_to_image_coords(rx, ly) # 右下角 (rx, ly)
# 绘制黄色矩形边框 (RGB: 255, 255, 0)
border_width = 10 # 边框宽度(像素)
draw.rectangle(
[top_left, bottom_right],
outline=(0, 255, 255), # 黄色
width=border_width
)
# 保存处理后的图片
img.save(output_image_name)
# print(f"已添加亮蓝色矩形边框: {output_image_name}")
except Exception as e:
print(f"All添加亮蓝色矩形边框异常: {str(e)}")
'''==============================='''
def saveImageByLayer(lx, ly, rx, ry, output_image_name, lv, target_layer_number, data_type,violation_info):
# 方案1: 使用re.search()
match = re.search(r'\((.*?)\)', violation_info[0]) # 非贪婪匹配
if match:
result = match.group(1) # group(1)直接获取括号内的内容
print(result) # 输出: 0,0;0,5000;5000,5000;5000,0
else:
result = "" # 或无匹配时的处理
# 获取源图层信息
for layer in lv.each_layer():
source_layer = layer.source_layer
# print(f"层索引 {layer.layer_index()}: Layer Name={source_layer}")
# 1. 首先将所有图层设置为不可见
for layer in lv.each_layer():
layer.visible = False
# 2. 只将目标图层设置为可见
found = False
for layer in lv.each_layer():
# 比较源层号而不是层索引
if layer.source_layer == target_layer_number and layer.source_datatype == data_type:
layer.visible = True
found = True
layer.frame_color = 0xFF0000 # 红色轮廓
# print(f"已设置层号 {target_layer_number} (索引 {layer.layer_index()}) 为可见")
if not found:
print(f"警告: 未找到层号 {target_layer_number}")
# 3. 更新视图
lv.update_content()
top_cell = lv.create_cell("TOP")
# 定义图层
layer_index = lv.insert_layer(pya.LayerInfo(1, 0))
points = [pya.DPoint(*map(float, pair.split(','))) for pair in result.split(';')]
# 创建多边形并添加到布局
polygon = pya.DPolygon(points)
top_cell.shapes(layer_index).insert(polygon)
# 3. 更新视图
lv.update_content()
# # 设置导出区域(单位为微米)- 添加缓冲区
# buffer = 1000 # 微米单位的缓冲区大小
# export_region = pya.DBox(lx - buffer, ly - buffer, rx + buffer, ry + buffer)
# 设置导出范围(包含整个多边形)
bbox = polygon.bbox()
margin = 1.0 # 添加1微米边距
export_box = pya.DBox(bbox.left - margin, bbox.bottom - margin,
bbox.right + margin, bbox.top + margin)
# 设置视图区域
lv.zoom_box(export_box)
# 导出图片
width = 2000
height = 1100
linewidth = 1
oversampling = -2
resolution = 0.0
# 导出原始图像
lv.save_image_with_options(output_image_name, width, height, linewidth, oversampling, resolution, export_box,
False)
# print(f"原始图片已导出到: {output_image_name}")
'''===== 新增部分:添加黄色矩形边框 ====='''
# try:
# # 打开导出的图片
# img = Image.open(output_image_name)
# draw = ImageDraw.Draw(img)
#
# # 计算图片坐标比例
# img_width, img_height = img.size
# region_width = (rx + buffer) - (lx - buffer)
# region_height = (ry + buffer) - (ly - buffer)
#
# # 将布局坐标转换为图片坐标
# def layout_to_image_coords(layout_x, layout_y):
# """将布局坐标转换为图片像素坐标"""
# x_percent = (layout_x - (lx - buffer)) / region_width
# y_percent = 1.0 - ((layout_y - (ly - buffer)) / region_height) # 反转Y轴
#
# img_x = int(x_percent * img_width)
# img_y = int(y_percent * img_height)
# return img_x, img_y
#
# # 计算矩形边界在图片上的坐标
# top_left = layout_to_image_coords(lx, ry) # 左上角 (lx, ry)
# bottom_right = layout_to_image_coords(rx, ly) # 右下角 (rx, ly)
#
# # 绘制黄色矩形边框 (RGB: 255, 255, 0)
# border_width = 10 # 边框宽度(像素)
# draw.rectangle(
# [top_left, bottom_right],
# outline=(0, 255, 255), # 黄色
# width=border_width
# )
#
# # 保存处理后的图片
# img.save(output_image_name)
# # print(f"已添加亮蓝色矩形边框: {output_image_name}")
# except Exception as e:
# print(f"ByLayer添加亮蓝色矩形边框异常: {str(e)}")
# '''==============================='''
# 加载DRC报告数据库
report_db = pya.ReportDatabase().load("NEPTUNEZ_FIN.out")
# 确保文件存在
gds_path = "NEPTUNEZ_FIN.oas"
if not os.path.exists(gds_path):
raise FileNotFoundError(f"GDS文件未找到: {gds_path}")
# 创建LayoutView实例
lv = pya.LayoutView()
# 加载GDS文件
lv.load_layout(gds_path)
# 获取当前布局和顶层单元
lv.max_hier() # Show full hierarchy
lv.zoom_fit() # Zoom to fit content
# 存储所有类别的数据结构
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
# 只保存每个类别的前三个项目
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()
# 读取Excel文件
file_path = 'layerInfo202509051508.xlsx'
df = pd.read_excel(file_path)
# 创建以layerName为键的字典
layer_dict = {}
for _, row in df.iterrows():
layer_name = row['Layer Name']
layer_dict[layer_name] = {
'GDS': row['GDS'],
'Data Type': row['Data Type'],
}
# 为每个违规类别创建详细页
for category in all_categories:
slide = prs.slides.add_slide(prs.slide_layouts[6])
# === 规则描述处理 ===
full_description = category["description"]
rule_name = category["ruleName"]
prefix_rule_name = rule_name.split('_', 1)[0]
total_errors = category_counter[rule_name]
pattern = r'Rule File Pathname:.*?Rule File Title:'
full_description = re.sub(pattern, 'Rule File Title:', full_description, flags=re.DOTALL)
# 规则描述超过50字符时显示省略号
if len(full_description) > 300:
display_description = full_description[:300] + "..."
else:
display_description = full_description
# === 将完整描述添加到备注 ===
notes_slide = slide.notes_slide
notes_text_frame = notes_slide.notes_text_frame
notes_text_frame.text = f"Rule Detail:\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 = "Rule Detail:"
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)
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)
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)
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(34, 139, 34)
# === 右侧区域:违规表格 ===
right_start = left_start + left_width + Inches(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
# 创建表格
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", f"{prefix_rule_name}"]
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)
# 缩短表头行高
table.rows[0].height = Inches(0.5)
# 填充数据行
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)
# 违规信息列
violation_info = category["items"][row_idx - 1]
coordinates = extract_coordinates(''.join(violation_info))
saveImageAll(coordinates[0], coordinates[1], coordinates[2], coordinates[3],
f"screenshots/{category['ruleName']}.png", lv)
if prefix_rule_name in layer_dict:
info = layer_dict[prefix_rule_name]
saveImageByLayer(coordinates[0], coordinates[1], coordinates[2], coordinates[3],
f"screenshots/{category['ruleName']}_{info['GDS']}_{info['Data Type']}.png", lv,
info['GDS'], info['Data Type'],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"1.png"
if col_idx == 2:
screenshot_path = f"screenshots/{category['ruleName']}.png"
elif col_idx == 3:
if prefix_rule_name in layer_dict:
info = layer_dict[prefix_rule_name]
screenshot_path = f"screenshots/{category['ruleName']}_{info['GDS']}_{info['Data Type']}.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文件
# === 动态生成PPT文件名 ===
oas_base_name = os.path.splitext(os.path.basename(gds_path))[0]
ppt_filename = f"{oas_base_name}_DRCReport.pptx"
prs.save(ppt_filename)
print("PPT报告生成完成")这是我的,下面是画多边形的 import pya
import os
# 创建输出目录
output_dir = r"D:\out"
os.makedirs(output_dir, exist_ok=True)
# 创建新布局
layout = pya.Layout()
top_cell = layout.create_cell("TOP")
# 定义图层
layer_index = layout.insert_layer(pya.LayerInfo(1, 0))
# 输入坐标点字符串并解析
coord_str = "614.22,0.5;614.22,3;615.335,4.11;615.335,4.945;613.665,4.945;613.665,4.39;613.11,4.39;613.11,5.5;615.89,5.5;615.89,3.835;614.78,2.72;614.78,0.5"
points = [pya.DPoint(*map(float, pair.split(','))) for pair in coord_str.split(';')]
# 创建多边形并添加到布局
polygon = pya.DPolygon(points)
top_cell.shapes(layer_index).insert(polygon)
# 获取当前视图
view = pya.Application.instance().main_window().current_view()
view.select_cell(top_cell.cell_index(), 0)
view.max_hier()
# 设置导出范围(包含整个多边形)
bbox = polygon.bbox()
margin = 1.0 # 添加1微米边距
export_box = pya.DBox(bbox.left - margin, bbox.bottom - margin,
bbox.right + margin, bbox.top + margin)
# 导出图片到指定路径
view.save_image("D:/out/output.png", 2048, 2048) # 参数:文件名、宽度、高度这是高亮多边形的,帮我修改saveImageByLayer的代码