import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import csv
import klayout.db as pya
class CSVProcessor:
def __init__(self):
self.header = [
"#chip_x_microns",
"#chip_y_microns",
"#output_file_name",
"#top_cell_name"
]
def validate_csv(self, filepath):
"""验证CSV文件格式是否正确"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
actual_header = [col.strip() for col in next(reader)]
expected_header = [col.strip() for col in self.header]
if actual_header != expected_header:
raise ValueError(
f"CSV header mismatch.\n"
f"Expected: {self.header}\n"
f"Got: {actual_header}\n"
f"Note: Please remove trailing spaces in the CSV header"
)
for row_num, row in enumerate(reader, 2):
if len(row) != 4:
raise ValueError(f"Row {row_num} has incorrect number of columns")
try:
float(row[0]), float(row[1])
except ValueError:
raise ValueError(f"Row {row_num} contains non-numeric values in x/y columns")
return True
except Exception as e:
print("CSV Validation Error", str(e))
return False
def process_csv(self, csv_path):
"""处理CSV文件并返回参数列表"""
params_list = []
with open(csv_path, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
next(reader)
for row in reader:
if not row:
continue
params = {
'chip_x_microns': float(row[0]),
'chip_y_microns': float(row[1]),
'output_file_name': row[2].strip(),
'top_cell_name': row[3].strip(),
}
params_list.append(params)
return params_list
class RopeOptimizer:
def __init__(self, lengths, target_length):
self.lengths = lengths
self.lengths.sort(reverse=True)
self.target_length = target_length
def optimize_rope_lengths(self):
rope_counts = {length: 0 for length in self.lengths}
remaining_length = self.target_length
for length in self.lengths:
max_count = int(remaining_length // length)
rope_counts[length] = max_count
remaining_length -= max_count * length
if remaining_length:
rope_counts[self.lengths[-1]] += 1
remaining_length -= self.lengths[-1]
return rope_counts, int(remaining_length)
class LayoutPattern:
def __init__(self, filename):
self.layout_name = filename
self.lay = None
def find_cell(self, name):
return self.lay.cell(name)
def size_get(self, cell):
bbox = cell.bbox()
if bbox.empty():
return 0, 0, pya.Box(left=0, bottom=0, right=0, top=0)
width = bbox.right - bbox.left
height = bbox.top - bbox.bottom
return width, height, bbox
def size_get_with_cell_name(self, cell_name):
cell = self.find_cell(cell_name)
return self.size_get(cell)
def dbu_get(self):
return self.lay.dbu
def destroy(self):
self.lay._destroy()
class LayoutReader(LayoutPattern):
def __init__(self, filename):
super().__init__(filename)
self.lay = pya.Layout(editable=False)
self.lay.read(filename)
class LayoutWriter(LayoutPattern):
def __init__(self, filename):
super().__init__(filename)
self.lay = pya.Layout(editable=True)
def create_cell(self, cell_name):
return self.lay.create_cell(cell_name)
def delete_cell(self, cell):
cell.delete()
def instance_to(self, cell, x, y):
pass
def dbu_set(self, dbu_val):
self.lay.dbu = dbu_val
def make_stream(self):
self.lay.write(self.layout_name)
def flatten(self, cell_name):
cell = self.find_cell(cell_name)
cell.flatten(-1, True)
def flatten_cell(self, cell):
cell.flatten(-1, True)
def run_fsr(in_file,
out_file,
chip_x_microns,
chip_y_microns,
input_corner_cell="cell_corner",
input_line_cell_longest="cell_1",
input_line_cell_medium="cell_2",
input_line_cell_shortest="cell_3",
output_topcell="top"):
pattern = LayoutReader(in_file)
corner_cell = pattern.find_cell(input_corner_cell)
if corner_cell is None:
print(f"ERROR: Cell {input_corner_cell} not found in {in_file}.")
return False
line10_cell = pattern.find_cell(input_line_cell_longest)
if line10_cell is None:
print(f"ERROR: Cell {input_line_cell_longest} not found in {in_file}.")
return False
line1_cell = pattern.find_cell(input_line_cell_medium)
if line1_cell is None:
print(f"ERROR: Cell {input_line_cell_medium} not found in {in_file}.")
return False
line0d1_cell = pattern.find_cell(input_line_cell_shortest)
if line0d1_cell is None:
print(f"ERROR: Cell {input_line_cell_shortest} not found in {in_file}.")
return False
sr = LayoutWriter(out_file)
sr.dbu_set(pattern.dbu_get())
sr_corner_cell = sr.create_cell(cell_name=input_corner_cell)
sr_line10_cell = sr.create_cell(cell_name=input_line_cell_longest)
sr_line1_cell = sr.create_cell(cell_name=input_line_cell_medium)
sr_line0d1_cell = sr.create_cell(cell_name=input_line_cell_shortest)
sr_corner_cell.copy_tree(corner_cell)
sr_line10_cell.copy_tree(line10_cell)
sr_line1_cell.copy_tree(line1_cell)
sr_line0d1_cell.copy_tree(line0d1_cell)
sr.flatten_cell(sr_corner_cell)
sr.flatten_cell(sr_line10_cell)
sr.flatten_cell(sr_line1_cell)
sr.flatten_cell(sr_line0d1_cell)
_, _, bbox_corner = sr.size_get(sr_corner_cell)
_, _, bbox_line10 = sr.size_get(sr_line10_cell)
_, _, bbox_line1 = sr.size_get(sr_line1_cell)
_, _, bbox_line0d1 = sr.size_get(sr_line0d1_cell)
sr_corner_cell.transform(pya.Trans(pya.Trans.R0, x=-bbox_corner.left, y=-bbox_corner.bottom))
sr_line10_cell.transform(pya.Trans(pya.Trans.R0, x=-bbox_line10.left, y=-bbox_line10.bottom))
sr_line1_cell.transform(pya.Trans(pya.Trans.R0, x=-bbox_line1.left, y=-bbox_line1.bottom))
sr_line0d1_cell.transform(pya.Trans(pya.Trans.R0, x=-bbox_line0d1.left, y=-bbox_line0d1.bottom))
width_corner, height_corner, bbox_corner = sr.size_get(sr_corner_cell)
width_line10, height_line10, bbox_line10 = sr.size_get(sr_line10_cell)
width_line1, height_line1, bbox_line1 = sr.size_get(sr_line1_cell)
width_line0d1, height_line0d1, bbox_line0d1 = sr.size_get(sr_line0d1_cell)
if not (height_line10 == height_line1 == height_line0d1):
print(
f"ERROR: heights of cell {input_line_cell_longest}, {input_line_cell_medium} and {input_line_cell_shortest} \
are not equal, check {in_file} for details.")
return False
lengths = [
width_line10,
width_line1,
width_line0d1
]
floating_len = width_corner + height_corner - 2 * height_line10
target_length_x = round(float(chip_x_microns) / pattern.dbu_get()) - floating_len
optimizer_x = RopeOptimizer(lengths, target_length_x)
result_x, error_x = optimizer_x.optimize_rope_lengths()
target_length_y = round(float(chip_y_microns) / pattern.dbu_get()) - floating_len
optimizer_y = RopeOptimizer(lengths, target_length_y)
result_y, error_y = optimizer_y.optimize_rope_lengths()
pattern.destroy()
sr_lines = [sr_line10_cell, sr_line1_cell, sr_line0d1_cell]
sr_linex_cell = sr.create_cell("linex")
sr_liney_cell = sr.create_cell("liney")
for item, line in zip(lengths, sr_lines):
_, _, linex_bbox = sr.size_get_with_cell_name("linex")
widthx, heightx, _ = sr.size_get(line)
_, _, liney_bbox = sr.size_get_with_cell_name("liney")
widthy, heighty, _ = sr.size_get(line)
if result_x[item] == 0 and result_y[item] == 0:
sr.delete_cell(line)
continue
if result_x[item] > 0:
sr_linex_trans = pya.Trans(pya.Trans.R0, x=linex_bbox.right, y=linex_bbox.bottom)
sr_linex_a = pya.Vector(x=widthx, y=0)
sr_linex_b = pya.Vector(x=0, y=heightx)
inst_array = pya.CellInstArray(line, sr_linex_trans, sr_linex_a, sr_linex_b, result_x[item], 1)
sr_linex_cell.insert(inst_array)
if result_y[item] > 0:
sr_liney_trans = pya.Trans(x=liney_bbox.right, y=liney_bbox.bottom)
sr_liney_a = pya.Vector(widthy, 0)
sr_liney_b = pya.Vector(0, heighty)
inst_array = pya.CellInstArray(line, sr_liney_trans, sr_liney_a, sr_liney_b, result_y[item], 1)
sr_liney_cell.insert(inst_array)
sr_top_cell = sr.create_cell(output_topcell)
width_top, height_top, bbox_top = sr.size_get(sr_top_cell)
inst = pya.CellInstArray(sr_corner_cell, pya.Trans(x=bbox_top.left, y=bbox_top.bottom))
sr_top_cell.insert(inst)
width_top, height_top, bbox_top = sr.size_get(sr_top_cell)
trans = pya.Trans(pya.Trans.R0, x=bbox_top.right, y=bbox_top.bottom)
inst = pya.CellInstArray(sr_linex_cell, trans)
sr_top_cell.insert(inst)
width_top, height_top, bbox_top = sr.size_get(sr_top_cell)
trans = pya.Trans(pya.Trans.R90, x=bbox_top.right + height_corner + error_x, y=bbox_top.bottom)
inst = pya.CellInstArray(sr_corner_cell, trans)
sr_top_cell.insert(inst)
width_top, height_top, bbox_top = sr.size_get(sr_top_cell)
trans = pya.Trans(pya.Trans.R90, x=bbox_top.right, y=bbox_top.top)
inst = pya.CellInstArray(sr_liney_cell, trans)
sr_top_cell.insert(inst)
width_top, height_top, bbox_top = sr.size_get(sr_top_cell)
trans = pya.Trans(pya.Trans.R180, x=bbox_top.right, y=bbox_top.top + height_corner + error_y)
inst = pya.CellInstArray(sr_corner_cell, trans)
sr_top_cell.insert(inst)
width_top, height_top, bbox_top = sr.size_get(sr_top_cell)
trans = pya.Trans(pya.Trans.R180, x=bbox_top.right - width_corner, y=bbox_top.top)
inst = pya.CellInstArray(sr_linex_cell, trans)
sr_top_cell.insert(inst)
width_top, height_top, bbox_top = sr.size_get(sr_top_cell)
trans = pya.Trans(pya.Trans.R270, x=bbox_top.left, y=bbox_top.top)
inst = pya.CellInstArray(sr_corner_cell, trans)
sr_top_cell.insert(inst)
width_top, height_top, bbox_top = sr.size_get(sr_top_cell)
trans = pya.Trans(pya.Trans.R270, x=bbox_top.left, y=bbox_top.top - width_corner)
inst = pya.CellInstArray(sr_liney_cell, trans)
sr_top_cell.insert(inst)
sr.flatten(output_topcell)
width_top, height_top, bbox_top = sr.size_get(sr_top_cell)
BORDER_LAYER = sr.layer(259, 99)
AA_box = pya.Box(0, 0, width_top, height_top)
output_topcell.shapes(BORDER_LAYER).insert(AA_box)
sr.make_stream()
return True
class DataProcessorApp:
def __init__(self):
# Style constants
self.BG_COLOR = "#f0f0f0"
self.ACCENT_COLOR = "#4a6fa5"
self.TEXT_COLOR = "#333333"
self.FONT = ("Segoe UI", 10)
# Input variables
self.x = None
self.y = None
self.input_gds_path = None
self.input_csv_path = None
self.output_file_path = None
self.setup_ui()
def setup_ui(self):
"""Initialize all UI components"""
self.root = tk.Tk()
self.root.title("Auto Fluid Seal Ring Tool")
self.root.geometry("600x400")
self.root.resizable(False, False)
self.root.configure(bg=self.BG_COLOR)
self.setup_styles()
self.create_widgets()
self.setup_layout()
# Initialize UI state
self.toggle_processing_mode()
def setup_styles(self):
"""Configure ttk styles"""
self.style = ttk.Style()
self.style.theme_use('clam')
self.style.configure('TFrame', background=self.BG_COLOR)
self.style.configure('TLabel', background=self.BG_COLOR,
font=self.FONT, foreground=self.TEXT_COLOR)
self.style.configure('TButton', font=self.FONT, padding=5)
self.style.configure('Accent.TButton', background=self.ACCENT_COLOR,
foreground='white')
self.style.map('Accent.TButton',
background=[('active', '#3a5a8f'), ('disabled', '#a0a0a0')])
def create_widgets(self):
"""Create all UI widgets"""
# Mode selection
self.mode_frame = ttk.Frame(self.root, padding=(10, 10, 10, 5))
self.create_label(self.mode_frame, "Processing Mode:").grid(row=0, column=0, sticky="w")
self.processing_mode = tk.StringVar(value="single")
self.mode_buttons_frame = ttk.Frame(self.mode_frame)
self.mode_buttons_frame.grid(row=0, column=1, sticky="w")
ttk.Radiobutton(self.mode_buttons_frame, text="Single Mode",
variable=self.processing_mode, value="single",
command=self.toggle_processing_mode).pack(side="left", padx=5)
ttk.Radiobutton(self.mode_buttons_frame, text="Batch Mode",
variable=self.processing_mode, value="batch",
command=self.toggle_processing_mode).pack(side="left", padx=5)
# Configuration file (common for both modes)
self.in_gds_frame = ttk.Frame(self.root, padding=(20, 10, 20, 10),
relief="groove", borderwidth=1)
self.create_label(self.in_gds_frame, "Input GDS File:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.entry_ingds = self.create_entry(self.in_gds_frame)
self.entry_ingds.grid(row=0, column=1, padx=5, pady=5, sticky="we")
self.create_button(self.in_gds_frame, "Browse...", self.select_in_gds_file).grid(row=0, column=2, padx=5,
pady=5)
# Single mode frame
self.frame_single = ttk.Frame(self.root, padding=(20, 10, 20, 10),
relief="groove", borderwidth=1)
self.create_label(self.frame_single, "X(um):").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.entry_x = self.create_entry(self.frame_single, width=31)
self.entry_x.grid(row=0, column=1, padx=5, pady=5, sticky="we")
self.create_label(self.frame_single, "Y(um):").grid(row=0, column=2, padx=5, pady=5, sticky="e")
self.entry_y = self.create_entry(self.frame_single, width=31)
self.entry_y.grid(row=0, column=3, padx=5, pady=5, sticky="we")
# Batch mode frame
self.frame_batch = ttk.Frame(self.root, padding=(20, 10, 20, 10),
relief="groove", borderwidth=1)
self.create_label(self.frame_batch, "Input CSV File:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.entry_batch_input = self.create_entry(self.frame_batch)
self.entry_batch_input.grid(row=0, column=1, padx=5, pady=5, sticky="we")
self.create_button(self.frame_batch, "Browse...", self.select_batch_input).grid(row=0, column=2, padx=5, pady=5)
# Output file
self.output_frame = ttk.Frame(self.root, padding=(10, 10, 10, 10))
self.create_label(self.output_frame, "Output GDS File:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
self.entry_output = self.create_entry(self.output_frame, width=52)
self.entry_output.grid(row=0, column=1, padx=5, pady=5, sticky="we")
self.create_button(self.output_frame, "Browse...", self.select_output_path).grid(row=0, column=2, padx=5, pady=5)
# Run button
self.button_frame = ttk.Frame(self.root, padding=(10, 10, 10, 10))
self.create_button(self.button_frame, "Run", self.run_process).grid(sticky="ew", pady=5)
def setup_layout(self):
"""Arrange widgets in the window"""
self.mode_frame.grid(row=0, column=0, columnspan=3, sticky="we")
self.in_gds_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=5, sticky="we")
self.frame_single.grid(row=2, column=0, columnspan=3, padx=10, pady=5, sticky="nsew")
self.frame_batch.grid(row=2, column=0, columnspan=3, padx=10, pady=5, sticky="nsew")
self.output_frame.grid(row=3, column=0, columnspan=3, sticky="we")
self.button_frame.grid(row=4, column=0, columnspan=3, sticky="we")
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_rowconfigure(2, weight=1)
def create_label(self, parent, text):
"""Helper to create styled labels"""
return ttk.Label(parent, text=text, background=self.BG_COLOR,
font=self.FONT, foreground=self.TEXT_COLOR)
def create_entry(self, parent, width=50):
"""Helper to create styled entry fields"""
return ttk.Entry(parent, font=self.FONT, width=width)
def create_button(self, parent, text, command):
"""Helper to create styled buttons"""
return ttk.Button(parent, text=text, command=command, style='Accent.TButton')
def run_process(self):
"""Execute data processing based on selected mode"""
try:
# First validate the config file
ingds_file = self.entry_ingds.get()
if not ingds_file:
messagebox.showerror("Error", "Please select input GDS file.")
return
self.input_gds_path = ingds_file
if self.processing_mode.get() == "single":
self.process_single_mode()
else:
self.process_batch_mode()
except Exception as e:
messagebox.showerror("Error", f"Processing error:\n{str(e)}")
def process_single_mode(self):
"""Process data in single mode"""
val1 = self.entry_x.get()
val2 = self.entry_y.get()
output_path = self.entry_output.get()
if not val1 or not val2:
messagebox.showerror("Error", "Please enter X and Y values.")
return
if not output_path:
messagebox.showerror("Error", "Please select output GDS file path.")
return
output_dir = os.path.dirname(output_path)
if output_dir:
os.makedirs(output_dir, exist_ok=True)
try:
self.x = float(val1)
self.y = float(val2)
self.output_file_path = output_path
except ValueError:
messagebox.showerror("Error", "Please enter valid X and Y numbers.")
return
print(f"input GDS: {self.input_gds_path}")
result = run_fsr(self.input_gds_path,
self.output_file_path,
self.x,
self.y)
print(f"size: {self.x}*{self.y}, stream out: {self.output_file_path}.")
if result is True:
messagebox.showinfo("Success", f"Processing complete!\nResult saved to:\n{output_path}")
else:
messagebox.showerror("Error", f"Processing error!\nCheck terminal output for details.\n")
def process_batch_mode(self):
"""Process data in batch mode"""
input_file = self.entry_batch_input.get()
output_path = self.entry_output.get()
if not input_file:
messagebox.showerror("Error", "Please select input CSV file")
return
self.input_csv_path = input_file
self.output_file_path = output_path
processor = CSVProcessor()
if not processor.validate_csv(self.input_csv_path):
return
params_list = processor.process_csv(self.input_csv_path)
print(f"input GDS: {self.input_gds_path}")
print(f"input CSV: {self.input_csv_path}")
success_count = 0
for param in params_list:
x = param.get('chip_x_microns')
y = param.get('chip_y_microns')
out_file = param.get('output_file_name')
result = run_fsr(self.input_gds_path,
out_file, x, y,
output_topcell=param.get('top_cell_name'))
print(f"size: {x}*{y}, stream out: {out_file}.")
if result: success_count += 1
if success_count != len(params_list):
messagebox.showerror("Error", f"Processing error!\nCheck terminal output for details.\n")
else:
messagebox.showinfo("Success", f"Processing complete!")
def select_in_gds_file(self):
"""Open file dialog for configuration file selection"""
file_path = filedialog.askopenfilename(
filetypes=[
("GDS files", "*.gds"),
("GDS files", "*.GDS"),
("All files", "*.*")
],
title="Select Input GDS File"
)
if file_path:
self.entry_ingds.delete(0, tk.END)
self.entry_ingds.insert(0, file_path)
def select_output_path(self):
"""Open file dialog for output path selection"""
try:
x, y = self.entry_x.get(), self.entry_y.get()
default_name = f"sealring_{x}x{y}.gds" if x and y else ""
except:
default_name = ""
file_path = filedialog.asksaveasfilename(
filetypes=[
("GDS files", "*.gds"),
("GDS files", "*.GDS"),
("All files", "*.*")
],
title="Save Output GDS File",
initialfile=default_name,
defaultextension=".gds"
)
if file_path:
self.entry_output.delete(0, tk.END)
self.entry_output.insert(0, file_path)
def select_batch_input(self):
"""Open file dialog for batch input selection"""
file_path = filedialog.askopenfilename(
filetypes=[
("CSV files", "*.csv"),
("CSV files", "*.CSV"),
("All files", "*.*")
],
title="Select CSV File"
)
if file_path:
self.entry_batch_input.delete(0, tk.END)
self.entry_batch_input.insert(0, file_path)
def toggle_processing_mode(self):
"""Switch between single and batch mode UI"""
if self.processing_mode.get() == "single":
self.frame_single.grid()
self.frame_batch.grid_remove()
self.output_frame.grid()
else:
self.frame_batch.grid()
self.frame_single.grid_remove()
self.output_frame.grid_remove()
self.root.update_idletasks()
self.root.geometry("")
def run(self):
"""Start the application"""
self.root.mainloop()
if __name__ == "__main__":
app = DataProcessorApp()
app.run()
中我加了 width_top, height_top, bbox_top = sr.size_get(sr_top_cell)
BORDER_LAYER = sr.layer(259, 99)
AA_box = pya.Box(0, 0, width_top, height_top)
output_topcell.shapes(BORDER_LAYER).insert(AA_box)这一段为什么不能运行了
最新发布