add tool of layout
This commit is contained in:
159
tools/klayoutconvertor.py
Normal file
159
tools/klayoutconvertor.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# tools/klayoutconvertor.py
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
KLayout GDS to PNG Converter
|
||||
|
||||
This script uses KLayout's Python API to convert GDS files to PNG images.
|
||||
It accepts command-line arguments for input parameters.
|
||||
|
||||
Requirements:
|
||||
pip install klayout
|
||||
|
||||
Usage:
|
||||
python klayoutconvertor.py input.gds output.png [options]
|
||||
"""
|
||||
|
||||
import klayout.db as pya
|
||||
import klayout.lay as lay
|
||||
from PIL import Image
|
||||
import os
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
|
||||
|
||||
def export_gds_as_image(
|
||||
gds_path: str,
|
||||
output_path: str,
|
||||
layers: list = [1, 2],
|
||||
center_um: tuple = (0, 0),
|
||||
view_size_um: float = 100.0,
|
||||
resolution: int = 2048,
|
||||
binarize: bool = True
|
||||
) -> None:
|
||||
"""
|
||||
Export GDS file as PNG image using KLayout.
|
||||
|
||||
Args:
|
||||
gds_path: Input GDS file path
|
||||
output_path: Output PNG file path
|
||||
layers: List of layer numbers to include
|
||||
center_um: Center coordinates in micrometers (x, y)
|
||||
view_size_um: View size in micrometers
|
||||
resolution: Output image resolution
|
||||
binarize: Whether to convert to black and white
|
||||
"""
|
||||
if not os.path.exists(gds_path):
|
||||
raise FileNotFoundError(f"Input file not found: {gds_path}")
|
||||
|
||||
# Ensure output directory exists
|
||||
output_dir = os.path.dirname(output_path)
|
||||
if output_dir:
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
layout = pya.Layout()
|
||||
layout.read(gds_path)
|
||||
top = layout.top_cell()
|
||||
|
||||
# Create layout view
|
||||
view = lay.LayoutView()
|
||||
view.set_config("background-color", "#ffffff")
|
||||
view.set_config("grid-visible", "false")
|
||||
|
||||
# Load layout into view correctly
|
||||
view.load_layout(gds_path)
|
||||
|
||||
# Add all layers
|
||||
view.add_missing_layers()
|
||||
|
||||
# Configure view to show entire layout with reasonable resolution
|
||||
if view_size_um > 0:
|
||||
# Use specified view size
|
||||
box = pya.DBox(
|
||||
center_um[0] - view_size_um / 2,
|
||||
center_um[1] - view_size_um / 2,
|
||||
center_um[0] + view_size_um / 2,
|
||||
center_um[1] + view_size_um / 2
|
||||
)
|
||||
else:
|
||||
# Use full layout bounds with size limit
|
||||
bbox = top.bbox()
|
||||
if bbox:
|
||||
# Convert to micrometers (KLayout uses database units)
|
||||
dbu = layout.dbu
|
||||
box = pya.DBox(
|
||||
bbox.left * dbu,
|
||||
bbox.bottom * dbu,
|
||||
bbox.right * dbu,
|
||||
bbox.top * dbu
|
||||
)
|
||||
|
||||
else:
|
||||
# Fallback to 100x100 um if empty layout
|
||||
box = pya.DBox(-50, -50, 50, 50)
|
||||
|
||||
view.max_hier()
|
||||
view.zoom_box(box)
|
||||
|
||||
# Save to temporary file first, then load with PIL
|
||||
import tempfile
|
||||
temp_path = tempfile.NamedTemporaryFile(suffix='.png', delete=False).name
|
||||
|
||||
try:
|
||||
view.save_image(temp_path, resolution, resolution)
|
||||
img = Image.open(temp_path)
|
||||
|
||||
if binarize:
|
||||
# Convert to grayscale and binarize
|
||||
img = img.convert("L")
|
||||
img = img.point(lambda x: 255 if x > 128 else 0, '1')
|
||||
else:
|
||||
# Convert to grayscale
|
||||
img = img.convert("L")
|
||||
|
||||
img.save(output_path)
|
||||
finally:
|
||||
# Clean up temp file
|
||||
if os.path.exists(temp_path):
|
||||
os.unlink(temp_path)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main CLI entry point."""
|
||||
parser = argparse.ArgumentParser(description='Convert GDS to PNG using KLayout')
|
||||
parser.add_argument('input', help='Input GDS file')
|
||||
parser.add_argument('output', help='Output PNG file')
|
||||
parser.add_argument('--layers', nargs='+', type=int, default=[1, 2],
|
||||
help='Layers to include (default: 1 2)')
|
||||
parser.add_argument('--center-x', type=float, default=0,
|
||||
help='Center X coordinate in micrometers (default: 0)')
|
||||
parser.add_argument('--center-y', type=float, default=0,
|
||||
help='Center Y coordinate in micrometers (default: 0)')
|
||||
parser.add_argument('--size', type=float, default=0,
|
||||
help='View size in micrometers (default: 0 = full layout)')
|
||||
parser.add_argument('--resolution', type=int, default=2048,
|
||||
help='Output image resolution (default: 2048)')
|
||||
parser.add_argument('--no-binarize', action='store_true',
|
||||
help='Disable binarization (keep grayscale)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
export_gds_as_image(
|
||||
gds_path=args.input,
|
||||
output_path=args.output,
|
||||
layers=args.layers,
|
||||
center_um=(args.center_x, args.center_y),
|
||||
view_size_um=args.size,
|
||||
resolution=args.resolution,
|
||||
binarize=not args.no_binarize
|
||||
)
|
||||
print("Conversion completed successfully!")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,88 +0,0 @@
|
||||
import gdstk
|
||||
import cairosvg
|
||||
import argparse
|
||||
import os
|
||||
|
||||
def convert_layout_to_png_via_svg(layout_path, png_path, cell_name=None, pixels_per_unit=10):
|
||||
"""
|
||||
通过先生成 SVG 再转换为 PNG 的方式,将 GDSII 或 OASIS 文件光栅化。
|
||||
此版本修正了 write_svg 的参数错误,兼容性更强。
|
||||
|
||||
参数:
|
||||
layout_path (str): 输入的版图文件路径(.gds 或 .oas)。
|
||||
png_path (str): 输出的 PNG 文件路径。
|
||||
cell_name (str, optional): 需要转换的单元名称。如果为 None,则使用顶层单元。
|
||||
pixels_per_unit (int, optional): 版图数据库单位到像素的转换比例,控制图像分辨率。
|
||||
"""
|
||||
print(f"正在从 '{layout_path}' 读取版图文件...")
|
||||
|
||||
# 1. 加载版图文件
|
||||
_, extension = os.path.splitext(layout_path)
|
||||
extension = extension.lower()
|
||||
|
||||
if extension == '.gds':
|
||||
lib = gdstk.read_gds(layout_path)
|
||||
elif extension == '.oas':
|
||||
lib = gdstk.read_oas(layout_path)
|
||||
else:
|
||||
raise ValueError(f"不支持的文件类型: '{extension}'。请输入 .gds 或 .oas 文件。")
|
||||
|
||||
if cell_name:
|
||||
cell = lib.cells[cell_name]
|
||||
else:
|
||||
top_cells = lib.top_level()
|
||||
if not top_cells:
|
||||
raise ValueError("错误:版图文件中没有找到顶层单元。")
|
||||
cell = top_cells[0]
|
||||
print(f"未指定单元名称,自动选择顶层单元: '{cell.name}'")
|
||||
|
||||
# 2. 将版图单元写入临时的 SVG 文件 (已移除无效的 padding 参数)
|
||||
temp_svg_path = png_path + ".temp.svg"
|
||||
print(f"步骤 1/2: 正在将单元 '{cell.name}' 转换为临时 SVG 文件...")
|
||||
cell.write_svg(
|
||||
temp_svg_path # 隐藏默认字体,避免影响边界
|
||||
)
|
||||
|
||||
# 3. 使用 cairosvg 将 SVG 文件转换为 PNG
|
||||
print(f"步骤 2/2: 正在将 SVG 转换为 PNG...")
|
||||
# 获取单元的精确边界框
|
||||
bb = cell.bb()
|
||||
if bb is None:
|
||||
raise ValueError(f"单元 '{cell.name}' 为空或无法获取其边界框。")
|
||||
|
||||
# 根据边界框和分辨率计算输出图像的宽度
|
||||
width, height = bb[1] - bb[0]
|
||||
output_width = width * pixels_per_unit
|
||||
|
||||
cairosvg.svg2png(url=temp_svg_path, write_to=png_path, output_width=output_width)
|
||||
|
||||
# 4. 清理临时的 SVG 文件
|
||||
os.remove(temp_svg_path)
|
||||
|
||||
print(f"成功!图像已保存至: '{png_path}'")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="将 GDSII (.gds) 或 OASIS (.oas) 版图文件转换为 PNG 图像 (通过SVG)。",
|
||||
epilog="示例: python rasterize.py -i my_chip.oas -o my_chip.png -ppu 20"
|
||||
)
|
||||
parser.add_argument('-i', '--input', type=str, required=True, help="输入的版图文件路径 (.gds 或 .oas)。")
|
||||
parser.add_argument('-o', '--output', type=str, help="输出的 PNG 文件路径。如果未提供,将使用输入文件名并替换扩展名为 .png。")
|
||||
parser.add_argument('-c', '--cell', type=str, default=None, help="要转换的特定单元的名称。默认为顶层单元。")
|
||||
parser.add_argument('-ppu', '--pixels_per_unit', type=int, default=10, help="每微米(um)的像素数,用于控制输出图像的分辨率。")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.output:
|
||||
base_name = os.path.splitext(os.path.basename(args.input))[0]
|
||||
args.output = f"{base_name}.png"
|
||||
print(f"未指定输出路径,将自动保存为: '{args.output}'")
|
||||
|
||||
try:
|
||||
convert_layout_to_png_via_svg(
|
||||
layout_path=args.input,
|
||||
png_path=args.output,
|
||||
cell_name=args.cell,
|
||||
pixels_per_unit=args.pixels_per_unit
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"\n处理失败: {e}")
|
||||
Reference in New Issue
Block a user