You may or may not find something useful here.

Browse the archive too see all entries.

Voronoi ordered to random

Somewhere I saw a picture of a high-performance computer, where the doors would have a hexagonal pattern that would "degrade" into a irregular Voronoi pattern as ventilation holes.

That can be replicated quite easily with a short python script! If you like, you can then use my Voronoi pattern generator for FreeCAD to build such ventilation holes yourself.

The python code is pretty straight forward. First, you need a couple of libraries:

import numpy as np
from scipy.spatial import Voronoi, voronoi_plot_2d
import matplotlib.pyplot as plt
from itertools import count

Next up, we generate a couple of points that are the centers of tiled hexagons:

def hex_pattern(a: float = 1, nx: int = 20, ny: int = 10, start: int = 1) -> np.array:
    Create a pattern with nx times ny cells
    :param float d: edge length of the hexagon
    :param int nx: number of cells in x-direction
    :param int ny: number of cells in y-direction
    :param int start: if the offset is on even (0) or odd (1) rows
    points = np.empty((nx * ny, 2), dtype=float)
    if start not in (0, 1):
        raise ValueError('start must be either 0 or 1')
    if nx < 1:
        raise ValueError("there must be a minimum of 1 cells in x")
    if ny < 1:
        raise ValueError("there must be a minimum of 1 cells in y")
    if a <= 0:
        raise ValueError("Edge length of hexagon must be positive and non-zero")
    dx = 2 * a
    dy = np.sqrt(3) * a
    i = count()
    for y in range(ny):
        offset = a if y % 2 == start else 0
        for x in range(nx):
            points[next(i)] = dx * x + offset, dy * y
    return points

points = hex_pattern()

Now, we want some randomness in there. We set up several functions to add the randomness:

def linear(x: np.array, x0: float = -0.5, x1: float = 0) -> np.array:
    Linear gradient between x0 and x1, values lower x0 are set to 0,
    higher than x1 to 1
    x = x.copy()
    zeros = x < x0
    ones = x > x1
    z = x[(x >= x0) & (x <= x1)]
    r = z.max() - z.min()
    z = (z - z.min()) / r
    x[(x >= x0) & (x <= x1)] = z
    x[zeros] = 0
    x[ones] = 1
    return x

def norm_interval(x: np.array, a: float = -1, b: float = 1) -> np.array:
    """Normalize values to the new interval [a, b]"""
    r = x.max() - x.min()
    return (((x - x.min()) / r) * (b - a)) + a

def add_random(p: np.array, fun, k: float = 1, coord: int = 0) -> np.array:
    """Add a random displacement to a point with a threshold function"""
    r = k * np.random.random(p.shape)
    # The idea here is to scale the x (or y) values of the points to [-1, 1],
    # then give it to the threshold function, which returns scaling factors [0, 1]
    c = fun(norm_interval(p[:, coord])).reshape(-1, 1)
    return p + r * c

points = add_random(points, linear)

Now, we can use the Voronoi function to generate the cells (or you use the points directly in the FreeCAD script):

vor = Voronoi(points)

voronoi_plot_2d(vor, plt.gca(), show_points=False, show_vertices=False)
plt.xlim(np.min(points[:, 0]), np.max(points[:, 0]))
plt.ylim(np.min(points[:, 1]), np.max(points[:, 1]))

And here we are:

Posted Thu Sep 22 20:13:36 2022
creating qcow2 with vmdb2

vmdb2 is the new version of vmdebootstrap. It can create disk images, for example for Raspberry PIs or VMs. You could also use it as a debian installer, if you like...

However, there was is a shortcomming in vmdb2: it can only create raw images - but what you eventually want for VMs is qcow2. However, when you install this patch (actually, only the changes around line 92 are required), you can do the following to install onto a qcow2 image directly:

qemu-img create -f qcow2 myimage.qcow2 40G
modprobe nbd max_part=8
qemu-nbd --connect=/dev/nbd0 --format=qcow2 --cache=none --detect-zeroes=on --aio=native myimage.qcow2
vmdb2 --image /dev/nbd0 config.vmdb2
qemu-nbd --disconnect /dev/nbd0

