Skip to content
Merged
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
31 changes: 31 additions & 0 deletions gplugins/common/utils/geometry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import List
import gdsfactory as gf
import kfactory as kf
from shapely.geometry import Polygon, MultiPolygon

def region_to_shapely_polygons(region: kf.kdb.Region) -> MultiPolygon:
"""Convert a kfactory Region to a list of Shapely polygons."""
polygons = []
for polygon_kdb in region.each():
exterior_coords = [
(gf.kcl.to_um(point.x), gf.kcl.to_um(point.y))
for point in polygon_kdb.each_point_hull()
]
# Extract hole coordinates
holes = []
num_holes = polygon_kdb.holes()
for hole_idx in range(num_holes):
hole_coords = []
for point in polygon_kdb.each_point_hole(hole_idx):
hole_coords.append((gf.kcl.to_um(point.x), gf.kcl.to_um(point.y)))
holes.append(hole_coords)


# Create Shapely polygon
if holes:
polygon = Polygon(exterior_coords, holes)
else:
polygon = Polygon(exterior_coords)
polygons.append(polygon)

return MultiPolygon(polygons)
66 changes: 20 additions & 46 deletions gplugins/meshwell/get_meshwell_3D.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,13 @@
import gdsfactory as gf
from meshwell.polyprism import PolyPrism
from typing import List, Dict
from typing import List, Dict, Literal
from shapely.geometry import Polygon, MultiPolygon
import math
import kfactory as kf
from gdsfactory.add_padding import add_padding_container, add_padding
from functools import partial
from gdsfactory.generic_tech.layer_map import LAYER
from typing import Literal


def region_to_shapely_polygons(region: kf.kdb.Region) -> List[Polygon]:
"""Convert a kfactory Region to a list of Shapely polygons."""
polygons = []
for polygon_kdb in region.each():
# Extract exterior coordinates
exterior_coords = []
for point in polygon_kdb.each_point_hull():
exterior_coords.append((gf.kcl.to_um(point.x), gf.kcl.to_um(point.y)))

# Extract hole coordinates
holes = []
for hole_idx in range(polygon_kdb.holes()):
hole_coords = []
hole = polygon_kdb.hole(hole_idx)
for point in hole.each_point():
hole_coords.append((gf.kcl.to_um(point.x), gf.kcl.to_um(point.y)))
holes.append(hole_coords)

# Create Shapely polygon
if holes:
polygon = Polygon(exterior_coords, holes)
else:
polygon = Polygon(exterior_coords)
polygons.append(polygon)

return MultiPolygon(polygons)
from gplugins.common.utils.geometry import region_to_shapely_polygons


