LoginSignup
0
0

gdstk と google colab ではじめる GDSII layout制作の自動化

Last updated at Posted at 2023-11-30

はじめに

gdstk(GDSII Tool Kit)は、集積回路(IC)やMEMSなどのマイクロファブリケーション設計のためのツールで、pythonを駆使して、GDSII layout制作の自動化を進めたい人向けの記事です。主なユーザーなプロセス関係者だと思いますので、プログラムに自信がない方でも、必ず動く環境を提供するために、google colab で全ての動作が確認できるようにしています。

この記事では、gdstk (ver 0.9.47) の getting started の下記のページ、

に即して、誰もが google アカウントがあれば確実に実行できるように、google colab 上で実行したページをセットで紹介しています。

基本を知ってる人は、

も参考ください。

gdstk の特徴

gdstk(GDSII Tool Kit)の主な特長として以下が挙げられます。

  • 高性能: gdstkはPythonで記述されており、高速なCライブラリを使用しています。これにより、複雑なジオメトリや大規模な設計データを効率的に処理できます。
  • 柔軟性: 幅広いジオメトリ操作が可能で、カスタマイズや拡張が容易です。ユーザーは自由にジオメトリを構築、変更、または組み合わせることができます。
  • 多様な機能: ポリゴン、パス、ラベルの作成、ブーリアン演算、オフセット、フィレットなど、豊富な機能を提供します。
  • 階層的デザインのサポート: 複雑なデザインをセルや参照を通じて効率的に管理でき、階層構造を利用してデータの重複を避けることができます。
  • GDSIIとOASISフォーマットのサポート: 半導体産業で広く使用されるGDSIIやOASISフォーマットの読み書きが可能です。
  • Pythonの利便性: Pythonの使いやすさと柔軟性を活かし、スクリプトや自動化が簡単に行えます。

GDSII とは?

GDSII は、定義がかなり古いもので、1989年からほとんど変わってないらしい。。

1989年以降、GDSII仕様の公式な更新はありませんでした。ご想像の通り、コンピューターやプロセッサーはその時以降、大きく進化しています。この仕様には、80年代のコンピューターの限界に基づいた制約がいくつかありますし、チップの複雑さが将来的に同程度であると開発者が信じていたことにも基づいています。

GDSIIの長い寿命は、そのエレガントなアーキテクチャと単純さによる、ということで、今でもよく使われているようです。

gtdtk の install

anaconda で インストールする場合

# Create a new conda environment named gdstk
conda create -n gdstk -c conda-forge --strict-channel-priority
# Activate the new environment
conda activate gdstk
# Install gdstk
conda install gdstk

2023.11.30時点では、version 0.9.47 がインストールされる。

google colab にインストールする場合

# install condacolab
!pip install -q condacolab
import condacolab
condacolab.install() # Mambaforge-23.1.0-1-Linux-x86_64.sh...  @2023.11.30

として、condacolab を入れて、google colab で anaconda 環境が動くようにまずは準備する。(pip install gsdtk だとハマりやすいです。この方法は多少時間がかかりますが、一番確実なはずです。)

# Create a new conda environment named gdstk
!conda create -n gdstk -c conda-forge --strict-channel-priority
# Activate the new environment
!conda activate gdstk
# Install gdstk
!conda install gdstk

google colab の場合は、文頭に ! がないと、shell コマンドとして認識されてないので、そこだけご注意ください。

必要なモジュールのインストール

この記事で使うのは、matplotlib と numpy と IPython だけです。

import matplotlib.pyplot as plt
import numpy as np
from IPython.display import *
import gdstk
print("gdstk.__version__ = ", gdstk.__version__) # gdstk.__version__ =  0.9.47 (2023.11.29)

gdstk のバージョンを確認しましょう。

GDS ファイルを matplotlib で表示する関数

はじめに一つだけ関数を用意します。gdstk を使う時には、生成した gds ファイルの確認作業も含めて素早くできないと、効率的に開発ができないので、生成した gdsファイルを読み込んで、matplotlibで描画する関数を用意しておきます。