Note however, that you must not use the kpartx module in the vmdb2 script. For this purpose, it is not necessary because nbd will detect the partitions automatically. A minimal vmdb script would for example be:


  - mklabel: msdos
    device: "{{ image }}"

  - mkpart: primary
    device: "{{ image }}"
    start: 10M
    end: 100%
    tag: rootfs

  - mkfs: ext4
    partition: rootfs
    options: -E lazy_itable_init=0,lazy_journal_init=0

  - mount: rootfs

  - debootstrap: bullseye
    target: rootfs

  - apt: install
      - linux-image-amd64
    tag: rootfs

  - grub: bios
    tag: rootfs
    image-dev: "{{ image }}"
Posted Tue Jan 4 13:54:01 2022
brother printer on arm64

I own a Brother HL-L5000D printer. This printer has no LAN port, thus I used an external print server. Many years I used a cheap Wifi access point with OpenWRT that had an extra USB port to run simultaneously as print server using the wonderful package p910nd. This server basically forwards data from port 9100 to the USB device. On the cheapo accesspoint, this was the only viable solution, as the device had only a tiny amount of RAM and flash...

Some day, the access point broke, so I swapped it for a Raspberry Pi 4B. It can easily handle cups but there is an issue: The Brother drivers are only published on their website for i686 and amd64... A week ago I noticed, that on the German website also a driver for Raspian is offered. The weird thing is, that is only shown there but not on the international page... Installing the package is easy, just enable armhf, install a libc and you are good to go:

dpkg --add-architecture armhf
apt update
apt install libc6:armhf

However, that package would not work for me. It would add the printer just fine, but I could not print the testpage. So lets look at the package content more closely:

# dpkg -L brgenprintml2pdrv:armhf

Uhhhm okay, there are x86_64 and i686 files as well... And now for the crucial part:

# ls -al /opt/brother/Printers/BrGenPrintML2/lpd
insgesamt 32
drwxr-xr-x 5 root root 4096 10. Dez 17:30 .
drwxr-xr-x 5 root root 4096 10. Dez 16:56 ..
drwxr-xr-x 2 root root 4096 10. Dez 16:56 armv7l
lrwxrwxrwx 1 root root   60 10. Dez 16:57 brprintconflsr3 -> /opt/brother/Printers/BrGenPrintML2/lpd/i686/brprintconflsr3
drwxr-xr-x 2 root root 4096 10. Dez 16:56 i686
-rwxr-xr-x 1 root root 6698 19. Apr 2017  lpdfilter
lrwxrwxrwx 1 root root   53 10. Dez 16:57 rawtobr3 -> /opt/brother/Printers/BrGenPrintML2/lpd/i686/rawtobr3
drwxr-xr-x 2 root root 4096 10. Dez 16:56 x86_64

Okay... thats the issue! The install scripts just assumes that it should install i686. Extracting the Debian package shows exactly that:

# mkdir -p brgen/DEBIAN
# dpkg -e brgenprintml2pdrv-4.0.0-1.armhf.deb brgen/DEBIAN/
# head -n 11 brgen/DEBIAN/postinst
if [ "$(echo $(uname -m) | grep -i 'arm')" != '' ]; then
  ln -s /opt/brother/Printers/BrGenPrintML2/lpd/armv7l/rawtobr3         /opt/brother/Printers/BrGenPrintML2/lpd/rawtobr3
  ln -s /opt/brother/Printers/BrGenPrintML2/lpd/armv7l/brprintconflsr3         /opt/brother/Printers/BrGenPrintML2/lpd/brprintconflsr3
elif [ "$(echo $(uname -m) | grep -i -e 'x86_64' -e 'amd64')" != '' ]; then
  ln -s /opt/brother/Printers/BrGenPrintML2/lpd/x86_64/rawtobr3         /opt/brother/Printers/BrGenPrintML2/lpd/rawtobr3
  ln -s /opt/brother/Printers/BrGenPrintML2/lpd/x86_64/brprintconflsr3         /opt/brother/Printers/BrGenPrintML2/lpd/brprintconflsr3
  ln -s /opt/brother/Printers/BrGenPrintML2/lpd/i686/rawtobr3         /opt/brother/Printers/BrGenPrintML2/lpd/rawtobr3
  ln -s /opt/brother/Printers/BrGenPrintML2/lpd/i686/brprintconflsr3         /opt/brother/Printers/BrGenPrintML2/lpd/brprintconflsr3

