vsketch and NixOS

July 2023

vsketch and NixOS

I like to plotter. I like to draw pictures with my axidraw and ogle with the terrapen .

I had a few tries using vsketch in NixOS but because I don’t know much about Python and never had enough time to dig into it, I never made vsketch work under NixOS. Same holds for the axidraw cli.

But lately I got introduced to pipenv which made everything possible and very easy.

Setup

I simple use a combination of direnv shell.nix , steam-run and pipenv to make everything work.

shell.nix

The shell nix is called by direnv simply by putting

use nix

in the repositories .envrc file.

In the shell.nix I simple define a all packages I need and override the steam-run binary to add some package. Getting to this point, was a bit fiddly, but once it works, it works :D This is my shell.nix

{ pkgs ? import <nixpkgs> { } }:
pkgs.mkShell {
  buildInputs =
    let
      steamrun = (pkgs.steam.override {
        extraPkgs = pkgs: [
          (pkgs.python3.withPackages (ps: with ps; [
            matplotlib
            pyside6
            setuptools
            scikitlearn
            toml
          ]))
          pkgs.qt6.qtbase
        ];
        extraLibraries = pkgs: [ pkgs.libkrb5 ];
      }).run;
    in
    [
      (pkgs.writers.writeDashBin "vsk" ''
        ${steamrun}/bin/steam-run pipenv run vsk "$@"
      '')
      (pkgs.writers.writeDashBin "axicli" ''
        ${steamrun}/bin/steam-run pipenv run axicli "$@"
      '')
      (pkgs.writers.writeDashBin "pipenv-shell" ''
        ${steamrun}/bin/steam-run pipenv shell
      '')
    ];
}

Pipenv

Now pipenv has all needed dependencies for it’s thing to do. But we need to set up pipenv itself as well, of course. Here is my Pipfile:

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
vsketch = {editable = true, git = "https://github.com/abey79/vsketch"}
axicli = {file = "https://cdn.evilmadscientist.com/dl/ad/public/AxiDraw_API.zip" }

[dev-packages]

[requires]
python_version = "3.10"
python_full_version = "3.10.11"

Now I can run

pipenv install

and magically the shell command vsk (which is defined in the shell.nix) works like charm.

My first sketch

After running

vsk init SimpleRings

I could instantly start fiddling around with the code.

I’m amazed how fast my development cycle was. I could instantly see results, and play around with seed and parameters. And once I liked something. I can just hit Like and it will be saved as svg.

Here is my first script + some screenshots and photos of the results:

import vsketch

class SimpleRingsSketch(vsketch.SketchClass):
    amount = vsketch.Param(3)
    space = vsketch.Param(30, min_value=10)
    min_radius = vsketch.Param(30, min_value=1)
    base_rotation = vsketch.Param(1)
    show_rings = vsketch.Param(True)
    show_rings_randomly = vsketch.Param(True)
    show_lines = vsketch.Param(True)

    def draw(self, vsk: vsketch.Vsketch) -> None:
        vsk.size("a4", landscape=False)

        # vsk.scale("mm")
        # height and width are in pixels (not mm)
        max_radius = min(vsk.height / 2, vsk.width / 2) - self.space

        for i in range(self.amount):
            radius = vsk.random(self.min_radius, max_radius)
            amount_of_lines = int(vsk.random(1, radius))
            line_length = vsk.random(1, 20)
            circle_line_height = vsk.random(1, line_length)

            # vsk.random() consumes the seed, so we have to consume it in every step, other changing the
            # options would result in different results in the other routines.
            if vsk.random(0, 1) > 0.5 or not self.show_rings_randomly:
                if self.show_rings:
                    vsk.circle(0, 0, radius - circle_line_height, mode="radius")

            if not self.show_lines:
                continue
            vsk.rotate(self.base_rotation, degrees=True)
            with vsk.pushMatrix():
                angle = 360.0 / amount_of_lines
                for j in range(0, amount_of_lines):
                    vsk.rotate(angle, degrees=True)
                    vsk.line(0, radius, 0, radius - line_length)

    def finalize(self, vsk: vsketch.Vsketch) -> None:
        vsk.vpype("linemerge linesimplify reloop linesort")


if __name__ == "__main__":
    SimpleRingsSketch.display()