Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions zeroheliumkit/fem/freefemer.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ def add_helium_curvature_edp(self, extract_cfg: ExtractConfig) -> str:


def write_edpScript(self):
# TODO: split the logic for caseA (coupling constants only) and caseB (Greens function extraction) into separate functions for better readability and maintainability
"""
Creates the main FreeFEM script based on the configuration and physical surfaces.
"""
Expand Down Expand Up @@ -517,6 +518,10 @@ def script_problem_definition(self, electrode_name: str) -> str:
return code


def script_include_charge(self, coordinate: list | tuple):
pass


def script_save_data(self, config: dict) -> str:
"""
Generates a code block for extracting 2D slice data based on the provided configuration.
Expand Down
6 changes: 3 additions & 3 deletions zeroheliumkit/helpers/resonator_calc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np

from math import pi, sinh
from math import pi, sinh, tanh
from scipy.special import ellipk

from tabulate import tabulate
Expand Down Expand Up @@ -193,7 +193,7 @@ def resonator_frequency(self,
"width, um": round(self.width/um, 2),
"gap, um": round(self.gap/um, 2),
"eps ": round(self.eps_substrate),
"eps_eff": round(self.eps_eff, 2),
"eps_eff": round(self.eps_eff2, 2),
"impedance, Ohm": round(self.Z, 2),
"L, nH/m": round(self.L * 1e9, 3),
"C, pF/m":round(self.C* 1e12, 3)
Expand All @@ -218,7 +218,7 @@ def resonator_length(self,
"width, um": round(self.width/um, 2),
"gap, um": round(self.gap/um, 2),
"eps ": round(self.eps_substrate),
"eps_eff": round(self.eps_eff, 2),
"eps_eff": round(self.eps_eff2, 2),
"impedance, Ohm": round(self.Z, 2),
"L, nH/m": round(self.L * 1e9, 3),
"C, pF/m":round(self.C* 1e12, 3)
Expand Down
4 changes: 2 additions & 2 deletions zeroheliumkit/src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .anchors import Anchor, MultiAnchor, Skeletone, Layer
from .core import Entity, Structure, GeomCollection
from .supercore import SuperStructure, ContinuousLineBuilder, RoutingConfig, ObjsAlongConfig
from .core import Entity, Structure
from .supercore import SuperStructure, ContinuousLineBuilder, RoutingConfig, ObjsAlongConfig, GeomCollection

from .geometries import (StraightLine, ArbitraryLine, Taper, Fillet,
MicroChannels, SpiralInductor, IDC,
Expand Down
25 changes: 17 additions & 8 deletions zeroheliumkit/src/anchors.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
Provides methods for creating and manipulating these paths.
`Layer`: Represents a layer containing polygons with attributes such as name, color, and grid snapping.
"""
from __future__ import annotations

import copy
import numpy as np
import matplotlib.pyplot as plt

from typing import Self
from tabulate import tabulate
from shapely import Point, MultiPoint, LineString, MultiLineString, Polygon, MultiPolygon, GeometryCollection
from shapely import (affinity, unary_union,
Expand Down Expand Up @@ -1036,54 +1039,60 @@ def mirror(


@snap_on_grid(attr="polygons")
def add(self, geom: Polygon | MultiPolygon) -> 'Layer':
def add(self, geom: Polygon | MultiPolygon | Layer) -> Self:
"""
Adds a polygon or multipolygon to the layer.

Args:
geom (Polygon | MultiPolygon): The polygon or multipolygon to add.
geom (Polygon | MultiPolygon | Layer): The polygon or multipolygon to add.

Returns:
Updated instance (self) of the class with the added polygon.
"""
if isinstance(geom, Layer):
geom = geom.polygons
return unary_union([self.polygons, geom])


@snap_on_grid(attr="polygons")
def cut(self,
geom: Polygon | MultiPolygon,
loc: tuple[float, float]=None) -> 'Layer':
geom: Polygon | MultiPolygon | Layer,
loc: tuple[float, float]=None) -> Self:
"""
Cuts the layer with a polygon or multipolygon.

Args:
geom (Polygon | MultiPolygon): The polygon to be cut.
geom (Polygon | MultiPolygon | Layer): The polygon to be cut.
loc (tuple[float, float], optional): The location where the polygon will be cut.
Defaults to None.

Returns:
Updated instance (self) of the class with the cut geometry.
"""
if isinstance(geom, Layer):
geom = geom.polygons
cut_geom = affinity.translate(geom, xoff=loc[0], yoff=loc[1]) if loc else geom
updated = self.polygons.difference(cut_geom)
return updated


@snap_on_grid(attr="polygons")
def crop(self,
geom: Polygon | MultiPolygon,
loc: tuple[float, float] = None) -> 'Layer':
geom: Polygon | MultiPolygon | Layer,
loc: tuple[float, float] = None) -> Self:
"""
Crops the layer with a polygon or multipolygon.

Args:
geom (Polygon | MultiPolygon): The polygon to be used for cropping.
geom (Polygon | MultiPolygon | Layer): The polygon to be used for cropping.
loc (tuple[float, float], optional): The location where the polygon will be applied.
Defaults to None.

Returns:
Updated instance (self) of the class with the cropped geometry.
"""
if isinstance(geom, Layer):
geom = geom.polygons
crop_geom = affinity.translate(geom, xoff = loc[0], yoff = loc[1]) if loc else geom
updated = self.polygons.intersection(crop_geom)
if isinstance(updated, (Point, MultiPoint, LineString, MultiLineString)):
Expand Down
82 changes: 20 additions & 62 deletions zeroheliumkit/src/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
import matplotlib.pyplot as plt
from warnings import warn

