gsim 0.0.0__py3-none-any.whl → 0.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,472 @@
1
+ """Geometry extraction and creation for Palace mesh generation.
2
+
3
+ This module handles extracting polygons from gdsfactory components
4
+ and creating 3D geometry in gmsh.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ from dataclasses import dataclass
11
+ from typing import TYPE_CHECKING
12
+
13
+ from . import gmsh_utils
14
+
15
+ if TYPE_CHECKING:
16
+ from gsim.common.stack import LayerStack
17
+ from gsim.palace.ports.config import PalacePort
18
+
19
+
20
+ @dataclass
21
+ class GeometryData:
22
+ """Container for geometry data extracted from component."""
23
+
24
+ polygons: list # List of (layer_num, pts_x, pts_y) tuples
25
+ bbox: tuple[float, float, float, float] # (xmin, ymin, xmax, ymax)
26
+ layer_bboxes: dict # layer_num -> (xmin, ymin, xmax, ymax)
27
+
28
+
29
+ def extract_geometry(component, stack: LayerStack) -> GeometryData:
30
+ """Extract polygon geometry from a gdsfactory component.
31
+
32
+ Args:
33
+ component: gdsfactory Component
34
+ stack: LayerStack for layer mapping
35
+
36
+ Returns:
37
+ GeometryData with polygons and bounding boxes
38
+ """
39
+ polygons = []
40
+ global_bbox = [math.inf, math.inf, -math.inf, -math.inf]
41
+ layer_bboxes = {}
42
+
43
+ # Get polygons from component
44
+ polygons_by_index = component.get_polygons()
45
+
46
+ # Build layer_index -> GDS tuple mapping
47
+ layout = component.kcl.layout
48
+ index_to_gds = {}
49
+ for layer_index in range(layout.layers()):
50
+ if layout.is_valid_layer(layer_index):
51
+ info = layout.get_info(layer_index)
52
+ index_to_gds[layer_index] = (info.layer, info.datatype)
53
+
54
+ # Build GDS tuple -> layer number mapping
55
+ gds_to_layernum = {}
56
+ for layer_data in stack.layers.values():
57
+ gds_tuple = tuple(layer_data.gds_layer)
58
+ gds_to_layernum[gds_tuple] = gds_tuple[0]
59
+
60
+ # Convert polygons
61
+ for layer_index, polys in polygons_by_index.items():
62
+ gds_tuple = index_to_gds.get(layer_index)
63
+ if gds_tuple is None:
64
+ continue
65
+
66
+ layernum = gds_to_layernum.get(gds_tuple)
67
+ if layernum is None:
68
+ continue
69
+
70
+ for poly in polys:
71
+ # Convert klayout polygon to lists (nm -> um)
72
+ points = list(poly.each_point_hull())
73
+ if len(points) < 3:
74
+ continue
75
+
76
+ pts_x = [pt.x / 1000.0 for pt in points]
77
+ pts_y = [pt.y / 1000.0 for pt in points]
78
+
79
+ polygons.append((layernum, pts_x, pts_y))
80
+
81
+ # Update bounding boxes
82
+ xmin, xmax = min(pts_x), max(pts_x)
83
+ ymin, ymax = min(pts_y), max(pts_y)
84
+
85
+ global_bbox[0] = min(global_bbox[0], xmin)
86
+ global_bbox[1] = min(global_bbox[1], ymin)
87
+ global_bbox[2] = max(global_bbox[2], xmax)
88
+ global_bbox[3] = max(global_bbox[3], ymax)
89
+
90
+ if layernum not in layer_bboxes:
91
+ layer_bboxes[layernum] = [xmin, ymin, xmax, ymax]
92
+ else:
93
+ bbox = layer_bboxes[layernum]
94
+ bbox[0] = min(bbox[0], xmin)
95
+ bbox[1] = min(bbox[1], ymin)
96
+ bbox[2] = max(bbox[2], xmax)
97
+ bbox[3] = max(bbox[3], ymax)
98
+
99
+ return GeometryData(
100
+ polygons=polygons,
101
+ bbox=(global_bbox[0], global_bbox[1], global_bbox[2], global_bbox[3]),
102
+ layer_bboxes=layer_bboxes,
103
+ )
104
+
105
+
106
+ def get_layer_info(stack: LayerStack, gds_layer: int) -> dict | None:
107
+ """Get layer info from stack by GDS layer number.
108
+
109
+ Args:
110
+ stack: LayerStack with layer definitions
111
+ gds_layer: GDS layer number
112
+
113
+ Returns:
114
+ Dict with layer info or None if not found
115
+ """
116
+ for name, layer in stack.layers.items():
117
+ if layer.gds_layer[0] == gds_layer:
118
+ return {
119
+ "name": name,
120
+ "zmin": layer.zmin,
121
+ "zmax": layer.zmax,
122
+ "thickness": layer.zmax - layer.zmin,
123
+ "material": layer.material,
124
+ "type": layer.layer_type,
125
+ }
126
+ return None
127
+
128
+
129
+ def add_metals(
130
+ kernel,
131
+ geometry: GeometryData,
132
+ stack: LayerStack,
133
+ ) -> dict:
134
+ """Add metal and via geometries to gmsh.
135
+
136
+ Creates extruded volumes for vias and shells (surfaces) for conductors.
137
+
138
+ Args:
139
+ kernel: gmsh OCC kernel
140
+ geometry: Extracted geometry data
141
+ stack: LayerStack with layer definitions
142
+
143
+ Returns:
144
+ Dict with layer_name -> list of (surface_tags_xy, surface_tags_z) for
145
+ conductors, or volume_tags for vias.
146
+ """
147
+ # layer_name -> {"volumes": [], "surfaces_xy": [], "surfaces_z": []}
148
+ metal_tags = {}
149
+
150
+ # Group polygons by layer
151
+ polygons_by_layer = {}
152
+ for layernum, pts_x, pts_y in geometry.polygons:
153
+ if layernum not in polygons_by_layer:
154
+ polygons_by_layer[layernum] = []
155
+ polygons_by_layer[layernum].append((pts_x, pts_y))
156
+
157
+ # Process each layer
158
+ for layernum, polys in polygons_by_layer.items():
159
+ layer_info = get_layer_info(stack, layernum)
160
+ if layer_info is None:
161
+ continue
162
+
163
+ layer_name = layer_info["name"]
164
+ layer_type = layer_info["type"]
165
+ zmin = layer_info["zmin"]
166
+ thickness = layer_info["thickness"]
167
+
168
+ if layer_type not in ("conductor", "via"):
169
+ continue
170
+
171
+ if layer_name not in metal_tags:
172
+ metal_tags[layer_name] = {
173
+ "volumes": [],
174
+ "surfaces_xy": [],
175
+ "surfaces_z": [],
176
+ }
177
+
178
+ for pts_x, pts_y in polys:
179
+ # Create extruded polygon
180
+ surfacetag = gmsh_utils.create_polygon_surface(kernel, pts_x, pts_y, zmin)
181
+ if surfacetag is None:
182
+ continue
183
+
184
+ if thickness > 0:
185
+ result = kernel.extrude([(2, surfacetag)], 0, 0, thickness)
186
+ volumetag = result[1][1]
187
+
188
+ if layer_type == "via":
189
+ # Keep vias as volumes
190
+ metal_tags[layer_name]["volumes"].append(volumetag)
191
+ else:
192
+ # For conductors, get shell surfaces and remove volume
193
+ _, surfaceloops = kernel.getSurfaceLoops(volumetag)
194
+ if surfaceloops:
195
+ metal_tags[layer_name]["volumes"].append(
196
+ (volumetag, surfaceloops[0])
197
+ )
198
+ kernel.remove([(3, volumetag)])
199
+
200
+ kernel.removeAllDuplicates()
201
+ kernel.synchronize()
202
+
203
+ return metal_tags
204
+
205
+
206
+ def add_dielectrics(
207
+ kernel,
208
+ geometry: GeometryData,
209
+ stack: LayerStack,
210
+ margin: float,
211
+ air_margin: float,
212
+ ) -> dict:
213
+ """Add dielectric boxes and airbox to gmsh.
214
+
215
+ Args:
216
+ kernel: gmsh OCC kernel
217
+ geometry: Extracted geometry data
218
+ stack: LayerStack with dielectric definitions
219
+ margin: XY margin around design (um)
220
+ air_margin: Air box margin (um)
221
+
222
+ Returns:
223
+ Dict with material_name -> list of volume_tags
224
+ """
225
+ dielectric_tags = {}
226
+
227
+ # Get overall geometry bounds
228
+ xmin, ymin, xmax, ymax = geometry.bbox
229
+ xmin -= margin
230
+ ymin -= margin
231
+ xmax += margin
232
+ ymax += margin
233
+
234
+ # Track overall z range
235
+ z_min_all = math.inf
236
+ z_max_all = -math.inf
237
+
238
+ # Sort dielectrics by z (top to bottom for correct layering)
239
+ sorted_dielectrics = sorted(
240
+ stack.dielectrics, key=lambda d: d["zmax"], reverse=True
241
+ )
242
+
243
+ # Add dielectric boxes
244
+ offset = 0
245
+ offset_delta = margin / 20
246
+
247
+ for dielectric in sorted_dielectrics:
248
+ material = dielectric["material"]
249
+ d_zmin = dielectric["zmin"]
250
+ d_zmax = dielectric["zmax"]
251
+
252
+ z_min_all = min(z_min_all, d_zmin)
253
+ z_max_all = max(z_max_all, d_zmax)
254
+
255
+ if material not in dielectric_tags:
256
+ dielectric_tags[material] = []
257
+
258
+ # Create box with slight offset to avoid mesh issues
259
+ box_tag = gmsh_utils.create_box(
260
+ kernel,
261
+ xmin - offset,
262
+ ymin - offset,
263
+ d_zmin,
264
+ xmax + offset,
265
+ ymax + offset,
266
+ d_zmax,
267
+ )
268
+ dielectric_tags[material].append(box_tag)
269
+
270
+ # Alternate offset to avoid coincident faces
271
+ offset = offset_delta if offset == 0 else 0
272
+
273
+ # Add surrounding airbox
274
+ air_xmin = xmin - air_margin
275
+ air_ymin = ymin - air_margin
276
+ air_xmax = xmax + air_margin
277
+ air_ymax = ymax + air_margin
278
+ air_zmin = z_min_all - air_margin
279
+ air_zmax = z_max_all + air_margin
280
+
281
+ airbox_tag = kernel.addBox(
282
+ air_xmin,
283
+ air_ymin,
284
+ air_zmin,
285
+ air_xmax - air_xmin,
286
+ air_ymax - air_ymin,
287
+ air_zmax - air_zmin,
288
+ )
289
+ dielectric_tags["airbox"] = [airbox_tag]
290
+
291
+ kernel.synchronize()
292
+
293
+ return dielectric_tags
294
+
295
+
296
+ def add_ports(
297
+ kernel,
298
+ ports: list[PalacePort],
299
+ stack: LayerStack,
300
+ ) -> tuple[dict, list]:
301
+ """Add port surfaces to gmsh.
302
+
303
+ Args:
304
+ kernel: gmsh OCC kernel
305
+ ports: List of PalacePort objects (single or multi-element)
306
+ stack: Layer stack
307
+
308
+ Returns:
309
+ (port_tags dict, port_info list)
310
+
311
+ For single-element ports: port_tags["P{num}"] = [surface_tag]
312
+ For multi-element ports: port_tags["P{num}"] = [surface_tag, surface_tag, ...]
313
+ """
314
+ from gsim.palace.ports.config import PortGeometry
315
+
316
+ port_tags = {} # "P{num}" -> [surface_tag(s)]
317
+ port_info = []
318
+ port_num = 1
319
+
320
+ for port in ports:
321
+ if port.multi_element:
322
+ # Multi-element port (CPW)
323
+ if port.layer is None or port.centers is None or port.directions is None:
324
+ continue
325
+ target_layer = stack.layers.get(port.layer)
326
+ if target_layer is None:
327
+ continue
328
+
329
+ z = target_layer.zmin
330
+ hw = port.width / 2
331
+ hl = (port.length or port.width) / 2
332
+
333
+ # Determine axis from orientation
334
+ angle = port.orientation % 360
335
+ is_y_axis = 45 <= angle < 135 or 225 <= angle < 315
336
+
337
+ surfaces = []
338
+ for cx, cy in port.centers:
339
+ if is_y_axis:
340
+ surf = gmsh_utils.create_port_rectangle(
341
+ kernel, cx - hw, cy - hl, z, cx + hw, cy + hl, z
342
+ )
343
+ else:
344
+ surf = gmsh_utils.create_port_rectangle(
345
+ kernel, cx - hl, cy - hw, z, cx + hl, cy + hw, z
346
+ )
347
+ surfaces.append(surf)
348
+
349
+ port_tags[f"P{port_num}"] = surfaces
350
+
351
+ port_info.append(
352
+ {
353
+ "portnumber": port_num,
354
+ "Z0": port.impedance,
355
+ "type": "cpw",
356
+ "elements": [
357
+ {"surface_idx": i, "direction": port.directions[i]}
358
+ for i in range(len(port.centers))
359
+ ],
360
+ "width": port.width,
361
+ "length": port.length or port.width,
362
+ "zmin": z,
363
+ "zmax": z,
364
+ }
365
+ )
366
+
367
+ elif port.geometry == PortGeometry.VIA:
368
+ # Via port: vertical between two layers
369
+ if port.from_layer is None or port.to_layer is None:
370
+ continue
371
+ from_layer = stack.layers.get(port.from_layer)
372
+ to_layer = stack.layers.get(port.to_layer)
373
+ if from_layer is None or to_layer is None:
374
+ continue
375
+
376
+ x, y = port.center
377
+ hw = port.width / 2
378
+
379
+ if from_layer.zmin < to_layer.zmin:
380
+ zmin = from_layer.zmax
381
+ zmax = to_layer.zmin
382
+ else:
383
+ zmin = to_layer.zmax
384
+ zmax = from_layer.zmin
385
+
386
+ # Create vertical port surface
387
+ if port.direction in ("x", "-x"):
388
+ surfacetag = gmsh_utils.create_port_rectangle(
389
+ kernel, x, y - hw, zmin, x, y + hw, zmax
390
+ )
391
+ else:
392
+ surfacetag = gmsh_utils.create_port_rectangle(
393
+ kernel, x - hw, y, zmin, x + hw, y, zmax
394
+ )
395
+
396
+ port_tags[f"P{port_num}"] = [surfacetag]
397
+ port_info.append(
398
+ {
399
+ "portnumber": port_num,
400
+ "Z0": port.impedance,
401
+ "type": "via",
402
+ "direction": "Z",
403
+ "length": zmax - zmin,
404
+ "width": port.width,
405
+ "xmin": x - hw if port.direction in ("y", "-y") else x,
406
+ "xmax": x + hw if port.direction in ("y", "-y") else x,
407
+ "ymin": y - hw if port.direction in ("x", "-x") else y,
408
+ "ymax": y + hw if port.direction in ("x", "-x") else y,
409
+ "zmin": zmin,
410
+ "zmax": zmax,
411
+ }
412
+ )
413
+
414
+ else:
415
+ # Inplane port: horizontal on single layer
416
+ if port.layer is None:
417
+ continue
418
+ target_layer = stack.layers.get(port.layer)
419
+ if target_layer is None:
420
+ continue
421
+
422
+ x, y = port.center
423
+ hw = port.width / 2
424
+ z = target_layer.zmin
425
+
426
+ hl = (port.length or port.width) / 2
427
+ if port.direction in ("x", "-x"):
428
+ surfacetag = gmsh_utils.create_port_rectangle(
429
+ kernel, x - hl, y - hw, z, x + hl, y + hw, z
430
+ )
431
+ length = 2 * hl
432
+ width = port.width
433
+ else:
434
+ surfacetag = gmsh_utils.create_port_rectangle(
435
+ kernel, x - hw, y - hl, z, x + hw, y + hl, z
436
+ )
437
+ length = port.width
438
+ width = 2 * hl
439
+
440
+ port_tags[f"P{port_num}"] = [surfacetag]
441
+ port_info.append(
442
+ {
443
+ "portnumber": port_num,
444
+ "Z0": port.impedance,
445
+ "type": "lumped",
446
+ "direction": port.direction.upper(),
447
+ "length": length,
448
+ "width": width,
449
+ "xmin": x - hl if port.direction in ("x", "-x") else x - hw,
450
+ "xmax": x + hl if port.direction in ("x", "-x") else x + hw,
451
+ "ymin": y - hw if port.direction in ("x", "-x") else y - hl,
452
+ "ymax": y + hw if port.direction in ("x", "-x") else y + hl,
453
+ "zmin": z,
454
+ "zmax": z,
455
+ }
456
+ )
457
+
458
+ port_num += 1
459
+
460
+ kernel.synchronize()
461
+
462
+ return port_tags, port_info
463
+
464
+
465
+ __all__ = [
466
+ "GeometryData",
467
+ "add_dielectrics",
468
+ "add_metals",
469
+ "add_ports",
470
+ "extract_geometry",
471
+ "get_layer_info",
472
+ ]
@@ -0,0 +1,170 @@
1
+ """Physical group assignment for Palace mesh generation.
2
+
3
+ This module handles assigning gmsh physical groups after geometry fragmentation.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ from . import gmsh_utils
11
+
12
+ if TYPE_CHECKING:
13
+ from gsim.common.stack import LayerStack
14
+
15
+
16
+ def assign_physical_groups(
17
+ kernel,
18
+ metal_tags: dict,
19
+ dielectric_tags: dict,
20
+ port_tags: dict,
21
+ port_info: list,
22
+ geom_dimtags: list,
23
+ geom_map: list,
24
+ _stack: LayerStack,
25
+ ) -> dict:
26
+ """Assign physical groups after fragmenting.
27
+
28
+ Args:
29
+ kernel: gmsh OCC kernel
30
+ metal_tags: Metal layer tags from add_metals()
31
+ dielectric_tags: Dielectric material tags from add_dielectrics()
32
+ port_tags: Port surface tags (may have multiple surfaces for CPW)
33
+ port_info: Port metadata including type info
34
+ geom_dimtags: Dimension tags from fragmentation
35
+ geom_map: Geometry map from fragmentation
36
+ _stack: Layer stack (unused; reserved for future material metadata)
37
+
38
+ Returns:
39
+ Dict with group info for config file generation:
40
+ {
41
+ "volumes": {material_name: {"phys_group": int, "tags": [int]}},
42
+ "conductor_surfaces": {layer_name: {"phys_group": int, "tags": [int]}},
43
+ "port_surfaces": {port_name: {"phys_group": int, "tags": [int]} or
44
+ {"type": "cpw", "elements": [...]}},
45
+ "boundary_surfaces": {"absorbing": {"phys_group": int, "tags": [int]}}
46
+ }
47
+ """
48
+ groups = {
49
+ "volumes": {},
50
+ "conductor_surfaces": {},
51
+ "port_surfaces": {},
52
+ "boundary_surfaces": {},
53
+ }
54
+
55
+ # Assign volume groups for dielectrics
56
+ for material_name, tags in dielectric_tags.items():
57
+ new_tags = gmsh_utils.get_tags_after_fragment(
58
+ tags, geom_dimtags, geom_map, dimension=3
59
+ )
60
+ if new_tags:
61
+ # Only take first N tags (same as original count)
62
+ new_tags = new_tags[: len(tags)]
63
+ phys_group = gmsh_utils.assign_physical_group(3, new_tags, material_name)
64
+ groups["volumes"][material_name] = {
65
+ "phys_group": phys_group,
66
+ "tags": new_tags,
67
+ }
68
+
69
+ # Assign surface groups for conductors
70
+ for layer_name, tag_info in metal_tags.items():
71
+ if tag_info["volumes"]:
72
+ all_xy_tags = []
73
+ all_z_tags = []
74
+
75
+ for item in tag_info["volumes"]:
76
+ if isinstance(item, tuple):
77
+ _volumetag, surface_tags = item
78
+ # Get updated surface tags after fragment
79
+ new_surface_tags = gmsh_utils.get_tags_after_fragment(
80
+ surface_tags, geom_dimtags, geom_map, dimension=2
81
+ )
82
+
83
+ # Separate xy and z surfaces
84
+ for tag in new_surface_tags:
85
+ if gmsh_utils.is_vertical_surface(tag):
86
+ all_z_tags.append(tag)
87
+ else:
88
+ all_xy_tags.append(tag)
89
+
90
+ if all_xy_tags:
91
+ phys_group = gmsh_utils.assign_physical_group(
92
+ 2, all_xy_tags, f"{layer_name}_xy"
93
+ )
94
+ groups["conductor_surfaces"][f"{layer_name}_xy"] = {
95
+ "phys_group": phys_group,
96
+ "tags": all_xy_tags,
97
+ }
98
+
99
+ if all_z_tags:
100
+ phys_group = gmsh_utils.assign_physical_group(
101
+ 2, all_z_tags, f"{layer_name}_z"
102
+ )
103
+ groups["conductor_surfaces"][f"{layer_name}_z"] = {
104
+ "phys_group": phys_group,
105
+ "tags": all_z_tags,
106
+ }
107
+
108
+ # Assign port surface groups
109
+ for port_name, tags in port_tags.items():
110
+ # Find corresponding port_info entry
111
+ port_num = int(port_name[1:]) # "P1" -> 1
112
+ info = next((p for p in port_info if p["portnumber"] == port_num), None)
113
+
114
+ if info and info.get("type") == "cpw":
115
+ # CPW port: create separate physical group for each element
116
+ element_phys_groups = []
117
+ for i, tag in enumerate(tags):
118
+ new_tag_list = gmsh_utils.get_tags_after_fragment(
119
+ [tag], geom_dimtags, geom_map, dimension=2
120
+ )
121
+ if new_tag_list:
122
+ elem_name = f"{port_name}_E{i}"
123
+ phys_group = gmsh_utils.assign_physical_group(
124
+ 2, new_tag_list, elem_name
125
+ )
126
+ element_phys_groups.append(
127
+ {
128
+ "phys_group": phys_group,
129
+ "tags": new_tag_list,
130
+ "direction": info["elements"][i]["direction"],
131
+ }
132
+ )
133
+
134
+ groups["port_surfaces"][port_name] = {
135
+ "type": "cpw",
136
+ "elements": element_phys_groups,
137
+ }
138
+ else:
139
+ # Regular single-element port
140
+ new_tags = gmsh_utils.get_tags_after_fragment(
141
+ tags, geom_dimtags, geom_map, dimension=2
142
+ )
143
+ if new_tags:
144
+ phys_group = gmsh_utils.assign_physical_group(2, new_tags, port_name)
145
+ groups["port_surfaces"][port_name] = {
146
+ "phys_group": phys_group,
147
+ "tags": new_tags,
148
+ }
149
+
150
+ # Assign boundary surfaces (from airbox)
151
+ if "airbox" in groups["volumes"]:
152
+ airbox_tags = groups["volumes"]["airbox"]["tags"]
153
+ if airbox_tags:
154
+ _, simulation_boundary = kernel.getSurfaceLoops(airbox_tags[0])
155
+ if simulation_boundary:
156
+ boundary_tags = list(next(iter(simulation_boundary)))
157
+ phys_group = gmsh_utils.assign_physical_group(
158
+ 2, boundary_tags, "Absorbing_boundary"
159
+ )
160
+ groups["boundary_surfaces"]["absorbing"] = {
161
+ "phys_group": phys_group,
162
+ "tags": boundary_tags,
163
+ }
164
+
165
+ kernel.synchronize()
166
+
167
+ return groups
168
+
169
+
170
+ __all__ = ["assign_physical_groups"]
@@ -12,8 +12,9 @@ from pathlib import Path
12
12
  from typing import TYPE_CHECKING