def build_buffer_dict_from_layer_level(
Expand Down Expand Up @@ -132,25 +104,27 @@ def get_meshwell_prisms(

return prisms


if __name__ == "__main__":
from gdsfactory.components import ge_detector_straight_si_contacts
from gdsfactory.components import ge_detector_straight_si_contacts, add_frame
from gdsfactory.generic_tech.layer_stack import get_layer_stack
from gdsfactory.generic_tech.layer_map import LAYER
from meshwell.cad import cad
from meshwell.mesh import mesh

prisms = get_meshwell_prisms(
component=ge_detector_straight_si_contacts(),
layer_stack=get_layer_stack(sidewall_angle_wg=0),
name_by="layer",
)

cad(entities_list=prisms, output_file="meshwell_prisms_3D.xao")
mesh(
input_file="meshwell_prisms_3D.xao",
output_file="meshwell_prisms_3D.msh",
default_characteristic_length=1000,
dim=3,
verbosity=10,
)

for component in [ge_detector_straight_si_contacts, add_frame]:
c = component()
prisms = get_meshwell_prisms(
component=c,
layer_stack=get_layer_stack(sidewall_angle_wg=0),
name_by="layer",
)

cad(entities_list=prisms, output_file=f"meshwell_prisms_3D_{c.name}.xao")
mesh(
input_file=f"meshwell_prisms_3D_{c.name}.xao",
output_file=f"meshwell_prisms_3D_{c.name}.msh",
default_characteristic_length=1000,
dim=3,
verbosity=10,
)
Comment on lines +123 to +130
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Output file naming may cause collisions if component names are not unique.

Overwriting may occur if component names are duplicated. Add unique identifiers to filenames or implement collision checks.

120 changes: 1 addition & 119 deletions gplugins/meshwell/get_meshwell_cross_section.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,125 +11,7 @@
from gdsfactory.generic_tech.layer_map import LAYER
from typing import Literal
import numpy as np


def region_to_shapely_polygons(region: kf.kdb.Region) -> List[Polygon]:
"""Convert a kfactory Region to a list of Shapely polygons."""
polygons = []
for polygon_kdb in region.each():
# Extract exterior coordinates
exterior_coords = []
for point in polygon_kdb.each_point_hull():
exterior_coords.append((gf.kcl.to_um(point.x), gf.kcl.to_um(point.y)))

# Extract hole coordinates
holes = []
for hole_idx in range(polygon_kdb.holes()):
hole_coords = []
hole = polygon_kdb.hole(hole_idx)
for point in hole.each_point():
hole_coords.append((gf.kcl.to_um(point.x), gf.kcl.to_um(point.y)))
holes.append(hole_coords)

# Create Shapely polygon
if holes:
polygon = Polygon(exterior_coords, holes)
else:
polygon = Polygon(exterior_coords)
polygons.append(polygon)

return MultiPolygon(polygons)


def build_buffer_dict_from_layer_level(layer_level: gf.technology.LayerLevel) -> Dict[float, float]:
"""Build buffer dictionary from LayerLevel properties."""
zmin = layer_level.zmin
zmax = zmin + layer_level.thickness

# Priority 1: z_to_bias if available
if layer_level.z_to_bias is not None:
z_values, bias_values = layer_level.z_to_bias
return dict(zip(z_values, bias_values))

# Priority 2: Handle sidewall angle
if layer_level.sidewall_angle != 0.0:
angle_rad = math.radians(layer_level.sidewall_angle)
height = layer_level.thickness
width_to_z = layer_level.width_to_z

# Calculate buffer change due to sidewall angle
# Positive angle means outward sloping, negative means inward
buffer_change = height * math.tan(angle_rad)

if width_to_z == 0.0: # Reference at bottom
bottom_buffer = 0.0
top_buffer = buffer_change
elif width_to_z == 1.0: # Reference at top
bottom_buffer = -buffer_change
top_buffer = 0.0
else: # Reference somewhere in middle
ref_height = height * width_to_z
bottom_buffer = -ref_height * math.tan(angle_rad)
top_buffer = (height - ref_height) * math.tan(angle_rad)

return {zmin: bottom_buffer, zmax: top_buffer}

# Default: Simple extrusion
return {zmin: 0.0, zmax: 0.0}


def get_meshwell_prisms(
component: gf.Component,
layer_stack: gf.technology.LayerStack,
wafer_layer: gf.typings.Layer | None = LAYER.WAFER,
wafer_padding: float | None = 0.0,
name_by: Literal["layer", "material"] = "layer"
) -> List[PolyPrism]:
"""Convert LayerStack + Component to meshwell PolyPrism objects."""
prisms = []

if wafer_padding is not None and wafer_layer is not None:
component = add_padding_container(component=component, function=partial(add_padding, layers=(wafer_layer,), default=wafer_padding))

# Iterate through each layer in the stack
for layer_name, layer_level in layer_stack.layers.items():

# Get shapes for this layer from the component
region = layer_level.layer.get_shapes(component)

# Skip if no shapes found
if region.is_empty():
continue

# Convert kfactory Region to Shapely polygons
shapely_polygons = region_to_shapely_polygons(region)

# Skip if no valid polygons
if not shapely_polygons:
continue

# Build buffer dictionary from layer level properties
buffers = build_buffer_dict_from_layer_level(layer_level)

# Create PolyPrism object
if name_by == "layer":
physical_name = layer_name
elif name_by == "material":
physical_name = layer_level.material
else:
raise ValueError("name_by must be 'layer' or 'material'")
prism = PolyPrism(
polygons=shapely_polygons,
buffers=buffers,
physical_name=physical_name,
mesh_order=layer_level.mesh_order,
mesh_bool=True,
additive=False
)

prisms.append(prism)

return prisms
from gplugins.common.utils.geometry import region_to_shapely_polygons


def get_u_bounds_polygons(
Expand Down
69 changes: 69 additions & 0 deletions gplugins/meshwell/tests/test_meshwell.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import pytest
import gdsfactory as gf


from gdsfactory.components import bend_circular, add_frame
from gdsfactory.generic_tech.layer_stack import get_layer_stack
from meshwell.cad import cad
from meshwell.mesh import mesh
from pathlib import Path
from tempfile import TemporaryDirectory
from gplugins.meshwell import (
get_meshwell_prisms, get_meshwell_cross_section
)
from shapely.geometry import LineString

@pytest.mark.parametrize("component", [(bend_circular), (add_frame)])
def test_prisms(component) -> None:
prisms = get_meshwell_prisms(
component=component(),
layer_stack=get_layer_stack(sidewall_angle_wg=0),
name_by="layer",
)

with TemporaryDirectory() as tmp_dir:
xao_file = Path(tmp_dir) / "meshwell_prisms_3D.xao"
msh_file = Path(tmp_dir) / "meshwell_prisms_3D.msh"
cad(entities_list=prisms, output_file=xao_file)
mesh(
input_file=xao_file,
output_file=msh_file,
default_characteristic_length=1000,
dim=3,
verbosity=10,
)


def test_prisms_empty_component() -> None:
"""Test that get_meshwell_prisms handles empty components gracefully."""
c = gf.Component()
prisms = get_meshwell_prisms(
component=c,
layer_stack=get_layer_stack(sidewall_angle_wg=0),
name_by="layer",
wafer_padding=None,
)
assert len(prisms) == 0


@pytest.mark.parametrize("component", [(bend_circular), (add_frame)])
def test_cross_section(component) -> None:
cross_section_line = LineString([(4, -15), (4, 15)])
surfaces = get_meshwell_cross_section(
component=component(),
line=cross_section_line,
layer_stack=get_layer_stack(sidewall_angle_wg=0),
name_by="layer",
)

with TemporaryDirectory() as tmp_dir:
xao_file = Path(tmp_dir) / "meshwell_prisms_3D.xao"
msh_file = Path(tmp_dir) / "meshwell_prisms_3D.msh"
cad(entities_list=surfaces, output_file=xao_file)
mesh(
input_file=xao_file,
output_file=msh_file,
default_characteristic_length=1000,
dim=2,
verbosity=10,
)
13 changes: 13 additions & 0 deletions gplugins/palace/get_capacitance.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,19 @@ def run_capacitive_simulation_palace(

.. _Palace: https://github.com/awslabs/palace
"""
if not isinstance(n_processes, int):
raise TypeError(f"n_processes must be an integer, got {type(n_processes)}")
if n_processes < 1:
raise ValueError(f"n_processes must be >= 1, got {n_processes}")

if solver_config:
order = solver_config.get("Order")
if order is not None:
if not isinstance(order, int):
raise TypeError(f"Solver Order must be an integer, got {type(order)}")
if order < 1:
raise ValueError(f"Solver Order must be >= 1, got {order}")

if layer_stack is None:
layer_stack = LayerStack(
layers={
Expand Down
Loading
Loading