Skip to content

Commit

Permalink
Store text polygons in oas file, load when creating labels
Browse files Browse the repository at this point in the history
  • Loading branch information
theandyguthrie authored and qpavsmi committed Aug 23, 2024
1 parent 38a14fe commit f9a2694
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 47 deletions.
34 changes: 12 additions & 22 deletions klayout_package/python/kqcircuits/masks/mask_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from typing import Tuple
from tqdm import tqdm

from kqcircuits.util.label_polygons import get_text_polygon
from kqcircuits.pya_resolver import pya
from kqcircuits.defaults import (
default_layers,
Expand Down Expand Up @@ -623,32 +624,21 @@ def _get_chip_name(self, search_cell):
return ""

def _add_chip_graphical_representation_layer(self, chip_name, position, pos_index_name, chip_width, cell):
chip_name_text = self.layout.create_cell(
"TEXT",
"Basic",
{
"layer": default_layers["mask_graphical_rep"],
"text": chip_name,
"mag": 15000 * self.mask_text_scale / len(chip_name),
},
)
pos_index_name_text = self.layout.create_cell(
"TEXT",
"Basic",
{
"layer": default_layers["mask_graphical_rep"],
"text": pos_index_name,
"mag": 4000 * self.mask_text_scale,
},
)
layout = cell.layout()
chip_name_text = get_text_polygon(chip_name, 15000 * self.mask_text_scale / len(chip_name))
chip_name_width = chip_name_text.bbox().to_dtype(layout.dbu).width()
pos_index_name_text = get_text_polygon(pos_index_name, 4000 * self.mask_text_scale)
pos_index_name_width = pos_index_name_text.bbox().to_dtype(cell.layout().dbu).width()
chip_name_trans = pya.DTrans(
position + pya.DVector((chip_width - chip_name_text.dbbox().width()) / 2, self.mask_text_scale * 750)
position + pya.DVector((chip_width - chip_name_width) / 2, self.mask_text_scale * 750)
)
cell.insert(pya.DCellInstArray(chip_name_text.cell_index(), chip_name_trans))
cell.shapes(cell.layout().layer(default_layers["mask_graphical_rep"])).insert(chip_name_text, chip_name_trans)
pos_index_trans = pya.DTrans(
position + pya.DVector((chip_width - pos_index_name_text.dbbox().width()) / 2, self.mask_text_scale * 6000)
position + pya.DVector((chip_width - pos_index_name_width) / 2, self.mask_text_scale * 6000)
)
cell.shapes(cell.layout().layer(default_layers["mask_graphical_rep"])).insert(
pos_index_name_text, pos_index_trans
)
cell.insert(pya.DCellInstArray(pos_index_name_text.cell_index(), pos_index_trans))

def _insert_mask_name_label(self, cell, layer, postfix=""):
cell_mask_name, trans = self._create_mask_name_label(layer, postfix)
Expand Down
Binary file not shown.
47 changes: 22 additions & 25 deletions klayout_package/python/kqcircuits/util/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement).

from enum import Enum, auto

from kqcircuits.util.label_polygons import get_text_polygon
from kqcircuits.pya_resolver import pya


Expand All @@ -31,7 +31,16 @@ class LabelOrigin(Enum):


def produce_label(
cell, label, location, origin, origin_offset, margin, layers, layer_protection, size=350, mirror=False
cell,
label,
location,
origin,
origin_offset,
margin,
layers,
layer_protection,
size=350,
mirror=False,
):
"""Produces a Text PCell accounting for desired relative position of the text respect to the given location
and the spacing.
Expand All @@ -46,13 +55,13 @@ def produce_label(
layers: list of layers where the label text is added
layer_protection: layer where a box around the label text is added
size: Character height in um, default 350
mirror: mirror label
Effect:
Shapes added to the corresponding layers
"""

layout = cell.layout()

if not label:
label = "A13" # longest label on 6 inch wafer
protection_only = True
Expand All @@ -62,34 +71,22 @@ def produce_label(
else:
protection_only = False

# text cell
subcells = []
for layer in layers:
subcells.append(
layout.create_cell(
"TEXT",
"Basic",
{
"layer": layer,
"text": label,
"mag": size / 350 * 500,
},
)
)
polygon = get_text_polygon(label, size / 350 * 500)
polygon_bbox = polygon.bbox().to_dtype(layout.dbu)

# relative placement with margin
relative_placement = {
LabelOrigin.BOTTOMLEFT: pya.Vector(
subcells[0].dbbox().p1.x - margin - origin_offset, subcells[0].dbbox().p1.y - margin - origin_offset
polygon_bbox.p1.x - margin - origin_offset, polygon_bbox.p1.y - margin - origin_offset
),
LabelOrigin.TOPLEFT: pya.Vector(
subcells[0].dbbox().p1.x - margin - origin_offset, subcells[0].dbbox().p2.y + margin + origin_offset
polygon_bbox.p1.x - margin - origin_offset, polygon_bbox.p2.y + margin + origin_offset
),
LabelOrigin.TOPRIGHT: pya.Vector(
subcells[0].dbbox().p2.x + margin + origin_offset, subcells[0].dbbox().p2.y + margin + origin_offset
polygon_bbox.p2.x + margin + origin_offset, polygon_bbox.p2.y + margin + origin_offset
),
LabelOrigin.BOTTOMRIGHT: pya.Vector(
subcells[0].dbbox().p2.x + margin + origin_offset, subcells[0].dbbox().p1.y - margin - origin_offset
polygon_bbox.p2.x + margin + origin_offset, polygon_bbox.p1.y - margin - origin_offset
),
}[origin] * (-1)

Expand All @@ -99,12 +96,12 @@ def produce_label(
trans = pya.DTrans(location + relative_placement)

if not protection_only:
for subcell in subcells:
cell.insert(pya.DCellInstArray(subcell.cell_index(), trans))
for layer in layers:
cell.shapes(layout.layer(layer)).insert(polygon, trans)

# protection layer with margin
protection = pya.DBox(
pya.DPoint(subcells[0].dbbox().p1.x - margin, subcells[0].dbbox().p1.y - margin),
pya.DPoint(subcells[0].dbbox().p2.x + margin, subcells[0].dbbox().p2.y + margin),
pya.DPoint(polygon_bbox.p1.x - margin, polygon_bbox.p1.y - margin),
pya.DPoint(polygon_bbox.p2.x + margin, polygon_bbox.p2.y + margin),
)
cell.shapes(layout.layer(layer_protection)).insert(trans.trans(protection))
75 changes: 75 additions & 0 deletions klayout_package/python/kqcircuits/util/label_polygons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# This code is part of KQCircuits
# Copyright (C) 2024 IQM Finland Oy
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# https://www.gnu.org/licenses/gpl-3.0.html.
#
# The software distribution should follow IQM trademark policy for open-source software
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code.
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement)
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement).