The reason is, that arm64 is actually called aarch64... Okay, so pretty easy to fix:

ln -s -f /opt/brother/Printers/BrGenPrintML2/lpd/armv7l/rawtobr3 /opt/brother/Printers/BrGenPrintML2/lpd/
ln -s -f /opt/brother/Printers/BrGenPrintML2/lpd/armv7l/brprintconflsr3 /opt/brother/Printers/BrGenPrintML2/lpd/

But then I noticed something else: The original driver actually ships the same files - including armhf, i686 and x86_64 - but a different PPD. The Raspbian uses a generic printer, but the other has a proper HL-L5000D.ppd. (Okay, to be fair: they are not the same files but the same folder structure and filenames)

So what you can do to get the original HL-L5000D printer driver:

mkdir -p hll5000dcupswrapper/DEBIAN
mkdir -p hll5000dlpr/DEBIAN
dpkg -x hll5000dcupswrapper-3.5.1-1.i386.deb hll5000dcupswrapper
dpkg -e hll5000dcupswrapper-3.5.1-1.i386.deb hll5000dcupswrapper/DEBIAN
dpkg -x hll5000dlpr-3.5.1-1.i386.deb hll5000dlpr
dpkg -e hll5000dlpr-3.5.1-1.i386.deb hll5000dlpr/DEBIAN
sed -i '/^Architecture/s/i386/all/' hll5000dcupswrapper/DEBIAN/control
sed "s/-i 'arm'/-i -e 'arm' -e 'aarch64'/" hll5000dlpr/DEBIAN/postinst
sed -i '/^Architecture/s/i386/all/' hll5000dlpr/DEBIAN/control
dpkg-deb -Z xz -b hll5000dcupswrapper
dpkg-deb -Z xz -b hll5000dlpr

This repackages the package to be installed on all architectures (okay, that is not correct but we do not care...) and also patches the line in the postinstall script to symlink the correct binaries. It would be neat to add the dependencies, like cups, because the postinst script actually requires some commands, but that is a job for another day...

I'm not sure if this works the same for other Brother printers, but I would assume it does.

Posted Fri Dec 10 18:38:58 2021
paraview reverse vector direction

I want to visualize vector fields in paraview. However, paraview has the property that it shows the vectors with the tail attached to the vertex at which they are specified. While this might be okay if you plot say velocity fields, it might be weird to look at when plotting force vectors acting on some surface vertices.

In this case, the vectors show inward into the surface, which is a bit confusing:

Thus, it would be nice to have the vectors pointing their tip at the specified node. Unfortunately, paraview does not seem to have a setting to do this automatically.

However, there is a trick to make it work! First of all, you have to invert the direction of the vector by using the calculator. Add a new Calculator filter, use Attribute Type "Point Data", select the vector and use the equation -dir (In my case the vector is called dir).

Then apply a new Glyph filter, however under the advanced properties, select "Invert":

Please find a paraview state file as an example here:

Posted Thu Jun 10 10:38:48 2021
wireguard dns only for vpn

I have to connect to a remote network using wireguard. The remote network has a DNS server to resolve all the internal hosts in the net. On Linux, I had a nice setup using systemd-resolved which would only ask the DNS server to resolve me hosts from that network but would still use my DNS server for all other requests. One reason might be that you also have internal hostnames on your own DNS and thus would loose ability to resolve them. Or, the VPN DNS server can not resolve AAAA records but you want to have a IPv6 connection as well. For Windows 10, I also had the issue that the DNS client would "loose" track which DNS server to ask and suddenly, I was not able to resolve the internal domains - even though the DNS server was set as the primary one.

The Linux setup is easy. Say there are the two DNS servers and in the remote network and the internal hosts are all in the domain Then specify in the wireguard config:

# ...
PostUp = resolvectl dns %i
PostUp = resolvectl dnssec %i no  # optional: enable, if the DNS server has DNSSec.
PostUp = resolvectl default-route %i no
PostUp = resolvectl domain %i
# ...

You do not need a PostDown here, because resolvectl removes the entries on the interface automatically, if the interface is going down. Of course, this requires you to actually use systemd...

However, now I have to use a Windows 10 machine. The key component is the NRPT - the network resolution policy table. Windows provides some PowerShell commands to edit this table. With this tool, you can do exactly what we want: specify certain domains which are exclusivly resolved via another DNS server than the system's one.

First, enable script execution in Wireguard in the registry:

Windows Registry Editor Version 5.00


Read more about that in the Wireguard for Windows Documentation. Then, you can use PostUp and PostDown in the wireguard config:

PostUp = powershell -command "Add-DnsClientNrptRule -Namespace '','' -NameServers '',''"
PostDown = powershell -command "Get-DnsClientNrptRule | Where { $_.Namespace -match '.*internal\.domain\.name' } | Remove-DnsClientNrptRule -force"

Note, that we have to give the domain name twice: the first time to be able to resolve and then with a prefixed dot, in order to resolve all hosts below. Also make sure that you comment out the DNS=, line in the wireguard config, as it would interfer with the powershell scripts.

One problem is, that you can not use nslookup to test if the setup works. You can use ping, which should be able to resolve correctly. Or you can use the powershell command Resolve-DnsName, which also gives a explaination how the name was resolved.

Posted Tue Mar 16 08:38:01 2021
no more nodm

Some time ago, I posted a recipe for running kodi-standalone in nodm.

Unfortunately, it is no longer viable, because nodm is deprecated and has two flaws:

  1. When the system is in shutdown, nodm will respawn itself after being killed by the system
  2. Since kodi 18.something, videos will freeze at the end and you can no longer see the kodi menus.

While the first one can be solved using a workaround in the systemd unit for nodm, the latter seems to be not fixable. Furthermore, nodm is no longer developed and also the developer suggest using lightdm and the autologin feature - which, by the way, solves both issues.

Posted Sat Nov 21 11:13:28 2020
recode samsung slowmo video

My Samsung S9 can produce nice slow-motion with 120fps. However, on the phone the videos are stored in 120fps, recoded to 29.99fps - so that you can view the slow-motion directly. But sometimes you also want the original video back. Here is a quick script to recode the video to normal speed:

ffmpeg -i "$FILENAME" -vn -acodec pcm_s16le -ar 44100 -ac 2 output.wav
sox output.wav fixed.wav speed 8.0
ffmpeg -i "$FILENAME" -vf setpts=1/8*PTS -an -r 30 -crf 18 output.mp4
ffmpeg -i output.mp4 -i fixed.wav -c:v copy -c:a aac realtime.mp4
rm output.wav output.mp4 fixed.wav

Unfortunately, you can not use ffmpeg for the audio too... But the trick using sox works fine!

Posted Mon Nov 2 11:30:27 2020
crack tip element in calculix

There is a special case of element, where the determinant of the Jacobian is "allowed" to be zero. This element is called the crack tip element, and is basically a quadratic quadrilateral element where two of the mid nodes are placed strategically in order to give a zero determinant at the corner node in between.

You can basically calculate the distance yourself, simply set up the trial function for the position, then differentiate to get the Jacobian and solve the determinant for zero at one of the corner nodes. It is actually a fun little exercise! If you calculate the length, you will note, that the resulting distance is 1/4 of the edge length - the undeformed element has the mid node at 1/2 the edge length.

But what does all this mean and can we do with this now? The determinant of the Jacobian effectively gives the volume (area) change for a given infitisimal point. That means, if det(J) equals 1, we have no change. If it is 2, we have an 100% increase. However, if it is zero, we produced a singularity, meaning we compressed everything into a point with no radius - even our infitisimal point gets even smaller. This zero determinant is the same concept of a division by zero. We can actually get a value from such a division, for example by looking at the limit at infinity. But back to our crack tip. The idea of the crack tip, as far as I understood it, is to model the emerging front of a crack where the material gets discontinuous. But as we can not really model discontinuity in the mesh, we use the singularity to model the stress concentrations at the point. By using an element where a single node gives a determinant of zero, allows us to model the diverging stress curve at that point, i.e. we can model a theoretical infinite stress.