from shapely import (Point, MultiPoint, LineString, MultiLineString,
Polygon, MultiPolygon, GeometryCollection)
from shapely import Point, LineString, Polygon, MultiPolygon

from .plotting import interactive_widget_handler, listify_colors, ColorHandler
from .importing import Exporter_DXF, Exporter_GDS, Exporter_Pickle
Expand Down Expand Up @@ -53,7 +52,7 @@ def __init__(self):
self.layers = []
self.skeletone = Skeletone()
self.anchors = MultiAnchor()
self.colors = ColorHandler({})
# self.colors = ColorHandler({})
self.errors = None


Expand Down Expand Up @@ -94,7 +93,7 @@ def add(self, layer: Layer):
Updated instance (self) of the class with the new layer added.
"""
self.layers.append(layer.name)
self.colors.add_color(layer.name, layer.color[0], layer.color[1])
# self.colors.add_color(layer.name, layer.color[0], layer.color[1])
setattr(self, layer.name, layer)
return self

Expand All @@ -112,7 +111,7 @@ def remove(self, lname: str):
if lname in self.layers:
self.layers.remove(lname)
delattr(self, lname)
self.colors.remove_color(lname)
# self.colors.remove_color(lname)
else:
print(f"Layer '{lname}' not found in layers.")

Expand All @@ -134,7 +133,7 @@ def rename(self, old: str, new: str) -> None:
self.__dict__[new] = self.__dict__.pop(old)
self.layers[self.layers.index(old)] = new
self.__dict__[new].name = new
self.colors.rename_color(old, new)
# self.colors.rename_color(old, new)
else:
print(f"Layer '{old}' not found in layers.")

Expand All @@ -153,7 +152,7 @@ def has_layer(self, name: str) -> bool:
return name in self.layers


def cut(self, geom: Polygon | MultiPolygon, loc: tuple[float, float]=None):
def cut(self, geom: Polygon | MultiPolygon, loc: tuple[float, float]=None, ignore: list[str]=[]):
"""
Cuts the specified polygon from polygons in all layers.

Expand All @@ -164,11 +163,12 @@ def cut(self, geom: Polygon | MultiPolygon, loc: tuple[float, float]=None):
Updated instance (self) of the class with the specified polygon cut from all layers.
"""
for lname in self.layers:
getattr(self, lname).cut(geom, loc)
if lname not in ignore:
getattr(self, lname).cut(geom, loc)
return self


def crop(self, geom: Polygon | MultiPolygon, loc: tuple[float, float]=None):
def crop(self, geom: Polygon | MultiPolygon, loc: tuple[float, float]=None, ignore: list[str]=[]):
"""
Crops polygons in all layers.

Expand All @@ -179,11 +179,12 @@ def crop(self, geom: Polygon | MultiPolygon, loc: tuple[float, float]=None):
Updated instance (self) of the class with polygons in all layers cropped by the specified polygon.
"""
for lname in self.layers:
getattr(self, lname).crop(geom, loc)
if lname not in ignore:
getattr(self, lname).crop(geom, loc)
return self


def slice(self, slice_line: LineString | list[LineString]):
def slice(self, slice_line: LineString | list[LineString], ignore: list[str]=[]):
"""
Slices polygons in a layer using a given line.

Expand All @@ -192,7 +193,8 @@ def slice(self, slice_line: LineString | list[LineString]):
slice_line (LineString): The line used for slicing.
"""
for lname in self.layers:
getattr(self, lname).slice(slice_line)
if lname not in ignore:
getattr(self, lname).slice(slice_line)
return self


Expand Down Expand Up @@ -369,7 +371,7 @@ def export_pickle(self, filename: str) -> None:
exp.save()


def export_gds(self, filename: str, layer_cfg: dict) -> None:
def export_gds(self, filename: str, layer_cfg: dict, cellname: str="toplevel") -> None:
"""
Exports all layers as a GDS file.