from functools import lru_cache
from pathlib import Path
from kqcircuits.pya_resolver import pya


# Expected properties of the text geometry as loaded from the ``font_polygons.oas`` file

# File path to the ``font_polygons.oas`` file
OAS_PATH = str(Path(__file__).parent.joinpath("font_polygons.oas"))
# Letters in the oas file
OAS_LETTERS = [chr(ord("A") + i) for i in range(0, 32)] + [chr(ord("0") + i) for i in range(0, 10)]
# Spacing between two letters
OAS_TEXT_SPACING = 300.0
# "mag" parameter set for TEXT pcell in oas file
OAS_TEXT_MAGNIFICATION = 500.0
# dbu in the oas file
OAS_DBU = 0.001


def get_text_polygon(label: str, size: float = OAS_TEXT_MAGNIFICATION) -> pya.Region:
"""Returns the given label string as a region.
Utilizes speed ups compared to generating text geometry with KLayout's TEXT PCells.
Only supports characters layed out in the ``font_polygons.oas`` file.
"""

font_polygons = load_font_polygons()
unsported_characters = set(x.upper() for x in label) - set(font_polygons.keys())
if unsported_characters:
raise ValueError(f"Unsupported characters for get_text_polygon: {unsported_characters}")

norm_size = size / OAS_TEXT_MAGNIFICATION
spacing = norm_size * OAS_TEXT_SPACING / OAS_DBU
label_region = pya.Region()
if label is not None:
for i, letter in enumerate(str(label)):
label_region += (
font_polygons[letter.upper()].scaled_and_snapped(0, norm_size, 1, 0, norm_size, 1).moved(i * spacing, 0)
)
return label_region


@lru_cache(maxsize=None)
def load_font_polygons() -> dict[str, pya.Region]:
"""Loads from static OAS file a region for each letter used in labels.
Cached for reuse.
"""
layout = pya.Layout()
layout.read(OAS_PATH)

font_dict = {letter: pya.Region() for letter in OAS_LETTERS}
for shape in pya.Region(layout.top_cells()[-1].begin_shapes_rec(layout.layer(129, 1))).each():
index = round(shape.bbox().to_dtype(OAS_DBU).p1.x) // OAS_TEXT_MAGNIFICATION
font_dict[OAS_LETTERS[int(index)]] += pya.Region(shape.moved(-OAS_TEXT_MAGNIFICATION * index / OAS_DBU, 0))

return font_dict
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<klayout-macro>
<description>Lay out all characters</description>
<version/>
<category>pymacros</category>
<prolog/>
<epilog/>
<doc/>
<autorun>false</autorun>
<autorun-early>false</autorun-early>
<priority>0</priority>
<shortcut/>
<show-in-menu>false</show-in-menu>
<group-name/>
<interpreter>python</interpreter>
<dsl-interpreter-name/>
<text># This code is part of KQCircuits
# Copyright (C) 2024 IQM Finland Oy
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# https://www.gnu.org/licenses/gpl-3.0.html.
#
# The software distribution should follow IQM trademark policy for open-source software
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code.
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement)
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement).

from kqcircuits.pya_resolver import pya
from kqcircuits.defaults import default_layers
from kqcircuits.klayout_view import KLayoutView

keyboard_chars = [chr(ord("A") + i) for i in range(0, 32)] + [chr(ord("0") + i) for i in range(0, 10)]

polygons = {}
view = KLayoutView()
layout = view.layout
for i, label in enumerate(keyboard_chars):

char_cell = layout.create_cell(
"TEXT",
"Basic",
{
"layer": default_layers["1t1_base_metal_gap"],
"text": label,
"mag": 500,
},
)
layout.top_cells()[0].insert(pya.DCellInstArray(char_cell.cell_index(), pya.DTrans(0, False, i*500, 0)))

print("Label character shapes generated. Please save the generated layout as kqcircuits/util/font_polygons.oas")
print("Uncheck 'Store PCell and library context information' in 'Save Layout options' window")
</text>
</klayout-macro>

0 comments on commit f9a2694

Please sign in to comment.