R reuter /

QFP58

CC BY-SA 4.0 Native

Vibe engineered circular polarized PCB antenna, inspired by the Triple Feed Patch.

Rendered
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
QFP58-feed-RHCP-drc.rpt 553 B
README.md 15.5 KB
run.sh 4.2 KB
uv.lock 189 KB
docs/
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
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
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
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
kicad/QFP58-patch/
fp-lib-table 149 B
QFP58-patch-drc.rpt 576 B
kicad/QFP58-patch-d23.06/
fp-lib-table 149 B
QFP58-patch-d23.06-drc.rpt 528 B
kicad/QFP58-patch-d23.36/
fp-lib-table 149 B
QFP58-patch-d23.36-drc.rpt 528 B
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.png 30.3 KB
summary.json 1.5 KB
sim/network/
network_sparams.png 45.0 KB
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
qfp_geom.py 11.6 KB
qfp_sim.py 27.4 KB
run.sh
#!/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