Skip to content

Commit

Permalink
Merge pull request #696 from thomasdorch/klayout-tech
Browse files Browse the repository at this point in the history
More KLayout tech
  • Loading branch information
joamatab committed Sep 12, 2022
2 parents f29abe6 + 450cd38 commit e380a23
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 38 deletions.
Empty file removed gdsfactory/klayout/__init__.py
Empty file.
135 changes: 97 additions & 38 deletions gdsfactory/klayout_tech.py
@@ -1,4 +1,4 @@
"""Classes for KLayout-specific layer settings.
"""Classes and utilities for working with KLayout technology files (.lyp, .lyt).
This module will enable conversion between gdsfactory settings and KLayout technology.
"""
Expand All @@ -13,13 +13,27 @@
from pydantic import BaseModel, Field, validator
from typing_extensions import Literal

from gdsfactory.types import Layer
import klayout.db as db
from gdsfactory.config import PATH

Layer = Tuple[int, int]


def append_file_extension(filename: str, extension: str) -> str:
"""Try appending extension to file."""
# Handle whether given with '.'
if "." not in extension:
extension = f".{extension}"

if not filename.endswith(extension):
filename += extension
return filename


class LayerView(BaseModel):
"""KLayout layer properties.
Docstrings copied from KLayout documentation:
Docstrings copied from KLayout documentation (with some modifications):
https://www.klayout.de/lyp_format.html
Parameters:
Expand Down Expand Up @@ -92,7 +106,7 @@ def color_is_valid(cls, color):
color = CSS4_COLORS[color.lower()]
except Exception as error:
raise ValueError(
"KLayoutLayerProperty color must be specified as a "
"LayerView color must be specified as a "
"0-1 RGB triplet, (e.g. [0.5, 0.1, 0.9]), an HTML hex color string "
"(e.g. '#a31df4'), or a CSS3 color name (e.g. 'gold' or "
"see http://www.w3schools.com/colors/colors_names.asp )"
Expand Down Expand Up @@ -233,15 +247,18 @@ def _properties_to_layerview(element, tag: Optional[str] = None) -> Optional[Lay
return LayerView(**prop_dict)


class KLayoutLayerProperties(BaseModel):
LayerViews = Dict[str, LayerView]


class LayerDisplayProperties(BaseModel):
"""A container for layer properties for KLayout layer property (.lyp) files."""

layer_views: Dict[str, LayerView] = Field(default_factory=dict)
layer_views: LayerViews = Field(default_factory=LayerViews)
custom_dither_patterns: Optional[Dict[str, CustomPattern]] = None
custom_line_styles: Optional[Dict[str, CustomPattern]] = None

def __init__(self, **data):
"""Initialize KLayoutLayerProperties object."""
"""Initialize LayerDisplayProperties object."""
super().__init__(**data)

for key, val in self.dict().items():
Expand All @@ -254,7 +271,7 @@ def __init__(self, **data):

def add_layer(
self,
klayout_layer_props: Optional[LayerView] = None,
layer_display_props: Optional[LayerView] = None,
layer: Optional[Layer] = None,
name: str = "unnamed",
valid: bool = True,
Expand All @@ -272,13 +289,13 @@ def add_layer(
width: Optional[int] = None,
group_members: Optional[Dict[str, LayerView]] = None,
) -> None:
"""Adds a layer to KLayoutLayerProperties.
"""Adds a layer to LayerDisplayProperties.
Docstrings copied from KLayout documentation:
https://www.klayout.de/lyp_format.html
Args:
klayout_layer_props: Add layer from existing KLayoutLayerProperty, overrides all other args.
layer_display_props: Add layer from existing LayerDisplayProperties, overrides all other args.
layer: GDSII layer.
name: Name of the Layer.
width: This is the line width of the frame in pixels (or empty for the default which is 1).
Expand All @@ -301,7 +318,7 @@ def add_layer(
valid: Whether the entry is valid. Invalid layers are drawn but shapes on those layers can't be selected.
group_members: Optional dict of LayerViews to group beneath this LayerView.
"""
new_layer = klayout_layer_props or LayerView(
new_layer = layer_display_props or LayerView(
layer=layer,
name=name,
valid=valid,
Expand Down Expand Up @@ -350,14 +367,9 @@ def get_layer_view_groups(self) -> Dict[str, LayerView]:
}

def __str__(self):
"""Prints the number of KLayoutLayerProperty objects in the KLayoutLayerProperties object."""
# if len(self.groups) == 0:
# return (
# f"KLayoutLayerProperties ({len(self.layers)} layers total) \n"
# f"{list(self.layers)}"
# )
"""Prints the number of LayerView objects in the LayerDisplayProperties object."""
return (
f"KLayoutLayerProperties ({len(self.get_layer_views())} layers total, {len(self.get_layer_view_groups())} groups) \n"
f"LayerDisplayProperties ({len(self.get_layer_views())} layers total, {len(self.get_layer_view_groups())} groups) \n"
f"{self.get_layer_views()}"
)

Expand All @@ -376,32 +388,32 @@ def __getitem__(self, val):
"""Allows accessing to the layer names like ls['gold2'].
Args:
val: Layer name to access within the KLayoutLayerProperties.
val: Layer name to access within the LayerDisplayProperties.
Returns:
self.layers[val]: KLayoutLayerProperty in the KLayoutLayerProperties.
self.layers[val]: LayerView in the LayerDisplayProperties.
"""
try:
return self.layer_views[val]
except Exception as error:
raise ValueError(
f"Layer {val!r} not in LayerColors {list(self.layer_views.keys())}"
f"LayerView {val!r} not in LayerDisplayProperties {list(self.layer_views.keys())}"
) from error

def get_from_tuple(self, layer_tuple: Tuple[int, int]) -> LayerView:
"""Returns KLayoutLayerProperty from layer tuple.
def get_from_tuple(self, layer_tuple: Layer) -> LayerView:
"""Returns LayerView from layer tuple.
Args:
layer_tuple: Tuple of (gds_layer, gds_datatype).
Returns:
KLayoutLayerProperty
LayerView
"""
tuple_to_name = {v.layer: k for k, v in self.layer_views.items()}
if layer_tuple not in tuple_to_name:
raise ValueError(
f"Layer color {layer_tuple} not in {list(tuple_to_name.keys())}"
f"LayerView {layer_tuple} not in {list(tuple_to_name.keys())}"
)

name = tuple_to_name[layer_tuple]
Expand All @@ -418,8 +430,7 @@ def to_lyp(self, filepath: str, overwrite: bool = True) -> None:
filepath: to write the .lyp file to (appends .lyp extension if not present)
overwrite: Whether to overwrite an existing file located at the filepath.
"""
if not filepath.endswith(".lyp"):
filepath += ".lyp"
filepath = append_file_extension(filepath, ".lyp")

if os.path.exists(filepath) and not overwrite:
raise OSError("File exists, cannot write.")
Expand All @@ -443,14 +454,13 @@ def to_lyp(self, filepath: str, overwrite: bool = True) -> None:
)

@staticmethod
def from_lyp(filepath: str) -> "KLayoutLayerProperties":
def from_lyp(filepath: str) -> "LayerDisplayProperties":
"""Write all layer properties to a KLayout .lyp file.
Args:
filepath: to write the .lyp file to (appends .lyp extension if not present)
"""
if not filepath.endswith(".lyp"):
filepath += ".lyp"
filepath = append_file_extension(filepath, ".lyp")

if not os.path.exists(filepath):
raise OSError("File not found!")
Expand Down Expand Up @@ -492,22 +502,68 @@ def from_lyp(filepath: str) -> "KLayoutLayerProperties":
[line.text for line in line_block.find("pattern").iterchildren()]
),
)
return KLayoutLayerProperties(
return LayerDisplayProperties(
layer_views=layer_views,
custom_dither_patterns=custom_dither_patterns,
custom_line_styles=custom_line_styles,
)


# TODO: Write to .lyt technology file
class KLayoutTechnology(BaseModel):
"""A container for working with KLayout technologies.
Useful for importing/exporting Layer Properties (.lyp) and Technology (.lyt) files.
Properties:
layer_properties: Defines all the layer display properties needed for a .lyp file from LayerView objects
technology: KLayout Technology object from the KLayout API. Set name, dbu, etc.
"""

layer_properties: Optional[LayerDisplayProperties] = None
technology: db.Technology = Field(default_factory=db.Technology)

def export_technology_files(
self,
tech_dir: str,
lyp_filename: str = "layers",
lyt_filename: str = "tech",
):
"""Write technology files into 'tech_dir'."""
# Format file names if necessary
lyp_filename = append_file_extension(lyp_filename, ".lyp")
lyt_filename = append_file_extension(lyt_filename, ".lyt")

lyp_path = f"{tech_dir}/{lyp_filename}"
lyt_path = f"{tech_dir}/{lyt_filename}"

# Specify relative file name for layer properties file
# This is the only modification that should be made to the Technology during export
self.technology.layer_properties_file = lyp_filename

# Also need to write the 2.5d info at the end of the lyt file?
# Also interop with xs scripts

# Write lyp to file
self.layer_properties.to_lyp(lyp_path)

# Write lyt to file
with open(lyt_path, "w") as file:
file.write(self.technology.to_xml())

class Config:
"""Allow db.Technology type."""

arbitrary_types_allowed = True


LAYER_PROPERTIES = LayerDisplayProperties.from_lyp(str(PATH.klayout_lyp))

if __name__ == "__main__":

# lc: LayerColors = LAYER_COLORS
# # class LayerViewGroup(LayerView):
#
# class DefaultProperties(KLayoutLayerProperties):
# class DefaultProperties(LayerDisplayProperties):
# WG = LayerView(layer=(1, 0))
# WGCLAD = LayerView(layer=(111, 0))
# SLAB150 = LayerView(layer=(2, 0))
Expand Down Expand Up @@ -563,9 +619,12 @@ def from_lyp(filepath: str) -> "KLayoutLayerProperties":
#
# lyp = DefaultProperties()

# print(lyp)
# lyp.to_lyp("test_lyp")
lyp_filepath = str(PATH.klayout_lyp)
lyp = LayerDisplayProperties.from_lyp(lyp_filepath)

str_xml = open(PATH.klayout_tech / "tech.lyt").read()
new_tech = db.Technology.technology_from_xml(str_xml)

generic_tech = KLayoutTechnology(layer_properties=lyp, technology=new_tech)

filepath = "/home/thomas/layout/gdsfactory/gdsfactory/klayout/tech/layers.lyp"
lyp = KLayoutLayerProperties.from_lyp(filepath)
lyp.to_lyp("test_lyp.lyp")
generic_tech.export_technology_files(tech_dir=str(PATH.module / "test_tech"))

0 comments on commit e380a23

Please sign in to comment.