13
13
 
14
14
  if TYPE_CHECKING:
15
+ from gsim.common.stack import LayerStack
16
+ from gsim.palace.models import DrivenConfig
15
17
  from gsim.palace.ports.config import PalacePort
16
- from gsim.palace.stack.extractor import LayerStack
17
18
 
18
19
  from gsim.palace.mesh.generator import generate_mesh as gen_mesh
19
20
 
@@ -138,6 +139,15 @@ class MeshResult:
138
139
  # Port metadata
139
140
  port_info: list = field(default_factory=list)
140
141
 
142
+ # Mesh statistics
143
+ mesh_stats: dict = field(default_factory=dict)
144
+
145
+ # Data needed for deferred config generation
146
+ groups: dict = field(default_factory=dict)
147
+ output_dir: Path | None = None
148
+ model_name: str = "palace"
149
+ fmax: float = 100e9
150
+
141
151
 
142
152
  def generate_mesh(
143
153
  component,
@@ -146,6 +156,8 @@ def generate_mesh(
146
156
  output_dir: str | Path,
147
157
  config: MeshConfig | None = None,
148
158
  model_name: str = "palace",
159
+ driven_config: DrivenConfig | None = None,
160
+ write_config: bool = True,
149
161
  ) -> MeshResult:
150
162
  """Generate mesh for Palace EM simulation.
151
163
 
@@ -156,6 +168,8 @@ def generate_mesh(
156
168
  output_dir: Directory for output files
157
169
  config: MeshConfig with mesh parameters
158
170
  model_name: Base name for output files (default: "mesh" -> mesh.msh)
171
+ driven_config: Optional DrivenConfig for frequency sweep settings
172
+ write_config: Whether to write config.json (default True)
159
173
 
160
174
  Returns:
161
175
  MeshResult with mesh path and metadata
@@ -178,6 +192,8 @@ def generate_mesh(
178
192
  air_margin=config.margin,
179
193
  fmax=config.fmax,
180
194
  show_gui=config.show_gui,
195
+ driven_config=driven_config,
196
+ write_config=write_config,
181
197
  )
182
198
 
183
199
  # Convert to pipeline's MeshResult format
@@ -185,4 +201,9 @@ def generate_mesh(
185
201
  mesh_path=result.mesh_path,
186
202
  config_path=result.config_path,
187
203
  port_info=result.port_info,
204
+ mesh_stats=result.mesh_stats,
205
+ groups=result.groups,
206
+ output_dir=result.output_dir,
207
+ model_name=result.model_name,
208
+ fmax=result.fmax,
188
209
  )