summaryrefslogtreecommitdiff
path: root/clef
diff options
context:
space:
mode:
authorNathan Perry <np@nathanperry.dev>2024-08-14 01:34:16 -0400
committerNathan Perry <np@nathanperry.dev>2024-08-14 03:29:44 -0400
commita330a1fad3c1ccdd926297747c03fe95bcaa6587 (patch)
treea8d7ccdfd0ac1d420b4346ad9c0badaf2d31868d /clef
parent7f5e8f7d7d467f6dd3321cfa7eb276396a21dc69 (diff)
nix: migrate functionality to clef
Diffstat (limited to 'clef')
-rw-r--r--clef/.gitignore1
-rw-r--r--clef/README.md196
-rw-r--r--clef/flake.lock209
-rw-r--r--clef/flake.nix81
-rw-r--r--clef/nix/board.nix37
-rw-r--r--clef/nix/default.nix58
-rw-r--r--clef/nix/fabrication.nix82
-rw-r--r--clef/nix/model.nix52
-rw-r--r--clef/nix/panel.nix46
-rw-r--r--clef/nix/schematic.nix51
-rw-r--r--clef/nix/svg.nix82
-rw-r--r--clef/overlays.nix49
12 files changed, 944 insertions, 0 deletions
diff --git a/clef/.gitignore b/clef/.gitignore
index 4d36fb6..3159192 100644
--- a/clef/.gitignore
+++ b/clef/.gitignore
@@ -33,3 +33,4 @@ __pycache__/
/panel/
panel.*
/bom/
+!panel.nix
diff --git a/clef/README.md b/clef/README.md
index 780fa8e..19520b9 100644
--- a/clef/README.md
+++ b/clef/README.md
@@ -1,3 +1,199 @@
# clef
My kicad templates, libraries, and sheets.
+
+Comes with a Nix function to process a KiCad project into:
+
+- Board SVGs
+- Board 3d model (.glb, .step)
+- Schematic PDF, SVGs
+- Fabrication files (using kikit)
+ - Currently untested &mdash; I had to use Fabrication Toolkit on my last
+ project
+- Panelized board (using kikit)
+ - Also minimal testing
+
+Example output for [ocularium](https://pub.npry.dev/ocularium), which provides
+the content for [this blog post](https://blog.npry.dev/resenv/ocularium):
+
+```bash
+$ nix build .# && nix run nixpkgs#tree result
+result
+└── share
+ └── npry
+ └── ocularium
+ ├── fab
+ │   ├── bom.csv
+ │   ├── gerbers.zip
+ │   └── pos.csv
+ ├── model
+ │   ├── okm.glb
+ │   └── okm.step
+ ├── schematic
+ │   ├── schematic.pdf
+ │   └── svg
+ │   ├── IO.svg
+ │   ├── MCU.svg
+ │   ├── Power.svg
+ │   ├── root.svg
+ │   └── Sensors.svg
+ └── svg
+ ├── back.mirror.svg
+ ├── back.svg
+ ├── front.mirror.svg
+ ├── front.svg
+ ├── in1.mirror.svg
+ ├── in1.svg
+ ├── in2.mirror.svg
+ └── in2.svg
+
+8 directories, 19 files
+```
+
+## usage: subtree
+
+I currently include this repo as a [git
+_subtree_](https://manpages.debian.org/testing/git-man/git-subtree.1.en.html)
+in kicad projects that use it:
+
+```bash
+# to init:
+$ git subtree add --squash --prefix clef https://pub.npry.dev/clef master
+
+# then to update:
+$ git subtree pull --squash --prefix clef https://pub.npry.dev/clef master
+```
+
+Submodules are closer to what I want semantically, but I consider them too
+fragile to use.
+
+## nix
+
+The flake provides `pkgs.clef`, a function building a derivation that contains
+the processed content.
+
+Please be aware that complete functionality will involve rebuilding a couple
+of large derivations from source (KiCad, occt and dependents) until I get
+around to upstreaming my overlays.
+
+### example
+
+```nix
+# flake.nix
+{
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/release-24.05";
+ flake-utils.url = "github:numtide/flake-utils/main";
+
+ clef = {
+ url = "git+https://pub.npry.dev/clef";
+
+ # or if using subtree/submodules:
+ # url = "path:clef";
+
+ inputs.nixpkgs.follows = "nixpkgs";
+ flake-utils.follows = "flake-utils";
+ };
+ };
+
+ outputs = { nixpkgs, clef }: (flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = import nixpkgs {
+ overlays = [
+ # NB: This causes another independent nixpkgs eval -- it's just:
+ #
+ # final: prev: { inherit (clef.packages.${prev.system}) clef; }
+ #
+ # This is the easiest way to get started, and if you don't know or
+ # care why you don't want to eval nixpkgs multiple times, this is
+ # probably what you want.
+ #
+ # If you are looking for a more efficient nixpkgs config, see the
+ # flake source for notes -- currently you need to include
+ # clef.overlays.kicad and clef.overlays.nix-filter.
+ clef.default
+ ];
+ };
+
+ clefDrv = pkgs.clef {
+ name = "my_board";
+
+ # Kicad project directory. Automatically filtered by clef to
+ # kicad-relevant files.
+ src = ./.;
+
+ main_pcb = "my_pcb.kicad_pcb";
+ main_sch = "my_sch.kicad_sch";
+
+ outPath = "share/npry/example";
+
+ layers = 4;
+
+ # Parameters to `kikit panelize -p`. Only required if you want
+ # to panelize or tile your board.
+ panelizeConfigs = [
+ ":jlcTooling"
+ ./kikit/jlc_frame.json
+ ];
+ };
+
+ in {
+ packages = {
+ # The main derivation has all of the processed output in it --
+ # gerbers, 3d files, SVGs, schematic PDFs, and a panelized
+ # board (if configured). They are found in the `outPath` you
+ # configure in the argument attrs.
+ default = clefDrv;
+
+ # You can use `passthru` attributes to access individual components
+ # of the build. Outputs are all found as subdirs of
+ # `share/npry/clef`.
+ inherit (clefDrv) models;
+
+ # `panelSrc` has the panelized kicad project (new .kicad_pcb with
+ # the original .kicad_sch and .kicad_pro).
+ panelizedProject = clefDrv.panelSrc;
+
+ # `panel` is the result of calling `clef` with `src = panelSrc;`,
+ # i.e. 3d models, fabrication outputs, and so on for the
+ # panelized board.
+ inherit (clefDrv) panel;
+
+ # If for you need a recursive panelization, you can in
+ # principle do that:
+ panelPanel = (clefDrv.panel.override {
+ panelizeConfigs = [ ":jlcTooling" ./my_nested_panel_config.json ];
+ }).panel;
+ };
+ }));
+}
+```
+
+### direct `callPackage`
+
+The individual component derivations are intentionally written to be usable
+directly:
+
+```nix
+pkgs.callPackage "${clef}/nix/model.nix" {
+ src = ./.;
+ pcb_path = "my_pcb.kicad_pcb";
+}
+```
+
+Bear in mind that you will need the relevant overlays in the calling `nixpkgs`
+for this usage mode:
+
+- `models` needs `clef.overlays.kicad` (adds RapidJSON support to occt,
+ enabling glb export)
+- `fab` wants `clef.overlays.kicad` for a newer version of kikit
+- Everything needs `nix-filter`.
+
+Output for the individual derivations will be found in subdirectories of
+`share/npry/clef`, with filenames normalized.
+
+## etymology
+
+French: "key", pronounced like "clay". (Cognate with the "clef" of "treble
+clef", "bass clef" ~> "treble key", "bass key").
+
+Train of thought: KiCad's "Ki" -> "key" -> "clef".
diff --git a/clef/flake.lock b/clef/flake.lock
new file mode 100644
index 0000000..79b9ab4
--- /dev/null
+++ b/clef/flake.lock
@@ -0,0 +1,209 @@
+{
+ "nodes": {
+ "cadquery-src": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1705326221,
+ "narHash": "sha256-f/qnq5g4FOiit9WQ7zs0axCJBITcAtqF18txMV97Gb8=",
+ "owner": "CadQuery",
+ "repo": "cadquery",
+ "rev": "c44978d60cee2d61bdadf4cb4498286b7034b4c6",
+ "type": "github"
+ },
+ "original": {
+ "owner": "CadQuery",
+ "ref": "2.4.0",
+ "repo": "cadquery",
+ "type": "github"
+ }
+ },
+ "cq": {
+ "inputs": {
+ "cadquery-src": "cadquery-src",
+ "cq-editor-src": "cq-editor-src",
+ "flake-utils": [
+ "flake-utils"
+ ],
+ "nixpkgs": [
+ "nixpkgs"
+ ],
+ "ocp-src": "ocp-src",
+ "ocp-stubs-src": "ocp-stubs-src",
+ "pybind11-stubgen-src": "pybind11-stubgen-src",
+ "pywrap-src": "pywrap-src"
+ },
+ "locked": {
+ "lastModified": 1723029696,
+ "narHash": "sha256-awv2fEsTkc749YcedWK1Y5P1xg5NfAosttlFnBsVVdg=",
+ "owner": "vinszent",
+ "repo": "cq-flake",
+ "rev": "27d9ac5b7f1fb5f271070ca0eed6a40c3fcfec17",
+ "type": "github"
+ },
+ "original": {
+ "owner": "vinszent",
+ "ref": "main",
+ "repo": "cq-flake",
+ "type": "github"
+ }
+ },
+ "cq-editor-src": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1701895648,
+ "narHash": "sha256-mHXEaA6vphps6F0WemdB6fGRY4lzpcxLU7WuYEp8c20=",
+ "owner": "CadQuery",
+ "repo": "CQ-editor",
+ "rev": "4ef178af06d24a53fee87d576f8cada14c0111a3",
+ "type": "github"
+ },
+ "original": {
+ "owner": "CadQuery",
+ "repo": "CQ-editor",
+ "rev": "4ef178af06d24a53fee87d576f8cada14c0111a3",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "ref": "main",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nix-filter": {
+ "locked": {
+ "lastModified": 1710156097,
+ "narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
+ "owner": "numtide",
+ "repo": "nix-filter",
+ "rev": "3342559a24e85fc164b295c3444e8a139924675b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "ref": "main",
+ "repo": "nix-filter",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1723556749,
+ "narHash": "sha256-+CHVZnTnIYRLYsARInHYoWkujzcRkLY/gXm3s5bE52o=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "4a92571f9207810b559c9eac203d1f4d79830073",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixos-24.05",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "ocp-src": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1701196143,
+ "narHash": "sha256-PMkMYEVBHt0i7ahgqF8jLhHHp7IRS7hd+JyydovNJ4A=",
+ "owner": "cadquery",
+ "repo": "ocp",
+ "rev": "4b98a5dc79fa900f7429975708f6a8c2e41cecd1",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cadquery",
+ "repo": "ocp",
+ "rev": "4b98a5dc79fa900f7429975708f6a8c2e41cecd1",
+ "type": "github"
+ }
+ },
+ "ocp-stubs-src": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1672527176,
+ "narHash": "sha256-m9Rg36GYlYfwEfF0PQJWEXf8TyM5HmjeuhJCODiurvY=",
+ "owner": "cadquery",
+ "repo": "ocp-stubs",
+ "rev": "e838ff400d5ee2f4a0579d2a713b19311855288f",
+ "type": "github"
+ },
+ "original": {
+ "owner": "cadquery",
+ "repo": "ocp-stubs",
+ "type": "github"
+ }
+ },
+ "pybind11-stubgen-src": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1700678104,
+ "narHash": "sha256-76u1GcHPPh8oYQeQZDJ4K/so0U7F6rznZ1xa6syqI9s=",
+ "owner": "CadQuery",
+ "repo": "pybind11-stubgen",
+ "rev": "6dc681d838d3ec9a8a9aa4260c8392d3fb700ff0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "CadQuery",
+ "repo": "pybind11-stubgen",
+ "type": "github"
+ }
+ },
+ "pywrap-src": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1676015766,
+ "narHash": "sha256-QhAvJHV5tFq9bjKOzEpcudZNnmUmNVrJ+BLCZJhO31g=",
+ "owner": "CadQuery",
+ "repo": "pywrap",
+ "rev": "f3bcde70fd66a2d884fa60a7a9d9f6aa7c3b6e16",
+ "type": "github"
+ },
+ "original": {
+ "owner": "CadQuery",
+ "repo": "pywrap",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "cq": "cq",
+ "flake-utils": "flake-utils",
+ "nix-filter": "nix-filter",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/clef/flake.nix b/clef/flake.nix
new file mode 100644
index 0000000..66e350f
--- /dev/null
+++ b/clef/flake.nix
@@ -0,0 +1,81 @@
+# vim: ft=nix :
+{
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05";
+ flake-utils.url = "github:numtide/flake-utils/main";
+ nix-filter.url = "github:numtide/nix-filter/main";
+
+ cq = {
+ url = "github:vinszent/cq-flake/main";
+ inputs.nixpkgs.follows = "nixpkgs";
+ inputs.flake-utils.follows = "flake-utils";
+ };
+ };
+
+ description = "clef: kicad shared data and utils";
+
+ outputs = { self, nixpkgs, flake-utils, ... } @ inputs: (flake-utils.lib.eachDefaultSystem (system: {
+ packages = let
+ pkgs = import nixpkgs {
+ inherit system;
+
+ overlays = with self.overlays; [
+ kicad
+ nix-filter
+ ];
+ };
+
+ clef = pkgs.callPackage ./nix;
+ in {
+ inherit clef;
+ default = clef;
+ };
+
+ devShells.default = let
+ pkgs = import nixpkgs {
+ inherit system;
+
+ overlays = with self.overlays; [
+ kicad
+ ];
+ };
+
+ in pkgs.mkShell {
+ name = "devshell";
+ version = self.rev or "dirty";
+
+ packages = with pkgs; [
+ kicad
+ kikit
+ ];
+ };
+ }
+ )) // {
+ overlays = let
+ base = (import ./overlays.nix { inherit inputs; });
+
+ in base // {
+ # clef built against its own nixpkgs import with its required overlay
+ # deps (cadquery, kicad, occt).
+ #
+ # _This will cause another evaluation of nixpkgs_, which is generally
+ # undesirable. However, this provides for the most hassle-free way to
+ # use clef, as you don't need to include all of the overlay dependencies
+ # in your nixpkgs.
+ default = final: prev: {
+ clef = self.packages.${prev.system}.default;
+ };
+
+ # No overlay dependencies included -- clef will not work by default.
+ # You can replicate overlays.default with:
+ #
+ # import nixpkgs { overlays = with clef.overlays; [ freestanding kicad nix-filter ]; }
+ #
+ # Assuming you've pinned clef's nixpkgs to the same version as yours.
+ # This functionality is provided to give you an option
+ freestanding = final: prev: {
+ clef = prev.callPackage ./nix;
+ };
+ };
+ };
+}
diff --git a/clef/nix/board.nix b/clef/nix/board.nix
new file mode 100644
index 0000000..d67417b
--- /dev/null
+++ b/clef/nix/board.nix
@@ -0,0 +1,37 @@
+{
+ runCommand,
+
+ svg,
+ model,
+ fabrication,
+ schematic,
+ panel,
+ panelSrc,
+
+ name,
+ outPath,
+}: let
+ pkg = runCommand name {} ''
+ set -e
+
+ mkdir -p "$out/${outPath}"
+ cd "$out/${outPath}"
+
+ echo "populating $(pwd)"
+
+ cp --reflink=auto -vr "${schematic}/share/npry/clef/schematic" ./
+ cp --reflink=auto -vr "${svg}/share/npry/clef/svg" ./
+ cp --reflink=auto -vr "${fabrication}/share/npry/clef/fab" ./
+ cp --reflink=auto -vr "${model}/share/npry/clef/model" ./
+
+ ${if panelSrc != null then ''
+ cp --reflink=auto -vr "${panelSrc}/share/npry/clef/panel" ./
+ '' else ""}
+ '';
+
+in pkg.overrideAttrs (prevAttrs: {
+ passthru = (prevAttrs.passthru or {}) // {
+ inherit schematic fabrication svg model panel panelSrc;
+ fab = fabrication;
+ };
+})
diff --git a/clef/nix/default.nix b/clef/nix/default.nix
new file mode 100644
index 0000000..4438bbe
--- /dev/null
+++ b/clef/nix/default.nix
@@ -0,0 +1,58 @@
+{
+ pkgs,
+
+ src,
+ main_pcb,
+ main_sch,
+
+ name,
+ outPath ? "share/npry/clef",
+ layers ? 2,
+
+ # set to a list of arguments to be passed to `kikit panelize -p`
+ panelizeConfigs ? null,
+}: let
+ pcb_path = main_pcb;
+ sch_path = main_sch;
+
+ pcb_args = {
+ inherit pcb_path src;
+ };
+
+ panelSrc = if panelizeConfigs != null then pkgs.callPackage ./panel.nix (pcb_args // { inherit panelizeConfigs; }) else null;
+
+ panel = if panelizeConfigs != null then
+ pkgs.callPackage ./. {
+ src = "${panelSrc}/share/npry/clef/panel";
+ main_pcb = "panel.kicad_pcb";
+
+ name = "${name}.sub.panel";
+ outPath = "${outPath}/panel";
+
+ inherit main_sch layers;
+ }
+ else null;
+
+ svg = pkgs.callPackage ./svg.nix (pcb_args // { nLayer = layers; });
+ model = pkgs.callPackage ./model.nix pcb_args;
+
+ fabrication = pkgs.callPackage ./fabrication.nix (pcb_args // {
+ inherit sch_path;
+ });
+
+ schematic = pkgs.callPackage ./schematic.nix {
+ inherit sch_path src;
+ };
+
+in pkgs.callPackage ./board.nix {
+ inherit
+ svg
+ model
+ fabrication
+ schematic
+ name
+ outPath
+ panel
+ panelSrc
+ ;
+}
diff --git a/clef/nix/fabrication.nix b/clef/nix/fabrication.nix
new file mode 100644
index 0000000..18bc6f6
--- /dev/null
+++ b/clef/nix/fabrication.nix
@@ -0,0 +1,82 @@
+{
+ kicad,
+ kikit,
+ zip,
+
+ runCommand,
+
+ nix-filter,
+ lib,
+
+ src,
+ pcb_path,
+ sch_path,
+
+ boardName ? (lib.removeSuffix ".kicad_pcb" (builtins.baseNameOf pcb_path)),
+
+ fabHouse ? "jlcpcb",
+ withAssembly ? true,
+}: let
+ sharePath = "share/npry/clef/fab";
+
+ fabSrc = nix-filter {
+ root = src;
+
+ include = [
+ (nix-filter.matchExt "kicad_sch")
+ (nix-filter.matchExt "kicad_wks")
+ (nix-filter.matchExt "kicad_pcb")
+ (nix-filter.matchExt "kicad_pro")
+
+ (_args: path: type: type == "directory")
+ ];
+ };
+
+in runCommand "${boardName}.fab" {
+ nativeBuildInputs = [
+ kicad
+ kikit
+ zip
+ ];
+
+ src = fabSrc;
+
+ allowedRequisites = [];
+} ''
+ set -e
+
+ export HOME=$(mktemp -d)
+
+ echo "board: '${boardName}'" >&2
+
+ mkdir -p "$out/${sharePath}"
+ cd "$src"
+
+ kikit fab ${fabHouse} \
+ ${if withAssembly then "--assembly" else ""} \
+ --no-drc \
+ --schematic "${sch_path}" \
+ --field 'LCSC Part #,LCSC Part No' \
+ --missingError \
+ "${pcb_path}" \
+ "$out/${sharePath}"
+
+ cd "$out/${sharePath}"
+ rm -vf "gerbers.zip"
+
+ pushd gerber
+
+ # normalize
+ for f in ${boardName}-*; do
+ mv -nv "$f" "''${f#${boardName}-}"
+ done
+
+ for f in ${boardName}.*; do
+ mv -nv "$f" "board''${f#${boardName}}"
+ done
+
+ popd
+
+ zip -vj "gerbers.zip" gerber/*
+ rm -vrf gerber
+''
diff --git a/clef/nix/model.nix b/clef/nix/model.nix
new file mode 100644
index 0000000..04bf1b1
--- /dev/null
+++ b/clef/nix/model.nix
@@ -0,0 +1,52 @@
+{
+ kicad,
+ runCommand,
+
+ nix-filter,
+ lib,
+
+ withTracks ? false,
+ withZones ? false,
+
+ src,
+ pcb_path,
+
+ boardName ? (lib.removeSuffix ".kicad_pcb" (builtins.baseNameOf pcb_path)),
+}: let
+ zonesArg = if withZones then "--include-zones" else "";
+ tracksArg = if withTracks then "--include-tracks" else "";
+ sharePath = "share/npry/clef/model";
+
+in runCommand "${boardName}.model" {
+ nativeBuildInputs = [
+ kicad
+ ];
+
+ src = nix-filter {
+ root = src;
+
+ include = [
+ (nix-filter.matchExt "kicad_pcb")
+
+ (nix-filter.matchExt "stp")
+ (nix-filter.matchExt "step")
+ (nix-filter.matchExt "wrl")
+ ];
+ };
+
+ allowedRequisites = [];
+
+ KICAD8_3DMODEL_DIR = "${kicad.libraries.packages3d}/share/kicad/3dmodels";
+} ''
+ set -e
+
+ export HOME=$(mktemp -d)
+
+ echo "board: '${boardName}'" >&2
+
+ mkdir -p $out/${sharePath}
+ cd "$out/${sharePath}"
+
+ kicad-cli pcb export step --subst-models --no-dnp ${tracksArg} ${zonesArg} "$src/${pcb_path}" -o "${boardName}.step"
+ kicad-cli pcb export glb --subst-models --no-dnp ${tracksArg} ${zonesArg} "$src/${pcb_path}" -o "${boardName}.glb"
+''
diff --git a/clef/nix/panel.nix b/clef/nix/panel.nix
new file mode 100644
index 0000000..1d88d46
--- /dev/null
+++ b/clef/nix/panel.nix
@@ -0,0 +1,46 @@
+{
+ kikit,
+ runCommand,
+
+ nix-filter,
+ lib,
+
+ panelizeConfigs,
+ src,
+ pcb_path,
+
+ boardName ? (lib.removeSuffix ".kicad_pcb" (builtins.baseNameOf pcb_path)),
+}: let
+ sharePath = "share/npry/clef/panel";
+
+ panelSrc = nix-filter {
+ root = src;
+
+ include = [
+ (nix-filter.matchExt "kicad_pcb")
+ ];
+ };
+
+in runCommand "${boardName}.panel" {
+ nativeBuildInputs = [
+ kikit
+ ];
+
+ allowedRequisites = [];
+} ''
+ set -e
+ export HOME=$(mktemp -d)
+
+ mkdir -p "$out/${sharePath}"
+
+ # do the panelize here rather than in $out because it creates junk we don't want
+ cd $HOME
+
+ kikit panelize \
+ ${lib.concatMapStrings (conf: "-p ${conf} \\\n") panelizeConfigs} \
+ "${panelSrc}/${pcb_path}" \
+ "panel.kicad_pcb"
+
+ cp -v panel.kicad_{pcb,pro} "$out/${sharePath}"
+ cp -v "${src}/"*.kicad_{sch,pro} "$out/${sharePath}"
+''
diff --git a/clef/nix/schematic.nix b/clef/nix/schematic.nix
new file mode 100644
index 0000000..acc136f
--- /dev/null
+++ b/clef/nix/schematic.nix
@@ -0,0 +1,51 @@
+{
+ kicad,
+ runCommand,
+
+ nix-filter,
+ lib,
+
+ src,
+ sch_path,
+
+ schName ? (lib.removeSuffix ".kicad_sch" (builtins.baseNameOf sch_path)),
+}: let
+ sharePath = "share/npry/clef/schematic";
+
+in runCommand "${schName}.schematic" {
+ nativeBuildInputs = [
+ kicad
+ ];
+
+ src = nix-filter {
+ root = src;
+
+ include = [
+ (nix-filter.matchExt "kicad_sch")
+ (nix-filter.matchExt "kicad_pro")
+ ];
+ };
+} ''
+ set -e
+ export HOME=$(mktemp -d)
+
+ echo "schematic: '${schName}'" >&2
+
+ mkdir -p "$out/${sharePath}/svg"
+ cd "$out/${sharePath}"
+
+ kicad-cli sch export pdf -o schematic.pdf $src/${sch_path}
+ kicad-cli sch export svg -n -o svg $src/${sch_path}
+
+ cd svg
+
+ # normalize
+ for f in *.svg; do
+ if [ "$f" = "${schName}.svg" ]; then
+ mv -nv "$f" "root.svg"
+ continue
+ fi
+
+ mv -nv "$f" "''${f#${schName}-}"
+ done
+''
diff --git a/clef/nix/svg.nix b/clef/nix/svg.nix
new file mode 100644
index 0000000..fc1a404
--- /dev/null
+++ b/clef/nix/svg.nix
@@ -0,0 +1,82 @@
+{
+ runCommand,
+ kicad,
+
+ nix-filter,
+ lib,
+
+ pcb_path,
+ src,
+
+ withSilk ? true,
+ withEdgeCuts ? true,
+ withMirrors ? true,
+ nLayer ? 2,
+
+ boardName ? (lib.removeSuffix ".kicad_pcb" (builtins.baseNameOf pcb_path)),
+}: let
+ sharePath = "share/npry/clef/svg";
+
+in runCommand "${boardName}.svg" {
+ nativeBuildInputs = [
+ kicad
+ ];
+
+ src = nix-filter {
+ root = src;
+
+ include = [
+ (nix-filter.matchExt "kicad_pcb")
+ ];
+ };
+
+ nInnerLayer = if nLayer < 2 then 0 else nLayer - 2;
+
+ allowedRequisites = [];
+} ''
+ set -e
+
+ export HOME=$(mktemp -d)
+
+ echo "board: '${boardName}'" >&2
+
+ mkdir -p $out/${sharePath}
+ cd "$out/${sharePath}"
+
+ mksvg() {
+ local infile=$1
+ local layers=$2
+ local outfile=$3
+
+ kicad-cli pcb export svg \
+ "$infile" \
+ -l "$layers" \
+ -o "$outfile.svg" \
+ --page-size-mode 2 \
+ --exclude-drawing-sheet
+
+ ${if withMirrors then ''
+ kicad-cli pcb export svg \
+ "$infile" \
+ -m \
+ -l "$layers" \
+ -o "$outfile.mirror.svg" \
+ --page-size-mode 2 \
+ --exclude-drawing-sheet
+ '' else ""}
+ }
+
+ mksvg "$src/${pcb_path}" \
+ "F.Cu,${if withSilk then "F.Silkscreen," else ""}${if withEdgeCuts then "Edge.Cuts," else ""}" \
+ front
+
+ mksvg "$src/${pcb_path}" \
+ "B.Cu,${if withSilk then "B.Silkscreen," else ""}${if withEdgeCuts then "Edge.Cuts," else ""}" \
+ back
+
+ for i in $(seq 1 $nInnerLayer); do
+ mksvg "$src/${pcb_path}" \
+ "In$i.Cu,${if withEdgeCuts then "Edge.Cuts," else ""}" \
+ "in$i"
+ done
+''
diff --git a/clef/overlays.nix b/clef/overlays.nix
new file mode 100644
index 0000000..7e3a00e
--- /dev/null
+++ b/clef/overlays.nix
@@ -0,0 +1,49 @@
+{
+ inputs,
+}: {
+ kicad = final: prev: {
+ opencascade-occt_7_6 = prev.opencascade-occt_7_6.overrideAttrs (finalAttrs: prevAttrs: {
+ buildInputs = (prevAttrs.buildInputs or []) ++ [
+ prev.rapidjson
+ ];
+
+ cmakeFlags = (prevAttrs.cmakeFlags or []) ++ [
+ "-DUSE_RAPIDJSON=1"
+ ];
+ });
+
+ kicad = prev.kicad.override {
+ with3d = true;
+ withI18n = false;
+
+ addons = with prev.kicadAddons; [
+ kikit
+ kikit-library
+ ];
+ };
+
+ kikit = let
+ version = "1.6.0";
+
+ in prev.kikit.overridePythonAttrs {
+ inherit version;
+
+ src = final.fetchFromGitHub {
+ owner = "yaqwsx";
+ repo = "KiKit";
+ rev = "refs/tags/v${version}";
+ hash = "sha256-r8LQcy3I6hmcrU/6HfPAYJd+cEZdhad6DUldC9HvXZU=";
+ };
+
+ doCheck = false;
+ };
+ };
+
+ cq = (final: prev: {
+ inherit (inputs.cq.packages.${prev.system}) cadquery;
+ });
+
+ nix-filter = (final: prev: {
+ nix-filter = inputs.nix-filter.lib;
+ });
+}