diff --git a/pkgs/applications/graphics/inkscape/extensions.nix b/pkgs/applications/graphics/inkscape/extensions.nix
index 48eb1f1f94b7..4499792cbbd0 100644
--- a/pkgs/applications/graphics/inkscape/extensions.nix
+++ b/pkgs/applications/graphics/inkscape/extensions.nix
@@ -44,6 +44,7 @@
mkdir -p $out/share/inkscape/extensions
cp ${inkcut}/share/inkscape/extensions/* $out/share/inkscape/extensions
'');
+ silhouette = callPackage ./extensions/silhouette { };
textext = callPackage ./extensions/textext {
pdflatex = texlive.combined.scheme-basic;
lualatex = texlive.combined.scheme-basic;
diff --git a/pkgs/applications/graphics/inkscape/extensions/silhouette/default.nix b/pkgs/applications/graphics/inkscape/extensions/silhouette/default.nix
new file mode 100644
index 000000000000..988b1ac3e9c8
--- /dev/null
+++ b/pkgs/applications/graphics/inkscape/extensions/silhouette/default.nix
@@ -0,0 +1,91 @@
+{ fetchFromGitHub
+, lib
+, gettext
+, pkgs
+, python3
+, umockdev
+, writeScript
+}:
+
+let
+ # We need these simple wrapper shell scripts because Inkscape extensions with
+ # interpreter="shell" always get invoked with the `sh` command [0], regardless of
+ # the shebang at the top of the script.
+ # [0]: https://gitlab.com/inkscape/inkscape/-/blob/d61d917afb94721c92a650b2c4b116b0a4826f41/src/extension/implementation/script.cpp#L93
+ launch-sendto_silhouette = writeScript "sendto_silhouette.sh" ''
+ cd $(dirname $0)
+ ./sendto_silhouette.py "$@"
+ '';
+ launch-silhouette_multi = writeScript "silhouette_multi.sh" ''
+ cd $(dirname $0)
+ ./silhouette_multi.py "$@"
+ '';
+in
+python3.pkgs.buildPythonApplication rec {
+ pname = "inkscape-silhouette";
+ version = "1.28";
+ format = "setuptools";
+
+ src = fetchFromGitHub {
+ owner = "fablabnbg";
+ repo = pname;
+ rev = "v${version}";
+ sha256 = "sha256-uNVhdkZFadL7QNlCsXq51TbhzRKH9KYDPDNCFhw3cQs=";
+ };
+
+ patches = [
+ ./interpreter.patch
+ ./use-prefix-for-udev.patch
+ ];
+
+ propagatedBuildInputs = [
+ python3.pkgs.pyusb
+ python3.pkgs.lxml
+ python3.pkgs.inkex
+ python3.pkgs.matplotlib
+ python3.pkgs.wxPython_4_2
+ python3.pkgs.xmltodict
+ ];
+
+ nativeBuildInputs = [
+ gettext # msgfmt
+ ];
+
+ nativeCheckInputs = [
+ python3.pkgs.pytestCheckHook
+ umockdev
+ ];
+
+ pytestFlagsArray = [
+ "test"
+ ];
+
+ doCheck = true;
+
+ installPhase = ''
+ runHook preInstall
+ make install PREFIX=$out
+ runHook postInstall
+ '';
+
+ postInstall = ''
+ # Unmark read_dump.py as executable so wrapPythonProgramsIn won't turn it
+ # into a shell script (thereby making it impossible to import as a Python
+ # module).
+ chmod -x $out/share/inkscape/extensions/silhouette/read_dump.py
+ cp ${launch-sendto_silhouette} $out/share/inkscape/extensions/sendto_silhouette.sh
+ cp ${launch-silhouette_multi} $out/share/inkscape/extensions/silhouette_multi.sh
+ '';
+
+ postFixup = ''
+ wrapPythonProgramsIn "$out/share/inkscape/extensions/" "$out $pythonPath"
+ '';
+
+ meta = with lib; {
+ description = "An extension to drive Silhouette vinyl cutters (e.g. Cameo, Portrait, Curio series) from within Inkscape.";
+ homepage = "https://github.com/fablabnbg/inkscape-silhouette";
+ license = licenses.gpl2Only;
+ maintainers = with maintainers; [ jfly ];
+ platforms = platforms.all;
+ };
+}
diff --git a/pkgs/applications/graphics/inkscape/extensions/silhouette/interpreter.patch b/pkgs/applications/graphics/inkscape/extensions/silhouette/interpreter.patch
new file mode 100644
index 000000000000..1b9f7c3ebaa4
--- /dev/null
+++ b/pkgs/applications/graphics/inkscape/extensions/silhouette/interpreter.patch
@@ -0,0 +1,24 @@
+diff --git a/sendto_silhouette.inx b/sendto_silhouette.inx
+index 55a3278..d780730 100644
+--- a/sendto_silhouette.inx
++++ b/sendto_silhouette.inx
+@@ -188,6 +188,6 @@ Always use the least amount of blade possible.
+
+
+
+
+diff --git a/silhouette_multi.inx b/silhouette_multi.inx
+index f6fd2ed..2d9dba6 100644
+--- a/silhouette_multi.inx
++++ b/silhouette_multi.inx
+@@ -31,6 +31,6 @@
+
+
+
+
diff --git a/pkgs/applications/graphics/inkscape/extensions/silhouette/use-prefix-for-udev.patch b/pkgs/applications/graphics/inkscape/extensions/silhouette/use-prefix-for-udev.patch
new file mode 100644
index 000000000000..3e2d1ecfe905
--- /dev/null
+++ b/pkgs/applications/graphics/inkscape/extensions/silhouette/use-prefix-for-udev.patch
@@ -0,0 +1,13 @@
+diff --git a/Makefile b/Makefile
+index 5aff25d..43c3fb0 100644
+--- a/Makefile
++++ b/Makefile
+@@ -22,7 +22,7 @@ VERS=$$(python3 ./sendto_silhouette.py --version)
+
+ DEST=$(DESTDIR)$(PREFIX)/share/inkscape/extensions
+ LOCALE=$(DESTDIR)$(PREFIX)/share/locale
+-UDEV=$(DESTDIR)/lib/udev
++UDEV=$(DESTDIR)$(PREFIX)/lib/udev
+ INKSCAPE_TEMPLATES=$(DESTDIR)$(PREFIX)/share/inkscape/templates
+
+ # User-specifc inkscape extensions folder for local install