128 files · 3.7 MB at 05eecdc Download ZIP
.dockerignore 72 B
.gitignore 170 B
CLAUDE.md 11.0 KB
Dockerfile 3.2 KB
fp-lib-table 152 B
pyproject.toml 497 B
QFP58-feed-RHCP-drc.rpt 553 B
QFP58-feed-RHCP.kicad_pcb 108 KB
QFP58-feed-RHCP.kicad_pro 9.6 KB
README.md 15.5 KB
run.sh 4.2 KB
uv.lock 189 KB
docs/
design_report.json 5.3 KB
preview_LHCP.png 195 KB
preview_RHCP.png 195 KB
docs/svg/
QFP58-feed-LHCP-B_Cu.svg 134 KB
QFP58-feed-LHCP-Edge_Cuts.svg 11.5 KB
QFP58-feed-LHCP-F_Cu.svg 61.9 KB
QFP58-feed-RHCP-B_Cu.svg 131 KB
QFP58-feed-RHCP-Edge_Cuts.svg 11.6 KB
QFP58-feed-RHCP-F_Cu.svg 61.9 KB
QFP58-patch-B_Cu.svg 37.2 KB
QFP58-patch-Edge_Cuts.svg 11.9 KB
QFP58-patch-F_Cu.svg 16.3 KB
gerbers/
QFP58-feed-LHCP-fab.zip 31.5 KB
QFP58-feed-RHCP-fab.zip 31.6 KB
QFP58-patch-d23.06-fab.zip 12.0 KB
QFP58-patch-d23.36-fab.zip 12.0 KB
QFP58-patch-fab.zip 11.7 KB
gerbers/QFP58-feed-LHCP/
QFP58-feed-LHCP-B_Cu.gbl 4.2 KB
QFP58-feed-LHCP-B_Mask.gbs 1.6 KB
QFP58-feed-LHCP-B_Silkscreen.gbo 41.5 KB
QFP58-feed-LHCP-Edge_Cuts.gm1 17.0 KB
QFP58-feed-LHCP-F_Cu.gtl 47.5 KB
QFP58-feed-LHCP-F_Mask.gts 1.2 KB
QFP58-feed-LHCP-F_Silkscreen.gto 1.2 KB
QFP58-feed-LHCP-job.gbrjob 2.4 KB
QFP58-feed-LHCP-NPTH.drl 383 B
QFP58-feed-LHCP-PTH.drl 530 B
gerbers/QFP58-feed-RHCP/
QFP58-feed-RHCP-B_Cu.gbl 4.2 KB
QFP58-feed-RHCP-B_Mask.gbs 1.6 KB
QFP58-feed-RHCP-B_Silkscreen.gbo 40.6 KB
QFP58-feed-RHCP-Edge_Cuts.gm1 17.0 KB
QFP58-feed-RHCP-F_Cu.gtl 47.5 KB
QFP58-feed-RHCP-F_Mask.gts 1.2 KB
QFP58-feed-RHCP-F_Silkscreen.gto 1.2 KB
QFP58-feed-RHCP-job.gbrjob 2.4 KB
QFP58-feed-RHCP-NPTH.drl 383 B
QFP58-feed-RHCP-PTH.drl 536 B
gerbers/QFP58-patch/
QFP58-patch-B_Cu.gbl 21.0 KB
QFP58-patch-B_Mask.gbs 653 B
QFP58-patch-B_Silkscreen.gbo 663 B
QFP58-patch-Edge_Cuts.gm1 640 B
QFP58-patch-F_Cu.gtl 937 B
QFP58-patch-F_Mask.gts 810 B
QFP58-patch-F_Silkscreen.gto 9.4 KB
QFP58-patch-job.gbrjob 2.4 KB
QFP58-patch-NPTH.drl 264 B
QFP58-patch-PTH.drl 375 B
gerbers/QFP58-patch-d23.06/
QFP58-patch-d23.06-B_Cu.gbl 21.0 KB
QFP58-patch-d23.06-B_Mask.gbs 660 B
QFP58-patch-d23.06-B_Silkscreen.gbo 670 B
QFP58-patch-d23.06-Edge_Cuts.gm1 647 B
QFP58-patch-d23.06-F_Cu.gtl 944 B
QFP58-patch-d23.06-F_Mask.gts 817 B
QFP58-patch-d23.06-F_Silkscreen.gto 10.5 KB
QFP58-patch-d23.06-job.gbrjob 2.4 KB
gerbers/QFP58-patch-d23.36/
QFP58-patch-d23.36-B_Cu.gbl 21.1 KB
QFP58-patch-d23.36-B_Mask.gbs 660 B
QFP58-patch-d23.36-B_Silkscreen.gbo 670 B
QFP58-patch-d23.36-Edge_Cuts.gm1 647 B
QFP58-patch-d23.36-F_Cu.gtl 944 B
QFP58-patch-d23.36-F_Mask.gts 817 B
QFP58-patch-d23.36-F_Silkscreen.gto 10.1 KB
QFP58-patch-d23.36-job.gbrjob 2.4 KB
kicad/QFP58-feed-LHCP/
fp-lib-table 149 B
QFP58-feed-LHCP-drc.rpt 553 B
QFP58-feed-LHCP.kicad_pcb 108 KB
QFP58-feed-LHCP.kicad_pro 9.6 KB
kicad/QFP58-patch/
fp-lib-table 149 B
QFP58-patch-drc.rpt 576 B
QFP58-patch.kicad_pcb 36.3 KB
QFP58-patch.kicad_pro 9.6 KB
kicad/QFP58-patch-d23.06/
fp-lib-table 149 B
QFP58-patch-d23.06-drc.rpt 528 B
QFP58-patch-d23.06.kicad_pcb 36.3 KB
QFP58-patch-d23.06.kicad_pro 9.5 KB
kicad/QFP58-patch-d23.36/
fp-lib-table 149 B
QFP58-patch-d23.36-drc.rpt 528 B
QFP58-patch-d23.36.kicad_pcb 36.4 KB
QFP58-patch-d23.36.kicad_pro 9.5 KB
kicad/QFP58.pretty/
CouplingDisk_3.6mm.kicad_mod 1.2 KB
FeedPin_1.1mm.kicad_mod 1.0 KB
MountingHole_2.2mm_M2.kicad_mod 1.0 KB
R_0603_Wilkinson.kicad_mod 1.2 KB
SMA_EdgeLaunch_1.0mm_generic.kicad_mod 1.7 KB
sim/full/
full_cuts_5.65GHz.png 86.6 KB
full_cuts_5.80GHz.png 87.0 KB
full_cuts_5.95GHz.png 88.6 KB
full_farfield_5.65GHz.npz 147 KB
full_farfield_5.80GHz.npz 147 KB
full_farfield_5.95GHz.npz 147 KB
full_pattern_5.65GHz.png 79.2 KB
full_pattern_5.80GHz.png 80.5 KB
full_pattern_5.95GHz.png 83.6 KB
full_pattern3d_5.65GHz.png 154 KB
full_pattern3d_5.80GHz.png 156 KB
full_pattern3d_5.95GHz.png 155 KB
full_sparams.csv 3.2 KB
full_sparams.png 30.3 KB
summary.json 1.5 KB
sim/network/
network_sparams.csv 12.9 KB
network_sparams.png 45.0 KB
summary.json 286 B
sim/patch/
patch_ideal_feed_pattern_5.65GHz.png 69.2 KB
patch_ideal_feed_pattern_5.80GHz.png 69.5 KB
patch_ideal_feed_pattern_5.95GHz.png 70.5 KB
patch_sparams.png 48.0 KB
summary.json 1.4 KB
src/
generate.py 1.9 KB
kicad_build.py 13.9 KB
qfp_geom.py 11.6 KB
qfp_layers.py 6.9 KB
qfp_sim.py 27.4 KB
#!/bin/sh
# Run the QFP-58 workflows in the build container; the only host requirement is Docker.
#
# ./run.sh [--build] [--sim] [--fullsim] [options] named workflows, run in the order given
# ./run.sh [options] <command...> anything else, e.g. ./run.sh bash
# ./run.sh same as --build
#
# --build boards (RHCP feed in the root, the rest in kicad/), DRC, Gerbers, docs/ (~10 s)
# --sim quick check: full model (SMA to far field), coarse mesh, 3.4 Mcells (~4 min)
# gain/S11 within ~0.1 dB of --fullsim, axial ratio only within ~0.4 dB
# --fullsim reference: full model, medium mesh, 7.6 Mcells; the README numbers (~10 min)
#
# --hand RHCP|LHCP polarization for the simulations (default RHCP)
# --res R override the mesh (coarse|medium|fine) for --sim/--fullsim
# --tag T result suffix (default: timestamp, so runs never overwrite each other)
# --cpus N use N cores (keeps the machine usable; openEMS gains little beyond ~5)
# --name N container name (default qfp58-build; -2, -3, ... appended while that one is running)
# --detach run in the background; follow with `docker logs -f <name>`, stop with `docker stop <name>`
#
# Simulation results go to sim_out/full_<hand>_<res>_<tag>/, the log to sim_out/<tag>.log.
# The image is rebuilt when Dockerfile, pyproject.toml or uv.lock change (Docker layer cache otherwise).
set -e
cd "$(dirname "$0")"
IMAGE=qfp58-build
usage() { sed -n '2,21p' "$0" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; }
STEPS= HAND=RHCP RES= TAG=$(date +%Y%m%d-%H%M%S) CPUS= DETACH= NAME=
while [ $# -gt 0 ]; do
case "$1" in
--build|--sim|--fullsim) STEPS="$STEPS ${1#--}" ;;
--hand) HAND=$2; shift ;;
--res) RES=$2; shift ;;
--tag) TAG=$2; shift ;;
--cpus) CPUS=$2; shift ;;
--name) NAME=$2; shift ;;
--detach) DETACH=1 ;;
-h|--help) usage ;;
--) shift; break ;;
-*) echo "unknown option: $1" >&2; usage 1 >&2 ;;
*) break ;;
esac
shift
done
[ -n "$STEPS" ] && [ $# -gt 0 ] && { echo "give either workflow flags or a command, not both" >&2; exit 1; }
[ -z "$STEPS" ] && [ $# -eq 0 ] && STEPS=build
# the in-container command: one shell line so combined steps share one container (and one log)
if [ -n "$STEPS" ]; then
THREADS=${CPUS:+--threads $CPUS}
CMD="set -e"
for s in $STEPS; do
case $s in
build) CMD="$CMD; echo '== build'; python src/generate.py" ;;
sim) CMD="$CMD; echo '== sim: full model, ${RES:-coarse} mesh'
python src/qfp_sim.py full --res ${RES:-coarse} --hand $HAND --out sim_out --tag _$TAG $THREADS" ;;
fullsim) CMD="$CMD; echo '== fullsim: full model, ${RES:-medium} mesh'
python src/qfp_sim.py full --res ${RES:-medium} --hand $HAND --out sim_out --tag _$TAG $THREADS" ;;
esac
done
mkdir -p sim_out
set -- bash -c "set -o pipefail; { $CMD; } 2>&1 | tee sim_out/$TAG.log"
fi
docker image inspect "$IMAGE" >/dev/null 2>&1 || echo "building $IMAGE (first time: ~15 min, openEMS is compiled)" >&2
out=$(docker build -q -t "$IMAGE" . 2>&1) || { echo "$out" >&2; exit 1; } # instant when cached; output only on failure
# --cpuset-cpus rather than --cpus: openEMS sizes its thread pool from the visible cores
# container name: fixed and recognizable in `docker ps`; the default steps aside for a run already in progress
taken() { docker container inspect "$1" >/dev/null 2>&1; }
if [ -n "$NAME" ]; then
if taken "$NAME"; then echo "container name '$NAME' is in use (docker ps -a)" >&2; exit 1; fi
else
NAME=$IMAGE; i=2
while taken "$NAME"; do NAME=$IMAGE-$i; i=$((i + 1)); done
fi
LIMIT=${CPUS:+--cpuset-cpus=0-$((CPUS - 1))}
run() { docker run --rm --name "$NAME" --user "$(id -u):$(id -g)" -v "$PWD":/work -w /work ${LIMIT:+"$LIMIT"} "$@"; }
if [ -n "$DETACH" ]; then
run -d "$IMAGE" "$@" >/dev/null
echo "started $NAME: follow with 'docker logs -f $NAME', stop with 'docker stop $NAME'"
if [ -n "$STEPS" ]; then echo "log also in sim_out/$TAG.log"; fi
else
[ -t 0 ] && [ -t 1 ] && TTY=-t || TTY=
run -i $TTY "$IMAGE" "$@"
fi