Let us look at this example in more detail using calculix to calculate some numbers. First, we create a normal mesh using four CPS8 elements. We fix them on one side and apply a displacement at the two corner nodes the opposite side, effectively loading the elements in tension. We can probably already guess what will happen: The stress will be highest at the two corner nodes and gradually decrease towards the center from both sides. Indeed, we see the same in the output:

But, what happens if we introduce the crack at the middle between the two nodes? We apply the moved nodes and run the simulation again:

We can immediately see the stress concentration in the middle. If we plot the stress along the edge, we can see this more clearly:

But wait! Why is the stress not infinity, as proposed by the limit when we set the determinant to zero? The reason is the numerical model we use. First of all, we are solving the differntial equations here at the integration points and then transforming the result back onto the nodes. You can see what that means if you switch from CPS8 to CPS8R elements, which use only a single integration point. In that case, we do not even see the singularity that easily! In the end, our finite element model is just a model - which has obviously some drawbacks. If you look carefully at the element stress distribution, you can also see where the interpolation comes from - for example you can see some diagonal lines forming between the mid-nodes.

So we see, that we can use such elements to model stress concentrations without actually meshing the discontinuity, for example by inserting many many small elements at the crack tip. On the other hand this also gives a nice example, what happens if the mesh quality is bad and for some reason we introduce such distorted elements into our mesh! In such a case, we would see high stress concentrations at points where we do not have them in the real world.

If you want to play around with the two models, here are the input decks for download:

Posted Wed Apr 29 10:05:06 2020
use nodm instead lightdm for kodi

The official kodi wiki suggests using lightdm with the autologin enabled in order to autostart kodi. But there is a problem with this approach: If kodi crashes (which it frequently does), you are greeted with the login screen instead of having kodi restart.

What you would need is a kiosk mode for lightdm - which is awefully heavy in configuration - at least in comparison with nodm:

  • Simply apt install nodm
  • then edit /etc/default/nodm and change NODM_USER=root for NODM_USER=kodi (or whatever kodi user you have)

Then, enable autostarting for the user:

$ mkdir -p ~/.config/autostart && ln -s /usr/bin/kodi ~/.config/autostart
Posted Mon Jan 6 12:05:57 2020
get local time via wifi

If you travel, you will probably have the problem that the time on your laptop is still the home time. On mobile phones, the time will automagically be retrieved from the mobile network time, but if you do not have a mobile connection on your laptop, this is quite hard. But there is a trick you can do, which requires the use of geoip-bin.

Just lookup your public wifi IP and compare it to the geoip database:

TZ=$(geoiplookup $(curl -s | cut -d " " -f 4 | tr -d ',') date

With a little bit of scripting, you can put this into a nice script, which will refresh the cache of the local timezone every hour.

# this script tries to get the local time by checking the public wifi IP

if ! [ -f ~/.cache/localtime.cache ] || [ $(($(date +%s) - $(stat --printf '%Y' ~/.cache/localtime.cache))) -gt 3600 ] ; then
    geoiplookup $(curl -s | cut -d " " -f 4 | tr -d ',' > ~/.cache/localtime.cache
    if [ $? -ne 0 ]; then
        exit 1

if [ -z "$1" ]; then

if [ -f ~/.cache/localtime.cache ]; then
    TZ=$(cat ~/.cache/localtime.cache) date +"$FMT"
    date +"$FMT"

Later I found out, that this script does only work, if the country you are in, is listed as an own timezone in /usr/share/zoneinfo. It is the case for GB but for example not for AT. There are some conversion lists, but you will obviously get problems when you are in the US or Russia, as those countries span multiple timezones. So yeah, this script kind of work in some cases only. But I hope you get the point and can adjust it to your needs!

Posted Thu Oct 3 10:03:31 2019