Expand All @@ -379,7 +381,7 @@ def export_gds(self, filename: str, layer_cfg: dict) -> None:
See `gdspy docs <https://gdspy.readthedocs.io/en/stable/gettingstarted.html#layer-and-datatype>`_ for 'datatype' details.
"""
zhkdict = self.export_dict(remove_holes=True)
exp = Exporter_GDS(filename, zhkdict, layer_cfg)
exp = Exporter_GDS(filename, zhkdict, layer_cfg, cellname)
exp.save()


Expand Down Expand Up @@ -450,7 +452,7 @@ def quickplot(
#plot layers
plot_config = {k:v for k,v in plot_config.items() if k not in off}
for lname, lcolor in plot_config.items():
if self.has_layer(lname):
if self.has_layer(lname) and (not getattr(self, lname).is_empty):
getattr(self, lname).color = lcolor
getattr(self, lname).plot(ax=ax, show_idx=show_idx, labels=labels, **kwargs)

Expand Down Expand Up @@ -514,15 +516,15 @@ def append(self,
Defaults to None.
"""
s = structure.copy()
if move_s:
s.move(*move_s)
if rotate_s:
s.rotate(rotate_s, origin=(0,0))
if move_s:
s.move(*move_s)
attr_list_device = self.layers
attr_list_structure = s.layers
self.layers = list(set(attr_list_device + attr_list_structure))

self.colors.colors = self.colors.colors | s.colors.colors
# self.colors.colors = self.colors.colors | s.colors.colors

# snapping direction
if direction_snap:
Expand Down Expand Up @@ -581,47 +583,3 @@ def return_mirrored(self, aroundaxis: str, **kwargs) -> 'Structure':
"""
cc = self.copy()
return cc.mirror(aroundaxis, **kwargs)


class GeomCollection(Structure):
"""
Represents a collection of geometries.
Class attributes are created by layers dictionary.

Args:
layers (dict): Dictionary containing the layers and corresponding polygons/skeletone/anchors/colors.
"""
def __init__(self, layers: dict=None):
super().__init__()
if layers:
for items in layers.items():
match items:
case ("skeletone", LineString()) | ("skeletone", MultiLineString()):
self.skeletone.lines = items[1]
case ("skeletone", Skeletone()):
self.skeletone = items[1]
case ("skeletone", GeometryCollection()):
warn(message="imported skeletone contains GeometryCollection object. It will be ignored.")
case ("anchors", MultiAnchor()):
self.anchors = items[1]
case ("anchors", MultiPoint()):
for i, pt in enumerate(items[1].geoms):
self.anchors.add(Anchor(pt, 0, "anchor" + str(i)))
case ("colors", ColorHandler()):
self.colors = items[1]
case (str(), Polygon()) | (str(), MultiPolygon()):
layer = Layer(name=items[0], polygons=items[1])
self.layers.append(items[0])
setattr(self, items[0], layer)
case _:
self.layers.append(items[0])
setattr(self, *items)

if not hasattr(self, "anchors"):
self.anchors = MultiAnchor()

if not hasattr(self, "skeletone"):
self.skeletone = Skeletone()

if self.colors.is_empty:
self.colors.update_colors(self.layers)
2 changes: 1 addition & 1 deletion zeroheliumkit/src/geometries.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ def __init__(self,
# create polygons
if layers:
for k, width in layers.items():
polygon = self.skeletone.buffer(offset=width/2, cap_style='square', **kwargs)
polygon = self.skeletone.buffer(offset=width/2, cap_style=cap_style, **kwargs)
self.add(Layer(name=k, polygons=polygon))

# create anchors
Expand Down
13 changes: 7 additions & 6 deletions zeroheliumkit/src/importing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from svgpathtools import parse_path, Line, CubicBezier, QuadraticBezier

from .errors import *
from .utils import to_geometry_list


def sample_bezier(bezier, num_points=20):
Expand All @@ -36,13 +37,13 @@ class Exporter_GDS():

__slots__ = "name", "zhk_layers", "gdsii", "layer_cfg"

def __init__(self, name: str, zhk_layers: dict, layer_cfg: dict) -> None:
def __init__(self, name: str, zhk_layers: dict, layer_cfg: dict, cellname: str="toplevel") -> None:
self.name = name
self.zhk_layers = zhk_layers
self.layer_cfg = layer_cfg
self.preapre_gds()
self.preapre_gds(cellname)

def preapre_gds(self) -> None:
def preapre_gds(self, cellname: str="toplevel") -> None:
"""
Prepare the GDSII library by creating a top-level cell and adding polygons.

Expand All @@ -51,12 +52,12 @@ def preapre_gds(self) -> None:
- gdstk polygons use `layer` and `datatype` (same concepts).
"""
self.gdsii = gdstk.Library()
cell = gdstk.Cell("toplevel")
cell = gdstk.Cell(cellname)
self.gdsii.add(cell)

for lname, l_property in self.layer_cfg.items():
polygons = self.zhk_layers[lname].polygons
for poly in polygons.geoms:
for poly in to_geometry_list(polygons):
points = list(poly.exterior.coords)

# Optional: shapely exterior repeats the first point at the end.
Expand Down Expand Up @@ -179,7 +180,7 @@ def preapre_dxf(self) -> None:
for i, lname in enumerate(self.layer_cfg):
self.dxf.layers.add(lname, color = i + 1)
polygons = self.zhk_layers[lname].polygons
for poly in polygons.geoms:
for poly in to_geometry_list(polygons):
points = list(poly.exterior.coords)
msp.add_lwpolyline(points, dxfattribs={"layer": lname,
"color": BYLAYER})
Expand Down
Loading