def plot_gds(gdsfile, transformation=None, maxlayer = 10, margin=3, debug=False):
  # GDSファイルを読み込む
  lib = gdstk.read_gds(gdsfile)
  # トップレベルのセルを取得
  top_cells = lib.top_level()
  # サブプロットを作成
  fig, axes = plt.subplots(1, 3, figsize=(10,6), tight_layout=True)
  # カラーマップを設定
  cmap = plt.get_cmap('viridis', maxlayer)

  # すべてのトップレベルセルのポリゴンをプロット
  for cell in top_cells:
    # セルの境界ボックスを取得
    (xmin,ymin),(xmax,ymax) = cell.bounding_box()
    if hasattr(cell, 'polygons'):
      print("polygons in cell is found. length = ", len(cell.get_polygons()))
      for polygon in cell.get_polygons(): # cell.polygons does not work when the reference is used.
        points = np.array(polygon.points)
        if debug: print("     layer = ", polygon.layer, " area = ", polygon.area())
        # ポリゴンをプロット
        axes[0].plot(*np.vstack((points, points[0])).T, linestyle='solid', linewidth = 1, alpha=0.8, color=cmap(polygon.layer))
    if hasattr(cell, 'paths'):
      print("paths in cell is found. Length = ", len(cell.paths))
      for path in cell.paths:
        polys = path.to_polygons()
        for poly in polys:
            if debug: print("     layer = ", poly.layer, " area = ", poly.area())
            points = np.array(poly.points)
            # パスをプロット
            axes[1].plot(*np.vstack((points, points[0])).T, linestyle='solid', linewidth = 1, alpha=0.8, color=cmap(poly.layer))

    if hasattr(cell, 'references'):
      print("references in cell is found. length = ", len(cell.references))
      for reference in cell.references:
        # 参照の位置、回転、スケーリングを取得
        origin = reference.origin
        rotation = -1 * reference.rotation 
        magnification = reference.magnification if reference.magnification is not None else 1
        # 変換を適用してプロット
        for polygon in reference.cell.get_polygons(): # cell.polygons does not work when the reference is used.
          if debug: print("     layer = ", polygon.layer, " area = ", polygon.area())
          points = np.array(polygon.points)
          # スケーリングと回転を適用
          transformed_points = magnification * points.dot(
          np.array([[np.cos(rotation), -np.sin(rotation)],
                         [np.sin(rotation),  np.cos(rotation)]])
          )  + origin
          # 参照をプロット
          axes[2].plot(*np.vstack((transformed_points, transformed_points[0])).T, linestyle='solid', linewidth=1, alpha=0.8, color=cmap(polygon.layer))

  # 各サブプロットのタイトルを設定
  axes[0].set_title("polygons")
  axes[1].set_title("paths")
  axes[2].set_title("references")
  # サブプロットの範囲を設定
  axes[0].set_xlim(xmin - margin, xmax + margin)
  axes[1].set_xlim(xmin - margin, xmax + margin)
  axes[2].set_xlim(xmin - margin, xmax + margin)
  axes[0].set_ylim(ymin - margin, ymax + margin)
  axes[1].set_ylim(ymin - margin, ymax + margin)
  axes[2].set_ylim(ymin - margin, ymax + margin)
  # サブプロットのアスペクト比を設定
  axes[0].set_aspect('equal', adjustable='box')
  axes[1].set_aspect('equal', adjustable='box')
  axes[2].set_aspect('equal', adjustable='box')
  # グリッドを表示
  axes[0].grid(alpha=0.3)
  axes[1].grid(alpha=0.3)
  axes[2].grid(alpha=0.3)

  # プロットを保存
  plt.savefig(gdsfile.replace(".gds",".png"))
  # プロットを表示
  plt.show()

ax.set_aspect('equal', adjustable='box') の役割

  • matplotlibにおいて、軸のアスペクト比を設定するためのオプションです。この設定は、グラフのx軸とy軸のスケールを同じにし、正方形または円などの形状が歪まずに正確に表示されるようにするために使用されます。

  • equal:このオプションは、x軸とy軸の単位が同じスケールで表示されるようにします。つまり、両軸の1単位の長さが画面上で同じになります。

  • adjustable='box':このオプションは、アスペクト比を保ちながらプロットボックスのサイズを調整する方法を指定します。'box' と設定すると、アスペクト比を維持するためにプロットボックス自体のサイズが調整されます。

この設定は、例えば円や正方形をプロットする際にその形状が歪まないようにするために重要です。そのため、グラフに幾何学的な図形を表示する場合によく使用されます。

ax.plot(*np.vstack((points, points[0])).T) の役割

このコードの ax.plot(*np.vstack((points, points[0])).T) 部分で使用されているアスタリスク (*) と points[0] には、特定の目的があります。

アスタリスク (*):

  • このアスタリスクはPythonの「アンパック演算子」として機能します。np.vstack((points, points[0])).T によって生成された配列を、ax.plot 関数に個別の引数として渡します。
    例えば、np.vstack((points, points[0])).T が 2D 配列 [x座標のリスト, y座標のリスト] を生成すると、ax.plot(*[x座標のリスト, y座標のリスト]) として、x座標のリストy座標のリスト を個別の引数として ax.plot に渡すことができます。

  • points[0] は、points 配列の最初の要素(始点の座標)を参照します。これを points 配列の最後に追加することで、ポリゴンやパスを閉じる(最初の点に戻る)ことができます。したがって、np.vstack((points, points[0])) により、最初の点を最後にも追加して配列を閉じる形にします。

このように、ax.plot(*np.vstack((points, points[0])).T) はポリゴンやパスの全ての点をプロットし、最後に始点に戻って図形を閉じるためのコードです。

アスタリスク(*)はPythonの「アンパック演算子」で、gdstkでは、ポリゴンのリストを一気に関数に渡すために多様されます。

np.array(polygon.points)で型変換

np.array(polygon.points)で型変換しているのには、いくつかの理由があります。

  • 配列操作のため: polygon.points は、通常Pythonのタプルやリストの形式でポリゴンの各頂点の座標を保持しています。np.arrayを用いてこれをNumPy配列に変換することで、NumPyが提供する便利な配列操作機能を利用できます。NumPy配列は、要素ごとの操作、形状の変更、数学的演算などが簡単にできるため、座標データの処理が容易になります。

  • 効率的な計算:NumPyは高速な数値計算をサポートしており、大きなデータセットに対しても効率的な計算が可能です。np.arrayによって通常のPythonリストやタプルをNumPy配列に変換することで、これらの高速な計算機能を利用できるようになります。

  • np.vstackの利用:np.vstack関数はNumPy配列を引数として取り、これらの配列を垂直方向(行方向)に積み上げる(結合する)ことができます。np.array(polygon.points)によってNumPy配列に変換することで、np.vstack関数を適切に使用し、ポリゴンを描画するための座標配列を効率的に作成することができます。

これらの理由から、np.arrayを使用してpolygon.pointsをNumPy配列に変換することは、データ処理と可視化において非常に便利です。特に、複雑な幾何学的形状や大量のデータを扱う場合、このような変換はデータ操作の柔軟性と効率を大いに向上させます。

gdstk の最初の一歩

First Layout

gdstkモジュールをインポートした後、デザインを保持するためのライブラリ lib を作成します。

すべてのレイアウト要素はセルに追加します。セルは幾何学が描かれる紙の一部と考えることができます。後から、セルは階層的なデザインを構築するために他のセルを参照できます。

