Step in Width
Step in depth antenna¶
A step in width microstrip antenna is a design variation where the radiating patch element has a non-uniform width that changes along its length. Instead of a simple rectangular patch, the conducting strip gradually narrows or widens at specific points.
import gmsh
import math
import os
import json
from palacetoolkit.viz import view_mesh
from palacetoolkit.mesh import (
Entity,
run_meshing_pipeline,
generate_3d_mesh,
refine_near_surfaces
)
Parameters:¶
l1 : Ground plane length along x-axis, specified as a scalar in meters
w1 : Ground plane width along y-axis, specified as a scalar in meters
h : Patch height along z-axis, specified as a scalar in meters.
strip_line_length : Notch length along x-axis, specified as a scalar in meters.
strip_lined_width_near_port: Notch width along x-axis near the port, specified as a scalar in meters.
strip_lined_width_far: Strip line width along y-axis far from the port, specified as a scalar in meters.
air_height : Air box height along z-axis, specified as a scalar in meters.
air_margin : Air box margin along x and y axes, specified as a scalar in meters.
freq : Simulation frequency in GHz, specified as a scalar.
filename : Output mesh filename, specified as a string.
l1: float = 0.06
w1: float = 0.06
strip_line_length: float = 0.06
strip_line_width_near_port: float = 0.001
strip_line_width_far: float = 0.003
h: float = 0.0013
air_height: float = 0.025
air_margin: float = 0.025
freq: float = 3.3
filename: str = "sw_antenna.msh"
wavelength = 3e8 / (freq * 1e9)
Initialize the model¶
gmsh.initialize()
gmsh.model.add("patch_antenna")
kernel = gmsh.model.occ
Geometry generation¶
# Total domain bounds
total_xmin = -l1/2 - air_margin
total_xmax = l1/2 + air_margin
total_ymin = -w1/2 - air_margin
total_ymax = w1/2 + air_margin
total_zmax = h + air_height
substrate = kernel.addBox(-l1/2, -w1/2, 0, l1, w1, h)
ground_plane = kernel.addRectangle(-l1/2, -w1/2, 0, l1, w1)
strip_line_1 = kernel.addRectangle(-l1/2, -strip_line_width_near_port/2, h, strip_line_length/2, strip_line_width_near_port)
strip_line_2 = kernel.addRectangle(0, -strip_line_width_far/2, h, strip_line_length/2, strip_line_width_far)
top_conductor, _ = kernel.fuse(
[(2, strip_line_1)], [(2, strip_line_2)],
removeObject=True, removeTool=True
)
kernel.synchronize()
gap = 0
lumped_port = kernel.addRectangle(-l1/2 + gap, -strip_line_width_near_port/2, 0, h - gap, strip_line_width_near_port)
kernel.rotate([(2, lumped_port)], -l1/2, 0, 0, 0, 1, 0, -math.pi/2)
kernel.synchronize()
air_box = kernel.addBox(
total_xmin, total_ymin, 0,
total_xmax - total_xmin,
total_ymax - total_ymin,
total_zmax
)
kernel.synchronize()
Info : [ 0%] Union Info : [ 10%] Union Info : [ 20%] Union Info : [ 30%] Union Info : [ 40%] Union Info : [ 50%] Union Info : [ 70%] Union Info : [ 80%] Union - Splitting faces Info : Cannot bind existing OpenCASCADE surface 8 to second tag 9 Info : Could not preserve tag of 2D object 9 (->8)
Entities definition¶
entities = [
Entity("air_box", dim = 3, mesh_order = 2, tags = [air_box]),
Entity("substrate", dim = 3, mesh_order = 1, tags = [substrate]),
Entity("top_conductor", dim = 2, mesh_order= 1, tags = [top_conductor[0][1]]),
Entity("ground_plane", dim = 2, mesh_order = 1, tags = [ground_plane]),
Entity("lumped_port", dim = 2, mesh_order = 0, tags = [lumped_port])
]
pg_map = run_meshing_pipeline(entities)
# Refine near the top conductor and locally the lumped port
refine_near_surfaces(entities[2].dimtags,
wavelength,
ppw_near=50,
ppw_far=30,
set_as_background=True,
local_refinements = {entities[-1].dimtags[0]: 150})
print(entities)
# Mesh sizes
mesh_sizes = {
"substrate": wavelength / 12,
"air_box": wavelength / 4,
"lumped_port": wavelength / 150,
"ground_plane" : wavelength / 10,
"top_conductor": wavelength / 50
}
generate_3d_mesh(entities, mesh_sizes, filename, optimize = True)
view_mesh(filename, transparent_groups="air_box__None")
Info : [ 0%] Difference
Info : [ 10%] Difference
Info : [ 20%] Difference
Info : [ 30%] Difference
Info : [ 40%] Difference
Info : [ 50%] Difference
Info : [ 60%] Difference
Info : [ 70%] Difference - Filling splits of edges
Info : [ 80%] Difference - Adding holes
Info : [ 90%] Difference
Info : [ 0%] Difference
Info : [ 10%] Difference
Info : [ 20%] Difference
Info : [ 30%] Difference
Info : [ 40%] Difference
Info : [ 50%] Difference
Info : [ 60%] Difference
Info : [ 70%] Difference
Info : [ 80%] Difference - Splitting faces
Info : [ 0%] Difference
Info : [ 10%] Difference
Info : [ 20%] Difference - Performing Vertex-Face intersection
Info : [ 30%] Difference
Info : [ 40%] Difference
Info : [ 50%] Difference
Info : [ 60%] Difference
Info : [ 70%] Difference - Filling splits of vertices
Info : [ 80%] Difference - Splitting faces
Info : [ 0%] Fragments
Info : [ 10%] Fragments
Info : [ 20%] Fragments
Info : [ 30%] Fragments
Info : [ 40%] Fragments
Info : [ 50%] Fragments
Info : [ 60%] Fragments
Info : [ 70%] Fragments - Filling splits of vertices
Info : [ 80%] Fragments
Info : [ 90%] Fragments
Physical group 'air_box' (dim=3): pg=1, tags=[2]
Physical group 'substrate' (dim=3): pg=2, tags=[1]
Physical group 'top_conductor' (dim=2): pg=3, tags=[8]
Physical group 'ground_plane' (dim=2): pg=4, tags=[7]
Physical group 'lumped_port' (dim=2): pg=5, tags=[9]
Physical group 'air_box__None' (dim=2): pg=6, tags=[17, 18, 19, 20, 21, 22]
Physical group 'air_box__substrate' (dim=2): pg=7, tags=[10, 11, 13, 12, 14, 15, 16]
ppw_near=50 ppw_far=30
SizeMax=0.0030 transition=0.0227
global: 8 curves, SizeMin=0.0018
local (2, 9): 4 curves, SizeMin=0.0006
Merged 2 fields with Min → field 5
[Entity('air_box', dim=3, order=2, tags=[2]), Entity('substrate', dim=3, order=1, tags=[1]), Entity('top_conductor', dim=2, order=1, tags=[8]), Entity('ground_plane', dim=2, order=1, tags=[7]), Entity('lumped_port', dim=2, order=0, tags=[9])]
Info : Meshing 1D...
Info : [ 0%] Meshing curve 32 (Line)
Info : [ 10%] Meshing curve 33 (Line)
Info : [ 10%] Meshing curve 34 (Line)
Info : [ 10%] Meshing curve 35 (Line)
Info : [ 20%] Meshing curve 36 (Line)
Info : [ 20%] Meshing curve 37 (Line)
Info : [ 20%] Meshing curve 38 (Line)
Info : [ 20%] Meshing curve 39 (Line)
Info : [ 30%] Meshing curve 40 (Line)
Info : [ 30%] Meshing curve 41 (Line)
Info : [ 30%] Meshing curve 42 (Line)
Info : [ 30%] Meshing curve 43 (Line)
Info : [ 40%] Meshing curve 44 (Line)
Info : [ 40%] Meshing curve 45 (Line)
Info : [ 40%] Meshing curve 46 (Line)
Info : [ 40%] Meshing curve 47 (Line)
Info : [ 50%] Meshing curve 48 (Line)
Info : [ 50%] Meshing curve 49 (Line)
Info : [ 50%] Meshing curve 50 (Line)
Info : [ 60%] Meshing curve 51 (Line)
Info : [ 60%] Meshing curve 52 (Line)
Info : [ 60%] Meshing curve 53 (Line)
Info : [ 60%] Meshing curve 54 (Line)
Info : [ 70%] Meshing curve 55 (Line)
Info : [ 70%] Meshing curve 56 (Line)
Info : [ 70%] Meshing curve 57 (Line)
Info : [ 70%] Meshing curve 58 (Line)
Info : [ 80%] Meshing curve 59 (Line)
Info : [ 80%] Meshing curve 60 (Line)
Info : [ 80%] Meshing curve 61 (Line)
Info : [ 80%] Meshing curve 62 (Line)
Info : [ 90%] Meshing curve 63 (Line)
Info : [ 90%] Meshing curve 64 (Line)
Info : [ 90%] Meshing curve 65 (Line)
Info : [ 90%] Meshing curve 66 (Line)
Info : [100%] Meshing curve 67 (Line)
Info : [100%] Meshing curve 68 (Line)
Info : [100%] Meshing curve 69 (Line)
Info : Done meshing 1D (Wall 0.0296993s, CPU 0.028366s)
Info : Meshing 2D...
Info : [ 0%] Meshing surface 7 (Plane, MeshAdapt)
Info : [ 10%] Meshing surface 8 (Plane, MeshAdapt)
Info : [ 20%] Meshing surface 9 (Plane, MeshAdapt)
Info : [ 20%] Meshing surface 10 (Plane, MeshAdapt)
Info : [ 30%] Meshing surface 11 (Plane, MeshAdapt)
Info : [ 40%] Meshing surface 12 (Plane, MeshAdapt)
Info : [ 40%] Meshing surface 13 (Plane, MeshAdapt)
Info : [ 50%] Meshing surface 14 (Plane, MeshAdapt)
Info : [ 60%] Meshing surface 15 (Plane, MeshAdapt)
Info : [ 60%] Meshing surface 16 (Plane, MeshAdapt)
Info : [ 70%] Meshing surface 17 (Plane, MeshAdapt)
Info : [ 70%] Meshing surface 18 (Plane, MeshAdapt)
Info : [ 80%] Meshing surface 19 (Plane, MeshAdapt)
Info : [ 90%] Meshing surface 20 (Plane, MeshAdapt) Info : [ 90%] Meshing surface 21 (Plane, MeshAdapt) Info : [100%] Meshing surface 22 (Plane, MeshAdapt) Info : Done meshing 2D (Wall 0.256575s, CPU 0.257093s) Info : Meshing 3D... Info : 3D Meshing 2 volumes with 1 connected component Info : Tetrahedrizing 6423 nodes... Info : Done tetrahedrizing 6431 nodes (Wall 0.0644159s, CPU 0.058673s) Info : Reconstructing mesh... Info : - Creating surface mesh Info : - Identifying boundary edges Info : - Recovering boundary
Info : Done reconstructing mesh (Wall 0.150254s, CPU 0.136357s) Info : Found volume 2 Info : Found volume 1 Info : It. 0 - 0 nodes created - worst tet radius 11.9517 (nodes removed 0 0) Info : It. 500 - 500 nodes created - worst tet radius 2.29033 (nodes removed 0 0) Info : It. 1000 - 1000 nodes created - worst tet radius 1.86764 (nodes removed 0 0)
Info : It. 1500 - 1500 nodes created - worst tet radius 1.6674 (nodes removed 0 0) Info : It. 2000 - 2000 nodes created - worst tet radius 1.53717 (nodes removed 0 0) Info : It. 2500 - 2500 nodes created - worst tet radius 1.44211 (nodes removed 0 0) Info : It. 3000 - 3000 nodes created - worst tet radius 1.359 (nodes removed 0 0) Info : It. 3500 - 3500 nodes created - worst tet radius 1.29288 (nodes removed 0 0) Info : It. 4000 - 4000 nodes created - worst tet radius 1.24093 (nodes removed 0 0) Info : It. 4500 - 4500 nodes created - worst tet radius 1.19871 (nodes removed 0 0) Info : It. 5000 - 5000 nodes created - worst tet radius 1.16311 (nodes removed 0 0) Info : It. 5500 - 5500 nodes created - worst tet radius 1.13059 (nodes removed 0 0) Info : It. 6000 - 6000 nodes created - worst tet radius 1.10171 (nodes removed 0 0)
Info : It. 6500 - 6500 nodes created - worst tet radius 1.07612 (nodes removed 0 0) Info : It. 7000 - 7000 nodes created - worst tet radius 1.05259 (nodes removed 0 0) Info : It. 7500 - 7500 nodes created - worst tet radius 1.0303 (nodes removed 0 0) Info : It. 8000 - 8000 nodes created - worst tet radius 1.01137 (nodes removed 0 0) Info : 3D refinement terminated (14721 nodes total): Info : - 2 Delaunay cavities modified for star shapeness Info : - 0 nodes could not be inserted Info : - 75787 tetrahedra created in 0.40934 sec. (185144 tets/s) Info : 0 node relocations Info : Done meshing 3D (Wall 0.735322s, CPU 0.723997s) Info : Optimizing mesh... Info : Optimizing volume 1 Info : Optimization starts (volume = 4.68e-06) with worst = 0.0142354 / average = 0.771421: Info : 0.00 < quality < 0.10 : 11 elements Info : 0.10 < quality < 0.20 : 32 elements Info : 0.20 < quality < 0.30 : 63 elements Info : 0.30 < quality < 0.40 : 90 elements Info : 0.40 < quality < 0.50 : 130 elements Info : 0.50 < quality < 0.60 : 234 elements Info : 0.60 < quality < 0.70 : 708 elements Info : 0.70 < quality < 0.80 : 1591 elements Info : 0.80 < quality < 0.90 : 2009 elements Info : 0.90 < quality < 1.00 : 868 elements Info : 104 edge swaps, 0 node relocations (volume = 4.68e-06): worst = 0.220281 / average = 0.781799 (Wall 0.00144388s, CPU 0.001524s) Info : 106 edge swaps, 0 node relocations (volume = 4.68e-06): worst = 0.220281 / average = 0.781824 (Wall 0.00178491s, CPU 0.001897s) Info : No ill-shaped tets in the mesh :-) Info : 0.00 < quality < 0.10 : 0 elements Info : 0.10 < quality < 0.20 : 0 elements Info : 0.20 < quality < 0.30 : 2 elements Info : 0.30 < quality < 0.40 : 91 elements Info : 0.40 < quality < 0.50 : 132 elements Info : 0.50 < quality < 0.60 : 220 elements Info : 0.60 < quality < 0.70 : 717 elements Info : 0.70 < quality < 0.80 : 1601 elements Info : 0.80 < quality < 0.90 : 2011 elements Info : 0.90 < quality < 1.00 : 868 elements Info : Optimizing volume 2
Info : Optimization starts (volume = 0.00031355) with worst = 0.0202287 / average = 0.770281: Info : 0.00 < quality < 0.10 : 148 elements Info : 0.10 < quality < 0.20 : 480 elements Info : 0.20 < quality < 0.30 : 825 elements Info : 0.30 < quality < 0.40 : 1261 elements Info : 0.40 < quality < 0.50 : 1992 elements Info : 0.50 < quality < 0.60 : 3533 elements Info : 0.60 < quality < 0.70 : 8170 elements Info : 0.70 < quality < 0.80 : 16973 elements Info : 0.80 < quality < 0.90 : 24740 elements Info : 0.90 < quality < 1.00 : 11929 elements Info : 1438 edge swaps, 42 node relocations (volume = 0.00031355): worst = 0.206273 / average = 0.782923 (Wall 0.0335785s, CPU 0.033219s) Info : 1449 edge swaps, 42 node relocations (volume = 0.00031355): worst = 0.238683 / average = 0.782998 (Wall 0.0399859s, CPU 0.039606s) Info : No ill-shaped tets in the mesh :-) Info : 0.00 < quality < 0.10 : 0 elements Info : 0.10 < quality < 0.20 : 0 elements Info : 0.20 < quality < 0.30 : 1 elements Info : 0.30 < quality < 0.40 : 1254 elements Info : 0.40 < quality < 0.50 : 1910 elements Info : 0.50 < quality < 0.60 : 3492 elements Info : 0.60 < quality < 0.70 : 8152 elements Info : 0.70 < quality < 0.80 : 17139 elements Info : 0.80 < quality < 0.90 : 24885 elements Info : 0.90 < quality < 1.00 : 11929 elements Info : Done optimizing mesh (Wall 0.133083s, CPU 0.124156s) Info : 14721 nodes 88001 elements Info : Optimizing mesh (Netgen)... Info : Optimizing volume 1 Info : CalcLocalH: 1929 Points 5643 Elements 3852 Surface Elements Info : Remove Illegal Elements Info : 399 illegal tets Info : SplitImprove Info : badmax = 49.5078 Info : 59 splits performed Info : SwapImprove Info : 61 swaps performed Info : SwapImprove2 Info : 1 swaps performed Info : 299 illegal tets Info : SplitImprove Info : badmax = 65.7179 Info : 63 splits performed Info : SwapImprove Info : 19 swaps performed Info : SwapImprove2 Info : 9 swaps performed Info : 153 illegal tets Info : SplitImprove Info : badmax = 4170.81 Info : 43 splits performed Info : SwapImprove Info : 6 swaps performed Info : SwapImprove2 Info : 3 swaps performed Info : 54 illegal tets Info : SplitImprove Info : badmax = 2366.64 Info : 17 splits performed Info : SwapImprove Info : 0 swaps performed Info : SwapImprove2 Info : 1 swaps performed Info : 17 illegal tets Info : SplitImprove Info : badmax = 4351.11 Info : 6 splits performed Info : SwapImprove Info : 0 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : 7 illegal tets Info : SplitImprove Info : badmax = 895.036 Info : 2 splits performed Info : SwapImprove Info : 0 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : 2 illegal tets Info : SplitImprove Info : badmax = 895.036 Info : 1 splits performed Info : SwapImprove Info : 0 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : 0 illegal tets Info : Volume Optimization Info : CombineImprove Info : 46 elements combined Info : ImproveMesh Info : Total badness = 10057.7 Info : Total badness = 9851.87 Info : SplitImprove Info : badmax = 24.0559 Info : 0 splits performed Info : ImproveMesh Info : Total badness = 9851.87 Info : Total badness = 9841.75 Info : SwapImprove Info : 328 swaps performed Info : SwapImprove2
Info : 0 swaps performed Info : ImproveMesh Info : Total badness = 9217.38 Info : Total badness = 9129.03 Info : CombineImprove Info : 1 elements combined Info : ImproveMesh Info : Total badness = 9121.2 Info : Total badness = 9118.87 Info : SplitImprove Info : badmax = 21.1776 Info : 0 splits performed Info : ImproveMesh Info : Total badness = 9118.87 Info : Total badness = 9118.8 Info : SwapImprove Info : 39 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : ImproveMesh Info : Total badness = 9053.71 Info : Total badness = 9033.85 Info : CombineImprove Info : 3 elements combined Info : ImproveMesh Info : Total badness = 9011.79 Info : Total badness = 9009.15 Info : SplitImprove Info : badmax = 14.654 Info : 0 splits performed Info : ImproveMesh Info : Total badness = 9009.15 Info : Total badness = 9009.02 Info : SwapImprove Info : 10 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : ImproveMesh Info : Total badness = 8990.14 Info : Total badness = 8985.13 Info : Optimizing volume 2 Info : CalcLocalH: 13897 Points 68762 Elements 11196 Surface Elements
Info : Remove Illegal Elements Info : 1348 illegal tets Info : SplitImprove Info : badmax = 24.1382 Info : 216 splits performed Info : SwapImprove Info : 254 swaps performed Info : SwapImprove2 Info : 8 swaps performed Info : 805 illegal tets Info : SplitImprove Info : badmax = 37.6708 Info : 202 splits performed Info : SwapImprove Info : 75 swaps performed Info : SwapImprove2 Info : 6 swaps performed Info : 291 illegal tets Info : SplitImprove Info : badmax = 1361.12 Info : 80 splits performed Info : SwapImprove Info : 23 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : 75 illegal tets Info : SplitImprove Info : badmax = 809.731 Info : 23 splits performed Info : SwapImprove Info : 8 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : 6 illegal tets Info : SplitImprove Info : badmax = 809.731 Info : 2 splits performed Info : SwapImprove
Info : 0 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : 2 illegal tets Info : SplitImprove Info : badmax = 809.731 Info : 1 splits performed Info : SwapImprove Info : 0 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : 0 illegal tets Info : Volume Optimization Info : CombineImprove
Info : 651 elements combined Info : ImproveMesh Info : Total badness = 95752.6 Info : Total badness = 91081 Info : SplitImprove Info : badmax = 31.6263
Info : 1 splits performed Info : ImproveMesh Info : Total badness = 91085 Info : Total badness = 90398.8 Info : SwapImprove
Info : 4195 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : ImproveMesh Info : Total badness = 83243
Info : Total badness = 81223.4 Info : CombineImprove Info : 117 elements combined Info : ImproveMesh Info : Total badness = 80184.2
Info : Total badness = 79909.9 Info : SplitImprove Info : badmax = 17.2868 Info : 0 splits performed Info : ImproveMesh Info : Total badness = 79909.9 Info : Total badness = 79862.9 Info : SwapImprove
Info : 655 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : ImproveMesh Info : Total badness = 79332.5
Info : Total badness = 78928.7 Info : CombineImprove Info : 39 elements combined Info : ImproveMesh Info : Total badness = 78600.9
Info : Total badness = 78540 Info : SplitImprove Info : badmax = 10.1249 Info : 0 splits performed Info : ImproveMesh Info : Total badness = 78540 Info : Total badness = 78528.1 Info : SwapImprove
Info : 214 swaps performed Info : SwapImprove2 Info : 0 swaps performed Info : ImproveMesh Info : Total badness = 78441.5
Info : Total badness = 78309.5 Info : Done optimizing mesh (Wall 3.48448s, CPU 3.49297s) Info : Writing 'sw_antenna.msh'... Mesh saved to sw_antenna.msh Info : Done writing 'sw_antenna.msh' Nodes: 14580 Elements: 83588
Loading mesh file: sw_antenna.msh Groups to render transparent: air_box__None
Mesh loaded successfully with 2 cell blocks
Found 12943 triangles total
Physical group tags in mesh: {3: 'top_conductor', 4: 'ground_plane', 5: 'lumped_port', 6: 'air_box__None', 7: 'air_box__substrate'}
Generate JSON config¶
output_file: str = "sw_antenna.json"
freq_min: float = 3.0
freq_max: float = 3.5
freq_step: float = 0.005
eps_r: float = 2.2
loss_tan: float = 0.0009
port_impedance: float = 50.0
solver_order: int = 2
def attr(name):
return [pg_map[name]] if name in pg_map else []
config = {
"Problem": {
"Type": "Driven",
"Verbose": 2,
"Output": "/work/results/sw_antenna/"
},
"Model": {
"Mesh": f"/work/{filename}",
"L0": 1.0,
"Refinement": {}
},
"Domains": {
"Materials": [
{
"Attributes": attr("substrate"),
"Permittivity": eps_r,
"Permeability": 1.0,
"LossTan": loss_tan
},
{
"Attributes": attr("air"),
"Permittivity": 1.0,
"Permeability": 1.0
}
]
},
"Boundaries": {
"PEC": {
"Attributes": attr("ground_plane") + attr("patch")
},
"LumpedPort": [
{
"Index": 1,
"Attributes": attr("lumped_port"),
"R": port_impedance,
"Excitation": True,
"Direction": "+Z"
}
],
"Absorbing": {
"Attributes": attr("farfield"),
"Order": 1
}
},
"Solver": {
"Order": solver_order,
"Device": "CPU",
"Driven": {
"MinFreq": freq_min,
"MaxFreq": freq_max,
"FreqStep": freq_step,
"AdaptiveTol": 0.001
},
"Linear": {
"Type": "Default",
"KSPType": "GMRES",
"Tol": 1.0e-8,
"MaxIts": 200,
"ComplexCoarseSolve": True
}
}
}
script_dir = os.getcwd()
config_path = os.path.join(script_dir, output_file)
with open(config_path, "w") as f:
json.dump(config, f, indent=2)
print(f"Palace config written to {config_path}")
Palace config written to /home/martin/Desktop/PalaceToolkit/docs/examples/sw_antenna.json