diff --git a/.changelog.d/767.added b/.changelog.d/767.added new file mode 100644 index 00000000..2874294e --- /dev/null +++ b/.changelog.d/767.added @@ -0,0 +1 @@ +Lumerical simulations now honor LayerStack sidewall angles through the Layer Builder. diff --git a/gplugins/lumerical/tests/test_write_sparameters_lumerical.py b/gplugins/lumerical/tests/test_write_sparameters_lumerical.py index d465ed6e..cdb4bbbf 100644 --- a/gplugins/lumerical/tests/test_write_sparameters_lumerical.py +++ b/gplugins/lumerical/tests/test_write_sparameters_lumerical.py @@ -10,6 +10,7 @@ class _Session: def __init__(self) -> None: self.ports = 0 + self.layer_settings: list[tuple[object, ...]] = [] def newproject(self) -> None: pass @@ -32,6 +33,21 @@ def addfdtd(self, **kwargs: object) -> None: def gdsimport(self, *args: object) -> None: pass + def addlayerbuilder(self) -> None: + pass + + def set(self, *args: object) -> None: + pass + + def loadgdsfile(self, *args: object) -> None: + pass + + def addlayer(self, *args: object) -> None: + pass + + def setlayer(self, *args: object) -> None: + self.layer_settings.append(args) + def addport(self) -> None: self.ports += 1 @@ -76,3 +92,30 @@ def test_keeps_ports_on_layers_not_in_layer_stack(monkeypatch, tmp_path) -> None assert result is session assert session.ports == 2 + + +def test_uses_layer_builder_for_sidewall_angle(monkeypatch, tmp_path) -> None: + monkeypatch.setitem(sys.modules, "lumapi", ModuleType("lumapi")) + component = gf.components.straight(length=10, cross_section="strip") + layer_stack = LayerStack( + layers={ + "core": LayerLevel( + layer=(1, 0), + thickness=0.22, + zmin=0, + material="sio2", + sidewall_angle=10, + ) + } + ) + session = _Session() + + write_sparameters_lumerical( + component, + session=session, + run=False, + dirpath=tmp_path, + layer_stack=layer_stack, + ) + + assert ("layer_1_0", "sidewall angle", 80) in session.layer_settings diff --git a/gplugins/lumerical/write_sparameters_lumerical.py b/gplugins/lumerical/write_sparameters_lumerical.py index 17b1cfce..0ccf6ae0 100644 --- a/gplugins/lumerical/write_sparameters_lumerical.py +++ b/gplugins/lumerical/write_sparameters_lumerical.py @@ -459,6 +459,8 @@ def write_sparameters_lumerical( exclude_layers = exclude_layers or [] polygons_per_layer = component_extended_beyond_pml.get_polygons_points(merge=True) + layer_builder_name = "gplugins_layer_builder" + has_layer_builder = False for level in layer_stack.layers.values(): layer = level.layer @@ -501,11 +503,33 @@ def write_sparameters_lumerical( zmax = zmin + thickness z = (zmax + zmin) / 2 - s.gdsimport(str(gdspath), "top", f"{layer_tuple[0]}:{layer_tuple[1]}") - layername = f"GDS_LAYER_{layer_tuple[0]}:{layer_tuple[1]}" - s.setnamed(layername, "z", z * 1e-6) - s.setnamed(layername, "z span", thickness * 1e-6) - set_material(session=s, structure=layername, material=material) + if level.sidewall_angle: + if not has_layer_builder: + s.addlayerbuilder() + s.set("name", layer_builder_name) + s.loadgdsfile(str(gdspath)) + has_layer_builder = True + + layername = f"layer_{layer_tuple[0]}_{layer_tuple[1]}" + s.addlayer(layername) + s.setlayer( + layername, "layer number", f"{layer_tuple[0]}:{layer_tuple[1]}" + ) + s.setlayer(layername, "start position", zmin * 1e-6) + s.setlayer(layername, "thickness", thickness * 1e-6) + s.setlayer(layername, "sidewall angle", 90 - level.sidewall_angle) + if not isinstance(material, str): + raise ValueError( + "Layer Builder sidewall angles require a material database name. " + f"Got {material!r} for layer {layer_tuple}." + ) + s.setlayer(layername, "pattern material", material) + else: + s.gdsimport(str(gdspath), "top", f"{layer_tuple[0]}:{layer_tuple[1]}") + layername = f"GDS_LAYER_{layer_tuple[0]}:{layer_tuple[1]}" + s.setnamed(layername, "z", z * 1e-6) + s.setnamed(layername, "z span", thickness * 1e-6) + set_material(session=s, structure=layername, material=material) logger.info( f"adding {layer_tuple}, thickness = {thickness} um, zmin = {zmin} um " )