名前「FIRST」で gdstk.Cell を作成します。セルは名前で識別されるため、ライブラリ内のセル名は一意でなければなりません。cell.add で、 gdstk.rectangle() を追加します。

最後に、ライブラリ全体が「first.gds」という名前のGDSIIファイルと、「first.oas」という名前のOASISファイルで保存されます。GDSIIまたはOASISファイルは、KLayoutなどの多くのビューアーやエディターで開くことができます。

import gdstk
# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("FIRST")

# Create the geometry (a single rectangle) and add it to the cell.
rect = gdstk.rectangle((0, 0), (2, 1))
cell.add(rect)

# Save the library in a GDSII or OASIS file.
lib.write_gds("first.gds")
lib.write_oas("first.oas")
# Optionally, save an image of the cell as SVG.
cell.write_svg("first.svg")
plot_gds("first.gds")
display_svg(SVG('first.svg'))

output_11_1.png

このように、polygons のみに長方形が描かれます。 paths と references が白紙、なのは first.gds ファイルには polygons しか含まれてないためです。この記事では、確認の意味を込めて、必ず、polygons, paths, references という gds ファイルの基本構成要素の3点セットを表示することにしています。

の方では、matplotlibの表示と、SVGの図の両方を表示させていますので、確認してください。

Polygons

一般的なポリゴンは、順序付けられた頂点のリストによって定義することができます。頂点の向き(時計回り/反時計回り)は重要ではありません。それらは内部で順序付けられます。

import gdstk
# Create a polygon from a list of vertices
# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("Polygons")
points = [(0, 0), (2, 2), (2, 6), (-6, 6), (-6, -6), (-4, -4), (-4, 4), (0, 4)]
poly = gdstk.Polygon(points)
cell.add(poly)
# Save the library in a GDSII or OASIS file.
lib.write_gds("poly.gds")
plot_gds("poly.gds")
cell.write_svg('poly.svg', background="none",) #svgファイル出力
display_svg(SVG('poly.svg'))

output_13_1.png

Holes

穴はポリゴンの外側の境界に接続します。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("Holes")
# Manually connect the hole to the outer boundary
cutout = gdstk.Polygon(
    [(0, 0), (5, 0), (5, 5), (0, 5), (0, 0), (2, 2), (2, 3), (3, 3), (3, 2), (2, 2)]
)
cell.add(cutout)
# Save the library in a GDSII or OASIS file.
lib.write_gds("holes.gds")
plot_gds("holes.gds")
cell.write_svg('holes.svg', background="none",) #svgファイル出力
display_svg(SVG('holes.svg'))

output_15_1.png

スクリーンショット 2023-12-01 0.11.14.png (svg ファイルの外観です。)

Circles

gdstk.ellipse() 関数は、円、楕円、ドーナツ形、弧、スライスを作成します。すべてのケースにおいて、引数toleranceは曲線形状を近似するために使用される頂点の数を制御します。

gdstk.Library.write_gds() でライブラリを保存する際、ポリゴン内の頂点の数が max_points(デフォルトでは199)よりも多い場合、それは多くの小さなポリゴンに分割され、それぞれが最大でmax_pointsの頂点を持ちます。

OASISファイルにはポリゴンの頂点数に制限がないため、保存時にポリゴンが分割されることはありません。また、OASISファイルは円をサポートしています。OASISファイルを保存する際、多角形の円は事前に定義された許容範囲内で検出され、自動的に変換されます。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("Circles")

# Circle centered at (0, 0), with radius 2 and tolerance 0.1
circle = gdstk.ellipse((0, 0), 2, tolerance=0.01)

# To create an ellipse, simply pass a list with 2 radii.
# Because the tolerance is small (resulting a large number of
# vertices), the ellipse is fractured in 2 polygons.
ellipse = gdstk.ellipse((4, 0), [1, 2], tolerance=1e-4)

# Circular arc example
arc = gdstk.ellipse(
    (2, 4),
    2,
    inner_radius=1,
    initial_angle=-0.2 * np.pi,
    final_angle=1.2 * np.pi,
    tolerance=0.01,
)

cell.add(circle)
cell.add(ellipse)
cell.add(arc)
# Save the library in a GDSII or OASIS file.
lib.write_gds("arc.gds")
plot_gds("arc.gds")
cell.write_svg('arc.svg', background="none") #svgファイル出力
display_svg(SVG('arc.svg'))

output_17_1.png

Curves

gdstk.Polygon において、すべての頂点を手動でリストすることで複雑なポリゴンを構築するのは難しいです。gdstk.Curve クラスは、形状を段階的に描画し、ポリゴンの作成が簡単になります。その構文はSVGパス仕様に準じています。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("Curves_1")

# Construct a curve made of a sequence of line segments
c1 = gdstk.Curve((0, 0)).segment([(1, 0), (2, 1), (2, 2), (0, 2)])
p1 = gdstk.Polygon(c1.points())

# Construct another curve using relative coordinates
c2 = gdstk.Curve((3, 1)).segment([(1, 0), (2, 1), (2, 2), (0, 2)], relative=True)
p2 = gdstk.Polygon(c2.points())

cell.add(p1)
cell.add(p2)
# Save the library in a GDSII or OASIS file.
lib.write_gds("curve1.gds")
plot_gds("curve1.gds")
cell.write_svg('curve1.svg', background="none") #svgファイル出力
display_svg(SVG('curve1.svg'))

output_19_1.png

座標のペアは複素数として与えることができます。実部と虚部はそれぞれx座標とy座標として使用されます。これは極座標で点を定義するのに便利です。

楕円弧の構文はgdstk.ellipse()に似ていますが、楕円の長軸を追加で回転させることができます。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("Curves_2")

