├── test
├── __init__.py
├── data
│ ├── multi.cPickle
│ ├── composed_transform.test.svg
│ ├── lines_and_circle.test.svg
│ ├── plus_with_duplicate.svg
│ ├── detect_regmarks_corrupted.svg
│ ├── detect_regmarks.svg
│ ├── curved_dashes.test.svg
│ ├── testcut_square_triangle_o.svg
│ └── multi.commands
├── umockdev
│ ├── run.sh
│ ├── record.sh
│ ├── cameo3.output
│ └── cameo3.ioctl
├── test_umockdev.py
├── test_multi.py
├── test_render_silhouette_regmark.py
└── test_run.py
├── doc
├── GP-GL.pdf
├── HP-GL.pdf
├── more_commands.txt
├── tri_fwd.txt
├── tri_fwd+50mm.txt
└── tri_ret.txt
├── silhouette
├── __init__.py
├── Dialog.py
├── StrategyMinTraveling.py
├── convert2dashes.py
├── pyusb-1.0.2
│ └── usb
│ │ ├── _debug.py
│ │ ├── _lookup.py
│ │ ├── _interop.py
│ │ ├── __init__.py
│ │ ├── _objfinalizer.py
│ │ └── libloader.py
├── read_dump.py
├── ColorSeparation.py
└── beutil.py
├── misc
├── ttf
│ ├── Vera.ttf
│ ├── Channel.ttf
│ ├── FreeSans.ttf
│ ├── VeraMono.ttf
│ ├── RIKY2vamp.ttf
│ ├── motorhead.ttf
│ ├── LeckerliOne-Regular.ttf
│ └── WC Wunderbach Wimpern.ttf
├── sharp_turns.pdf
├── screenshot_20130517.png
├── dump
│ ├── boxed_triangle.dump
│ └── star_man.dump
├── silhouette_move.py
├── arrow_test.py
├── font_metrics_bug.py
├── silhouette_cut.py
└── MAT_FREE_CUTTING.txt
├── silhouette-icon.png
├── distribute
├── win
│ ├── zadig-2.4.exe
│ └── INSTALL.txt
├── rpm
│ └── dist.sh
├── deb
│ ├── description-pak
│ └── dist.sh
└── distribute.sh
├── examples
├── testcut_square_triangle.dump
├── testcut_square_triangle_o.cmd
├── dashline.svg
├── testcut_sq_tr.py
├── testcut_matfree.svg
├── testcut_square_triangle.svg
├── testcut_square_triangle_o.svg
├── default_us_letter_landscape_cameo_3.svg
├── registration-marks
│ └── inkscape-0.91
│ │ └── registration-marks-cameo-silhouette.svg
└── gecko_silhouette.svg
├── logs
├── silhouette_cameo3_pen.pcapng.gz
├── silhouette_cameo3_tool_2.pcapng.gz
├── silhouette_cameo3_ratchet_blade.pcapng.gz
├── silhouette_cameo3_autoblade_depth5.pcapng.gz
├── silhouette_cameo3_default_settings.pcapng.gz
├── silhouette_cameo3_no_cutting_mat.pcapng.gz
├── silhouette_cameo3_track_enhancing.pcapng.gz
├── silhouette_cameo3_12x24_cutting_mat.pcapng.gz
├── silhouette_cameo3_line_segment_overcut.pcapng.gz
├── silhouette_cameo3_max_speed_max_force.pcapng.gz
└── silhouette_cameo3_line_segment_overcut_0.9mm.pcapng.gz
├── assets
├── screenshot_of_send_to_silhouette.png
├── screenshot_of_the_regmarked_document.png
└── screenshot_of_registration_mark_settings_page.png
├── .gitignore
├── .github
├── dependabot.yml
└── workflows
│ ├── release.yml
│ └── run-tests.yml
├── requirements.txt
├── po
├── README.md
└── its
│ └── inx.its
├── install_osx.sh
├── CONTRIBUTING.md
├── render_silhouette_regmarks.inx
├── setup.py
├── silhouette_multi.inx
├── silhouette-udev-notify.sh
├── silhouette-udev.rules
├── Makefile
├── install_osx_stage2.py
├── USERGUIDE.md
├── HISTORY.txt
└── render_silhouette_regmarks.py
/test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/doc/GP-GL.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/doc/GP-GL.pdf
--------------------------------------------------------------------------------
/doc/HP-GL.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/doc/HP-GL.pdf
--------------------------------------------------------------------------------
/silhouette/__init__.py:
--------------------------------------------------------------------------------
1 | # __init__ is needed for import statements across subdirectories.
2 |
--------------------------------------------------------------------------------
/misc/ttf/Vera.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/misc/ttf/Vera.ttf
--------------------------------------------------------------------------------
/silhouette-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/silhouette-icon.png
--------------------------------------------------------------------------------
/misc/sharp_turns.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/misc/sharp_turns.pdf
--------------------------------------------------------------------------------
/misc/ttf/Channel.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/misc/ttf/Channel.ttf
--------------------------------------------------------------------------------
/misc/ttf/FreeSans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/misc/ttf/FreeSans.ttf
--------------------------------------------------------------------------------
/misc/ttf/VeraMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/misc/ttf/VeraMono.ttf
--------------------------------------------------------------------------------
/misc/ttf/RIKY2vamp.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/misc/ttf/RIKY2vamp.ttf
--------------------------------------------------------------------------------
/misc/ttf/motorhead.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/misc/ttf/motorhead.ttf
--------------------------------------------------------------------------------
/test/data/multi.cPickle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/test/data/multi.cPickle
--------------------------------------------------------------------------------
/distribute/win/zadig-2.4.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/distribute/win/zadig-2.4.exe
--------------------------------------------------------------------------------
/examples/testcut_square_triangle.dump:
--------------------------------------------------------------------------------
1 | [[(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)], [(2, 1), (9, 5), (2, 9), (2, 1)]]
2 |
--------------------------------------------------------------------------------
/misc/screenshot_20130517.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/misc/screenshot_20130517.png
--------------------------------------------------------------------------------
/misc/ttf/LeckerliOne-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/misc/ttf/LeckerliOne-Regular.ttf
--------------------------------------------------------------------------------
/misc/ttf/WC Wunderbach Wimpern.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/misc/ttf/WC Wunderbach Wimpern.ttf
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_pen.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_pen.pcapng.gz
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_tool_2.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_tool_2.pcapng.gz
--------------------------------------------------------------------------------
/assets/screenshot_of_send_to_silhouette.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/assets/screenshot_of_send_to_silhouette.png
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_ratchet_blade.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_ratchet_blade.pcapng.gz
--------------------------------------------------------------------------------
/test/umockdev/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | umockdev-run --device=/tmp/cameo3.umockdev --ioctl=/dev/bus/usb/003/017=/tmp/cameo3.ioctl python sendto_silhouette.py
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.mo
3 | /distribute/deb/files/
4 | /distribute/out/
5 |
6 | # Test artifact locally
7 | silhouette.log
8 | testcut_square_triangle_o.cmd
9 |
--------------------------------------------------------------------------------
/assets/screenshot_of_the_regmarked_document.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/assets/screenshot_of_the_regmarked_document.png
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_autoblade_depth5.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_autoblade_depth5.pcapng.gz
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_default_settings.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_default_settings.pcapng.gz
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_no_cutting_mat.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_no_cutting_mat.pcapng.gz
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_track_enhancing.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_track_enhancing.pcapng.gz
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_12x24_cutting_mat.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_12x24_cutting_mat.pcapng.gz
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_line_segment_overcut.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_line_segment_overcut.pcapng.gz
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_max_speed_max_force.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_max_speed_max_force.pcapng.gz
--------------------------------------------------------------------------------
/test/umockdev/record.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #umockdev-record /dev/bus/usb/003/017
3 | umockdev-record --ioctl=/dev/bus/usb/003/017=/tmp/test.io python sendto_silhouette.py
4 |
--------------------------------------------------------------------------------
/assets/screenshot_of_registration_mark_settings_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/assets/screenshot_of_registration_mark_settings_page.png
--------------------------------------------------------------------------------
/logs/silhouette_cameo3_line_segment_overcut_0.9mm.pcapng.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabnbg/inkscape-silhouette/HEAD/logs/silhouette_cameo3_line_segment_overcut_0.9mm.pcapng.gz
--------------------------------------------------------------------------------
/misc/dump/boxed_triangle.dump:
--------------------------------------------------------------------------------
1 | # device version: 'None'
2 | # driver version: '1.13'
3 | [[(20., 17.), (70., 17.), (70., 67.), (20., 67.), (20., 17.)], [(60., 57.), (30., 57.), (45., 27.), (60., 57.)]]
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Maintain dependencies for GitHub Actions
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "daily"
8 |
--------------------------------------------------------------------------------
/distribute/rpm/dist.sh:
--------------------------------------------------------------------------------
1 | # silhouette-udev.rules uses Ubuntu paths.
2 | # patch them up for SUSE.
3 | #
4 | sed -i -e 's@"/lib/udev/@"/usr/lib/udev/@' files/silhouette-udev.rules
5 |
6 | # override UDEV to match SUSE:
7 | make install UDEV=/usr/lib/udev
8 |
9 |
--------------------------------------------------------------------------------
/distribute/deb/description-pak:
--------------------------------------------------------------------------------
1 | An inkscape extension to drive a Silhouette Cameo plotter.
2 |
3 | Requires python3-usb 1.0.0 -- if this causes errors, please try:
4 |
5 | sudo apt install python3-pip python3-setuptools
6 | sudo pip3 install --upgrade pyusb
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/testcut_square_triangle_o.cmd:
--------------------------------------------------------------------------------
1 | FW132!10FX10FC18FY1FN0TB50,0FE0,0\0,0Z6000,8000L0FE0,0FF0,0,0M10,0D0,0D0,188D187,188D187,0D0,0D0,10M24,46D19,38D19,38D67,38D66,47D68,58D74,67D83,73D94,75D105,73D114,67D120,58D122,47D120,38D169,38D94,169D19,38D19,38D29,38M187,0SO0
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy >= 1.21.5
2 | pyusb >= 1.2.1
3 | lxml >= 4.8.0
4 | xmltodict >= 0.13.0 # required for Silhouette Multiple Actions
5 | cssselect >= 1.1.0 # required for inkscape >= 1.2
6 | tinycss2 >= 1.1.1 # required for inkscape >= 1.4
7 | matplotlib >= 3.7.2 # required displaying cut preview
8 |
--------------------------------------------------------------------------------
/po/README.md:
--------------------------------------------------------------------------------
1 | # Contribute Translations
2 |
3 | To contribute translations follow these steps
4 |
5 | 1. Generate a PO file with your locale using the POT template
6 |
7 | i.e. for de use
8 | `cp po/inkscape-silhouette.pot po/de.po`
9 |
10 | 2. Translate to your locale
11 |
12 | i.e. use lokalize (KDE) or Gtranslator (GNOME)
13 | `lokalize po/de.po`
14 |
15 | 3. Contribute the translated PO
16 |
17 | i.e. make a Pull Request
18 |
--------------------------------------------------------------------------------
/test/umockdev/cameo3.output:
--------------------------------------------------------------------------------
1 | Silhouette Cameo3 found on usb bus=3 addr=17
2 | status=ready
3 | device version: 'CAMEO 3 V1.60 '
4 | 7 self.docHeight= 10.0
5 | 8 self.docWidth= 3200.0
6 | FG: 'CAMEO 3 V1.60 '
7 | TB71: ' 0, 0'
8 | FA: ' 0, 0'
9 | TC: ' 0, 0'
10 | Media=132, cap='blue', name='Print Paper Light Weight'
11 | toolholder: 1
12 | speed: 10
13 | pressure: 10
14 | Loaded media is expected left-aligned.
15 | mediabox: (0,0)-(304.8,2.64583)
16 | 0% (+100%) bbox: (0.0,0.0)-(0.0,0.0)mm, 0 points
17 | 0% (+100%) .
18 | status=ready
19 |
--------------------------------------------------------------------------------
/misc/silhouette_move.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python
2 | #
3 | # simple demo program to feed paper in the silhouette cameo.
4 | # (C) 2015 juewei@fabmail.org
5 | #
6 | # Requires: python-usb # from Factory
7 |
8 | import sys
9 |
10 | sys.path.extend(['..','.']) # make it callable from top or misc directory.
11 | from silhouette.Graphtec import SilhouetteCameo
12 |
13 | dev = SilhouetteCameo() # no dev.setup() needed here.
14 |
15 | feed_mm = 10
16 | if len(sys.argv) > 1:
17 | feed_mm = float(sys.argv[1])
18 |
19 | if not feed_mm:
20 | print("Usage: %s [PAPER_FORWARD_MM]" % sys.argv[0])
21 |
22 | dev.move_origin(feed_mm)
23 |
--------------------------------------------------------------------------------
/examples/dashline.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/distribute/win/INSTALL.txt:
--------------------------------------------------------------------------------
1 | 1. Install zadig (https://zadig.akeo.ie/)
2 | 2. Run zadig
3 | 3. Check "Options" > "List all devices"
4 | 4. Select "USB Printing Support" (DE:"USB-Druckerunterstützung") in main window dropdown
5 | 5. In selectbox select "libusb-win32"
6 | 6. Click "replace driver"; After installation close zadig
7 | # No more needed with 1.22: 7. Enter "pyusb-1.0.2" and copy folder "usb" to C:\Progams\Inkscape\lib\python2.7\site-packages"
8 | 8. Copy all files in "inkscape-silhouette-main" to C:\Programs\Inkscape\share\extensions
9 | 9. Run inkscape; The extension can be found under "Extensions" > "Export" > "Send to Silhouette"
10 |
--------------------------------------------------------------------------------
/test/test_umockdev.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import pytest
4 | import unittest
5 | import subprocess
6 | import sys
7 | import platform
8 |
9 | @pytest.mark.skipif(platform.system() != "Linux", reason="only runs on Linux")
10 | class TestUmockdev(unittest.TestCase):
11 |
12 | def test_run_cameo3(self):
13 | try:
14 | result = subprocess.check_output(["umockdev-run", "--device=test/umockdev/cameo3.umockdev", "--ioctl=/dev/bus/usb/003/017=test/umockdev/cameo3.ioctl", sys.executable, "sendto_silhouette.py"], stderr=subprocess.STDOUT)
15 | print(result.decode())
16 | except subprocess.CalledProcessError as e:
17 | print(e)
18 | print(e.output.decode())
19 | self.assertEqual(e.returncode, 0)
20 | assert False
21 |
--------------------------------------------------------------------------------
/install_osx.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | VENV="$HOME/.local/share/venvs/inkscape"
4 |
5 | INK_PY=$(\
6 | find /Applications/Inkscape.app \
7 | -type f \
8 | -path "*/Python.framework/Versions/*/bin/python3*" \
9 | -not -path "*/Python.framework/Versions/*/bin/python3*-*" \
10 | | sort -V | tail -n1 \
11 | )
12 |
13 | if [ -x "$INK_PY" ]; then
14 | echo "Using Inkscape Python: $INK_PY"
15 | else
16 | echo "Could not find Inkscape Python. Got: '$INK_PY'"
17 | echo "Bailing out."
18 | exit -1
19 | fi
20 |
21 | echo "Removing old venv (if any)..."
22 | rm -rf "$VENV"
23 | mkdir -p "$(dirname "$VENV")"
24 |
25 | echo "Creating new venv (inherits Inkscape's packages)..."
26 | "$INK_PY" -m venv --system-site-packages "$VENV"
27 |
28 | echo "Activating and chaining up to second stage install"
29 | source "$VENV/bin/activate"
30 | python -m ensurepip -U || true
31 | pip install --upgrade pip
32 |
33 | ./install_osx_stage2.py
34 |
--------------------------------------------------------------------------------
/distribute/deb/dist.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | # Make a debian/ubuntu distribution
3 |
4 | name=$1
5 | vers=$2
6 | url=http://github.com/fablabnbg/$name
7 | # versioned dependencies need \ escapes to survive checkinstall mangling.
8 | # requires="python3-usb\ \(\>=1.0.0\), bash"
9 |
10 | ## not even ubuntu 16.04 has python-usb 1.0, we requre any python-usb
11 | ## and check at runtime again.
12 | requires="python3-usb, bash"
13 |
14 | tmp=../out
15 |
16 | [ -d $tmp ] && rm -rf $tmp/*.deb
17 | mkdir -p $tmp
18 | cp *-pak files/
19 | cd files
20 | fakeroot checkinstall --fstrans --reset-uid --type debian \
21 | --install=no -y --pkgname $name --pkgversion $vers --arch all \
22 | --pkglicense LGPL --pkggroup other --pakdir ../$tmp --pkgsource $url \
23 | --pkgaltsource "http://fablab-nuernberg.de" \
24 | --maintainer "'Juergen Weigert (juewei@fabmail.org)'" \
25 | --requires "$requires" make install \
26 | -e PREFIX=/usr || { echo "fakeroot checkinstall error "; exit 1; }
27 |
28 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # inkscape-silhouette contributing
2 |
3 | This file outlines guidelines and instructions for contributors who wish to contribute to the project. It may include information on how to submit bug reports, suggest enhancements, or create pull requests.
4 |
5 | ## Developer Commands
6 |
7 | Use `make help` to find all makefile commands you can use as a developer
8 |
9 | - **clean**: Cleanup generated/compiled files and restore project back to nominal state
10 | - **dist**: Genearate OS specific packagings and install files (Windows, Linux Distros, etc...)
11 | - **help**: Show help for each of the Makefile recipes.
12 | - **install-local**: Use this with `make install-local` to install just in your user account
13 | - **install**: Install is used by dist or use this with this command `sudo make install` to install for all users
14 | - **mdhelp**: Render help for each of the Makefile recipes in a markdown friendly manner
15 | - **mo**: Compile transations for different human languages into binary .mo file for internationalisation and localisation purposes. (e.g. ./po/de.po)
16 | - **test**: run local test (Must have umockdev installed)
17 |
--------------------------------------------------------------------------------
/silhouette/Dialog.py:
--------------------------------------------------------------------------------
1 | class Dialog:
2 | def confirm(parent, question, caption = 'Silhouette Multiple Actions'):
3 | import wx
4 | from wx.lib.agw import genericmessagedialog as gmd
5 | dlg = wx.MessageDialog(parent, question, caption, wx.YES_NO | wx.ICON_QUESTION)
6 | result = dlg.ShowModal() == wx.ID_YES
7 | dlg.Destroy()
8 | return result
9 |
10 | def info(parent, message, extended = '',
11 | caption = 'Silhouette Multiple Actions',):
12 | import wx
13 | from wx.lib.agw import genericmessagedialog as gmd
14 | dlg = gmd.GenericMessageDialog(
15 | parent, message, caption, wrap=1000,
16 | agwStyle=wx.OK | wx.ICON_INFORMATION | gmd.GMD_USE_AQUABUTTONS)
17 | # You might wonder about the choice of "aquabuttons" above. It's the
18 | # only option that led to the buttons being visible on the system
19 | # this was first tested on.
20 | dlg.SetLayoutAdaptationMode(wx.DIALOG_ADAPTATION_MODE_ENABLED)
21 | if extended:
22 | dlg.SetExtendedMessage(extended)
23 | dlg.ShowModal()
24 | dlg.Destroy()
25 |
--------------------------------------------------------------------------------
/examples/testcut_sq_tr.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | #
3 | # This test cut differs from the test cut in Silhouette Studio:
4 | # as our triangle is not a simple triangle, but a more complex
5 | # Arrow head. The outer square is an ordinary 1cm x 1cm square.
6 | #
7 |
8 | import sys, os
9 | sys.path.append('/usr/share/inkscape/extensions')
10 | sys.path.append(os.path.expanduser('~/.config/inkscape/extensions/'))
11 | from silhouette.Graphtec import SilhouetteCameo
12 |
13 | __version__ = '0.1'
14 | __author__ = 'Juergen Weigert '
15 |
16 | k=0.2 # knive-center offset, (0.1 is not enough)
17 |
18 | test_sq_tr = \
19 | [
20 | [(4+k, 5+k), (2, 3), (2, 1), (3, 1), (9, 5), (3, 9), (2, 9), (2, 7), (4+k, 5-k)],
21 | [(0, 0), (0, 10), (10, 10), (10, 0), (0, 0-k)]
22 | ]
23 |
24 |
25 | def write_progress(done, total, msg):
26 | perc = 100.*done/total
27 | print("%d%% %s\r" % (perc, msg))
28 |
29 | dev = SilhouetteCameo(progress_cb=write_progress, dry_run=False)
30 | state = dev.status() # hint at loading paper, if not ready.
31 | print("status=%s" % (state))
32 | print("device version: '%s'" % dev.get_version())
33 |
34 | dev.setup(media=132, pen=False, pressure=1, speed=10)
35 | dev.plot(pathlist=test_sq_tr, offset=(30, 30))
36 |
--------------------------------------------------------------------------------
/test/test_multi.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import unittest
4 | import subprocess
5 | import sys
6 | from pathlib import Path
7 | import difflib
8 |
9 |
10 | class TestMulti(unittest.TestCase):
11 |
12 | def test_01multi_nogui(self):
13 | try:
14 | # The -Wignore flag to Python is for the sake of an
15 | # inkscape-internal use of a deprecated Python construct. When
16 | # we are no longer testing on the offending version of Inkscape
17 | # (1.2 as released), it can be removed.
18 | commands = subprocess.run([sys.executable, "-Wignore::DeprecationWarning", "silhouette_multi.py", "--block=true", "-d=true", "-g=false", "-p=test/data/multi.cPickle", "test/data/multi_color.svg"], check=True, capture_output=True).stderr.decode().replace("\r","")
19 | commandref = Path("./test/data/multi.commands").read_text()
20 | if (commandref != commands):
21 | diffs = difflib.context_diff(
22 | commandref.split(), commands.split())
23 | sys.stdout.writelines(diffs)
24 | self.assertEqual(commandref, commands)
25 | except subprocess.CalledProcessError as e:
26 | print(e.output.decode())
27 | print(e)
28 | self.assertEqual(e.returncode, 0)
29 | assert False
30 |
--------------------------------------------------------------------------------
/test/data/composed_transform.test.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
56 |
--------------------------------------------------------------------------------
/misc/arrow_test.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python
2 | #
3 | # simple demo program to drive the silhouette cameo.
4 | # (C) 2013 jw@suse.de
5 | # (C) 2015 juewei@fabmail.org
6 | #
7 | # Requires: python-usb # from Factory
8 |
9 | import time, sys, os
10 | sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/..') # make it callable from anywhere
11 | from silhouette.Graphtec import SilhouetteCameo
12 |
13 | # coordinates in mm, origin int top lefthand corner
14 | arrow1 = [ (0,5), (21,5), (18,0), (31,10), (18,20), (21,15), (0,15), (3,10), (0,5) ]
15 | arrow2 = [(x[0]+263, x[1]+0) for x in arrow1]
16 | arrow3 = [(x[0]+30, x[1]+0) for x in arrow1]
17 |
18 | dev = SilhouetteCameo()
19 | print("status=%s" % dev.wait_for_ready(20))
20 |
21 | tmp_fwd=85 # enough to show 20mm of the latest drawing on the far side of the device.
22 |
23 | dev.setup(media=113, pressure=0, trackenhancing=True)
24 |
25 | for i in range(8):
26 | bbox = dev.plot(pathlist=[ arrow1,[(0,tmp_fwd),(0,tmp_fwd)] ], mediaheight=180, offset=(60,0), bboxonly=False, end_paper_offset=-tmp_fwd+1)
27 | dev.wait_for_ready()
28 | print(i, "path done.")
29 | time.sleep(5)
30 | dev.send_command(bbox['trailer'])
31 | # something is still wrong after we finish a job. The next job does not draw anything.
32 | # we can workaround by sending a dummy job.
33 | bbox = dev.plot(pathlist=[ [(0,0),(0,0)] ], offset=(0,0))
34 | dev.wait_for_ready()
35 | print(bbox)
36 |
--------------------------------------------------------------------------------
/doc/more_commands.txt:
--------------------------------------------------------------------------------
1 | ```
2 | "FW%d\x03" % media # select media id [100..300]
3 | "!%d\x03" % speed # without ',n' in CAMEO 1
4 | "FX%d\x03" % pressure # without ',n' in CAMEO 1
5 | # pressure 19 or higher triggers track enhancing in CAMEO 1
6 | "FY0\x03" # ?enable track enhancing in CAMEO 1
7 | "FY1\x03" # ?disable track enhancing in CAMEO 1
8 | "FN0\x03TB50,1\x03" # landscape mode in CAMEO 1
9 | "FN0\x03TB50,0\x03" # portrait mode in CAMEO 1
10 | "FE0,0\x03" # Don't lift plotter head between paths.
11 | "D%d,%d" % (y,x) # draw, move with blade down.
12 | "TB50,0\x03" # Unknown. Seen with registration marks
13 | "TB99\x03" # Unknown
14 | "TB52,2\x03" # set type of regmarks: 0='Original,SD', 2='Cameo,Portrait'
15 | "TB51,400\x03" # length of regmarks
16 | "TB53,10\x03" # width of regmarks
17 | "TB55,1\x03" # Unknown
18 | "TB123,%i,%i,%i,%i\x03" % ... # automatic regmark test, height, width, top, left
19 | "TB23,%i,%i\x03" % ... # manual regmark, height, width
20 | "FO%d\x03" % (height-top)) # ? a feed command. Sometimes it is 5588
21 | ```
22 |
23 | There is some more guesswork in the comments near https://github.com/fablabnbg/inkscape-silhouette/blob/main/silhouette/Graphtec.py#L788
24 |
25 | https://github.com/fablabnbg/inkscape-silhouette/blob/main/silhouette/Graphtec.py#L788
26 |
--------------------------------------------------------------------------------
/render_silhouette_regmarks.inx:
--------------------------------------------------------------------------------
1 |
2 |
3 | Silhouette Regmarks
4 | com.github.fablabnbg.inkscape-silhouette.silhouette-regmarks
5 | render_silhouette_regmarks.py
6 | 10
7 | 10
8 |
9 |
10 |
11 |
12 |
13 | false
14 |
15 | all
16 |
17 |
18 |
19 |
20 |
23 |
24 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | from os import path
3 | import re
4 | #import glob
5 |
6 | from distutils.core import setup
7 | from sendto_silhouette import __author__, __version__
8 |
9 | # read the contents of your README file
10 | this_directory = path.abspath(path.dirname(__file__))
11 | with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
12 | long_description = f.read()
13 |
14 | m = re.match(r'(.*)\s+<(.*)>', __author__)
15 |
16 |
17 | setup(name='inkscape-silhouette',
18 | version=__version__,
19 | description='Inkscape extension for driving a silhouette cameo',
20 | author=m.groups()[0],
21 | author_email=m.groups()[1],
22 | url='https://github.com/jnweiger/inkscape-silhouette',
23 | scripts=['sendto_silhouette.py'],
24 | #scripts=filter(path.isfile,
25 | # ['sendto_silhouette.py',
26 | # 'sendto_silhouette.inx',
27 | # 'README.md'] +
28 | # glob.glob('silhouette-*') +
29 | # glob.glob('misc/*') +
30 | # glob.glob('misc/*/*')),
31 |
32 | packages=['silhouette'],
33 | license='GPL-2.0',
34 | classifiers=[
35 | 'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
36 | 'Environment :: Console',
37 | 'Development Status :: 5 - Production/Stable',
38 | 'Programming Language :: Python :: 3',
39 | ],
40 | long_description=long_description,
41 | long_description_content_type='text/markdown',
42 | )
43 |
--------------------------------------------------------------------------------
/silhouette_multi.inx:
--------------------------------------------------------------------------------
1 |
2 |
3 | Silhouette Multiple Actions
4 | com.github.fablabnbg.inskscape-silhouette.silhouette_multi
5 | org.inkscape.output.svg.inkscape
6 | com.github.fablabnbg.inkscape-silhouette.sendto_silhouette
7 | sendto_silhouette.py
8 | silhouette_multi.py
9 |
10 |
11 |
12 |
13 |
14 | false
15 | false
16 | false
17 | false
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | all
29 |
30 |
31 |
32 |
33 |
34 |
37 |
38 |
--------------------------------------------------------------------------------
/distribute/distribute.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | test -e /usr/bin/xpath || sudo apt-get install libxml-xpath-perl
3 | test -e /usr/bin/checkinstall || sudo apt-get install checkinstall
4 | #
5 | VERSION=$( python3 ../sendto_silhouette.py --version )
6 | INX_VERSION=$( xpath -q -e '//*[@name="about_version"]/text()' ../sendto_silhouette.inx | sed -e 's/^version //i' ) # grep Version ../*.inx
7 | echo "Source version is: \"$VERSION\""
8 | echo "INX version is: \"$INX_VERSION\""
9 | test "$VERSION" = "$INX_VERSION" || ( echo "Error: python source and .inx version differ" && exit 1 )
10 |
11 |
12 |
13 | name=inkscape-silhouette
14 | if [ -d $name ]
15 | then
16 | echo "Removing leftover files"
17 | rm -rf $name
18 | fi
19 | echo "Copying contents ..."
20 | mkdir $name
21 | cp ../README.md $name/README
22 | cp ../LICENSE* $name/
23 | cp -a ../silhouette $name/
24 | cp ../*silhouette*.py ../*.inx $name/
25 | cp -a ../locale $name/
26 | cp -a ../templates $name/
27 |
28 |
29 | echo "****************************************************************"
30 | echo "Build Windows Version (Y/n)?"
31 | read answer
32 | if [ "$answer" != "n" ]
33 | then
34 | mkdir -p out
35 | zip -r out/$name-winpackage_$VERSION.zip $name --exclude \*.pyc \*__pycache__\*
36 | zip -j out/$name-winpackage_$VERSION.zip win/*
37 | fi
38 |
39 |
40 | # add linux-specific content
41 | cp ../*.sh ../*.rules ../*.png ../Makefile $name/
42 |
43 |
44 | echo "****************************************************************"
45 | echo "Ubuntu Version: For Building you must have checkinstall and dpkg"
46 | echo "Build Ubuntu Version (Y/n)?"
47 | read answer
48 | if [ "$answer" != "n" ]
49 | then
50 | mkdir -p deb/files
51 | cp -a $name/* deb/files
52 | (cd deb && sh ./dist.sh $name $VERSION)
53 | fi
54 |
55 |
56 | echo "Built packages are in distribute/out :"
57 | ls -la out
58 | echo "Cleaning up..."
59 | rm -rf $name
60 | echo "done."
61 |
--------------------------------------------------------------------------------
/po/its/inx.its:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/misc/font_metrics_bug.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python
2 | #
3 | # expose a bug in font metrics handling.
4 | # (c) 2013, jw@suse.de
5 | #
6 | # Requires: zypper in python-goocanvas
7 | # https://bugzilla.novell.com/show_bug.cgi?id=820593
8 | # https://bugzilla.gnome.org/show_bug.cgi?id=700664
9 |
10 | import gtk
11 | from goocanvas import *
12 | import cairo
13 |
14 |
15 | def scale_up(win, ev):
16 | s = canvas.get_scale()
17 | if chr(ev.keyval) == '+': canvas.set_scale(s*1.2)
18 | elif chr(ev.keyval) == '-': canvas.set_scale(s*.8)
19 | else: gtk.main_quit()
20 | print(canvas.get_scale())
21 |
22 |
23 | def button_press(win, ev):
24 | win.click_x = ev.x
25 | win.click_y = ev.y
26 | print(win.click_x, win.click_y)
27 |
28 |
29 | def button_release(win, ev):
30 | win.click_x = None
31 | win.click_y = None
32 |
33 |
34 | def motion_notify(win, ev):
35 | try:
36 | # 3.79 is the right factor for units='mm'
37 | dx = (ev.x-win.click_x) / canvas.get_scale() / 3.79
38 | dy = (ev.y-win.click_y) / canvas.get_scale() / 3.79
39 | win.click_x = ev.x
40 | win.click_y = ev.y
41 | (x1,y1,x2,y2) = canvas.get_bounds()
42 | canvas.set_bounds(x1-dx,y1-dy,x2-dx,y2-dy)
43 | except:
44 | pass
45 |
46 |
47 | win = gtk.Window()
48 | win.connect("destroy", gtk.main_quit)
49 | win.connect("key-press-event", scale_up)
50 | win.connect("motion-notify-event", motion_notify)
51 | win.connect("button-press-event", button_press)
52 | win.connect("button-release-event", button_release)
53 |
54 | canvas = Canvas(units='mm', scale=2)
55 | canvas.set_size_request(1000, 400)
56 | root = canvas.get_root_item()
57 |
58 | Text(parent=root, x=0, y=0, text="TooT, Hifi VA World", font="6")
59 | Text(parent=root, x=0, y=8, text="TooT, Hifi VA World", font="sans 9")
60 | Text(parent=root, x=0, y=20, text="TooT, Hifi VA World", font="Arial 12")
61 | Text(parent=root, x=0, y=30, text="TooT, Hifi VA World", font="24")
62 |
63 | win.add(canvas)
64 | win.show_all()
65 |
66 | gtk.main()
67 |
--------------------------------------------------------------------------------
/silhouette-udev-notify.sh:
--------------------------------------------------------------------------------
1 | #! /bin/sh
2 | # silhouette-udev-notify.sh -- a helper triggered by silhouette.rules RUN=
3 | #
4 | # (c) 2013, jw@suse.de - All rights reserved. Distribute under GPL-2.0
5 | # (c) 2016, juewei@fabmail.org - using Ubuntu paths.
6 | #
7 | # Popup a notification and instruct users how to access the device.
8 | #
9 |
10 | # udev RUN= fires 3 times when plugging in, but only once we have an ID_SERIAL
11 | # udev PROGRAM= also fires 3 times, but without any uniq parameter.
12 | #
13 | # Use this script with RUN=only, or you end up with multiple notifications!
14 | #
15 | # TODO:
16 | # * Prevent systemd, udev-add-printer, udev-configure-printer
17 | # from trolling the device too. # Blacklist somewhere?
18 | # * Find out how we can find and notify a KDE user.
19 | # * Confirm, we always have an $ID_SERIAL once, and exactly once.
20 | # * Check if a user has permission to access the device.
21 | # Tell him if this is not the case.
22 |
23 | # logger "$0 [$$]: Hello Syslog xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
24 |
25 | test -z "$ID_SERIAL" && exit 0
26 |
27 | title="Silhouette CAMEO ($ACTION)"
28 | text="use via inkscape -> Extensions -> Export"
29 | test "$ACTION" = 'add' || text=
30 | timeout=10000 # Milliseconds
31 | icon=printer
32 | icon=/lib/udev/silhouette-icon.png # Ubuntu path
33 | test -f "$icon" || icon=/usr/$icon # SUSE path
34 | test -f "$icon" || icon=printer # any other stock values allowed?
35 |
36 | ## FIXME: how is this triggered for KDE?
37 | pids=$(pgrep 'gnome-panel|xfce4-panel')
38 | # logger "$0 [$$]: pids=$pids"
39 | for pid in $pids; do
40 | # find DBUS session bus for this session
41 | CRED=$(egrep -z 'USER|DBUS_SESSION_BUS_ADDRESS' /proc/$pid/environ | tr \\0 ' ')
42 | if [ "$CRED" != "" ]; then
43 | eval export $CRED
44 | su $USER -c "notify-send -i '$icon' '$title' '$text'"
45 | logger "$0 [$$]: notify-send via USER=$USER SESS=$DBUS_SESSION_BUS_ADDRESS"
46 | fi
47 | done
48 |
49 | exit 0
50 |
--------------------------------------------------------------------------------
/test/data/lines_and_circle.test.svg:
--------------------------------------------------------------------------------
1 |
2 |
51 |
--------------------------------------------------------------------------------
/test/data/plus_with_duplicate.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
60 |
--------------------------------------------------------------------------------
/misc/silhouette_cut.py:
--------------------------------------------------------------------------------
1 | #! /usr/bin/python
2 | #
3 | # simple demo program to cut with the silhouette cameo.
4 | # (C) 2015 juewei@fabmail.org
5 | #
6 | # Requires: python-usb # from Factory
7 |
8 | import re,time,sys,string,argparse
9 |
10 | sys.path.extend(['..','.']) # make it callable from top or misc directory.
11 | from silhouette.Graphtec import SilhouetteCameo
12 |
13 | ArgParser = argparse.ArgumentParser(description='Cut a dumpfile from sendto_silhouette without using inkscape.')
14 | ArgParser.add_argument('-P', '--pen', action='store_true', help="switch to pen mode. Default: knive mode")
15 | ArgParser.add_argument('-b', '--bbox', action='store_true', help="Bounding box only. Default: entire design")
16 | ArgParser.add_argument('-x', '--xoff', type=float, default=0.0, help="Horizontal offset [mm]. Positive values point rightward")
17 | ArgParser.add_argument('-y', '--yoff', type=float, default=0.0, help="Vertical offset [mm]. Positive values point downward")
18 | #ArgParser.add_argument('-S', '--scale',type=float, default=1.0, help="Scale the design.")
19 | ArgParser.add_argument('-p', '--pressure', type=int, default=3, help="Pressure value [1..18]")
20 | ArgParser.add_argument('-s', '--speed', type=int, default=10, help="Speed value [1..10]")
21 | ArgParser.add_argument('-W', '--width', type=float, default=210.0, help="Media width [mm].")
22 | ArgParser.add_argument('-H', '--height', type=float, default=297.0, help="Media height [mm].")
23 | ArgParser.add_argument('dumpfile')
24 | args = ArgParser.parse_args()
25 |
26 | dev = SilhouetteCameo()
27 | dev.setup(speed=args.speed, pressure=args.pressure, pen=args.pen)
28 |
29 | # print args
30 |
31 | dumpdata=dev.load_dumpfile(args.dumpfile)
32 |
33 | dev.wait_for_ready()
34 | meta = dev.plot(pathlist=[], bboxonly=args.bbox,
35 | mediawidth=args.width, mediaheight=args.height, offset=(args.xoff,args.yoff))
36 | print(meta)
37 |
38 | dev.wait_for_ready()
39 | cmd_list = dev.plot_cmds(dumpdata, meta['bbox'], args.xoff, args.yoff)
40 | dev.send_command(cmd_list)
41 | dev.send_command(meta['trailer'])
42 | dev.wait_for_ready()
43 |
--------------------------------------------------------------------------------
/test/data/detect_regmarks_corrupted.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
69 |
--------------------------------------------------------------------------------
/doc/tri_fwd.txt:
--------------------------------------------------------------------------------
1 | 00000000 1B 05 ..
2 | 00000000 30 03 0.
3 |
4 | 00000000 46 4E 30 03 54 42 35 30 2C 30 03 5C 33 30 2C 33
5 | FN0
6 | TB50,0
7 | \30,30
8 | Z6066.00,6066.00
9 | 00000010 30 03 5A 36 30 36 36 2E 30 30 2C 36 30 36 36 2E
10 | 00000020 30 30 03
11 |
12 | 00000000 46 58 31 03 21 38 03 46 43 31 38 03 46 45 30 2C
13 | FX1
14 | !8
15 | FC18
16 | FE0,0
17 | FF0,0,0
18 | 00000010 30 03 46 46 30 2C 30 2C 30 03
19 |
20 | 00000000 4D 31 37 35 2E 32 34 2C 35 37 37 2E 30 38 03 44
21 | M175.24,577.08
22 | D157.96,587.14
23 | D678.70,884.62
24 | D675.96,284.92
25 | D157.96,587.14
26 | D175.32,597.06
27 | 00000010 31 35 37 2E 39 36 2C 35 38 37 2E 31 34 03 44 36
28 | 00000020 37 38 2E 37 30 2C 38 38 34 2E 36 32 03 44 36 37
29 | 00000030 35 2E 39 36 2C 32 38 34 2E 39 32 03 44 31 35 37
30 | 00000040 2E 39 36 2C 35 38 37 2E 31 34 03 44 31 37 35 2E
31 | 00000050 33 32 2C 35 39 37 2E 30 36 03
32 |
33 | 00000000 1B 05 ..
34 | 00000000 31 03 1.
35 |
36 | 00000000 1B 05 ..
37 | 00000000 30 03 0.
38 |
39 | 00000000 46 58 35 03 21 31 30 03 46 43 31 38 03 46 45 30
40 | FX5
41 | !10
42 | FC18
43 | FE0,0
44 | FF0,0,0
45 | L0
46 | \0,0
47 | M678.70,30
48 | SO0
49 | FN0
50 | TB50,0
51 | 00000010 2C 30 03 46 46 30 2C 30 2C 30 03 4C 30 03 5C 30
52 | 00000020 2C 30 03 4D 36 37 38 2E 37 30 2C 33 30 03 53 4F
53 | 00000030 30 03 46 4E 30 03 54 42 35 30 2C 30 03
54 |
55 | 00000000 1B 05 ..
56 | 00000000 31 03 1.
57 | 00000000 1B 05 ..
58 | 00000000 31 03 1.
59 | 00000000 1B 05 ..
60 | 00000000 30 03 0.
61 |
--------------------------------------------------------------------------------
/silhouette/StrategyMinTraveling.py:
--------------------------------------------------------------------------------
1 | # (c) 2017 Johann Gail
2 | #
3 | # StrategyMinTraveling.py -- cut strategy algorithms for a Graphtec Silhouette Cameo plotter
4 | #
5 | # Strategy is:
6 | # At each end of a cut search the nearest starting point for the next cut.
7 | # This will probably not find find the global optimum, but works well enough.
8 |
9 |
10 | # Calculates the distance between two given points.
11 | # The result does not calculate the root for performance reasons,
12 | # because it is only compared to others, the actual value does not matter.
13 | def dist_sq(a,b):
14 | dx = a[0]-b[0]
15 | dy = a[1]-b[1]
16 | return dx*dx+dy*dy
17 |
18 |
19 | # Finds the nearest path in a list from a given position
20 | def findnearestpath(paths, pos, entrycircular, reversible):
21 | nearestindex=0
22 | nearestdist=float("inf")
23 | for index,path in enumerate(paths):
24 | distance = dist_sq(pos,path[0]) # original direction
25 | if (nearestdist > distance):
26 | nearestdist = distance
27 | nearestindex = index
28 | selected = path
29 | if reversible:
30 | distance = dist_sq(pos,path[-1]) # reverse direction
31 | if (nearestdist > distance):
32 | nearestdist = distance
33 | nearestindex = index
34 | selected = path[::-1]
35 | if (entrycircular & (path[0] == path[-1])): # break up circular path. Not sure, if this saves real much time
36 | for i,p in enumerate(path): # test each point in closed path
37 | distance = dist_sq(pos,p)
38 | if (nearestdist > distance):
39 | nearestdist = distance
40 | nearestindex = index
41 | selected = path[i:] + path[1:i+1]
42 | return nearestindex,selected
43 |
44 |
45 | # Sort paths to approximate minimal traveling times
46 | # (greedy algorithm not necessarily optimal)
47 | def sort(paths, entrycircular=False, reversible=True):
48 | pos=(0,0)
49 | sortedpaths=[]
50 | while (len(paths) > 0):
51 | i,path = findnearestpath(paths,pos,entrycircular,reversible)
52 | paths.pop(i) # delete found index
53 | pos = path[-1] # endpoint is next start point for search
54 | sortedpaths.append(path) # append to output list
55 | return sortedpaths
56 |
--------------------------------------------------------------------------------
/doc/tri_fwd+50mm.txt:
--------------------------------------------------------------------------------
1 | 00000000 1B 05 ..
2 | 00000000 30 03 0.
3 |
4 | 00000000 46 4E 30 03 54 42 35 30 2C 30 03 5C 33 30 2C 33 FN0.TB50,0.\30,3
5 | 00000010 30 03 5A 36 30 36 36 2E 30 30 2C 36 30 36 36 2E 0.Z6066.00,6066.
6 | 00000020 30 30 03 00.
7 |
8 | 00000000 46 58 31 03 21 38 03 46 43 31 38 03 46 45 30 2C FX1.!8.FC18.FE0,
9 | 00000010 30 03 46 46 30 2C 30 2C 30 03 0.FF0,0,0.
10 |
11 | 00000000 4D 31 37 35 2E 32 34 2C 35 37 37 2E 30 38 03 44 M175.24,577.08.D
12 | 00000010 31 35 37 2E 39 36 2C 35 38 37 2E 31 34 03 44 36 157.96,587.14.D6
13 | 00000020 37 38 2E 37 30 2C 38 38 34 2E 36 32 03 44 36 37 78.70,884.62.D67
14 | 00000030 35 2E 39 36 2C 32 38 34 2E 39 32 03 44 31 35 37 5.96,284.92.D157
15 | 00000040 2E 39 36 2C 35 38 37 2E 31 34 03 44 31 37 35 2E .96,587.14.D175.
16 | 00000050 33 32 2C 35 39 37 2E 30 36 03 32,597.06.
17 |
18 | 00000000 1B 05 ..
19 | 00000000 31 03 1.
20 | 00000000 1B 05 ..
21 | 00000000 31 03 1.
22 |
23 | 00000000 1B 05 ..
24 | 00000000 30 03 0.
25 |
26 | 00000000 46 58 35 03 21 31 30 03 46 43 31 38 03 46 45 30 FX5.!10.FC18.FE0
27 | 00000010 2C 30 03 46 46 30 2C 30 2C 30 03 4C 30 03 5C 30 ,0.FF0,0,0.L0.\0
28 | 00000020 2C 30 03 4D 31 36 37 38 2E 37 30 2C 33 30 03 53 ,0.M1678.70,30.S
29 | 00000030 4F 30 03 46 4E 30 03 54 42 35 30 2C 30 03 O0.FN0.TB50,0.
30 |
31 | 00000000 1B 05 ..
32 | 00000000 31 03 1.
33 |
34 | 00000000 1B 05 ..
35 | 00000000 30 03 0.
36 | 00000000 1B 05 ..
37 | 00000000 30 03 0.
38 |
--------------------------------------------------------------------------------
/test/data/detect_regmarks.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
73 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | release:
10 | name: Create Release Package
11 | runs-on: ubuntu-20.04
12 |
13 | steps:
14 | - uses: actions/checkout@v6
15 | - name: 🐧 Install Inkscape
16 | run: |
17 | sudo add-apt-repository ppa:inkscape.dev/stable -y # inkscape 1.2 from https://launchpad.net/~inkscape.dev/+archive/ubuntu/stable
18 | sudo apt-get update
19 | sudo apt-get -y install inkscape
20 | sudo apt-get -y install gettext # translation with msgfmt
21 | sudo apt-get -y install checkinstall libxml-xpath-perl # xpath
22 | inkscape --version
23 | python -m pip install --upgrade pip
24 | pip install -r requirements.txt
25 | - name: Branch name
26 | id: branch_name
27 | run: |
28 | echo TAG_VERSION=$( echo ${GITHUB_REF#refs/tags/} | sed -e 's/^v//' ) >> $GITHUB_OUTPUT
29 | echo SOURCE_VERSION=$( python ./sendto_silhouette.py --version ) >> $GITHUB_OUTPUT
30 | echo INX_VERSION=$( xpath -q -e '//*[@name="about_version"]/text()' sendto_silhouette.inx | sed -e 's/^version //i' ) >> $GITHUB_OUTPUT
31 | - name: Version Check
32 | env:
33 | TAG_VERSION: ${{ steps.branch_name.outputs.TAG_VERSION }}
34 | SOURCE_VERSION: ${{ steps.branch_name.outputs.SOURCE_VERSION }}
35 | INX_VERSION: ${{ steps.branch_name.outputs.INX_VERSION }}
36 | run: |
37 | echo "::notice::tag version: $TAG_VERSION"
38 | echo "::notice::source version: $SOURCE_VERSION"
39 | echo "::notice::inx version: $INX_VERSION"
40 | [ "$SOURCE_VERSION" == "$INX_VERSION" ] || ( echo "::error::Error: source version and .inx version differ" && exit 1 )
41 | [ "$SOURCE_VERSION" == "$TAG_VERSION" ] || ( echo "::error::Error: source version and git tag differ" && exit 1 )
42 | - name: Build Release Package
43 | run: |
44 | make dist
45 | # - name: Upload .deb Package
46 | # uses: actions/upload-artifact@v3
47 | # with:
48 | # name: inkscape-silhouette_${{ steps.branch_name.outputs.SOURCE_SOURCE_VERSION }}-1_all.deb
49 | # path: distribute/out/inkscape-silhouette_${{ steps.branch_name.outputs.SOURCE_SOURCE_VERSION }}-1_all.deb
50 | - name: Release
51 | uses: softprops/action-gh-release@v2
52 | if: startsWith(github.ref, 'refs/tags/')
53 | with:
54 | draft: true
55 | files: |
56 | distribute/out/*
57 |
--------------------------------------------------------------------------------
/test/test_render_silhouette_regmark.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | from render_silhouette_regmarks import InsertRegmark
4 | from inkex import BoundingBox
5 | from inkex.tester import TestCase
6 | from pytest import mark
7 |
8 | try:
9 | from inkex import __version__ as __inkex_version__
10 | except:
11 | __inkex_version__ = "1.1.0_or_smaller_needs_this_hotfix"
12 |
13 | REGMARK_LAYERNAME = 'Regmarks'
14 | REGMARK_LAYER_ID = 'regmark'
15 | REGMARK_TOP_LEFT_ID = 'regmark-tl'
16 | REGMARK_TOP_RIGHT_ID = 'regmark-tr'
17 | REGMARK_BOTTOM_LEFT_ID = 'regmark-bl'
18 | REGMARK_SAFE_AREA_ID = 'regmark-safe-area'
19 | REGMARK_NOTES_ID = 'regmark-notes'
20 |
21 | class InsertRegmarkTest(TestCase):
22 | """Tests for Inkscape Extensions"""
23 |
24 | effect_class = InsertRegmark
25 |
26 | def setUp(self):
27 | self.e = self.effect_class()
28 |
29 | #@mark.xfail(__inkex_version__[0:3] < "1.2", reason="inkex < 1.2 is not supported")
30 |
31 | class RegmarkTest(InsertRegmarkTest):
32 | source_file = "plus_with_duplicate.svg"
33 |
34 | def test_regmarks(self):
35 | self.e.parse_arguments([self.data_file(self.source_file), "--reglength=300"])
36 | self.e.load_raw()
37 | self.e.effect()
38 | self.e.clean_up()
39 |
40 | """Ensure top-left regmark"""
41 | self.assertEqual(
42 | self.e.svg.getElementById(REGMARK_TOP_LEFT_ID).tostring(),
43 | b''
44 | )
45 |
46 | """Ensure top-right regmark"""
47 | self.assertEqual(
48 | self.e.svg.getElementById(REGMARK_TOP_RIGHT_ID).bounding_box(),
49 | BoundingBox((390.0, 410.0),(10.0, 30.0))
50 | )
51 |
52 | """Ensure x distance"""
53 | self.assertEqual(
54 | self.e.svg.unit_to_viewport(
55 | (self.e.svg.getElementById(REGMARK_TOP_RIGHT_ID).bounding_box(transform=True).x.maximum
56 | - self.e.svg.getElementById(REGMARK_BOTTOM_LEFT_ID).bounding_box(transform=True).x.minimum),
57 | "mm"),
58 | 400
59 | )
60 |
61 | """Ensure y distance"""
62 | self.assertEqual(
63 | self.e.svg.unit_to_viewport(
64 | (self.e.svg.getElementById(REGMARK_BOTTOM_LEFT_ID).bounding_box(transform=True).y.maximum
65 | - self.e.svg.getElementById(REGMARK_TOP_RIGHT_ID).bounding_box(transform=True).y.minimum),
66 | "mm"),
67 | 300
68 | )
69 |
--------------------------------------------------------------------------------
/test/umockdev/cameo3.ioctl:
--------------------------------------------------------------------------------
1 | @DEV /dev/bus/usb/003/017
2 | USBDEVFS_GET_CAPABILITIES 0 FD010000
3 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
4 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 2 2 0 1B05
5 | USBDEVFS_REAPURBNDELAY 0 3 130 0 0 64 2 0 3003
6 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
7 | USBDEVFS_REAPURBNDELAY 0 3 130 0 0 64 2 0 3003
8 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
9 | USBDEVFS_REAPURBNDELAY 0 3 130 0 0 64 2 0 3003
10 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 3 3 0 464703
11 | USBDEVFS_REAPURBNDELAY 0 3 130 0 0 64 18 0 43414D454F20332056312E36302020202003
12 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
13 | USBDEVFS_REAPURBNDELAY 0 3 130 0 0 64 18 0 43414D454F20332056312E36302020202003
14 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
15 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 2 2 0 1B04
16 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
17 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 5 5 0 5442373103
18 | USBDEVFS_REAPURBNDELAY 0 3 130 0 0 64 12 0 20202020302C202020203003
19 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
20 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 3 3 0 464103
21 | USBDEVFS_REAPURBNDELAY 0 3 130 0 0 64 12 0 20202020302C202020203003
22 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
23 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 3 3 0 544303
24 | USBDEVFS_REAPURBNDELAY 0 3 130 0 0 64 12 0 20202020302C202020203003
25 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
26 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 4 4 0 54473103
27 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
28 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 11 11 0 464E3003544235302C3003
29 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
30 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 16 16 0 5C302C30035A363039362C3630393603
31 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
32 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 3 3 0 4A3103
33 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
34 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 6 6 0 2131302C3103
35 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
36 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 7 7 0 465831302C3103
37 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
38 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 6 6 0 4645302C3103
39 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
40 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 16 16 0 4646312C302C31034646312C312C3103
41 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
42 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 17 17 0 4643302C312C3103464331382C312C3103
43 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
44 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 1 1 0 03
45 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
46 | USBDEVFS_REAPURBNDELAY 0 3 1 0 0 9 9 0 4D302C3003534F3003
47 | USBDEVFS_REAPURBNDELAY 0 3 130 -2 0 64 0 0
48 |
--------------------------------------------------------------------------------
/doc/tri_ret.txt:
--------------------------------------------------------------------------------
1 | 00000000 1B 05 ..
2 | 00000000 30 03 0.
3 | 00000000 1B 05 ..
4 | 00000000 30 03 0.
5 | 00000000 46 4E 30 03 54 42 35 30 2C 30 03 5C 33 30 2C 33 FN0.TB50,0.\30,3
6 | 00000010 30 03 5A 36 30 36 36 2E 30 30 2C 36 30 36 36 2E 0.Z6066.00,6066.
7 | 00000020 30 30 03 00.
8 |
9 | 00000000 46 58 31 03 21 38 03 46 43 31 38 03 46 45 30 2C FX1.!8.FC18.FE0,
10 | 00000010 30 03 46 46 30 2C 30 2C 30 03 0.FF0,0,0.
11 |
12 | 00000000 4D 31 37 35 2E 32 34 2C 35 37 37 2E 30 38 03 44 M175.24,577.08.D
13 | 00000010 31 35 37 2E 39 36 2C 35 38 37 2E 31 34 03 44 36 157.96,587.14.D6
14 | 00000020 37 38 2E 37 30 2C 38 38 34 2E 36 32 03 44 36 37 78.70,884.62.D67
15 | 00000030 35 2E 39 36 2C 32 38 34 2E 39 32 03 44 31 35 37 5.96,284.92.D157
16 | 00000040 2E 39 36 2C 35 38 37 2E 31 34 03 44 31 37 35 2E .96,587.14.D175.
17 | 00000050 33 32 2C 35 39 37 2E 30 36 03 32,597.06.
18 |
19 | 00000000 1B 05 ..
20 | 00000000 31 03 1.
21 | 00000000 1B 05 ..
22 | 00000000 31 03 1.
23 | 00000000 1B 05 ..
24 | 00000000 31 03 1.
25 | 00000000 1B 05 ..
26 | 00000000 30 03 0.
27 | 00000000 1B 05 ..
28 | 00000000 30 03 0.
29 |
30 | 00000000 46 58 35 03 21 31 30 03 46 43 31 38 03 46 45 30 FX5.!10.FC18.FE0
31 | 00000010 2C 30 03 46 46 30 2C 30 2C 30 03 4C 30 03 5C 30 ,0.FF0,0,0.L0.\0
32 | 00000020 2C 30 03 4D 30 2C 30 03 46 4E 30 03 54 42 35 30 ,0.M0,0.FN0.TB50
33 | 00000030 2C 30 03 ,0.
34 |
35 | 00000000 1B 05 ..
36 | 00000000 31 03 1.
37 | 00000000 1B 05 ..
38 | 00000000 30 03 0.
39 | 00000000 1B 05 ..
40 | 00000000 30 03 0.
41 |
--------------------------------------------------------------------------------
/test/data/curved_dashes.test.svg:
--------------------------------------------------------------------------------
1 |
2 |
74 |
--------------------------------------------------------------------------------
/silhouette-udev.rules:
--------------------------------------------------------------------------------
1 | ATTRS{idVendor}=="3844", ATTRS{idProduct}=="0001", MODE="666", ENV{silhouette_cameo5alpha}="yes", GOTO="end"
2 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="1140", MODE="666", ENV{silhouette_cameo5}="yes", GOTO="end"
3 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="1139", MODE="666", ENV{silhouette_cameo4pro}="yes"
4 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="1138", MODE="666", ENV{silhouette_cameo4plus}="yes"
5 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="1137", MODE="666", ENV{silhouette_cameo4}="yes"
6 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="112f", MODE="666", ENV{silhouette_cameo3}="yes"
7 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="112b", MODE="666", ENV{silhouette_cameo2}="yes"
8 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="1121", MODE="666", ENV{silhouette_cameo}="yes"
9 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="1123", MODE="666", ENV{silhouette_portrait}="yes"
10 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="1132", MODE="666", ENV{silhouette_portrait2}="yes"
11 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="113a", MODE="666", ENV{silhouette_portrait3}="yes"
12 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="113f", MODE="666", ENV{silhouette_portrait4}="yes"
13 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="111d", MODE="666", ENV{silhouette_sd_2}="yes"
14 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="111c", MODE="666", ENV{silhouette_sd_1}="yes"
15 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="110a", MODE="666", ENV{craftrobo_cc200_20}="yes"
16 | ATTRS{idVendor}=="0b4d", ATTRS{idProduct}=="111a", MODE="666", ENV{craftrobo_cc300_20}="yes"
17 | LABEL="end"
18 |
19 | ENV{silhouette_cameo5alpha}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh", GOTO="very_end"
20 | ENV{silhouette_cameo5}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh", GOTO="very_end"
21 | ENV{silhouette_cameo4pro}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
22 | ENV{silhouette_cameo4plus}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
23 | ENV{silhouette_cameo4}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
24 | ENV{silhouette_cameo3}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
25 | ENV{silhouette_cameo2}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
26 | ENV{silhouette_cameo}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
27 | ENV{silhouette_portrait}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
28 | ENV{silhouette_portrait2}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
29 | ENV{silhouette_portrait3}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
30 | ENV{silhouette_portrait4}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
31 | ENV{silhouette_sd_2}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
32 | ENV{silhouette_sd_1}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
33 | ENV{craftrobo_cc200_20}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
34 | ENV{craftrobo_cc300_20}=="yes", RUN="/lib/udev/silhouette-udev-notify.sh"
35 | LABEL="very_end"
36 |
--------------------------------------------------------------------------------
/silhouette/convert2dashes.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | #
3 | # Extracted from inkscape extension; original comments below:
4 | #
5 | # Copyright (C) 2005,2007 Aaron Spike, aaron@ekips.org
6 | # Copyright (C) 2009 Alvin Penner, penner@vaxxine.com
7 | #
8 | # This program is free software; you can redistribute it and/or modify
9 | # it under the terms of the GNU General Public License as published by
10 | # the Free Software Foundation; either version 2 of the License, or
11 | # (at your option) any later version.
12 | #
13 | # This program is distributed in the hope that it will be useful,
14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | # GNU General Public License for more details.
17 | #
18 | # You should have received a copy of the GNU General Public License
19 | # along with this program; if not, write to the Free Software
20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 | #
22 | """
23 | This extension converts a path into a dashed line using 'stroke-dasharray'
24 | It is a modification of the file addnodes.py
25 | """
26 | from inkex import bezier, CubicSuperPath
27 |
28 |
29 | def convert2dash(node):
30 | dashes = []
31 | offset = 0
32 | style = node.style
33 | if 'stroke-dasharray' in style:
34 | if style['stroke-dasharray'].find(',') > 0:
35 | dashes = [float(dash) for dash in style['stroke-dasharray'].split(',')]
36 | if 'stroke-dashoffset' in style:
37 | offset = style['stroke-dashoffset']
38 | if not dashes:
39 | return
40 | new = []
41 | for sub in node.path.to_superpath():
42 | idash = 0
43 | dash = dashes[0]
44 | length = float(offset)
45 | while dash < length:
46 | length = length - dash
47 | idash = (idash + 1) % len(dashes)
48 | dash = dashes[idash]
49 | new.append([sub[0][:]])
50 | i = 1
51 | while i < len(sub):
52 | dash = dash - length
53 | length = bezier.cspseglength(new[-1][-1], sub[i])
54 | while dash < length:
55 | new[-1][-1], nxt, sub[i] = \
56 | bezier.cspbezsplitatlength(new[-1][-1], sub[i], dash/length)
57 | if idash % 2: # create a gap
58 | new.append([nxt[:]])
59 | else: # splice the curve
60 | new[-1].append(nxt[:])
61 | length = length - dash
62 | idash = (idash + 1) % len(dashes)
63 | dash = dashes[idash]
64 | if idash % 2:
65 | new.append([sub[i]])
66 | else:
67 | new[-1].append(sub[i])
68 | i += 1
69 | style.pop('stroke-dasharray')
70 | node.pop('sodipodi:type')
71 | node.path = CubicSuperPath(new)
72 | node.style = style
73 |
--------------------------------------------------------------------------------
/examples/testcut_matfree.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
57 |
--------------------------------------------------------------------------------
/silhouette/pyusb-1.0.2/usb/_debug.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2009-2014 Wander Lairson Costa
2 | #
3 | # The following terms apply to all files associated
4 | # with the software unless explicitly disclaimed in individual files.
5 | #
6 | # The authors hereby grant permission to use, copy, modify, distribute,
7 | # and license this software and its documentation for any purpose, provided
8 | # that existing copyright notices are retained in all copies and that this
9 | # notice is included verbatim in any distributions. No written agreement,
10 | # license, or royalty fee is required for any of the authorized uses.
11 | # Modifications to this software may be copyrighted by their authors
12 | # and need not follow the licensing terms described here, provided that
13 | # the new terms are clearly indicated on the first page of each file where
14 | # they apply.
15 | #
16 | # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
17 | # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
18 | # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
19 | # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
20 | # POSSIBILITY OF SUCH DAMAGE.
21 | #
22 | # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
23 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
24 | # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
25 | # IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
26 | # NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
27 | # MODIFICATIONS.
28 |
29 | __author__ = 'Wander Lairson Costa'
30 |
31 | __all__ = ['methodtrace', 'functiontrace']
32 |
33 | import logging
34 | import usb._interop as _interop
35 |
36 | _enable_tracing = False
37 |
38 | def enable_tracing(enable):
39 | global _enable_tracing
40 | _enable_tracing = enable
41 |
42 | def _trace_function_call(logger, fname, *args, **named_args):
43 | logger.debug(
44 | # TODO: check if 'f' is a method or a free function
45 | fname + '(' + \
46 | ', '.join((str(val) for val in args)) + \
47 | ', '.join((name + '=' + str(val) for name, val in named_args.items())) + ')'
48 | )
49 |
50 | # decorator for methods calls tracing
51 | def methodtrace(logger):
52 | def decorator_logging(f):
53 | if not _enable_tracing:
54 | return f
55 | def do_trace(*args, **named_args):
56 | # this if is just a optimization to avoid unecessary string formatting
57 | if logging.DEBUG >= logger.getEffectiveLevel():
58 | fn = type(args[0]).__name__ + '.' + f.__name__
59 | _trace_function_call(logger, fn, *args[1:], **named_args)
60 | return f(*args, **named_args)
61 | _interop._update_wrapper(do_trace, f)
62 | return do_trace
63 | return decorator_logging
64 |
65 | # decorator for methods calls tracing
66 | def functiontrace(logger):
67 | def decorator_logging(f):
68 | if not _enable_tracing:
69 | return f
70 | def do_trace(*args, **named_args):
71 | # this if is just a optimization to avoid unecessary string formatting
72 | if logging.DEBUG >= logger.getEffectiveLevel():
73 | _trace_function_call(logger, f.__name__, *args, **named_args)
74 | return f(*args, **named_args)
75 | _interop._update_wrapper(do_trace, f)
76 | return do_trace
77 | return decorator_logging
78 |
--------------------------------------------------------------------------------
/silhouette/pyusb-1.0.2/usb/_lookup.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2009-2014 Walker Inman
2 | #
3 | # The following terms apply to all files associated
4 | # with the software unless explicitly disclaimed in individual files.
5 | #
6 | # The authors hereby grant permission to use, copy, modify, distribute,
7 | # and license this software and its documentation for any purpose, provided
8 | # that existing copyright notices are retained in all copies and that this
9 | # notice is included verbatim in any distributions. No written agreement,
10 | # license, or royalty fee is required for any of the authorized uses.
11 | # Modifications to this software may be copyrighted by their authors
12 | # and need not follow the licensing terms described here, provided that
13 | # the new terms are clearly indicated on the first page of each file where
14 | # they apply.
15 | #
16 | # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
17 | # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
18 | # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
19 | # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
20 | # POSSIBILITY OF SUCH DAMAGE.
21 | #
22 | # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
23 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
24 | # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
25 | # IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
26 | # NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
27 | # MODIFICATIONS.
28 |
29 | r"""usb._lookups - Lookup tables for USB
30 | """
31 |
32 | descriptors = {
33 | 0x1 : "Device",
34 | 0x2 : "Configuration",
35 | 0x3 : "String",
36 | 0x4 : "Interface",
37 | 0x5 : "Endpoint",
38 | 0x6 : "Device qualifier",
39 | 0x7 : "Other speed configuration",
40 | 0x8 : "Interface power",
41 | 0x9 : "OTG",
42 | 0xA : "Debug",
43 | 0xB : "Interface association",
44 | 0xC : "Security",
45 | 0xD : "Key",
46 | 0xE : "Encryption type",
47 | 0xF : "Binary device object store (BOS)",
48 | 0x10 : "Device capability",
49 | 0x11 : "Wireless endpoint companion",
50 | 0x30 : "SuperSpeed endpoint companion",
51 | }
52 |
53 | device_classes = {
54 | 0x0 : "Specified at interface",
55 | 0x2 : "Communications Device",
56 | 0x9 : "Hub",
57 | 0xF : "Personal Healthcare Device",
58 | 0xDC : "Diagnostic Device",
59 | 0xE0 : "Wireless Controller",
60 | 0xEF : "Miscellaneous",
61 | 0xFF : "Vendor-specific",
62 | }
63 |
64 | interface_classes = {
65 | 0x0 : "Reserved",
66 | 0x1 : "Audio",
67 | 0x2 : "CDC Communication",
68 | 0x3 : "Human Interface Device",
69 | 0x5 : "Physical",
70 | 0x6 : "Image",
71 | 0x7 : "Printer",
72 | 0x8 : "Mass Storage",
73 | 0x9 : "Hub",
74 | 0xA : "CDC Data",
75 | 0xB : "Smart Card",
76 | 0xD : "Content Security",
77 | 0xE : "Video",
78 | 0xF : "Personal Healthcare",
79 | 0xDC : "Diagnostic Device",
80 | 0xE0 : "Wireless Controller",
81 | 0xEF : "Miscellaneous",
82 | 0xFE : "Application Specific",
83 | 0xFF : "Vendor Specific",
84 | }
85 |
86 | ep_attributes = {
87 | 0x0 : "Control",
88 | 0x1 : "Isochronous",
89 | 0x2 : "Bulk",
90 | 0x3 : "Interrupt",
91 | }
92 |
93 | MAX_POWER_UNITS_USB2p0 = 2 # mA
94 | MAX_POWER_UNITS_USB_SUPERSPEED = 8 # mA
95 |
--------------------------------------------------------------------------------
/misc/MAT_FREE_CUTTING.txt:
--------------------------------------------------------------------------------
1 | jw, Sat May 18 23:56:20 CEST 2013
2 | Goal:
3 | Allow cutting of complex designs without a cutting mat and
4 | without crumpling the paper.
5 |
6 | Rationale:
7 | A cutting mat recommended, whenever the knive moves in a way so that the
8 | paper becomes unstable due to the cuts. The cutting mat is sticky and keeps
9 | the paper in place, even if the knive moves through a piece of paper that was
10 | disconnected from the sheet by previous cuts.
11 |
12 | After the design was cut, the paper needs to be peeled off the cutting mat.
13 | This a manual process, that becomes especially tedious when a large number of small
14 | disconnected holes were cut. E.g. text set in a a stencil font.
15 |
16 | It is assumend that the ordering of cuts can be chosen to minimize the
17 | risk of breaking the paper without a cutting mat.
18 |
19 | The sheet is transported by two rollers at the left and right edge of the paper.
20 | As long as the paper is sufficiently stable, the cut force of the knive is
21 | held by the traction of the transport rollers.
22 |
23 | To reliably cut (any design) without relying on the extra stability
24 | of a cutting mat, the cuts must be organized in such a way, that always a sufficiently
25 | stable portion of the paper is beetween rollers and knive.
26 |
27 | Observations:
28 | * Cutting the paper only halfway through, is always safe. Cutting the same patch in
29 | multiple strokes increases the depth until we finally cut the paper completly through.
30 | With this technique, the force at the point of the knive can be minimized.
31 | * The mechanics of a Silouhette Cameo allows to cut the same path multiple times.
32 | There is no visible offset introduced by going back and forth a sheet multiple times.
33 | The machine also has a feature called Track Enhancement, to further reduce
34 | ths risk of such offset in case the material is more difficult to handle
35 | that ordinary printer paper. With Track Enhancement enabled, the sheet is rolled back
36 | and fort multiple times about its full length, before cutting is started. This visible
37 | imprints the track of the transport rollers into the material, thus reducing the
38 | chance of slip.
39 | * See misc/narrow_serpentine.svg
40 | - Sharp pointed turns do not work well. It is better to start a new cut in the middle
41 | of uncut paper end end the cut to meet another cut.
42 | - A narrow horizontally layed out serpentine path advancing downward the sheet
43 | can be cut without much problems. We even introduce slight backwards movement, where
44 | the knive cuts towards the nearest roller.
45 | * When interrupting a path, to cut something else, and resuming the path, we should
46 | create a slight overlap (1mm from the provios path segment, where we left off.)
47 |
48 | Implementation with version 0.9
49 | -------------------------------
50 | A barrier moves downward the page, enabling points above the barrier.
51 | At each 'barrier_increment' line segments are drawn.
52 | Line segments are subdivisions of input paths so that a maximum segment length of
53 | 'monotone_allow_back_travel' is not exceeded. Drawing directions are chosen, so that
54 | we always draw towards points with sharp edges. A point is marked having a sharp edge, if
55 | any two line segments meeting at the point form an angle of less than 90 degrees.
56 | An attempt is made to minimize idle sideways movement.
57 | No attempt is currently made to avoid cutting towards the traction rollers or to
58 | avoid cutting inwards from the edges.
59 |
60 | Class MatFree() can be initialzed using parameter presets. This needs testing and tuning.
61 |
--------------------------------------------------------------------------------
/examples/testcut_square_triangle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
110 |
--------------------------------------------------------------------------------
/silhouette/pyusb-1.0.2/usb/_interop.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2009-2014 Wander Lairson Costa
2 | #
3 | # The following terms apply to all files associated
4 | # with the software unless explicitly disclaimed in individual files.
5 | #
6 | # The authors hereby grant permission to use, copy, modify, distribute,
7 | # and license this software and its documentation for any purpose, provided
8 | # that existing copyright notices are retained in all copies and that this
9 | # notice is included verbatim in any distributions. No written agreement,
10 | # license, or royalty fee is required for any of the authorized uses.
11 | # Modifications to this software may be copyrighted by their authors
12 | # and need not follow the licensing terms described here, provided that
13 | # the new terms are clearly indicated on the first page of each file where
14 | # they apply.
15 | #
16 | # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
17 | # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
18 | # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
19 | # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
20 | # POSSIBILITY OF SUCH DAMAGE.
21 | #
22 | # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
23 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
24 | # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
25 | # IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
26 | # NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
27 | # MODIFICATIONS.
28 |
29 | # All the hacks necessary to assure compatibility across all
30 | # supported versions come here.
31 | # Please, note that there is one version check for each
32 | # hack we need to do, this makes maintenance easier... ^^
33 |
34 | import sys
35 | import array
36 |
37 | __all__ = ['_reduce', '_set', '_next', '_update_wrapper']
38 |
39 | # we support Python >= 2.4
40 | assert sys.hexversion >= 0x020400f0
41 |
42 | # On Python 3, reduce became a functools module function
43 | try:
44 | import functools
45 | _reduce = functools.reduce
46 | except (ImportError, AttributeError):
47 | _reduce = reduce
48 |
49 | # all, introduced in Python 2.5
50 | try:
51 | _all = all
52 | except NameError:
53 | _all = lambda iter_ : _reduce( lambda x, y: x and y, iter_, True )
54 |
55 | # we only have the builtin set type since 2.5 version
56 | try:
57 | _set = set
58 | except NameError:
59 | import sets
60 | _set = sets.Set
61 |
62 | # On Python >= 2.6, we have the builtin next() function
63 | # On Python 2.5 and before, we have to call the iterator method next()
64 | def _next(iter):
65 | try:
66 | return next(iter)
67 | except NameError:
68 | return iter.next()
69 |
70 | # functools appeared in 2.5
71 | try:
72 | import functools
73 | _update_wrapper = functools.update_wrapper
74 | except (ImportError, AttributeError):
75 | def _update_wrapper(wrapper, wrapped):
76 | wrapper.__name__ = wrapped.__name__
77 | wrapper.__module__ = wrapped.__module__
78 | wrapper.__doc__ = wrapped.__doc__
79 | wrapper.__dict__ = wrapped.__dict__
80 |
81 | # this is used (as of May 2015) twice in core, once in backend/openusb, and in
82 | # some unit test code. It would probably be clearer if written in terms of some
83 | # definite 3.2+ API (bytearrays?) with a fallback provided for 2.4+.
84 | def as_array(data=None):
85 | if data is None:
86 | return array.array('B')
87 |
88 | if isinstance(data, array.array):
89 | return data
90 |
91 | try:
92 | return array.array('B', data)
93 | except TypeError:
94 | # When you pass a unicode string or a character sequence,
95 | # you get a TypeError if the first parameter does not match
96 | a = array.array('B')
97 | a.fromstring(data) # deprecated since 3.2
98 | return a
99 |
--------------------------------------------------------------------------------
/silhouette/pyusb-1.0.2/usb/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2009-2014 Wander Lairson Costa
2 | #
3 | # The following terms apply to all files associated
4 | # with the software unless explicitly disclaimed in individual files.
5 | #
6 | # The authors hereby grant permission to use, copy, modify, distribute,
7 | # and license this software and its documentation for any purpose, provided
8 | # that existing copyright notices are retained in all copies and that this
9 | # notice is included verbatim in any distributions. No written agreement,
10 | # license, or royalty fee is required for any of the authorized uses.
11 | # Modifications to this software may be copyrighted by their authors
12 | # and need not follow the licensing terms described here, provided that
13 | # the new terms are clearly indicated on the first page of each file where
14 | # they apply.
15 | #
16 | # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
17 | # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
18 | # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
19 | # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
20 | # POSSIBILITY OF SUCH DAMAGE.
21 | #
22 | # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
23 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
24 | # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
25 | # IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
26 | # NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
27 | # MODIFICATIONS.
28 |
29 | r"""PyUSB - Easy USB access in Python
30 |
31 | This package exports the following modules and subpackages:
32 |
33 | core - the main USB implementation
34 | legacy - the compatibility layer with 0.x version
35 | backend - the support for backend implementations.
36 | control - USB standard control requests.
37 | libloader - helper module for backend library loading.
38 |
39 | Since version 1.0, main PyUSB implementation lives in the 'usb.core'
40 | module. New applications are encouraged to use it.
41 | """
42 |
43 | import logging
44 | import os
45 |
46 | __author__ = 'Wander Lairson Costa'
47 |
48 | # Use Semantic Versioning, http://semver.org/
49 | version_info = (1, 0, 2)
50 | __version__ = '%d.%d.%d' % version_info
51 |
52 | __all__ = ['legacy', 'control', 'core', 'backend', 'util', 'libloader']
53 |
54 | def _setup_log():
55 | from usb import _debug
56 | logger = logging.getLogger('usb')
57 | debug_level = os.getenv('PYUSB_DEBUG')
58 |
59 | if debug_level is not None:
60 | _debug.enable_tracing(True)
61 | filename = os.getenv('PYUSB_LOG_FILENAME')
62 |
63 | LEVELS = {'debug': logging.DEBUG,
64 | 'info': logging.INFO,
65 | 'warning': logging.WARNING,
66 | 'error': logging.ERROR,
67 | 'critical': logging.CRITICAL}
68 |
69 | level = LEVELS.get(debug_level, logging.CRITICAL + 10)
70 | logger.setLevel(level = level)
71 |
72 | try:
73 | handler = logging.FileHandler(filename)
74 | except:
75 | handler = logging.StreamHandler()
76 |
77 | fmt = logging.Formatter('%(asctime)s %(levelname)s:%(name)s:%(message)s')
78 | handler.setFormatter(fmt)
79 | logger.addHandler(handler)
80 | else:
81 | class NullHandler(logging.Handler):
82 | def emit(self, record):
83 | pass
84 |
85 | # We set the log level to avoid delegation to the
86 | # parent log handler (if there is one).
87 | # Thanks to Chris Clark to pointing this out.
88 | logger.setLevel(logging.CRITICAL + 10)
89 |
90 | logger.addHandler(NullHandler())
91 |
92 |
93 | _setup_log()
94 |
95 | # We import all 'legacy' module symbols to provide compatibility
96 | # with applications that use 0.x versions.
97 | from usb.legacy import *
98 |
--------------------------------------------------------------------------------
/examples/testcut_square_triangle_o.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
108 |
--------------------------------------------------------------------------------
/test/data/testcut_square_triangle_o.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
108 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Run Python tests
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 | pull_request:
9 | branches:
10 | - main
11 | schedule:
12 | - cron: '0 3 * * 5'
13 |
14 | jobs:
15 | test:
16 | name: Inkscape ${{ matrix.inkscape-version }} - Python ${{ matrix.python-version }} - OS ${{ matrix.os }}
17 | runs-on: ${{ matrix.os }}
18 | strategy:
19 | fail-fast: false
20 | matrix:
21 | os: [ubuntu-latest, macos-latest, windows-latest]
22 | python-version: ['3.12']
23 | inkscape-version: ['STABLE']
24 | continue-on-error: [false]
25 | include:
26 | - { os: 'windows-latest', python-version: '3.12', inkscape-version: '1.3.0', continue-on-error: false }
27 | - { os: 'windows-latest', python-version: '3.11', inkscape-version: '1.2', continue-on-error: false }
28 | - { os: 'windows-latest', python-version: '3.10', inkscape-version: '1.1', continue-on-error: false }
29 | - { os: 'ubuntu-latest', python-version: '3.12', inkscape-version: 'TRUNK', continue-on-error: true }
30 | - { os: 'ubuntu-latest', python-version: 'native', inkscape-version: 'inkex', continue-on-error: false } # native python to work around https://github.com/actions/runner-images/issues/10781
31 | - { os: 'ubuntu-latest', python-version: '3.13', inkscape-version: '1.4.0', continue-on-error: false }
32 | - { os: 'ubuntu-latest', python-version: '3.14', inkscape-version: '1.4.0', continue-on-error: false }
33 | # - { os: 'ubuntu-latest', python-version: '3.9', inkscape-version: 'inkex==1.0.1', continue-on-error: false }
34 | continue-on-error: ${{ matrix.continue-on-error }}
35 |
36 | steps:
37 | - uses: actions/checkout@v6
38 | - name: Set up Python ${{ matrix.python-version }}
39 | if: ${{ matrix.python-version != 'native' }}
40 | uses: actions/setup-python@v6
41 | with:
42 | python-version: ${{ matrix.python-version }}
43 | - name: 🐧 install Inkscape & umockdev
44 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.inkscape-version != 'TRUNK' && ! startsWith(matrix.inkscape-version, 'inkex') }}
45 | run: |
46 | sudo add-apt-repository ppa:inkscape.dev/stable -y # inkscape STABLE from https://launchpad.net/~inkscape.dev/+archive/ubuntu/stable
47 | sudo apt-get update
48 | sudo apt-get -y install inkscape umockdev
49 | - name: 🐧 install Inkscape TRUNK & umockdev
50 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.inkscape-version == 'TRUNK' }}
51 | run: |
52 | sudo add-apt-repository ppa:inkscape.dev/trunk -y # inkscape DEV from https://launchpad.net/~inkscape.dev/+archive/ubuntu/trunk
53 | sudo apt-get update
54 | sudo apt-get -y install inkscape-trunk umockdev
55 | - name: 🐧 install Inkex standalone & umockdev
56 | if: ${{ matrix.os == 'ubuntu-latest' && startsWith(matrix.inkscape-version, 'inkex') }}
57 | run: |
58 | sudo apt-get update
59 | sudo apt-get -y install umockdev libgirepository1.0-dev
60 | pip install --break-system-packages ${{ matrix.inkscape-version }}
61 | - name: 🍏 install Inkscape
62 | if: ${{ matrix.os == 'macos-latest' }}
63 | run: |
64 | brew install --cask inkscape
65 | brew install libusb
66 | - name: 🗖 install Inkscape (fixed)
67 | if: ${{ matrix.os == 'windows-latest' && startsWith(matrix.inkscape-version, '1.') }}
68 | run: |
69 | choco install -y inkscape --version ${{ matrix.inkscape-version }}
70 | - name: 🗖 install Inkscape
71 | if: ${{ matrix.os == 'windows-latest' && matrix.inkscape-version == 'STABLE' }}
72 | run: |
73 | choco install -y inkscape
74 | - name: inkscape version
75 | run: |
76 | inkscape --version || echo "inkscape not found"
77 | - name: install requirements (macos)
78 | if: ${{ matrix.os == 'macos-latest' }}
79 | run: |
80 | ./install_osx.sh
81 | . "$HOME/.local/share/venvs/inkscape/bin/activate"
82 | pip install pytest
83 | - name: install requirements (non-macos)
84 | if: ${{ matrix.os != 'macos-latest' }}
85 | run: |
86 | python -m pip install --break-system-packages --upgrade pip
87 | pip install -r requirements.txt
88 | pip install pytest
89 | - name: test
90 | shell: bash
91 | env:
92 | PYTHONWARNINGS: default
93 | run: |
94 | [[ -e "$HOME/.local/share/venvs/inkscape/bin/activate" ]] && source "$HOME/.local/share/venvs/inkscape/bin/activate"
95 | pytest -s -vv test
96 |
--------------------------------------------------------------------------------
/misc/dump/star_man.dump:
--------------------------------------------------------------------------------
1 | # device version: 'None'
2 | # driver version: '1.13'
3 | [[(84.18286157777777, 90.57460788888888), (58.37402035555555, 46.40319791111111), (30.185173733333336, 90.71191464444443), (37.73574988888889, 52.274676022222216), (13.368382937777776, 39.39949222222222), (46.2067402, 39.04277744444445), (56.9727221111111, 7.549363728888888), (67.45958906666667, 38.670266688888894), (100.73847715555554, 39.17732406666667), (76.96167826666665, 52.31702064444444), (84.18286157777777, 90.57460788888888)], [(52.15175385111111, 30.7307996), (52.22432543496332, 31.31989370627513), (52.43263259750907, 31.85968854576657), (52.76256385478195, 32.323862168324865), (53.200007722815585, 32.68609262380057), (53.710023209803495, 32.912956290214005), (54.248261666666664, 32.98857751235182), (54.78650012352984, 32.912956290214005), (55.29651561051775, 32.68609262380057), (55.733959478551384, 32.323862168324865), (56.063890735824266, 31.85968854576657), (56.27219789837002, 31.31989370627513), (56.34476948222222, 30.7307996), (56.27219787132084, 30.14170552056422), (56.0638906941704, 29.601910711111113), (55.733959434101415, 29.137737118188564), (55.29651557444444, 28.775506688344468), (54.786500103886084, 28.54864303875278), (54.248261666666664, 28.47302182222222), (53.710023229447245, 28.54864303875278), (53.200007758888894, 28.775506688344468), (52.76256389923192, 29.137737118188564), (52.43263263916294, 29.601910711111113), (52.22432546201249, 30.14170552056422), (52.15175385111111, 30.7307996)], [(59.02476186444444, 30.74349113333333), (59.09175104899838, 31.37466331922209), (59.28403461223645, 31.953014862222226), (59.58858656852295, 32.45034367862684), (59.99238093222221, 32.83844768472905), (60.46316448047544, 33.081515864515595), (60.959999999999994, 33.16253859111111), (61.45683551952456, 33.081515864515595), (61.92761906777777, 32.83844768472905), (62.33141343147703, 32.45034367862684), (62.635965387763534, 31.953014862222226), (62.8282489510016, 31.37466331922209), (62.89523813555555, 30.74349113333333), (62.8282489510016, 30.11231894744457), (62.635965387763534, 29.533967404444436), (62.33141343147703, 29.036638588039825), (61.92761906777777, 28.64853458193761), (61.45683551952456, 28.405466402151063), (60.959999999999994, 28.32444367555555), (60.46316448047544, 28.405466402151063), (59.99238093222221, 28.64853458193761), (59.58858656852295, 29.036638588039825), (59.28403461223645, 29.533967404444436), (59.09175104899838, 30.11231894744457), (59.02476186444444, 30.74349113333333)], [(49.34857062222222, 38.323175688888895), (49.945586289236104, 38.785570473958344), (51.594409341666655, 39.80202683611112), (52.74658116150172, 40.34424936184895), (54.081703856597215, 40.816452785763886), (55.573110436588536, 41.14912560915798), (57.1941339111111, 41.272756333333334), (58.792308310720486, 41.142905040017354), (60.22347642048611, 40.807312357638885), (61.47343438181423, 40.33472808255208), (62.52797833611111, 39.79390201111111), (63.37290442478298, 39.253583939670136), (63.9940087892361, 38.78252366458333), (64.5079369111111, 38.323175688888895), (64.5079369111111, 38.323175688888895)], [(83.89520375555556, 22.33484484), (81.92847611111111, 18.806874868888887), (79.19968637777778, 21.784843399999996), (80.30365222222223, 17.89950615333333), (76.2683655111111, 18.075702848888888), (79.79633802222222, 16.10897633333333), (76.81836864444445, 13.380187446666666), (80.70370504444443, 14.48415357333333), (80.52750806666667, 10.448866579999995), (82.4942357111111, 13.976836833333328), (85.22302544444445, 10.998868302222219), (84.11905677777779, 14.884205548888884), (88.1543434888889, 14.708008853333329), (84.62637380000001, 16.67473508666666), (87.60434317777779, 19.403524255555553), (83.71900677777779, 18.299558128888883), (83.89520375555556, 22.33484484)], [(61.01341309555556, 29.915041911111107), (61.07142481530934, 30.13490642777777), (61.22991578111111, 30.29585842475935), (61.446418466666664, 30.354770944444443), (61.662921152222225, 30.29585842475935), (61.82141211802399, 30.13490642777777), (61.87942383777777, 29.915041911111107), (61.82141211802399, 29.695177394444443), (61.662921152222225, 29.534225397462873), (61.446418466666664, 29.475312877777778), (61.22991578111111, 29.534225397462873), (61.07142481530934, 29.695177394444443), (61.01341309555556, 29.915041911111107)], [(54.64473562888888, 29.915041911111107), (54.70274734864267, 30.13490642777777), (54.86123831444444, 30.29585842475935), (55.077740999999996, 30.354770944444443), (55.29424368555556, 30.29585842475935), (55.45273465135733, 30.13490642777777), (55.510746371111104, 29.915041911111107), (55.45273465135733, 29.695177394444443), (55.29424368555556, 29.534225397462873), (55.077740999999996, 29.475312877777778), (54.86123831444444, 29.534225397462873), (54.70274734864267, 29.695177394444443), (54.64473562888888, 29.915041911111107)]]
4 |
--------------------------------------------------------------------------------
/examples/default_us_letter_landscape_cameo_3.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
124 |
--------------------------------------------------------------------------------
/silhouette/read_dump.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # this script reads inkscape dumpfiles and shows the plotter path
3 |
4 | import sys
5 | try:
6 | import matplotlib.pyplot as plt
7 | from matplotlib.widgets import Button
8 | except:
9 | plt = None
10 | from pathlib import Path
11 |
12 |
13 | # From https://stackoverflow.com/questions/24852345/hsv-to-rgb-color-conversion
14 | def hsv_to_rgb(h, s, v):
15 | if s == 0.0: return (v, v, v)
16 | i = int(h*6.)
17 | f = (h*6.)-i; p,q,t = v*(1.-s), v*(1.-s*f), v*(1.-s*(1.-f)); i%=6
18 | if i == 0: return (v, t, p)
19 | if i == 1: return (q, v, p)
20 | if i == 2: return (p, v, t)
21 | if i == 3: return (p, q, v)
22 | if i == 4: return (t, p, v)
23 | if i == 5: return (v, p, q)
24 |
25 | def show_plotcuts(cuts, buttons=False, extraText=None):
26 | """
27 | Show a graphical representation of the cut paths in (the argument) cuts,
28 | and block until the display window has been closed.
29 |
30 | buttons: display Cut/Cancel buttons
31 |
32 | Returns > 0 on failure
33 | return value:
34 | 1: cut canceled
35 | 2: matplotlib missing
36 | 3: cut path empty
37 | """
38 | if plt is None:
39 | print("Install matplotlib for python to allow graphical display of cuts",
40 | file=sys.stderr)
41 | return 2
42 | if cuts == []:
43 | print("Empty cut path", file=sys.stderr)
44 | return 3
45 | xy = sum(cuts, [])
46 | least = min(min(p[0],p[1]) for p in xy)
47 | greatest = max(max(p[0],p[1]) for p in xy)
48 | scale = greatest - least
49 | plt.figure("Sendto Silhouette - Preview")
50 | plt.plot(*zip(*sum(cuts, [])), color="lightsteelblue")
51 | plt.plot(xy[0][0],xy[0][1],'go')
52 | plt.plot(xy[-1][0],xy[-1][1],'ro')
53 | ncuts = len(cuts)
54 | maxhue = 0.33
55 | for i, xy in enumerate(cuts):
56 | plt.plot(*zip(*xy), color=hsv_to_rgb(maxhue*(1.0-i/ncuts),0.9,0.7))
57 | plt.arrow(xy[-2][0], xy[-2][1], xy[-1][0]-xy[-2][0], xy[-1][1]-xy[-2][1],
58 | color="lightblue", length_includes_head=True,
59 | head_width=min(3,scale/50))
60 | plt.axis([plt.axis()[0], plt.axis()[1], plt.axis()[3], plt.axis()[2]])
61 | plt.gca().set_aspect('equal')
62 | class Response:
63 | returnvalue = 1 if buttons == True else 0
64 | def pushedcut(self, event):
65 | self.returnvalue = 0
66 | plt.close('all')
67 | def pushedcancel(self, event):
68 | plt.close('all')
69 | response = Response()
70 | if buttons:
71 | bcut = Button(plt.axes([0.7,0.9,0.1,0.075]), 'Cut')
72 | bcancel = Button(plt.axes([0.81,0.9,0.1,0.075]), 'Cancel')
73 | bcut.on_clicked(response.pushedcut)
74 | bcancel.on_clicked(response.pushedcancel)
75 | bcut.connect_event('key_press_event', lambda event: response.pushedcut(event) if(event.key=='enter') else None)
76 | bcancel.connect_event('key_press_event', lambda event: response.pushedcancel(event) if(event.key=='escape') else None)
77 | if extraText:
78 | plt.text(-1.3, 0.5, str(extraText), fontsize = 8, horizontalalignment='right')
79 |
80 | plt.show()
81 |
82 | return response.returnvalue
83 |
84 | if __name__ == "__main__":
85 | # The below is not correct under Windows; please help to correct.
86 | # I am not sure if it is correct under MacOS.
87 | maybeconfig = Path.home() / '.config' / 'inkscape' / 'preferences.xml'
88 |
89 | olddefault = '/tmp/silhouette.dump'
90 | filename = ''
91 | if len(sys.argv) > 1:
92 | filename = sys.argv[1]
93 | elif maybeconfig.is_file():
94 | with open(maybeconfig, 'rt') as config:
95 | for line in config:
96 | if 'com.github.fablabnbg.inkscape-silhouette.sendto_silhouette.logfile' in line:
97 | maybefilename = line.split('"')[1]
98 | if Path(maybefilename).is_file():
99 | filename = maybefilename
100 | break
101 | if not filename and Path(olddefault).is_file():
102 | filename = olddefault
103 | if not filename:
104 | sys.exit("Cannot find file with cut paths to display.\nUsage:\n read_dump.py FILENAME_OF_LOG_OR_DUMP")
105 |
106 | print("Reading cut paths from:", filename)
107 | cutpaths = ''
108 | triggered = False
109 | with open(filename, 'rt') as file:
110 | for line in file:
111 | if triggered and line[0] != '#':
112 | cutpaths = line
113 | break
114 | if '# driver version' in line:
115 | triggered = True
116 |
117 | if not cutpaths:
118 | sys.exit("Cannot find any cut paths in " + filename + ".\n Make sure it is an inkscape_silhouette dump or log file with log_paths on.")
119 |
120 | retval = show_plotcuts(eval(cutpaths))
121 | sys.exit(retval)
122 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # a simple makefile to pull a tar ball.
2 |
3 | PREFIX?=/usr
4 | DISTNAME=inkscape-silhouette
5 | EXCL=--exclude \*.orig --exclude \*.pyc
6 | ALL=README.md *.png *.sh *.rules *.py *.inx examples misc silhouette locale
7 | VERS=$$(python3 ./sendto_silhouette.py --version)
8 |
9 | ## echo python3 ./sendto_silhouette.py
10 | # 'module' object has no attribute 'core'
11 | # 'module' object has no attribute 'core'
12 | # done. 0 min 0 sec
13 | #
14 | # debian 8
15 | # --------
16 | # echo > /etc/apt/sources.list.d/backports.list 'deb http://ftp.debian.org debian jessie-backports main'
17 | # apt-get update
18 | # apt-get -t jessie-backports install python3-usb
19 | # vi /etc/group
20 | # lp:x:debian
21 |
22 |
23 | DEST=$(DESTDIR)$(PREFIX)/share/inkscape/extensions
24 | LOCALE=$(DESTDIR)$(PREFIX)/share/locale
25 | UDEV=$(DESTDIR)$(PREFIX)/lib/udev
26 | INKSCAPE_TEMPLATES=$(DESTDIR)$(PREFIX)/share/inkscape/templates
27 |
28 | # User-specifc inkscape extensions folder for local install
29 | DESTLOCAL=$(HOME)/.config/inkscape/extensions
30 | USER_INKSCAPE_TEMPLATES=$(HOME)/.config/inkscape/templates
31 |
32 | .PHONY: help dist install install-local tar_dist_classic tar_dist clean generate_pot update_po mo
33 | help: # Show help for each of the Makefile recipes.
34 | @grep -E '^[a-zA-Z0-9 -]+:.*#' Makefile | sort | while read -r l; do printf "\033[1;32m$$(echo $$l | cut -f 1 -d':')\033[00m:$$(echo $$l | cut -f 2- -d'#')\n"; done
35 |
36 | mdhelp: # Render help for each of the Makefile recipes in a markdown friendly manner
37 | @grep -E '^[a-zA-Z0-9 -]+:.*#' Makefile | sort | while read -r l; do printf " - **$$(echo $$l | cut -f 1 -d':')**:$$(echo $$l | cut -f 2- -d'#')\n"; done
38 |
39 | .PHONY: dist
40 | dist: mo # Genearate OS specific packagings and install files (Windows, Linux Distros, etc...)
41 | cd distribute; sh ./distribute.sh
42 |
43 | .PHONY: install
44 | install: mo # Install is used by dist or use this with this command `sudo make install` to install for all users
45 | mkdir -p $(DEST)
46 | @# CAUTION: cp -a does not work under fakeroot. Use cp -r instead.
47 | cp -r silhouette $(DEST)
48 | install -m 755 *silhouette*.py $(DEST)
49 | install -m 644 *.inx $(DEST)
50 | cp -r locale $(LOCALE)
51 | mkdir -p $(UDEV)/rules.d
52 | install -m 644 -T silhouette-udev.rules $(UDEV)/rules.d/40-silhouette-udev.rules
53 | install -m 644 silhouette-icon.png $(UDEV)
54 | install -m 644 silhouette-udev-notify.sh $(UDEV)
55 | mkdir -p $(INKSCAPE_TEMPLATES)
56 | install -m 644 templates/*.svg $(INKSCAPE_TEMPLATES)
57 |
58 | .PHONY: install-local
59 | install-local: mo # Use this with `make install-local` to install just in your user account
60 | mkdir -p $(DESTLOCAL)
61 | @# CAUTION: cp -a does not work under fakeroot. Use cp -r instead.
62 | cp -r silhouette $(DESTLOCAL)
63 | install -m 755 *silhouette*.py $(DESTLOCAL)
64 | install -m 644 *.inx $(DESTLOCAL)
65 | cp -r locale $(DESTLOCAL)
66 | mkdir -p $(USER_INKSCAPE_TEMPLATES)
67 | install -m 644 templates/*.svg $(USER_INKSCAPE_TEMPLATES)
68 |
69 | .PHONY: tar_dist_classic
70 | tar_dist_classic: clean mo # Create a compressed tarball archive file (.tar.bz2) that contains the distribution files for the Inkscape Silhouette project. (Using a fixed list defined in $ALL)
71 | name=$(DISTNAME)-$(VERS); echo "$$name"; echo; \
72 | tar jcvf $$name.tar.bz2 $(EXCL) --transform="s,^,$$name/," $(ALL)
73 | grep about_version ./sendto_silhouette.inx
74 | @echo version should be $(VERS)
75 |
76 | .PHONY: tar_dist
77 | tar_dist: mo # Create a compressed tarball archive file (.tar.bz2) that contains the distribution files for the Inkscape Silhouette project. (Using distutils.core parameter like format bztar)
78 | python3 setup.py sdist --format=bztar
79 | mv dist/*.tar* .
80 | rm -rf dist
81 |
82 | .PHONY: clean
83 | clean: # Cleanup generated/compiled files and restore project back to nominal state
84 | rm -f *.orig */*.orig
85 | rm -rf distribute/$(DISTNAME)
86 | rm -rf distribute/deb/files
87 | rm -rf locale
88 |
89 | .PHONY: generate_pot
90 | generate_pot: # Updates Portable Object Template
91 | mkdir -p po/its
92 | curl -s -o po/its/inx.its https://gitlab.com/inkscape/inkscape/-/raw/master/po/its/inx.its
93 | xgettext --its po/its/inx.its --no-wrap -o po/inkscape-silhouette.pot *.inx
94 | xgettext --no-wrap -j -o po/inkscape-silhouette.pot *silhouette*.py
95 |
96 | .PHONY: update_po
97 | update_po: # Updates localised translation/internationalisation with any new UI language entries from the main Portable Object Template file ./po/inkscape-silhouette.pot
98 | $(foreach po, $(wildcard po/*.po), \
99 | msgmerge -q --update --no-wrap $(po) po/inkscape-silhouette.pot; )
100 |
101 | .PHONY: mo
102 | mo: # Compile transations for different human languages into binary .mo file for internationalisation and localisation purposes. (e.g. ./po/de.po)
103 | mkdir -p locale
104 | $(foreach po, $(wildcard po/*.po), \
105 | mkdir -p locale/$(basename $(notdir $(po)))/LC_MESSAGES; \
106 | msgfmt -c -o locale/$(basename $(notdir $(po)))/LC_MESSAGES/inkscape-silhouette.mo $(po); )
107 |
108 | .PHONY: test
109 | test: # Run local test (Must have umockdev installed)
110 | pytest -s -vv test
111 |
--------------------------------------------------------------------------------
/silhouette/ColorSeparation.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pickle
3 |
4 | from silhouette.Dialog import Dialog
5 |
6 | class ColorSeparation:
7 | """Keep sendto_silhouette settings on a per-color basis"""
8 | def __init__(self, *args, **kwargs):
9 | self.colors = kwargs.pop('colors', [])
10 | self.options = kwargs.pop('options')
11 | self.logger = kwargs.pop('logger')
12 | self.color_settings = {}
13 | self.color_enabled = {}
14 |
15 | def activate_preset(self, preset_name, silent=False):
16 | preset = self.read_preset(preset_name)
17 | self.logger("Loaded preset " + preset_name + ": "
18 | + str(preset), not silent)
19 | if not preset:
20 | return preset
21 | self.extract_settings_from_preset(preset, silent)
22 | return preset
23 |
24 | def extract_settings_from_preset(self, preset, silent=False):
25 | old_colors = self.colors
26 | self.colors = []
27 | extra_colors = []
28 |
29 | for color in preset['colors']:
30 | if color in old_colors:
31 | old_colors.remove(color)
32 | self.colors.append(color)
33 | self.color_enabled[color] = preset['color_enabled'].get(
34 | color, True)
35 | self.color_settings[color] = preset['color_settings'].get(
36 | color, {})
37 | else:
38 | extra_colors.append(color)
39 |
40 | reassigned = 0
41 | # If there are any leftover colors in this SVG that weren't in the
42 | # preset, we have to add them back into the list. Let's try to
43 | # use the settings from one of the "unclaimed" colors in the preset.
44 |
45 | for color in old_colors:
46 | self.colors.append(color)
47 |
48 | if extra_colors:
49 | reassigned += 1
50 | assigned_color = extra_colors.pop(0)
51 | self.color_enabled[color] = preset['color_enabled'].get(
52 | assigned_color, True)
53 | self.color_settings[color] = preset['color_settings'].get(
54 | assigned_color, {})
55 | else:
56 | self.color_enabled[color] = False
57 |
58 | message = []
59 |
60 | self.logger("Reassigned " + str(reassigned) + " colors.",
61 | self.options.verbose)
62 | self.logger("Colors remaining: " + str(extra_colors),
63 | self.options.verbose)
64 | self.logger("Final colors: " + str(self.colors),
65 | self.options.verbose)
66 | self.logger("Color settings: " + str(self.color_settings),
67 | self.options.verbose)
68 |
69 | if reassigned:
70 | message.append("%d colors were reassigned." % reassigned)
71 |
72 | if extra_colors:
73 | message.append("%d colors from the preset were not used." % len(extra_colors))
74 |
75 | if message and not silent:
76 | Dialog.info(None, "Colors in the preset and this SVG did not match fully. " + " ".join(message))
77 |
78 | def generate_actions(self, default_actions):
79 | actions = []
80 | for color in self.colors:
81 | if self.color_enabled.get(color, True):
82 | actions.append(
83 | (color, self.color_settings.get(color) or default_actions))
84 | return actions
85 |
86 | def get_preset_data(self):
87 | return { 'colors': self.colors,
88 | 'color_enabled': self.color_enabled,
89 | 'color_settings': self.color_settings }
90 |
91 | def read_preset(self, name):
92 | return self.read_presets().get(name)
93 |
94 | def read_presets(self):
95 | try:
96 | with open(self.presets_path(), 'rb') as presets:
97 | presets = pickle.load(presets)
98 | return presets
99 | except:
100 | return {}
101 |
102 | def save_presets(self, presets, write_log=False):
103 | self.logger("saving presets: " + str(presets), write_log)
104 | with open(self.presets_path(), 'wb') as presets_file:
105 | pickle.dump(presets, presets_file)
106 |
107 | def save_preset(self, name, data, write_log=False):
108 | presets = self.read_presets()
109 | presets[name] = data
110 | self.save_presets(presets, write_log)
111 |
112 | def remove_preset(self, name):
113 | presets = self.read_presets()
114 | presets.pop(name, None)
115 | self.save_presets(presets)
116 |
117 | def presets_path(self):
118 | if self.options.pickle_path:
119 | return self.options.pickle_path
120 | try:
121 | import appdirs
122 | config_path = appdirs.user_config_dir('inkscape-silhouette')
123 | except ImportError:
124 | config_path = os.path.expanduser('~/.inkscape-silhouette')
125 |
126 | if not os.path.exists(config_path):
127 | os.makedirs(config_path)
128 | return os.path.join(config_path, 'presets.cPickle')
129 |
--------------------------------------------------------------------------------
/install_osx_stage2.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | '''
3 | Makes installing inkscape_silhouette easier on OSX
4 |
5 | This installer adds the extension to the user's local Inkscape extensions rather
6 | than inside the Inkscape.app bundle. This should, in theory, allow it to survive
7 | between inscape upgrades.
8 |
9 | @author: Brian Bustin
10 | @contact: brian at bustin.us
11 | '''
12 | import os, sys, shutil, logging, subprocess
13 |
14 | logger = logging.getLogger(__name__)
15 |
16 | prerequisites = ["cssselect", "xmltodict", "lxml", "pyusb", "libusb1", "matplotlib", "wxPython"]
17 | extensions_dir = os.path.join(os.path.expanduser("~"), "Library","Application Support","org.inkscape.Inkscape","config","inkscape","extensions")
18 | extension_files = ["sendto_silhouette.inx", "sendto_silhouette.py",
19 | "silhouette_multi.inx", "silhouette_multi.py",
20 | "render_silhouette_regmarks.inx", "render_silhouette_regmarks.py",
21 | "silhouette"]
22 |
23 |
24 | def install_inkscape_silhouette():
25 | try:
26 | logger.info("inkscape_silhouette install starting")
27 |
28 | # make sure this is os x
29 | logger.debug("making sure running on Mac OS")
30 | sys_platform = sys.platform.lower()
31 | logger.debug("platform: %s", sys_platform)
32 | if not sys_platform.startswith('darwin'):
33 | logger.error("Installer only works with Mac OS")
34 | return
35 |
36 | if not os.path.isdir(extensions_dir):
37 | logger.info("creating extensions dir %s", extensions_dir)
38 | os.makedirs(extensions_dir)
39 |
40 | install_prerequisites()
41 | install_extension()
42 | check_libusb()
43 | logger.info("inkscape_silhouette extension install ended")
44 | logger.info("\x1b[31;20mDon't forget\x1b[0m to add 'python-interpreter=\"%s\"' to your extension preference file.", subprocess.check_output(["which", "python3"]).decode("utf-8").replace("\n", ""))
45 | except Exception as ex:
46 | logger.warning("inkscape_silhouette install was unsuccessful. Please check previous messages for the cause. Details: %s", ex)
47 |
48 |
49 | def install_prerequisites():
50 | logger.info("installing inkscape_silhouette prerequisites")
51 | for prerequisite in prerequisites:
52 | logger.debug("installing %s", prerequisite)
53 | try:
54 | return_code = subprocess.call("pip3 install {}".format(prerequisite), shell=True)
55 | if return_code > 0:
56 | raise OSError("command returned code {}, try running again using sudo".format(return_code))
57 | except OSError:
58 | logger.error("unable to install module. Try running 'pip3 install %s' manually", prerequisite)
59 | raise
60 |
61 |
62 | def check_libusb():
63 | logger.info("making sure libusb is installed")
64 | try:
65 | import usb.core
66 | except ImportError:
67 | logger.error("pyusb is not installed. Install script usually fails here on first attempt. Running it again should allow installation to complete.")
68 | raise
69 |
70 | try:
71 | usb.core.find()
72 | except usb.core.NoBackendError:
73 | logger.error("libusb is probably not installed. Refer to the instructions in README.md to install it.")
74 | raise
75 | except Exception as ex:
76 | logger.error("something is not right with either pyusb or libusb. Details: %s", ex)
77 | raise
78 |
79 |
80 | def install_extension():
81 | logger.info("installing extension")
82 | for file in extension_files:
83 | path = os.path.join(os.getcwd(), file)
84 | if os.path.isfile(path):
85 | logger.debug("copying %s => %s", path, extensions_dir)
86 | shutil.copy(path, extensions_dir)
87 | else:
88 | destination_dir = os.path.join(extensions_dir, file)
89 | if os.path.isdir(destination_dir):
90 | logger.info("directory already exists '%s'. Removing it and recreating it.", destination_dir)
91 | shutil.rmtree(destination_dir)
92 | logger.debug("copying %s => %s", path, destination_dir)
93 | shutil.copytree(path, destination_dir)
94 |
95 |
96 | def uninstall_extension():
97 | logger.info("uninstalling extension")
98 | for file in extension_files:
99 | file_path = os.path.join(extensions_dir, file)
100 | logger.debug("deleting %s", file_path)
101 | try:
102 | os.remove(file_path)
103 | except OSError:
104 | logger.info("unable to delete %s. It may have been previously removed or was never installed", file_path)
105 |
106 |
107 | if __name__ == "__main__":
108 | import logging.handlers
109 |
110 | log_level = logging.INFO
111 |
112 | # set up logging
113 | logger.setLevel(log_level)
114 | log_console = logging.StreamHandler()
115 | log_console.setLevel(log_level)
116 | log_formatter = logging.Formatter('%(levelname)s: %(message)s')
117 | log_console.setFormatter(log_formatter)
118 | logger.addHandler(log_console)
119 |
120 | if sys.prefix == sys.base_prefix:
121 | logger.critical("The installer should be running in a virtual environment. \x1b[31;20mPlease run install_osx.sh\x1b[0m. Bailing out.")
122 | sys.exit(-1)
123 |
124 | # run installer
125 | install_inkscape_silhouette()
126 |
--------------------------------------------------------------------------------
/USERGUIDE.md:
--------------------------------------------------------------------------------
1 | # User Guide
2 |
3 | This contains typical usage examples and workflow for users using this plugin.
4 |
5 | ---
6 |
7 | ## Prepping new design files
8 |
9 |
10 | ### Adding Print and Cut Layers
11 |
12 | You are recommended to add a layer named `Print` and `Cut`.
13 | The `Print` layer in addition to `Regmarks` will be ignored by `Send to Silhouette` extention.
14 |
15 |
16 | ### Add registration mark
17 |
18 |
19 |
20 | The plotter will search the registration marks at the given positions.
21 | If it locates the marks, they will serve as accurate reference and define the origin.
22 | Therefore it is necessary to set the correct offset values of the mark.
23 | As a result the cut will go precisely along the graphics.
24 |
25 | 1. Extention > Render > Silhouette Regmarks
26 | 1. Check regmark from document left and top is set to desired value
27 | 1. Set mark to mark distance or clear it to zero if autocalculating from document size
28 | 1. Press Apply
29 |
30 | This will create a new layer called `Regmarks` with the newly generated registration mark.
31 |
32 | On the bottom will also be a string shown below that would remind you what settings this was generated with.
33 | - `mark distance from document: Left=10.0mm, Top=10.0mm; mark to mark distance: X=190.0mm, Y=277.0mm;`
34 |
35 | Note: You have the option of using the provided template at `examples/registration-marks-cameo-silhouette-a4-maxi.svg` for Silhouette Cameo using A4 paper format.
36 |
37 |
38 | ---
39 |
40 | ## Plot
41 |
42 |
43 |
44 | 1. Open your document with inkscape.
45 | - Note: documents in px are plotted at 96dpi
46 | 2. Convert text objects to paths (Path - Convert object to path)
47 | 3. Select the parts you want to plot.
48 | 4. Open the extension. If you want to use the same cut settings for all of the paths in your file, use "Extensions -> Export -> Send to Silhouette." If you want use different cut settings based on the colors of different items in your file, use "Extensions -> Export -> Silhouette Multi Action."
49 | 5. In the case of Multi Action, there is a first screen that is primarily for debugging. Typically you can just leave all of the boxes on this unchecked and click "Apply."
50 | 6. Set your desired plot parameters. There are numerous aspects you can control with the dialog, here are just the core highlights:
51 | - **X-Offset, Y-Offset** An additional offset of your drawing from the top left corner. Default is 0/0
52 | - **Tool Cut/Pen** Cut mode drews small circles for orientation of the blade, Pen mode draws exactly as given.
53 | - **Media** Select a predfined media or set to custom settings.
54 | - **Speed** Custom speed of the movements
55 | - **Pressure** Custom Pressure on the blade. One unit is said to be 7g force.
56 | - Note: In Multi Action, you can select the color you want settings to apply to and then set all the same parameters, but with potentially different settings for each color. You can also change the order in which the colors are cut, and uncheck the box in the "Perform Action?" column to ignore a color altogether.
57 | 7. To start the cut, in "Send to Silhouette, click the "Apply" button; in "Silhouette Multi" click the "Execute" button.
58 |
59 |
60 | ### Plot with registration marks steps
61 |
62 |
63 |
64 | 1. Open the document which fit to your setup (e.g. With rendered registration mark as illustrated above)
65 | 2. Insert your cutting paths and graphics on the apropriate layers.
66 | 3. Printout the whole document including registration marks. You probably want to hide the cutting layer.
67 | 4. Select your cutting paths in the document, but exclude regmarks and graphics.
68 | - The extention is smart enough to ignore any layers that has the word `Print` or `Regmarks` in it.
69 | 5. On the **Regmarks** tab:
70 | - Check **Document has registration marks**
71 | - Check **Search for registration marks**
72 | 6. Set all following parameters according to the registration file used:
73 | - **X mark distance** (e.g. *190*)
74 | - **Y mark distance** (e.g. *277*)
75 | - **Position of regmark from document left** (e.g. *10*)
76 | - **Position of regmark from document top** (e.g. *10*)
77 | - Note: values are read from regmarks layer if 0 is entered
78 | 7. Set desired plot parameters as usual. Already explained in previous section.
79 | 8. Start cut.
80 |
81 | On some devices have an offset between the search optics and the cutting knife.
82 | For enhanced precision, you may have to set an offset on **X-Offset** and/or **Y-Offset** on the **Silhouette** tab to compensate.
83 |
84 | ---
85 |
86 | ## Design Tips
87 |
88 | ### Getting an outline of a vector object
89 |
90 | 1. Path > Break Apart
91 | 1. Path > Union
92 | 1. Depening on your sticker cutting requirement:
93 | - Path > Inset
94 | - Path > Outset
95 | - Path > Dynamic Offset
96 |
97 |
98 | ### Getting an outline of a bitmap object
99 |
100 | 1. Path > Trace Bitmap
101 | 1. Use brightness cutoff detection mode.
102 | 1. Select threshold to get as much detail of the object in the image.
103 | 1. Press Apply
104 | 1. Select all the vector outlines that was detected and generated
105 | 1. Path > Break Apart
106 | 1. Path > Union
107 | 1. Depening on your sticker cutting requirement:
108 | - Path > Inset
109 | - Path > Outset
110 | - Path > Dynamic Offset
111 |
112 |
113 |
--------------------------------------------------------------------------------
/silhouette/pyusb-1.0.2/usb/_objfinalizer.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Copyright (C) 2014 André Erdmann
4 | #
5 | # The following terms apply to all files associated
6 | # with the software unless explicitly disclaimed in individual files.
7 | #
8 | # The authors hereby grant permission to use, copy, modify, distribute,
9 | # and license this software and its documentation for any purpose, provided
10 | # that existing copyright notices are retained in all copies and that this
11 | # notice is included verbatim in any distributions. No written agreement,
12 | # license, or royalty fee is required for any of the authorized uses.
13 | # Modifications to this software may be copyrighted by their authors
14 | # and need not follow the licensing terms described here, provided that
15 | # the new terms are clearly indicated on the first page of each file where
16 | # they apply.
17 | #
18 | # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
19 | # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
20 | # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
21 | # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
22 | # POSSIBILITY OF SUCH DAMAGE.
23 | #
24 | # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
25 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
26 | # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
27 | # IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
28 | # NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
29 | # MODIFICATIONS.
30 |
31 | import sys
32 |
33 | __all__ = ['AutoFinalizedObject']
34 |
35 |
36 | class _AutoFinalizedObjectBase(object):
37 | """
38 | Base class for objects that get automatically
39 | finalized on delete or at exit.
40 | """
41 |
42 | def _finalize_object(self):
43 | """Actually finalizes the object (frees allocated resources etc.).
44 |
45 | Returns: None
46 |
47 | Derived classes should implement this.
48 | """
49 | pass
50 |
51 | def __new__(cls, *args, **kwargs):
52 | """Creates a new object instance and adds the private finalizer
53 | attributes to it.
54 |
55 | Returns: new object instance
56 |
57 | Arguments:
58 | * *args, **kwargs -- ignored
59 | """
60 | instance = super(_AutoFinalizedObjectBase, cls).__new__(cls)
61 | instance._finalize_called = False
62 | return instance
63 |
64 | def _do_finalize_object(self):
65 | """Helper method that finalizes the object if not already done.
66 |
67 | Returns: None
68 | """
69 | if not self._finalize_called: # race-free?
70 | self._finalize_called = True
71 | self._finalize_object()
72 |
73 | def finalize(self):
74 | """Finalizes the object if not already done.
75 |
76 | Returns: None
77 | """
78 | # this is the "public" finalize method
79 | raise NotImplementedError(
80 | "finalize() must be implemented by AutoFinalizedObject."
81 | )
82 |
83 | def __del__(self):
84 | self.finalize()
85 |
86 |
87 | if sys.hexversion >= 0x3040000:
88 | # python >= 3.4: use weakref.finalize
89 | import weakref
90 |
91 | def _do_finalize_object_ref(obj_ref):
92 | """Helper function for weakref.finalize() that dereferences a weakref
93 | to an object and calls its _do_finalize_object() method if the object
94 | is still alive. Does nothing otherwise.
95 |
96 | Returns: None (implicit)
97 |
98 | Arguments:
99 | * obj_ref -- weakref to an object
100 | """
101 | obj = obj_ref()
102 | if obj is not None:
103 | # else object disappeared
104 | obj._do_finalize_object()
105 |
106 |
107 | class AutoFinalizedObject(_AutoFinalizedObjectBase):
108 |
109 | def __new__(cls, *args, **kwargs):
110 | """Creates a new object instance and adds the private finalizer
111 | attributes to it.
112 |
113 | Returns: new object instance
114 |
115 | Arguments:
116 | * *args, **kwargs -- passed to the parent instance creator
117 | (which ignores them)
118 | """
119 | # Note: Do not pass a (hard) reference to instance to the
120 | # finalizer as func/args/kwargs, it'd keep the object
121 | # alive until the program terminates.
122 | # A weak reference is fine.
123 | #
124 | # Note 2: When using weakrefs and not calling finalize() in
125 | # __del__, the object may already have disappeared
126 | # when weakref.finalize() kicks in.
127 | # Make sure that _finalizer() gets called,
128 | # i.e. keep __del__() from the base class.
129 | #
130 | # Note 3: the _finalize_called attribute is (probably) useless
131 | # for this class
132 | instance = super(AutoFinalizedObject, cls).__new__(
133 | cls, *args, **kwargs
134 | )
135 |
136 | instance._finalizer = weakref.finalize(
137 | instance, _do_finalize_object_ref, weakref.ref(instance)
138 | )
139 |
140 | return instance
141 |
142 | def finalize(self):
143 | """Finalizes the object if not already done."""
144 | self._finalizer()
145 |
146 |
147 | else:
148 | # python < 3.4: keep the old behavior (rely on __del__),
149 | # but don't call _finalize_object() more than once
150 |
151 | class AutoFinalizedObject(_AutoFinalizedObjectBase):
152 |
153 | def finalize(self):
154 | """Finalizes the object if not already done."""
155 | self._do_finalize_object()
156 |
--------------------------------------------------------------------------------
/HISTORY.txt:
--------------------------------------------------------------------------------
1 | # code snippets visited to learn the extension 'effect' interface:
2 | # - http://sourceforge.net/projects/inkcut/
3 | # - http://code.google.com/p/inkscape2tikz/
4 | # - http://wiki.inkscape.org/wiki/index.php/PythonEffectTutorial
5 | # - http://github.com/jnweiger/inkscape-gears-dev
6 | # - http://code.google.com/p/eggbotcode/
7 | # - http://www.bobcookdev.com/inkscape/better_dxf_output.zip
8 | #
9 | # Porting to OSX
10 | # - https://github.com/pmonta/gerber2graphtec/blob/master/file2graphtec
11 | # - https://github.com/pmonta/gerber2graphtec/blob/master/README
12 | #
13 | # 2013-05-09 jw, V0.1 -- initial draught
14 | # 2013-05-10 jw, V0.2 -- can plot simple cases without transforms.
15 | # 2013-05-11 jw, V0.3 -- still using inkcut/plot.py -- fixed write(),
16 | # improved logging, flipped y-axis.
17 | # 2013-05-12 jw, v0.4 -- No unintended multipass when nothing is selected.
18 | # Explicit multipass option added.
19 | # Emplying recursivelyTraverseSvg() from eggbotcode
20 | # TODO: coordinate system of page is not exact.
21 | # 2013-05-13 jw, v0.5 -- transporting docWidth/docHeight to dev.plot()
22 | # 2013-05-15 jw, v0.6 -- Replaced recursivelyTraverseSvg() and friends with the
23 | # versions from eggbot.py, those from eggbot_hatch.py
24 | # would only do closed paths. Makes sense for them, but
25 | # not for us.
26 | # Added no_device=True debugging aid to SilhouetteCameo()
27 | # 2013-05-17 jw, v0.7 -- Honor layer visibility by checking style="display:none"
28 | # penUP()/penDown() bugfix to avoid false connections between objects.
29 | # Added option reversetoggle, to cut the opposite direction.
30 | # 2013-05-19 jw, v0.8 -- Split GUI into two pages. Added dummy and mat-free checkboxes.
31 | # misc/corner_detect.py done, can now load a dump saved by dummy.
32 | # Udev rules and script added, so that we get a nice notify
33 | # guiding users towards inkscape, when connecting a device.
34 | # 2013-05-25 jw, v0.9 -- mat_free option added. The slicing and sharp corner strategy
35 | # appears useful.
36 | # 2013-05-26 jw, v1.0 -- Some tuning done. fixed preset scaling, improved path recombination.
37 | # 2013-05-26 jw, v1.1 -- Strategy.MatFree.path_overshoot() added. With 0.2mm overshoot
38 | # the paper now comes apart almost by itself. great.
39 | # Buffer percent estimation added. We now have an estimate
40 | # how long the buffered data will need.
41 | # 2013-05-30 jw, v1.2 -- Option autocrop added. Speed improvement: only parse visible layers.
42 | # 2013-05-31 jw, v1.3 -- sharp_turn() now takes self.sharp_turn_fwd_ratio parameter.
43 | # test_drive.py now draws arrows. All [0], [1] converted to new .x, .y syntax.
44 | # Split Geometry.py from Strategy.py; class Barrier implemented.
45 | # 2013-10-24 jw, v1.4 -- Fixed an abort in Strategy. when pt has no seg.
46 | # 2013-11-02 jw, v1.5 -- Added protability code. This might eventually lead to
47 | # working code on windows and macosx too. Still linux only.
48 | # 2013-11-08 jw, v1.6 -- supporting mm in getLength().
49 | # 2013-12-16 jw, v1.7 -- https://github.com/jnweiger/inkscape-silhouette/issues/1
50 | # fixed. Silly copy/paste bug. Looks like I miss a testsuite.
51 | # 2014-01-23 jw, v1.8 -- improving portability by using os.devnull, os.path.join, tempfile.
52 | # Partial fixes for https://github.com/jnweiger/inkscape-silhouette/issues/2
53 | # Enumerating devices if none are found.
54 | # 2014-01-28 jw, v1.9 -- We cannot expect posix semantics from windows.
55 | # Experimental retry added when write returns 0.
56 | # issues/2#issuecomment-33526659
57 | # 2014-02-04 jw, v1.9a -- new default: matfree false, about page added.
58 | # 2014-03-29 jw, v1.9b -- added own dir to sys.path for issue#3.
59 | # 2014-04-06 jw, v1.9c -- attempted workaround for issue#4
60 | # 2014-07-18 jw, v1.9d -- better diagnostics. hints *and* (further down) a stack backtrace.
61 | # 2014-09-18 jw, v1.10 -- more diagnostics, fixed trim margins aka autocrop to still honor hardware margins.
62 | # 2014-10-11 jw, v1.11 -- no more complaints about empty elements. Ignoring
63 | # 2014-10-25 jw, v1.12 -- better error messages.
64 | # 2014-10-31 jw, v1.13 -- fixed usb.core.write() without interface parameter. Set Graphtec.py/need_interface if needed.
65 | # 2015-06-06 jw, v1.14 -- fixed timout errors, refactored much code.
66 | # Added misc/silhouette_move.py misc/silhouette_cut.py, misc/endless_clock.py
67 | # 2016-01-15 jw, v1.15 -- ubuntu loads the wrong usb library.
68 | # 2016-05-15 jw, v1.16 -- merged regmarks code from https://github.com/fablabnbg/inkscape-silhouette/pull/23
69 | # 2016-05-17 jw, v1.17 -- fix avoid dev.reset in Graphtec.py, fix helps with
70 | # https://github.com/fablabnbg/inkscape-silhouette/issues/10
71 | # 2016-05-21 jw, v1.18 -- warn about python-usb < 1.0 and give instructions.
72 | # Limit pressure to 18. 19 or 20 make the machine
73 | # scroll forward backward for several minutes.
74 | # Support document unit inches. https://github.com/fablabnbg/inkscape-silhouette/issues/19
75 | # 2016-12-18 jw, v1.19 -- support for dashed lines added. Thanks to mehtank
76 | # https://github.com/fablabnbg/inkscape-silhouette/pull/33
77 | # Added new cutting strategy "Minimized Traveling"
78 | # Added parameter for blade diameter
79 | # 2018-06-01 jw, v1.20 -- Make it compile again. Hmm.
80 | # 2019-07-25 jw, v1.21 -- merge from github.com/olegdeezus/inkscape-silhouette
81 | # merge from fablabnbg
82 | # 2019-08-03 jw, v1.22 -- added a copy of pyusb-1.0.2 as a fallback on any platform.
83 | # 2020-07-01 uw, v1.23 -- port to inkscape version 1.00
84 | # 2020-12-29 tb, v1.24 -- restore compatiblity with any inkscape version, add automated tests for win, osx, linux, lots of bugfixes
85 | # 2021-06-08 tb, v1.25 -- add cameo 4 plus and cameo 4 pro, add preview feature, allow speed 30
86 | # 2022-05-29 tb, v1.26 -- support inkscape 1.2, add portrait 3, new mats, restore silhouette multi
87 |
88 |
--------------------------------------------------------------------------------
/silhouette/beutil.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Conversion utilities for printer's binary encoding commands
4 | # (c) 2018 D. Bajar
5 |
6 | import sys
7 |
8 |
9 | def to_BE(x, y):
10 | if abs(x) < 112 and abs(y) < 112:
11 | index = 224 * (x + 112) + (y + 112)
12 | d2 = index // 224
13 | index -= d2 * 224
14 | d1 = index
15 | be1 = "%02X%02X" % (d1 + 0x20, d2 + 0x20)
16 | # print("BE1:", be1)
17 | return ("BE1", be1)
18 | elif abs(x) < 1676 and abs(y) < 1676:
19 | index = 3352 * (x + 1676) + (y + 1676)
20 | d3 = index // (224 * 224)
21 | index -= d3 * (224 * 224)
22 | d2 = index // 224
23 | index -= d2 * 224
24 | d1 = index
25 | be2 = "%02X%02X%02X" % (d1 + 0x20, d2 + 0x20, d3 + 0x20)
26 | # print("BE2:", be2)
27 | return ("BE2", be2)
28 | elif abs(x) < 375482 and abs(y) < 375482:
29 | index = 750964 * (x + 375482) + (y + 375482)
30 | d5 = index // (224 * 224 * 224 * 224)
31 | index -= d5 * (224 * 224 * 224 * 224)
32 | d4 = index // (224 * 224 * 224)
33 | index -= d4 * (224 * 224 * 224)
34 | d3 = index // (224 * 224)
35 | index -= d3 * (224 * 224)
36 | d2 = index // 224
37 | index -= d2 * 224
38 | d1 = index
39 | be3 = "%02X%02X%02X%02X%02X" % (d1 + 0x20, d2 + 0x20, d3 + 0x20, d4 + 0x20, d5 + 0x20)
40 | # print("BE3:", be3)
41 | return ("BE3", be3)
42 | else:
43 | raise ValueError("Invalid coordinate")
44 | # end if
45 | # end def to_BE
46 |
47 |
48 | def from_BE(be_stream):
49 |
50 | if len(be_stream) == 4:
51 | d1 = int(be_stream[:2], 16)
52 | d2 = int(be_stream[2:], 16)
53 | if d1 < 0x20 or d2 < 0x20:
54 | raise ValueError("Invalid BE1 stream digit")
55 | # end if
56 | index = (d2 - 0x20) * 224 + (d1 - 0x20)
57 | x = index // 224 - 112
58 | y = index % 224 - 112
59 | # print("BE1: %d,%d" % (x, y))
60 | return ("BE1", (x, y))
61 | elif len(be_stream) == 6:
62 | d1 = int(be_stream[0:2], 16)
63 | d2 = int(be_stream[2:4], 16)
64 | d3 = int(be_stream[4:6], 16)
65 | if d1 < 0x20 or d2 < 0x20 or d3 < 0x20:
66 | raise ValueError("Invalid BE2 stream digit")
67 | # end if
68 | index = (d3 - 0x20) * (224 * 224) + (d2 - 0x20) * 224 + (d1 - 0x20)
69 | x = index // 3352 - 1676
70 | y = index % 3352 - 1676
71 | # print("BE2: %d,%d" % (x, y))
72 | return ("BE2", (x, y))
73 | elif len(be_stream) == 10:
74 | d1 = int(be_stream[0:2], 16)
75 | d2 = int(be_stream[2:4], 16)
76 | d3 = int(be_stream[4:6], 16)
77 | d4 = int(be_stream[6:8], 16)
78 | d5 = int(be_stream[8:10], 16)
79 | if d1 < 0x20 or d2 < 0x20 or d3 < 0x20 or d4 < 0x20 or d5 < 0x20:
80 | raise ValueError("Invalid BE3 stream digit")
81 | # end if
82 | index = (d5 - 0x20) * (224 * 224 * 224 * 224) + (d4 - 0x20) * (224 * 224 * 224) + (d3 - 0x20) * (224 * 224) + (d2 - 0x20) * 224 + (d1 - 0x20)
83 | x = index // 750964 - 375482
84 | y = index % 750964 - 375482
85 | # print("BE3: %d,%d" % (x, y))
86 | return ("BE3", (x, y))
87 | else:
88 | raise ValueError("Invalid length hex stream")
89 | # end if
90 |
91 | # end def from_BE
92 |
93 |
94 | def test_BE(x, y, be_stream, be_enc):
95 |
96 | enc, stream = to_BE(x, y)
97 | passed = True if enc == be_enc and stream == be_stream else False
98 | print("to_BE: (%d, %d) -> '%s' %s= '%s' : %s" % (x, y, stream, '=' if passed else '!', be_stream, "PASSED" if passed else "FAILED"))
99 | if not passed:
100 | sys.exit(-1)
101 | # end if
102 |
103 | enc, xy = from_BE(be_stream)
104 | passed = True if enc == be_enc and xy == (x, y) else False
105 | print("from_BE: '%s -> (%d, %d) %s= (%d, %d) : %s" % (be_stream, xy[0], xy[1], '=' if passed else '!', x, y, "PASSED" if passed else "FAILED"))
106 | if not passed:
107 | sys.exit(-1)
108 | # end if
109 |
110 | # end def test_BE
111 |
112 |
113 | def test():
114 |
115 | print("Running tests ...")
116 |
117 | test_BE( 0, 0, "9090", "BE1")
118 | test_BE( 0, 1, "9190", "BE1")
119 | test_BE( 1, 0, "9091", "BE1")
120 | test_BE( 0, -1, "8F90", "BE1")
121 | test_BE( -1, 0, "908F", "BE1")
122 | test_BE( 1, 1, "9191", "BE1")
123 | test_BE( 1, -1, "8F91", "BE1")
124 | test_BE( -1, -1, "8F8F", "BE1")
125 | test_BE( -1, 1, "918F", "BE1")
126 | test_BE( 0, 111, "FF90", "BE1")
127 | test_BE( 111, 0, "90FF", "BE1")
128 | test_BE( 0, -111, "2190", "BE1")
129 | test_BE(-111, 0, "9021", "BE1")
130 | test_BE( 111, 111, "FFFF", "BE1")
131 | test_BE( 111, -111, "21FF", "BE1")
132 | test_BE(-111, -111, "2121", "BE1")
133 | test_BE(-111, 111, "FF21", "BE1")
134 | test_BE( 56, -27, "75C8", "BE1")
135 | test_BE( -77, 44, "BC43", "BE1")
136 | test_BE( -39, 106, "FA69", "BE1")
137 | test_BE( 72, -25, "77D8", "BE1")
138 |
139 | test_BE( 0, 112, "3C2090", "BE2")
140 | test_BE( 112, 0, "AC8B97", "BE2")
141 | test_BE( 0, -112, "3CFF8F", "BE2")
142 | test_BE( -112, 0, "AC9388", "BE2")
143 | test_BE( 112, 112, "3C8C97", "BE2")
144 | test_BE( 112, -112, "3C8B97", "BE2")
145 | test_BE( -112, -112, "3C9388", "BE2")
146 | test_BE( -112, 112, "3C9488", "BE2")
147 | test_BE( 0, 1675, "372790", "BE2")
148 | test_BE( 1675, 0, "D4E8FF", "BE2")
149 | test_BE( 0, -1675, "41F88F", "BE2")
150 | test_BE(-1675, 0, "843620", "BE2")
151 | test_BE( 1675, 1675, "5FF0FF", "BE2")
152 | test_BE( 1675, -1675, "69E1FF", "BE2")
153 | test_BE(-1675, -1675, "F92E20", "BE2")
154 | test_BE(-1675, 1675, "EF3D20", "BE2")
155 | test_BE( 1091, 674, "B6E8D8", "BE2")
156 | test_BE( 116, 1421, "D9CD97", "BE2")
157 | test_BE( -702, 485, "E13861", "BE2")
158 | test_BE(-1463, -1153, "C3552E", "BE2")
159 |
160 | print("All test PASSED !!!")
161 | sys.exit(0)
162 |
163 | # end def test
164 |
165 |
166 | def main(argv):
167 |
168 | # test()
169 |
170 | if len(argv) <= 1:
171 | print("Usage: %s | " % (argv[0]))
172 | return 1
173 | # end if
174 |
175 | if len(argv) <= 2:
176 | be_stream = argv[1]
177 | res = from_BE(be_stream)
178 | print("%s %s -> %d,%d" % (res[0], be_stream, res[1][0], res[1][1]))
179 | else:
180 | x = int(argv[1], 0)
181 | y = int(argv[2], 0)
182 | res = to_BE(x, y)
183 | print("%d,%d -> %s %s" % (x, y, res[0], res[1]))
184 | # end if
185 |
186 | return 0
187 |
188 | # end def main
189 |
190 |
191 | if __name__ == '__main__':
192 | sys.exit(main(sys.argv))
193 | # end if
194 |
--------------------------------------------------------------------------------
/test/test_run.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import unittest
4 | import subprocess
5 | import sys
6 | import os
7 |
8 | from tempfile import gettempdir
9 |
10 | class TestRun(unittest.TestCase):
11 |
12 | def test_00import_inkex(self):
13 | try:
14 | result = subprocess.check_output([sys.executable, "-c", "import sendto_silhouette;import inkex;print(inkex)"], stderr=subprocess.STDOUT)
15 | #print(result.decode())
16 | except subprocess.CalledProcessError as e:
17 | print(e.output.decode())
18 | print(e)
19 | self.assertEqual(e.returncode, 0)
20 | assert False
21 |
22 |
23 | def test_01help(self):
24 | try:
25 | result = subprocess.check_output([sys.executable, "sendto_silhouette.py", "--help"], stderr=subprocess.STDOUT)
26 | #print(result.decode())
27 | self.assertIn('sage: sendto_silhouette.py', str(result))
28 | self.assertIn('--help', str(result))
29 | except subprocess.CalledProcessError as e:
30 | print(e.output.decode())
31 | print(e)
32 | self.assertEqual(e.returncode, 0)
33 | assert False
34 |
35 |
36 | def test_02version(self):
37 | try:
38 | result = subprocess.check_output([sys.executable, "sendto_silhouette.py", "--version"], stderr=subprocess.STDOUT)
39 | #print(result.decode())
40 | self.assertIn('1.', str(result))
41 | except subprocess.CalledProcessError as e:
42 | print(e.output.decode())
43 | print(e)
44 | self.assertEqual(e.returncode, 0)
45 | assert False
46 |
47 |
48 | def test_03run(self):
49 | try:
50 | result = subprocess.check_output([sys.executable, "sendto_silhouette.py"], stderr=subprocess.STDOUT)
51 | print(result.decode())
52 | except subprocess.CalledProcessError as e:
53 | print(e.output.decode())
54 | print(e)
55 | self.assertEqual(e.returncode, 0)
56 | assert False
57 |
58 |
59 | def test_04dry_run(self):
60 | try:
61 | result = subprocess.check_output([sys.executable, "sendto_silhouette.py", "--dry_run=True", "--preview=False", "--log_paths=True", "--logfile=silhouette.log", "examples/testcut_square_triangle.svg"], stderr=subprocess.STDOUT)
62 | print(result.decode())
63 | filehandle = open("silhouette.log",'r')
64 | log = filehandle.read()
65 | filehandle.close()
66 | self.assertIn('driver version', log)
67 | except subprocess.CalledProcessError as e:
68 | print(e.output.decode())
69 | print(e)
70 | self.assertEqual(e.returncode, 0)
71 | assert False
72 |
73 |
74 | def test_05dry_run(self):
75 | try:
76 | result = subprocess.check_output([sys.executable, "sendto_silhouette.py", "--dry_run=True", "--preview=False", "--log_paths=True", "--logfile=silhouette.log", "examples/testcut_square_triangle_o.svg"], stderr=subprocess.STDOUT)
77 | print(result.decode())
78 | filehandle = open("silhouette.log",'r')
79 | log = filehandle.read()
80 | filehandle.close()
81 | self.assertIn('driver version', log)
82 | except subprocess.CalledProcessError as e:
83 | print(e.output.decode())
84 | print(e)
85 | self.assertEqual(e.returncode, 0)
86 | assert False
87 |
88 |
89 | def test_06dry_run(self):
90 | try:
91 | result = subprocess.check_output([sys.executable, "sendto_silhouette.py", "--dry_run=True", "--preview=False", "--log_paths=True", "--logfile=silhouette.log", "--output=silhouette.svg", "examples/sharp_turns.svg"], stderr=subprocess.STDOUT)
92 | print(result.decode())
93 | filehandle = open("silhouette.log",'r')
94 | log = filehandle.read()
95 | filehandle.close()
96 | self.assertIn('driver version', log)
97 | except subprocess.CalledProcessError as e:
98 | print(e.output.decode())
99 | print(e)
100 | self.assertEqual(e.returncode, 0)
101 | assert False
102 |
103 |
104 | def test_07dry_run(self):
105 | try:
106 | result = subprocess.check_output([sys.executable, "sendto_silhouette.py", "--dashes=True", "--preview=False", "--dry_run=True", "--log_paths=True", "--logfile=silhouette.log", "--output=silhouette.svg", "examples/dashline.svg"], stderr=subprocess.STDOUT)
107 | print(result.decode())
108 | filehandle = open("silhouette.log",'r')
109 | log = filehandle.read()
110 | filehandle.close()
111 | self.assertIn('driver version', log)
112 | except subprocess.CalledProcessError as e:
113 | print(e.output.decode())
114 | print(e)
115 | self.assertEqual(e.returncode, 0)
116 | assert False
117 |
118 | def test_08cmd_file(self):
119 | try:
120 | result = subprocess.check_output([sys.executable, "sendto_silhouette.py", "--dry_run=True", "--preview=False", "--cmdfile=testcut_square_triangle_o.cmd", "--force_hardware=Silhouette SD 1", "examples/testcut_square_triangle_o.svg"], stderr=subprocess.STDOUT)
121 | print(result.decode())
122 | filehandle = open("examples/testcut_square_triangle_o.cmd",'r')
123 | cmdref = filehandle.read()
124 | filehandle.close()
125 | filehandle = open("testcut_square_triangle_o.cmd",'r')
126 | cmd = filehandle.read()
127 | filehandle.close()
128 | self.assertEqual(cmdref, cmd)
129 | except subprocess.CalledProcessError as e:
130 | print(e.output.decode())
131 | print(e)
132 | self.assertEqual(e.returncode, 0)
133 | assert False
134 |
135 | def test_09dry_run(self):
136 | try:
137 | result = subprocess.check_output([sys.executable, "sendto_silhouette.py", "--dry_run=True", "--preview=False", "--strategy=matfree", "--logfile=silhouette.log", "examples/testcut_matfree.svg"], stderr=subprocess.STDOUT)
138 | print(result.decode())
139 | filehandle = open("silhouette.log",'r')
140 | log = filehandle.read()
141 | filehandle.close()
142 | self.assertIn('matfree', log)
143 | except subprocess.CalledProcessError as e:
144 | print(e.output.decode())
145 | print(e)
146 | self.assertEqual(e.returncode, 0)
147 | assert False
148 |
149 | def test_10dry_run(self):
150 | try:
151 | # Test that default temporary log filepath is writable
152 | result = subprocess.check_output([sys.executable, "sendto_silhouette.py", "--dry_run=True", "--preview=False", "--strategy=matfree", "examples/testcut_matfree.svg"], stderr=subprocess.STDOUT)
153 | print(result.decode())
154 | filehandle = open(os.path.join(gettempdir(), "silhouette.log"),'r')
155 | log = filehandle.read()
156 | filehandle.close()
157 | self.assertIn('matfree', log)
158 | except subprocess.CalledProcessError as e:
159 | print(e.output.decode())
160 | print(e)
161 | self.assertEqual(e.returncode, 0)
162 | assert False
163 |
--------------------------------------------------------------------------------
/silhouette/pyusb-1.0.2/usb/libloader.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Copyright (C) 2013-2014 André Erdmann
4 | #
5 | # The following terms apply to all files associated
6 | # with the software unless explicitly disclaimed in individual files.
7 | #
8 | # The authors hereby grant permission to use, copy, modify, distribute,
9 | # and license this software and its documentation for any purpose, provided
10 | # that existing copyright notices are retained in all copies and that this
11 | # notice is included verbatim in any distributions. No written agreement,
12 | # license, or royalty fee is required for any of the authorized uses.
13 | # Modifications to this software may be copyrighted by their authors
14 | # and need not follow the licensing terms described here, provided that
15 | # the new terms are clearly indicated on the first page of each file where
16 | # they apply.
17 | #
18 | # IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
19 | # FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
20 | # ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
21 | # DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
22 | # POSSIBILITY OF SUCH DAMAGE.
23 | #
24 | # THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
25 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
26 | # FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
27 | # IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
28 | # NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
29 | # MODIFICATIONS.
30 |
31 | import ctypes
32 | import ctypes.util
33 | import logging
34 | import sys
35 |
36 | __all__ = [
37 | 'LibraryException',
38 | 'LibraryNotFoundException',
39 | 'NoLibraryCandidatesException',
40 | 'LibraryNotLoadedException',
41 | 'LibraryMissingSymbolsException',
42 | 'locate_library',
43 | 'load_library',
44 | 'load_locate_library'
45 | ]
46 |
47 |
48 | _LOGGER = logging.getLogger('usb.libloader')
49 |
50 |
51 | class LibraryException(OSError):
52 | pass
53 |
54 | class LibraryNotFoundException(LibraryException):
55 | pass
56 |
57 | class NoLibraryCandidatesException(LibraryNotFoundException):
58 | pass
59 |
60 | class LibraryNotLoadedException(LibraryException):
61 | pass
62 |
63 | class LibraryMissingSymbolsException(LibraryException):
64 | pass
65 |
66 |
67 | def locate_library (candidates, find_library=ctypes.util.find_library):
68 | """Tries to locate a library listed in candidates using the given
69 | find_library() function (or ctypes.util.find_library).
70 | Returns the first library found, which can be the library's name
71 | or the path to the library file, depending on find_library().
72 | Returns None if no library is found.
73 |
74 | arguments:
75 | * candidates -- iterable with library names
76 | * find_library -- function that takes one positional arg (candidate)
77 | and returns a non-empty str if a library has been found.
78 | Any "false" value (None,False,empty str) is interpreted
79 | as "library not found".
80 | Defaults to ctypes.util.find_library if not given or
81 | None.
82 | """
83 | if find_library is None:
84 | find_library = ctypes.util.find_library
85 |
86 | use_dll_workaround = (
87 | sys.platform == 'win32' and find_library is ctypes.util.find_library
88 | )
89 |
90 | for candidate in candidates:
91 | # Workaround for CPython 3.3 issue#16283 / pyusb #14
92 | if use_dll_workaround:
93 | candidate += '.dll'
94 |
95 | libname = find_library(candidate)
96 | if libname:
97 | return libname
98 | # -- end for
99 | return None
100 |
101 | def load_library(lib, name=None, lib_cls=None):
102 | """Loads a library. Catches and logs exceptions.
103 |
104 | Returns: the loaded library or None
105 |
106 | arguments:
107 | * lib -- path to/name of the library to be loaded
108 | * name -- the library's identifier (for logging)
109 | Defaults to None.
110 | * lib_cls -- library class. Defaults to None (-> ctypes.CDLL).
111 | """
112 | try:
113 | if lib_cls:
114 | return lib_cls(lib)
115 | else:
116 | return ctypes.CDLL(lib)
117 | except Exception:
118 | if name:
119 | lib_msg = '%s (%s)' % (name, lib)
120 | else:
121 | lib_msg = lib
122 |
123 | lib_msg += ' could not be loaded'
124 |
125 | if sys.platform == 'cygwin':
126 | lib_msg += ' in cygwin'
127 | _LOGGER.error(lib_msg, exc_info=True)
128 | return None
129 |
130 | def load_locate_library(candidates, cygwin_lib, name,
131 | win_cls=None, cygwin_cls=None, others_cls=None,
132 | find_library=None, check_symbols=None):
133 | """Locates and loads a library.
134 |
135 | Returns: the loaded library
136 |
137 | arguments:
138 | * candidates -- candidates list for locate_library()
139 | * cygwin_lib -- name of the cygwin library
140 | * name -- lib identifier (for logging). Defaults to None.
141 | * win_cls -- class that is used to instantiate the library on
142 | win32 platforms. Defaults to None (-> ctypes.CDLL).
143 | * cygwin_cls -- library class for cygwin platforms.
144 | Defaults to None (-> ctypes.CDLL).
145 | * others_cls -- library class for all other platforms.
146 | Defaults to None (-> ctypes.CDLL).
147 | * find_library -- see locate_library(). Defaults to None.
148 | * check_symbols -- either None or a list of symbols that the loaded lib
149 | must provide (hasattr(<>)) in order to be considered
150 | valid. LibraryMissingSymbolsException is raised if
151 | any symbol is missing.
152 |
153 | raises:
154 | * NoLibraryCandidatesException
155 | * LibraryNotFoundException
156 | * LibraryNotLoadedException
157 | * LibraryMissingSymbolsException
158 | """
159 | if sys.platform == 'cygwin':
160 | if cygwin_lib:
161 | loaded_lib = load_library(cygwin_lib, name, cygwin_cls)
162 | else:
163 | raise NoLibraryCandidatesException(name)
164 | elif candidates:
165 | lib = locate_library(candidates, find_library)
166 | if lib:
167 | if sys.platform == 'win32':
168 | loaded_lib = load_library(lib, name, win_cls)
169 | else:
170 | loaded_lib = load_library(lib, name, others_cls)
171 | else:
172 | _LOGGER.error('%r could not be found', (name or candidates))
173 | raise LibraryNotFoundException(name)
174 | else:
175 | raise NoLibraryCandidatesException(name)
176 |
177 | if loaded_lib is None:
178 | raise LibraryNotLoadedException(name)
179 | elif check_symbols:
180 | symbols_missing = [
181 | s for s in check_symbols if not hasattr(loaded_lib, s)
182 | ]
183 | if symbols_missing:
184 | msg = ('%r, missing symbols: %r', lib, symbols_missing )
185 | _LOGGER.error(msg)
186 | raise LibraryMissingSymbolsException(lib)
187 | else:
188 | return loaded_lib
189 | else:
190 | return loaded_lib
191 |
--------------------------------------------------------------------------------
/examples/registration-marks/inkscape-0.91/registration-marks-cameo-silhouette.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
182 |
--------------------------------------------------------------------------------
/examples/gecko_silhouette.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
129 |
--------------------------------------------------------------------------------
/test/data/multi.commands:
--------------------------------------------------------------------------------
1 | sendto_silhouette.py --x_off=0.0 --y_off=0.0 --cuttingmat=no_mat --toolholder=1 --tool=cut --media=300 --speed=12 --pressure=1 --depth=-1 --preview=True --dashes=False --autocrop=False --bbox-only=False --multipass=1 --reversetoggle=False --endposition=start --end_offset=0.0 --regmark=False --regsearch=False --regwidth=180.0 --reglength=230.0 --regoriginx=15.0 --regoriginy=20.0 --wait_done=False --sharpencorners=False --sharpencorners_start=0.1 --sharpencorners_end=0.1 --overcut=0.5 --strategy=zorder --orient_paths=natural --fuse_paths=False --sw_clipping=False --logfile=/home/glen/jnk/istest.log --log_paths=False --cmdfile=/home/glen/jnk/istest.gpgl --inc_queries=False --append_logs=False --dry_run=True --force_hardware=Silhouette_Cameo4_Pro --bladediameter=0.9 --id=line680 --id=line682 --id=line684 --id=line686 --id=line688 --id=line690 --id=line692 --id=line694 --id=line696 --id=line698 --id=line700 --id=line702 --id=line704 --id=line706 --id=line708 --id=line710 --id=line712 --id=line714 --id=line716 --id=line718 --id=line720 --id=line722 --id=line724 --id=line726 --id=line728 --id=line730 --id=line732 --id=line734 --id=line736 --id=line738 --id=line740 --id=line742 --id=line744 --id=line746 --id=line748 --id=line750 --id=line752 --id=line754 --id=line756 --id=line758 --id=line760 --id=line762 --id=line764 --id=line766 --id=line768 --id=line770 --id=line772 --id=line774 --id=line776 --id=line778 --id=line780 --id=line782 --id=line784 --id=line786 --id=line788 --id=line790 --id=line792 --id=line794 --id=line796 --id=line798 --id=line800 --id=line802 --id=line804 --id=line806 --id=line808 --id=line810 --id=line812 --id=line814 --id=line816 --id=line818 --id=line820 --id=line822 --id=line824 --id=line826 --id=line828 --id=line830 --id=line832 --id=line834 --id=line836 --id=line838 --id=line840 --id=line842 --id=line844 --id=line846 --id=line848 --id=line850 --id=line852 --id=line854 --id=line856 --id=line858 --id=line860 --id=line862 --id=line864 --id=line866 --id=line868 --id=line870 --id=line872 --id=line874 --id=line876 --id=line878 --id=line880 --id=line882 --id=line884 --id=line886 --id=line888 --id=line890 --id=line892 --id=line894 --id=line896 --id=line898 --id=line900 --id=line902 --id=line904 --id=line906 --id=line908 --id=line910 --id=line912 --id=line914 --id=line916 --id=line918 --id=line920 --id=line922 --id=line924 --id=line926 --id=line928 --id=line930 --id=line932 --id=line934 --id=line936 --id=line938 --id=line940 --id=line942 --id=line944 --id=line946 --id=line948 --id=line950 --id=line952 --id=line954 --id=line956 --id=line958 --id=line960 --id=line962 --id=line964 --id=line966 --id=line968 --id=line970 --id=line972 --id=line974 --id=line976 --id=line978 --id=line980 --id=line982 --id=line984 --id=line986 --id=line988 --id=line990 --id=line992 --id=line994 --id=line996 --id=line998 --id=line1000 --id=line1002 --id=line1004 --id=line1006 --id=line1008 --id=line1010 --id=line1012 --id=line1014 --id=line1016 --id=line1018 --id=line1020 --id=line1022 --id=line1024 --id=line1026 --id=line1028 --id=line1030 --id=line1032 --id=line1034 --id=line1036 --id=line1038 --id=line1040 --id=line1042 --id=line1044 --id=line1046 --id=line1048 --id=line1050 --id=line1052 --id=line1054 --id=line1056 --id=line1058 --id=line1060 --id=line1062 --id=line1064 --id=line1066 --id=line1068 --id=line1070 --id=line1072 --id=line1074 --id=line1076 --id=line1078 --id=line1080 --id=line1082 --id=line1084 --id=line1086 --id=line1088 --id=line1090 --id=line1092 --id=line1094 --id=line1096 --id=line1098 --id=line1100 --id=line1102 --id=line1104 --id=line1106 --id=line1108 --id=line1110 --id=line1112 --id=line1114 --id=line1116 --id=line1118 --id=line1120 --id=line1122
2 |
3 | sendto_silhouette.py --x_off=0.0 --y_off=0.0 --cuttingmat=no_mat --toolholder=1 --tool=cut --media=300 --speed=2 --pressure=29 --depth=-1 --preview=True --dashes=False --autocrop=False --bbox-only=False --multipass=1 --reversetoggle=False --endposition=start --end_offset=0.0 --regmark=False --regsearch=False --regwidth=180.0 --reglength=230.0 --regoriginx=15.0 --regoriginy=20.0 --wait_done=False --sharpencorners=False --sharpencorners_start=0.1 --sharpencorners_end=0.1 --overcut=0.5 --strategy=zorder --orient_paths=natural --fuse_paths=False --sw_clipping=False --logfile=/home/glen/jnk/istest.log --log_paths=False --cmdfile=/home/glen/jnk/istest.gpgl --inc_queries=False --append_logs=True --dry_run=True --force_hardware=Silhouette_Cameo4_Pro --bladediameter=0.9 --id=defs1130 --id=namedview1128 --id=line2 --id=line4 --id=line6 --id=line8 --id=line10 --id=line12 --id=line14 --id=line16 --id=line18 --id=line20 --id=line22 --id=line24 --id=line30 --id=line32 --id=line34 --id=line36 --id=line38 --id=line40 --id=line42 --id=line44 --id=line46 --id=line48 --id=line50 --id=line52 --id=line54 --id=line56 --id=line58 --id=line60 --id=line62 --id=line64 --id=line66 --id=line68 --id=line70 --id=line72 --id=line74 --id=line76 --id=path78 --id=path80 --id=path82 --id=line84 --id=line86 --id=line88 --id=line90 --id=line92 --id=line94 --id=line96 --id=line98 --id=path100 --id=line102 --id=line104 --id=line106 --id=line108 --id=line110 --id=line112 --id=path114 --id=line116 --id=line118 --id=line120 --id=line122 --id=line124 --id=line126 --id=path128 --id=path130 --id=path132 --id=path134 --id=line136 --id=line138 --id=path140 --id=path142 --id=path144 --id=line146 --id=line148 --id=path150 --id=path152 --id=path154 --id=line156 --id=line158 --id=path160 --id=path162 --id=path164 --id=line166 --id=line168 --id=path170 --id=path172 --id=path174 --id=line176 --id=line178 --id=line180 --id=line182 --id=line184 --id=line186 --id=line188 --id=line190 --id=path192 --id=line194 --id=line196 --id=line198 --id=line200 --id=line202 --id=line204 --id=path206 --id=line208 --id=line210 --id=line212 --id=line214 --id=line216 --id=line218 --id=line220 --id=line222 --id=line224 --id=line226 --id=line228 --id=line230 --id=line232 --id=line234 --id=line236 --id=line238 --id=line240 --id=line242 --id=path244 --id=line246 --id=line248 --id=path250 --id=line252 --id=line254 --id=path256 --id=line258 --id=line260 --id=path262 --id=line264 --id=line266 --id=path268 --id=line270 --id=line272 --id=path274 --id=line276 --id=line278 --id=path280 --id=line282 --id=line284 --id=path286 --id=line288 --id=line290 --id=path292 --id=line294 --id=line296 --id=path298 --id=line300 --id=line302 --id=path304 --id=line306 --id=line308 --id=path310 --id=line312 --id=line314 --id=path316 --id=path318 --id=path320 --id=path322 --id=line324 --id=line326 --id=line328 --id=line330 --id=line332 --id=line334 --id=line336 --id=line338 --id=path340 --id=line342 --id=line344 --id=line346 --id=line348 --id=line350 --id=line352 --id=path354 --id=line356 --id=line358 --id=line360 --id=line362 --id=line364 --id=line366 --id=path368 --id=path370 --id=path372 --id=path374 --id=line376 --id=line378 --id=path380 --id=path382 --id=path384 --id=line386 --id=line388 --id=path390 --id=path392 --id=path394 --id=line396 --id=line398 --id=path400 --id=path402 --id=path404 --id=line406 --id=line408 --id=path410 --id=path412 --id=path414 --id=line416 --id=line418 --id=line420 --id=line422 --id=line424 --id=line426 --id=line428 --id=line430 --id=path432 --id=line434 --id=line436 --id=line438 --id=line440 --id=line442 --id=line444 --id=path446 --id=line448 --id=line450 --id=line452 --id=line454 --id=line456 --id=line458 --id=line460 --id=line462 --id=line464 --id=line466 --id=line468 --id=line470 --id=line472 --id=line474 --id=line476 --id=line478 --id=line480 --id=line482 --id=path484 --id=line486 --id=line488 --id=path490 --id=line492 --id=line494 --id=path496 --id=line498 --id=line500 --id=path502 --id=line504 --id=line506 --id=path508 --id=line510 --id=line512 --id=path514 --id=line516 --id=line518 --id=path520 --id=line522 --id=line524 --id=path526 --id=line528 --id=line530 --id=path532 --id=line534 --id=line536 --id=path538 --id=line540 --id=line542 --id=path544 --id=line546 --id=line548 --id=path550 --id=line552 --id=line554 --id=path556 --id=path558 --id=path560 --id=path562 --id=line564 --id=line566 --id=path568 --id=path570 --id=path572 --id=line574 --id=line576 --id=path578 --id=path580 --id=path582 --id=line584 --id=line586 --id=path588 --id=path590 --id=path592 --id=line594 --id=line596 --id=path598 --id=path600 --id=path602 --id=line604 --id=line606 --id=path608 --id=path610 --id=path612 --id=line614 --id=line616 --id=path618 --id=path620 --id=path622 --id=line624 --id=line626 --id=path628 --id=path630 --id=path632 --id=line634 --id=line636 --id=path638 --id=path640 --id=path642 --id=line644 --id=line646 --id=path648 --id=path650 --id=path652 --id=line654 --id=line656 --id=path658 --id=path660 --id=path662 --id=line664 --id=line666 --id=path668 --id=path670 --id=path672 --id=line674 --id=line676
4 |
--------------------------------------------------------------------------------
/render_silhouette_regmarks.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (C) 2021 miLORD1337
3 | #
4 | # This program is free software; you can redistribute it and/or modify
5 | # it under the terms of the GNU General Public License as published by
6 | # the Free Software Foundation; either version 2 of the License, or
7 | # (at your option) any later version.
8 | #
9 | # This program is distributed in the hope that it will be useful,
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | # GNU General Public License for more details.
13 | #
14 | # You should have received a copy of the GNU General Public License
15 | # along with this program; if not, write to the Free Software
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA.
17 | #
18 | """
19 | Base module for rendering regmarks for Silhouette CAMEO products in Inkscape.
20 | """
21 |
22 | import sys, os
23 | # Enables stand alone mode and helps for tests #
24 | # We append the directory where this script lives and inkscape extension folder to sys.path
25 | sys.path.append(os.path.dirname(os.path.abspath(sys.argv[0])))
26 | sys_platform = sys.platform.lower()
27 | if sys_platform.startswith("win"):
28 | sys.path.append(r"C:\Program Files\Inkscape\share\inkscape\extensions")
29 | elif sys_platform.startswith("darwin"):
30 | sys.path.append("/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions")
31 | else: # linux
32 | sys.path.append("/usr/share/inkscape/extensions")
33 |
34 | import inkex
35 | from inkex import EffectExtension, Boolean, Rectangle, PathElement, Layer, Group, TextElement, Transform
36 | from gettext import gettext
37 | from inkex.units import convert_unit
38 |
39 | # Temporary Monkey Backport Patches to support functions that exist only after v1.2
40 | # TODO: If support for Inkscape v1.1 is dropped then this backport can be removed
41 | if not hasattr(inkex, "__version__") or inkex.__version__[0:3] < "1.2":
42 | from inkex import BaseElement, SvgDocumentElement, paths
43 | import re
44 | SvgDocumentElement.viewport_width = property(lambda self: convert_unit(self.get("width"), "px") or self.get_viewbox()[2])
45 | SvgDocumentElement.viewport_height = property(lambda self: convert_unit(self.get("height"), "px") or self.get_viewbox()[3])
46 | SvgDocumentElement._base_scale = lambda self, unit="px": (convert_unit(1, unit) or 1.0) if not all(self.get_viewbox()[2:]) else max([convert_unit(self.viewport_width, unit) / self.get_viewbox()[2], convert_unit(self.viewport_height, unit) / self.get_viewbox()[3]]) or convert_unit(1, unit) or 1.0
47 | BaseElement.to_dimensional = staticmethod(lambda value, to_unit="px": convert_unit(value, to_unit))
48 | BaseElement.to_dimensionless = staticmethod(lambda value: convert_unit(value, "px"))
49 | BaseElement.viewport_to_unit = lambda self, value, unit="px": self.to_dimensional(self.to_dimensionless(value) / self.root._base_scale(), unit)
50 | BaseElement.unit_to_viewport = lambda self, value, unit="px": self.to_dimensional(self.to_dimensionless(value) * self.root._base_scale(), unit)
51 | BaseElement.set_sensitive = lambda self, sensitive="true": self.set("sodipodi:insensitive", ["true", None][sensitive])
52 | paths.strargs = lambda string, kind=float: [kind(val) for val in re.compile(r"(?:[+-]?(?:(?:(?:[0-9]+)?\.(?:[0-9]+)|(?:[0-9]+)\.)(?:[eE][+-]?(?:[0-9]+))?|(?:[0-9]+)(?:[eE][+-]?(?:[0-9]+)))|[+-]?(?:[0-9]+))").findall(string)]
53 |
54 | REGMARK_LAYERNAME = 'Regmarks'
55 | REGMARK_LAYER_ID = 'regmark'
56 | REGMARK_TOP_LEFT_ID = 'regmark-tl'
57 | REGMARK_TOP_RIGHT_ID = 'regmark-tr'
58 | REGMARK_BOTTOM_LEFT_ID = 'regmark-bl'
59 | REGMARK_SAFE_AREA_ID = 'regmark-safe-area'
60 | REGMARK_NOTES_ID = 'regmark-notes'
61 |
62 | REG_SQUARE_MM = 5
63 | REG_LINE_MM = 20
64 | REG_SAFE_AREA_MM = 20
65 |
66 | # https://www.reddit.com/r/silhouettecutters/comments/wcdnzy/the_key_to_print_and_cut_success_an_extensive/
67 | # > The registration mark thickness is actually very important. For some reason, 0.3 mm marks work perfectly.
68 | # > The thicker you get, the less accurate registration will be. ~~~ galaxyman47
69 | REG_MARK_LINE_WIDTH_MM = 0.3
70 |
71 | REG_MARK_INFO_FONT_SIZE_PX = 2.5
72 |
73 | ENABLE_CHECKERBOARD = True
74 |
75 | class InsertRegmark(EffectExtension):
76 | def add_arguments(self, pars):
77 | # Parse arguments
78 | pars.add_argument("-X", "--reg-x", "--regwidth", type = float, dest = "regwidth", default = 0.0, help="X mark to mark distance [mm]")
79 | pars.add_argument("-Y", "--reg-y", "--reglength", type = float, dest = "reglength", default = 0.0, help="Y mark to mark distance [mm]")
80 | pars.add_argument("--rego-x", "--regoriginx", type = float, dest = "regoriginx", default = 10.0, help="X mark origin from left [mm]")
81 | pars.add_argument("--rego-y", "--regoriginy", type = float, dest = "regoriginy", default = 10.0, help="X mark origin from top [mm]")
82 | pars.add_argument("--verbose", dest = "verbose", type = Boolean, default = False, help="enable log messages")
83 |
84 | def effect(self):
85 | reg_origin_X = self.options.regoriginx
86 | reg_origin_Y = self.options.regoriginy
87 | reg_width = self.options.regwidth or self.svg.to_dimensional(self.svg.viewport_width, "mm") - reg_origin_X * 2
88 | reg_length = self.options.reglength or self.svg.to_dimensional(self.svg.viewport_height, "mm") - reg_origin_Y * 2
89 |
90 | if self.options.verbose == True:
91 | self.msg(gettext("[INFO]: page width ")+str(self.svg.to_dimensional(self.svg.viewport_width, "mm")))
92 | self.msg(gettext("[INFO]: page height ")+str(self.svg.to_dimensional(self.svg.viewport_height, "mm")))
93 | self.msg(gettext("[INFO]: regmark from document left ")+str(reg_origin_X))
94 | self.msg(gettext("[INFO]: regmark from document top ")+str(reg_origin_Y))
95 | self.msg(gettext("[INFO]: regmark to regmark spacing X ")+str(reg_width))
96 | self.msg(gettext("[INFO]: regmark to regmark spacing Y ")+str(reg_length))
97 |
98 | # Check if existing regmark layer exist and delete it
99 | old_regmark_layer = self.svg.getElementById(REGMARK_LAYER_ID)
100 | if old_regmark_layer is not None:
101 | old_regmark_layer.delete()
102 |
103 | # Register Mark #
104 | mm_to_user_unit = self.svg.viewport_to_unit('1mm')
105 |
106 | # Create a new register mark layer
107 | regmark_layer = Layer.new(REGMARK_LAYERNAME, id=REGMARK_LAYER_ID)
108 | regmark_layer.transform = Transform(scale=mm_to_user_unit)
109 |
110 | # Create square in top left corner
111 | regmark_layer.append(Rectangle.new(left=reg_origin_X, top=reg_origin_Y, width=REG_SQUARE_MM, height=REG_SQUARE_MM, id=REGMARK_TOP_LEFT_ID, style='fill:black;'))
112 |
113 | # Create horizontal and vertical lines in group for top right corner
114 | top_right_x = reg_origin_X+reg_width
115 | top_right_path = [(top_right_x-REG_LINE_MM,reg_origin_Y), (top_right_x,reg_origin_Y), (top_right_x,reg_origin_Y + REG_LINE_MM)]
116 | regmark_layer.append(PathElement.new(path="M"+str(top_right_path), id=REGMARK_TOP_RIGHT_ID, style=f"fill:none; stroke:black; stroke-width:{REG_MARK_LINE_WIDTH_MM};"))
117 |
118 | # Create horizontal and vertical lines in group for bottom left corner
119 | bottom_left_y = reg_origin_Y+reg_length
120 | bottom_left_path = [(reg_origin_X+REG_LINE_MM,bottom_left_y), (reg_origin_X,bottom_left_y), (reg_origin_X,bottom_left_y - REG_LINE_MM)]
121 | regmark_layer.append(PathElement.new(path="M"+str(bottom_left_path), id=REGMARK_BOTTOM_LEFT_ID, style=f"fill:none; stroke:black; stroke-width:{REG_MARK_LINE_WIDTH_MM};"))
122 |
123 | # Safe Area Marker #
124 | # This draws the safe drawing area
125 | safearea_left_x = reg_origin_X+REG_LINE_MM
126 | safearea_top_y = reg_origin_Y+REG_LINE_MM
127 | safearea_right_x = reg_origin_X+reg_width-REG_LINE_MM
128 | safearea_bottom_y = reg_origin_Y+reg_length-REG_LINE_MM
129 | safe_area_points = [
130 | (safearea_left_x-REG_SAFE_AREA_MM,safearea_top_y),
131 | (safearea_left_x,safearea_top_y),
132 | (safearea_left_x,safearea_top_y-REG_SAFE_AREA_MM),
133 | (safearea_right_x,safearea_top_y-REG_SAFE_AREA_MM),
134 | (safearea_right_x,safearea_top_y),
135 | (safearea_right_x+REG_SAFE_AREA_MM,safearea_top_y),
136 | (safearea_right_x+REG_SAFE_AREA_MM,safearea_bottom_y+REG_SAFE_AREA_MM),
137 | (safearea_left_x,safearea_bottom_y+REG_SAFE_AREA_MM),
138 | (safearea_left_x,safearea_bottom_y),
139 | (safearea_left_x-REG_SAFE_AREA_MM,safearea_bottom_y),
140 | ]
141 | regmark_layer.append(PathElement.new(path="M"+str(safe_area_points)+"Z", id=REGMARK_SAFE_AREA_ID, style='fill:white;stroke:none;'))
142 |
143 | # Add some settings reminders to the print layer as a reminder
144 | safe_area_note = f"mark distance from document: Left={reg_origin_X}mm, Top={reg_origin_Y}mm; mark to mark distance: X={reg_width}mm, Y={reg_length}mm; "
145 | regmark_layer.append(TextElement(safe_area_note, x=f"{(safearea_left_x+3)}", y=f"{(safearea_bottom_y+(REG_SAFE_AREA_MM+reg_origin_Y/2))}", id = REGMARK_NOTES_ID, style=f"font-size:{REG_MARK_INFO_FONT_SIZE_PX}px;"))
146 |
147 | # Lock Layer
148 | regmark_layer.set_sensitive(False)
149 |
150 | # Insert regmark layer to the bottom of the svg layer stack to avoid covering any existing artwork
151 | self.svg.insert(0, regmark_layer)
152 |
153 | # Set Page Setting to enable checkerboard (This is required so that safe area is easier to see)
154 | self.svg.namedview.set('inkscape:pagecheckerboard', str(ENABLE_CHECKERBOARD).lower())
155 |
156 | if __name__ == '__main__':
157 | InsertRegmark().run()
--------------------------------------------------------------------------------