# Use complex numbers to facilitate writing polar coordinates
c3 = gdstk.Curve(2j).segment(4 * np.exp(1j * np.pi / 6), relative=True)
# Elliptical arcs have syntax similar to gdstk.ellipse
c3.arc((4, 2), 0.8 * np.pi, -0.25 * np.pi)
print("c3.points() = ", c3.points())
p3 = gdstk.Polygon(c3.points())
cell.add(p3)
# Save the library in a GDSII or OASIS file.
lib.write_gds("curve2.gds")
plot_gds("curve2.gds")
cell.write_svg('curve2.svg', background="none") #svgファイル出力
display_svg(SVG('curve2.svg'))

output_21_1.png

カーブのセクションは、三次、二次、および一般次数のベジエ曲線として構築することができます。さらに、gdstk.Curve.interpolation()メソッドを使用して滑らかな補間曲線を計算することができ、カーブの形状を制御するためのいくつかの引数があります。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("Curves_3")

# Cubic Bezier curves can be easily created
c4 = gdstk.Curve((0, 0), tolerance=1e-3)
c4.cubic([(0, 1), (1, 1), (1, 0)])
# Smooth continuation:
c4.cubic_smooth([(1, -1), (1, 0)], relative=True)

# Similarly for quadratic Bezier curves
c4.quadratic([(0.5, 1), (1, 0)], relative=True)
c4.quadratic_smooth((1, 0), relative=True)

# Smooth interpolating curve
c4.interpolation([(4, -1), (3, -2), (2, -1.5), (1, -2), (0, -1), (0, 0)])
p4 = gdstk.Polygon(c4.points())

cell.add(p4)
# Save the library in a GDSII or OASIS file.
lib.write_gds("curve3.gds")
plot_gds("curve3.gds")
cell.write_svg('curve3.svg', background="none") #svgファイル出力
display_svg(SVG('curve3.svg'))

output_23_1.png

Transformations

すべてのポリゴンは、gdstk.Polygon.translate()gdstk.Polygon.rotate()gdstk.Polygon.scale()、および gdstk.Polygon.mirror() を通じて変形することができます。これらの変形はインプレース(上書き)で適用されます。つまり、新しいポリゴンは作成されません。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("trans")

poly = gdstk.rectangle((-2, -2), (2, 2))
poly.rotate(np.pi / 4)
poly.scale(1, 0.5)

cell.add(poly)
# Save the library in a GDSII or OASIS file.
lib.write_gds("trans.gds")
plot_gds("trans.gds")
cell.write_svg('trans.svg', background="none") #svgファイル出力
display_svg(SVG('trans.svg'))

output_25_1.png

Layer and Datatype

すべての形状には2つのプロパティがタグ付けされています。レイヤーとデータタイプ(またはgdstk.Labelの場合はテキストタイプ)です。デフォルトでは常に0ですが、0から255の範囲内の任意の整数にすることができます。

これらのプロパティには事前に定義された意味はありません。ファイルを使用するシステムによって、これらのタグをどのように使用するかが決まります。例えば、CMOS製造プロセスでは、各レイヤーが異なるリソグラフィレベルを表すことがあります。

以下の例では、単一のファイルが異なる製造マスクを別々のレイヤーおよびデータタイプの設定で格納しています。Pythonの辞書を使用して、各ポリゴンに簡単に割り当てることができます。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("layer_dtype")

# Layer/datatype definitions for each step in the fabrication
ld = {
    "full etch": {"layer": 1, "datatype": 3},
    "partial etch": {"layer": 2, "datatype": 3},
    "lift-off": {"layer": 0, "datatype": 7},
}

p1 = gdstk.rectangle((-3, -3), (3, 3), **ld["full etch"])
p2 = gdstk.rectangle((-5, -3), (-3, 3), **ld["partial etch"])
p3 = gdstk.rectangle((5, -3), (3, 3), **ld["partial etch"])
p4 = gdstk.regular_polygon((0, 0), 2, 6, **ld["lift-off"])

cell.add(p1)
cell.add(p2)
cell.add(p3)
cell.add(p4)

# Save the library in a GDSII or OASIS file.
lib.write_gds("layer_dtype.gds")
plot_gds("layer_dtype.gds")
cell.write_svg('layer_dtype.svg', background="none") #svgファイル出力
display_svg(SVG('layer_dtype.svg'))

output_27_1.png

スクリーンショット 2023-11-30 23.38.11.png

Reference

参照はレイアウトの階層構造を担っています。参照を通じて、セルの内容を別のセルで再利用できます(実際には全ての幾何学をコピーすることなく)。例として、同じ形のトランジスタを数百個使用する電子回路を設計していると想像してみてください。トランジスタを一度描画し、回路全体で必要に応じて回転させたり反転させたりしながら参照することができます。

単一の参照を作成するだけでなく、gdstk.Reference を使用して1つのエンティティで完全な2Dアレイを作成することも可能です。以下に両方の使用例が示されています。

は、不完全なサンプルなので、

さんの例を拝借して説明します。

import gdstk

lib = gdstk.Library() #特に指定しなければ単位はum
topcell = lib.new_cell("Top") #Topセルを作成

subcell = lib.new_cell("subcell") #サブセルを作成
box = gdstk.rectangle((0,0), (1,2)) #左下(0,0),右上(1,2)の四角形を作成
subcell.add(box) #四角形をサブセルに追加
ref = gdstk.Reference(subcell, origin=(0,0), columns =3, rows =2, spacing = (5,5)) #サブセルへの配列リファレンスを原点(0,0)に挿入。3行2列で、間隔は(5,5)
topcell.add(ref) #サブセルへのリファレンスをTopセルに挿入。

text = gdstk.text('First GDS',2, (0,10)) #高さ2のテキストを位置(0,10)に挿入。
topcell.add(*text) #gdstk.textは1文字ずつのPolygonのリストを返すので、*textでリストをunpackしてadd()に渡す。

lib.write_gds('output.gds') #gdsファイル出力
plot_gds("output.gds") # need to update plot_gds

topcell.write_svg('output.svg') #svgファイル出力
display_svg(SVG('output.svg'))

output_29_1.png

Paths

ポリゴンの他に、GDSIIおよびOASISフォーマットではパスが定義されており、これは関連する幅と端のキャップを持つ多角形の連鎖です。幅は一定の数値で、パス全体を通じて一定です。端のキャップはフラッシュ、ラウンド(GDSIIのみ)、またはカスタム距離によって延長することができます。

隣接するセグメント間の接合部についての仕様はありませんので、ファイルを使用するシステムによってこれらを指定することになります。通常、接合部はパスの境界の直線的な延長で、ある程度のベベルリング制限まで行われます。Gdstkもこの仕様を接合部に使用しています。

Gdstk内で、GDSIIまたはOASISファイルにパスをポリゴンとして保存することで、上記の制限をすべて回避することが可能です。この解決策の欠点は、他のソフトウェアがその情報が失われてしまうため、パスとしての幾何学を編集できないことです。

Gdstkにおけるパス(GDSII/OASISパスまたは多角形パス)の構築は、gdstk.FlexPathおよびgdstk.RobustPathに基づいています。

「Beveling limit」(ベベルリング制限) の補足

「Beveling limit」(ベベルリング制限)は、主にCAD(コンピュータ支援設計)やグラフィックデザインにおいて、パスやポリゴンの角を成形する際に設定される制限を指します。具体的には、二つのパスやポリゴンセグメントが接合する場所において、その角をどの程度切り取るか(ベベルするか)を決定するための値です。

パスのベベルリングは、接合点における角度の鋭さを緩和するために用いられます。例えば、二つのセグメントが直角に接合する場合、ベベルリングによってその角が斜めに切り取られ、より滑らかな接合が実現されます。ベベルリングの限界(limit)は、この切り取る角度や長さの最大値を指し、デザインの要件や製造プロセスの制約に基づいて設定されます。

CADやEDA(電子設計自動化)ソフトウェアでは、このベベルリング制限を設定することで、製造やプリント時に望ましい形状を保持し、素材の加工性や組立ての容易さを向上させることができます。

FlexPath の基本

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("paths")

# Path defined by a sequence of points and stored as a GDSII path
fp1 = gdstk.FlexPath(
    [(0, 0), (3, 0), (3, 2), (5, 3), (3, 4), (0, 4)], 1, simple_path=True
)

cell.add(fp1)
# Save the library in a GDSII or OASIS file.
lib.write_gds("paths.gds")
plot_gds("paths.gds")
cell.write_svg('paths.svg', background="none") #svgファイル出力
display_svg(SVG('paths.svg'))

output_32_1.png

次に、interpolation を追加してみましょう。

# fp1.interpolation is added.
import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("paths2")

# Path defined by a sequence of points and stored as a GDSII path
fp1 = gdstk.FlexPath(
    [(0, 0), (3, 0), (3, 2), (5, 3), (3, 4), (0, 4)], 1, simple_path=True
)

# Other construction methods can still be used
fp1.interpolation([(0, 4),(3, 6),(-2, 6),(-1,5.5),(0,7)], relative=True)

cell.add(fp1)
# Save the library in a GDSII or OASIS file.
lib.write_gds("paths2.gds")
plot_gds("paths2.gds")
cell.write_svg('paths2.svg', background="none") #svgファイル出力
display_svg(SVG('paths2.svg'))

output_33_1.png

Flexible Paths の高度な使い方

gdstk.FlexPath クラスは、パス作成を容易にするための追加機能を持つ、以前の gdstk.Curve のミラーです。

  • すべてのカーブ構築方法が利用可能です。
  • パスの幅はパス全体にわたって簡単に制御できます。
  • 端のキャップや接合部はユーザーによって指定できます。
  • 直線セグメントは円弧によって自動的に接合されます。
  • 複数の平行なパスを同時に設計できます。
  • 平行なパス間の間隔は任意です
  • 各パスのオフセットはユーザーによって個別に指定されます。
class gdstk.FlexPathの定義
class gdstk.FlexPath(points, width, offset=0, joins='natural', ends='flush', bend_radius=0, bend_function=None, tolerance=1e-2, simple_path=False, scale_width=True, layer=0, datatype=0)

まずは、一本だけ書いてみましょう。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("paths3")

fp2 = gdstk.FlexPath(
    [(0, 0), (0, 5), (8, 10), (8, 12)],
    [1.0],
    3,
    ends=["flush"],
    joins=["natural"],
)
cell.add(fp2)
# Save the library in a GDSII or OASIS file.
lib.write_gds("paths3.gds")
plot_gds("paths3.gds")
cell.write_svg('paths3.svg', background="none") #svgファイル出力
display_svg(SVG('paths3.svg'))

output_35_1.png

次に、太さの異なる3本を書いてみます。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("paths4")

fp2 = gdstk.FlexPath(
    [(0, 0), (0, 5), (8, 10), (8, 12)],
    [0.5, 1.0, 2.0],
    3,
    ends=["extended", "flush", "round"],
    joins=["bevel", "miter", "round"],
)
cell.add(fp2)
# Save the library in a GDSII or OASIS file.
lib.write_gds("paths4.gds")
plot_gds("paths4.gds")
cell.write_svg('paths4.svg', background="none") #svgファイル出力
display_svg(SVG('paths4.svg'))

output_36_1.png

次に、arc をつけてみます。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("paths5")

fp2 = gdstk.FlexPath(
    [(0, 0), (0, 5), (8, 10), (8, 12)],
    [0.5, 1.0, 0.3],
    3,
    ends=["extended", "flush", "round"],
    joins=["bevel", "miter", "round"],
)
# arc(radius, initial_angle, final_angle, rotation=0, width=None, offset=None)
fp2.arc(2, 0.1 * np.pi, 0.6 * np.pi)
cell.add(fp2)
# Save the library in a GDSII or OASIS file.
lib.write_gds("paths5.gds")
plot_gds("paths5.gds")
cell.write_svg('paths5.svg', background="none") #svgファイル出力
display_svg(SVG('paths5.svg'))

output_37_1.png

circular bend の使い方

「circular bend (円形の曲がり角)」(bend_radius引数と共に)を使用すると、パスを自動的に曲げることができます。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("paths34")

# Path created with automatic bends of radius 5
points = [(0, 0), (0, 10), (20, 0), (18, 15), (8, 15)]
fp3 = gdstk.FlexPath(points, 0.5, bend_radius=5, simple_path=True)

# Same path, generated with natural joins, for comparison
fp4 = gdstk.FlexPath(points, 0.5, layer=1, simple_path=True)

cell.add(fp3)
cell.add(fp4)

# Save the library in a GDSII or OASIS file.
lib.write_gds("paths_fp34.gds")
plot_gds("paths_fp34.gds")
cell.write_svg('paths_fp34.svg', background="none") #svgファイル出力
display_svg(SVG('paths_fp34.svg'))

output_39_1.png

スクリーンショット 2023-11-30 23.48.45.png

幅とオフセットの変化方法

パス全体にわたって幅とオフセットの変化が可能です。変更は定義されたパスセクションで線形にテーパーされます。ただし、GDSII/OASISパスでは幅の変更ができないため、ポリゴンオブジェクトとして保存されることに注意してください。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("paths34")

# Straight segment showing the possibility of width and offset changes
# class gdstk.FlexPath(points, width, offset=0, ...)
fp5 = gdstk.FlexPath((0, 0), [2, 3], 6)
fp5.horizontal(2)
fp5.horizontal(4, width=1, offset=2)
fp5.horizontal(10)

cell.add(fp5)

# Save the library in a GDSII or OASIS file.
lib.write_gds("paths_fp5.gds")
plot_gds("paths_fp5.gds")
cell.write_svg('paths_fp5.svg', background="none") #svgファイル出力
display_svg(SVG('paths_fp5.svg'))

output_41_1.png

Text

gdstk.textは1文字ずつのPolygonのリストを返すので、*textでリストをunpackしてadd()に渡すことに注意。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("text")

# Label centered at (1, 3)
label = gdstk.Label("Sample label", (5, 3), texttype=2)

# Horizontal text with height 2.25
htext = gdstk.text("12345", 2.25, (0.25, 6))

# Vertical text with height 1.5
vtext = gdstk.text("ABC", 1.5, (10.5, 4), vertical=True)

rect = gdstk.rectangle((0, 0), (10, 6), layer=10)

cell.add(label)
cell.add(*htext)
cell.add(*vtext)
cell.add(rect)

# Save the library in a GDSII or OASIS file.
lib.write_gds("text.gds")
plot_gds("text.gds") # need to update
cell.write_svg('text.svg', background="none") #svgファイル出力
display_svg(SVG('text.svg'))

output_44_1.png

スクリーンショット 2023-11-30 23.50.24.png

Robust Path

いくつかの状況において、gdstk.FlexPathは全ての接合部を適切に計算することができません。これは、パスの幅やオフセットが接合されるセグメントの長さに対して相対的に大きい場合によく発生します。他のカーブやセグメントと鋭角で交わるカーブは、このような状況がよく発生する典型的な例です。

カーブのセクションが鋭角で交わることが予想されるようなシナリオでは、gdstk.RobustPathクラスを使用することができます。gdstk.RobustPathを使用する際の欠点は、全ての接合部を計算するために必要な追加の計算リソースと、接合部を指定することができない点です。利点は、前述のように、最終的な幾何学を生成する際のより高い堅牢性と、パスの幅やオフセットをパラメータ化するためにカスタム関数を使用する自由度です。

class gdstk.RobustPathの定義
class gdstk.RobustPath(initial_point, width, offset=0, ends='flush', tolerance=1e-2, max_evals=1000, simple_path=False, scale_width=True, layer=0, datatype=0)

同様に、gdstk.FlexPathと同じく、gdstk.RobustPathはその幅が一定に保たれている限り、GDSII/OASISパスとして保存することができます。

RubustPath の例(ステップ1)

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("rp1")

# Create 4 parallel paths in different layers
rp = gdstk.RobustPath(
    (0, 50),
    [0.1, 1, 1],
    [0, -1, 1],
    ends=["round", "flush", "flush"],
    layer=[0, 2, 2],
)
rp.segment((0, 45))
rp.segment((0, 5), width=[0.5, 1, 1],
offset=[
        lambda u: 8 * u * (1 - u) * np.cos(12 * np.pi * u),
        lambda u: -1 - 8 * u * (1 - u),
        lambda u: 1 + 8 * u * (1 - u),
    ],)

rp.segment((0, 0))

cell.add(rp)

# Save the library in a GDSII or OASIS file.
lib.write_gds("rp1.gds")
plot_gds("rp1.gds") # need to update
cell.write_svg('rp1.svg', background="none") #svgファイル出力
display_svg(SVG('rp1.svg'))

output_47_1.png

RubustPath の例(ステップ2)

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("rp2")

# Create 4 parallel paths in different layers
rp = gdstk.RobustPath(
    (0, 50),
    [2, 0.5, 1, 1],
    [0, 0, -1, 1],
    ends=["extended", "round", "flush", "flush"],
    layer=[1, 0, 2, 2],
)
rp.segment((0, 45))
rp.segment((0, 5), width=[lambda u: 2 + 16 * u * (1 - u), 0.5, 1, 1],
offset=[
        0,
        lambda u: 8 * u * (1 - u) * np.cos(12 * np.pi * u),
        lambda u: -1 - 8 * u * (1 - u),
        lambda u: 1 + 8 * u * (1 - u),
    ],)

rp.segment((0, 0))

cell.add(rp)

# Save the library in a GDSII or OASIS file.
lib.write_gds("rp2.gds")
plot_gds("rp2.gds") # need to update
cell.write_svg('rp2.svg', background="none") #svgファイル出力
display_svg(SVG('rp2.svg'))

output_49_1.png

RubustPath の例(ステップ3)

interpolation により、滑らかに線をつなぐ。

interpolationの定義
interpolation(points, angles=None, tension_in=1, tension_out=1, initial_curl=1, final_curl=1, cycle=False, width=None, offset=None, relative=True)
import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("rp3")

# Create 4 parallel paths in different layers
rp = gdstk.RobustPath(
    (0, 50),
    [2, 0.5, 1, 1],
    [0, 0, -1, 1],
    ends=["extended", "round", "flush", "flush"],
    layer=[1, 0, 2, 2],
)
rp.segment((0, 45))
rp.segment((0, 5), width=[lambda u: 2 + 16 * u * (1 - u), 0.5, 1, 1],
offset=[
        0,
        lambda u: 8 * u * (1 - u) * np.cos(12 * np.pi * u),
        lambda u: -1 - 8 * u * (1 - u),
        lambda u: 1 + 8 * u * (1 - u),
    ],)

rp.segment((0, 0))
rp.interpolation(
    [(15, 5)],
    angles=[0, 0.5 * np.pi],
    width=0.5,
    offset=[-0.25, 0.25, -0.75, 0.75],
)

cell.add(rp)

# Save the library in a GDSII or OASIS file.
lib.write_gds("rp3.gds")
plot_gds("rp3.gds") # need to update
cell.write_svg('rp3.svg', background="none") #svgファイル出力
display_svg(SVG('rp3.svg'))

output_51_1.png

Append a parametric curve

path_function は、引数が1つ(0から1まで変化する)の関数でなければならず、パスの座標を2要素のシーケンスまたは複素数で返す、ものを定義する必要がある。

parametricの定義
parametric(path_function, path_gradient=None, width=None, offset=None, relative=True)

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("rp4")

# Create 4 parallel paths in different layers
rp = gdstk.RobustPath(
    (0, 50),
    [2, 0.5, 1, 1],
    [0, 0, -1, 1],
    ends=["extended", "round", "flush", "flush"],
    layer=[1, 0, 2, 2],
)
rp.segment((0, 45))
rp.segment((0, 5), width=[lambda u: 2 + 16 * u * (1 - u), 0.5, 1, 1],
offset=[
        0,
        lambda u: 8 * u * (1 - u) * np.cos(12 * np.pi * u),
        lambda u: -1 - 8 * u * (1 - u),
        lambda u: 1 + 8 * u * (1 - u),
    ],)

rp.segment((0, 0))
rp.interpolation(
    [(15, 5)],
    angles=[0, 0.5 * np.pi],
    width=0.2,
    offset=[-0.25, 0.25, -0.75, 0.75],
)
rp.parametric(
    lambda u: np.array((4 * np.sin(12 * np.pi * u), 45 * u)),
    offset=[
        lambda u: -0.25 * np.cos(2 * 24 * np.pi * u),
        lambda u: 0.25 * np.cos(2 * 24 * np.pi * u),
        -0.75,
        0.75,
    ],
)

cell.add(rp)

# Save the library in a GDSII or OASIS file.
lib.write_gds("rp4.gds")
plot_gds("rp4.gds") # need to update
cell.write_svg('rp4.svg', background="none") #svgファイル出力
display_svg(SVG('rp4.svg'))

output_53_1.png

スクリーンショット 2023-11-30 23.55.18.png

このような複雑な形も、lambda 関数の活用により自由に表現できます。(python の lambda 関数は無名関数と呼ばれて、python で quick に関数を使う上で便利です。)

ブーリアン演算

ポリゴン、パス、および全セルに対してブーリアン演算(gdstk.boolean())を行うことができます。四つの演算が定義されています: union (“or”), intersection (“and”), subtraction (“not”), and symmetric difference ("xor")。これらは計算コストが高いため、通常は可能な限りブーリアン演算を使用しないことが望ましいとされています。もし必要な場合は、全てのポリゴンの頂点数をできるだけ少なくすることも助けになります。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("inv")

# Create some text
text = gdstk.text("GDSTK", 4, (0, 0))
# Create a rectangle extending the text's bounding box by 1
rect = gdstk.rectangle((-1, -1), (5 * 4 * 9 / 16 + 1, 4 + 1))

# Subtract the text from the rectangle
inv = gdstk.boolean(rect, text, "not")
print(inv)
cell.add(*inv)

# Save the library in a GDSII or OASIS file.
lib.write_gds("inv.gds")
plot_gds("inv.gds") # need to update
cell.write_svg('inv.svg', background="none") #svgファイル出力
display_svg(SVG('inv.svg'))

output_55_1.png

スクリーンショット 2023-12-01 0.00.06.png

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("slices")

ring1 = gdstk.ellipse((-6, 0), 6, inner_radius=4)
ring2 = gdstk.ellipse((0, 0), 6, inner_radius=4)
ring3 = gdstk.ellipse((6, 0), 6, inner_radius=4)

# Slice the first ring across x=-3, the second ring across x=-3
# and x=3, and the third ring across x=3
slices1 = gdstk.slice(ring1, -3, "x")
slices2 = gdstk.slice(ring2, [-3, 3], "x")
slices3 = gdstk.slice(ring3, 3, "x")

# Keep only the left side of slices1, the center part of slices2
# and the right side of slices3
cell.add(*slices1[0])
cell.add(*slices2[1])
cell.add(*slices3[1])

# Save the library in a GDSII or OASIS file.
lib.write_gds("slices.gds")
plot_gds("slices.gds") # need to update
cell.write_svg('slices.svg', background="none") #svgファイル出力
display_svg(SVG('slices.svg'))

output_56_1.png

オフセット操作

gdstk.offset()関数は、ポリゴンを一定の量で拡大または縮小します。個々のポリゴンやそれらのセットに対して操作を行うことができ、内側のエッジの影響を取り除くために use_union = True を設定する必要がある場合があります。これは穴のあるポリゴンにも同様に適用されます。

gdstk.offset の定義
gdstk.offset(polygons, distance, join='miter', tolerance=2, precision=1e-3, use_union=False, layer=0, datatype=0)
import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("outer")

rect1 = gdstk.rectangle((-4, -4), (1, 1))
rect2 = gdstk.rectangle((-1, -1), (6, 6))

# Erosion: because we set `use_union=True`, the inner boundaries have no effect
outer = gdstk.offset([rect1, rect2], -0.5, use_union=True, layer=1)

cell.add(*outer)

# Save the library in a GDSII or OASIS file.
lib.write_gds("outer.gds")
plot_gds("outer.gds") # need to update
cell.write_svg('outer.svg', background="none") #svgファイル出力
display_svg(SVG('outer.svg'))

output_58_1.png

フィレット操作

メソッド gdstk.Polygon.fillet() は、ポリゴンの角を丸めるために使用できます。

import gdstk

# The GDSII file is called a library, which contains multiple cells.
lib = gdstk.Library()

# Geometry must be placed in cells.
cell = lib.new_cell("outer")

flexpath = gdstk.FlexPath([(-8, -4), (0, -4), (0, 5), (9, 2)], 4)
filleted_path = flexpath.to_polygons()[0]
filleted_path.fillet(1.5)

cell.add(filleted_path)

# Save the library in a GDSII or OASIS file.
lib.write_gds("outer.gds")
plot_gds("outer.gds") # need to update
cell.write_svg('outer.svg', background="none") #svgファイル出力
display_svg(SVG('outer.svg'))

output_60_1.png

GDSII/OASISライブラリに関して

GDSII/OASISファイルを作成するために使用されるすべての情報は、gdstk.Libraryのインスタンス内に保持されています。このクラスは、すべての幾何学的および階層的情報に加えて、名前とすべてのエンティティの単位も保持しています。名前は任意のASCII文字列であり、ファイル内に単純に保存されるだけで、Gdstk内で他の目的はありません。単位には注意が必要で、ファイルに書き込まれる際にライブラリ内のポリゴンの解像度に影響を与える可能性があります。

単位についての注意

gdstk.Libraryを作成する際には、unitprecisionの2つの値が定義されます。unitの値は、ライブラリ内のすべてのエンティティの単位サイズをメートル単位で定義します。例えば、unit = 1e-6(10⁻⁶ m、デフォルト値)の場合、(1, 2)の頂点は実際の位置(1 × 10⁻⁶ m, 2 × 10⁻⁶ m)の頂点として解釈されるべきです。unitが0.001に変わると、その同じ頂点は実際の座標では(0.001 m, 0.002 m)、つまり(1 mm, 2 mm)に位置します。

precisionの値は、GDSIIファイル内で座標を格納するために使用されるタイプ、すなわち符号付き4バイト整数に関係しています。そのため、通常は単位よりも細かい座標グリッドを定義する座標を定義することが望まれます。そのグリッドは、precisionによってメートル単位で定義され、デフォルトでは1e-9(10⁻⁹ m)です。GDSIIファイルが書かれると、すべての頂点はprecisionで定義されたグリッドにスナップされます。例えば、unitprecisionのデフォルト値に対して、(1.0512, 0.0001)の頂点は実際の座標(1.0512 × 10⁻⁶ m, 0.0001 × 10⁻⁶ m)、または(1051.2 × 10⁻⁹ m, 0.1 × 10⁻⁹ m)を表し、整数に丸められます:(1051 × 10⁻⁹ m, 0 × 10⁻⁹ m)、または(1.051 × 10⁻⁶ m, 0 × 10⁻⁶ m)。GDSIIファイルに書かれる実際の座標値は整数(1051, 0)です。precisionの値を10⁻⁹ mから10⁻¹² mに減らすと、座標は3桁の追加の精度を持ち、格納される値は(1051200, 100)になります。

ファイル内の小数点以下の桁数を増やすデメリットは、(実際の単位で)格納できる座標の範囲が縮小されることです。これは、ファイル内に書き込むことができる座標値の範囲が[-(2³²); 2³¹ - 1] = [-2,147,483,648; 2,147,483,647]であるためです。デフォルトのprecsisionの範囲は[-2.147483648 m; 2.147483647 m]です。precisionが10⁻¹² mに設定されている場合、同じ範囲は1000倍縮小されます:[-2.147483648 mm; 2.147483647 mm]。

GDSIIファイルは、unitprecisionの両方の記録を保持するため、異なるファイルからのジオメトリを混在させる際には、同じ単位を持っていることを確認するために注意が必要です。そのため、業界標準(unit = 1e-6)の使用が推奨されます。OASISファイルは常にこの標準を単位に使用しますが、precisionは自由に選択できます。そのため、GdstkでOASISファイルを保存または読み込む際には、単位を自動的に変換することができます。

最後に

ここまでくれば、公式ページの GEtting Started の内容は制覇できたはずです。多少でも、ものづくりが好きな方の一助になれば幸いです。間違いやコメントなどあればお願いします。

応用編として、

も参考ください。

Shapely を用いた例は、

で紹介しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0