py_silhouette
: Control Silhouette plotters/cutters from Python¶
py_silhouette
is a Python library for controlling the Silhouette series of
desktop plotters/cutters.
This library is intended to serve two purposes:
It is intended to form the basis of both general and special purpose plotting software.
To document the outcome of a reverse engineering effort for the protocol used to control Silhouette plotters.
This library is not:
A complete plotting tool – it is just a library.
A general purpose plotter control library – it only controls the Silhouette series of desktop plotters.
A library of generic utilities for plotting – it only contains low-level device control functionality.
A complete reverse engineering of every hardware command – it contains enough to implement all advertised device functionality though some software emulation of certain functions may be required (e.g. for manual head movement control).
Quick-and-dirty example¶
The following complete example illustrates how this library could be used to draw a simple 20mm x 10mm rectangle:
from py_silhouette import SilhouetteDevice
# Connect to first available device
d = SilhouetteDevice()
# Move to (10, 10) mm as a starting point without drawing
d.move_to(10, 10, False)
# Draw the rectangle
d.move_to(30, 10, True)
d.move_to(30, 20, True)
d.move_to(10, 20, True)
d.move_to(10, 10, True)
# Finish plotting and return to the home position (don't forget this!)
d.move_home()
# Flush the command buffer and wait for all commands to be acknowledged
# (nothing will happen until this is called)
d.flush()
A better example¶
The minimal example above will work but has numerous shortcomings. Specifically:
If multiple devices are connected, one will be chosen arbitrarily.
Printed registration marks will be ignored.
The speed and force applied by the device is undefined.
If using a cutting tool, corners will not be cut correctly.
To improve this example we should first present the user with a choice of
devices by using enumerate_devices()
to discover what is available:
from py_silhouette import SilhouetteDevice, enumerate_devices
devices = list(enumerate_devices())
for num, (usb_device, device_params) in enumerate(devices):
print("{}: {}".format(num, device_params.name))
num = int(input("Choose a device number to use: "))
usb_device, device_params = devices[num]
d = SilhouetteDevice(usb_device, device_params)
Next we can use SilhouetteDevice.zero_on_registration_mark()
to zero
the device’s coordinate system on a set of printed registration marks (which
we’ll assume in this example mark a 200x100mm area):
from py_silhouette import RegistrationMarkNotFoundError
try:
d.zero_on_registration_mark(200, 100)
except RegistrationMarkNotFoundError:
print("Registration marks not found! Continuing without...")
Next, we can inform the device of the tool’s diameter which will ensure corners
will be cut out correctly using SilhouetteDevice.set_tool_diameter()
and tool information in SilhouetteDevice.params
.tool_diameters
:
d.set_tool_diameter(d.params.tool_diameters["Knife"])
Next we’ll choose what speed and force we wish to use, again choosing
parameters based on the DeviceParameters
object in
DeviceParameters.params
:
d.set_speed(d.params.tool_speed_min)
d.set_force(d.params.tool_force_max)
Now we’re ready to cut out the rectangle:
d.move_to(10, 10, False)
d.move_to(30, 10, True)
d.move_to(30, 20, True)
d.move_to(10, 20, True)
d.move_to(10, 10, True)
d.move_home()
d.flush()
The SilhouetteDevice.flush()
method will block untli all commands have
been received into the plotters buffer and so will probably return before
plotting has actually completed. We can use
SilhouetteDevice.get_state()
to wait until the plotter has actually
finished plotting (i.e. is nolonger in the DeviceState.moving
state):
import time
from py_silhouette import DeviceState
while d.get_state() == DeviceState.moving:
time.sleep(0.5)
print("Cutting complete!")
API¶
The complete API is contained within the py_silhouette
module. The
principal class, SilhouetteDevice
, represents a connection to a
plotter connected via USB and provides a number of low-level methods for
controling the device (e.g. SilhouetteDevice.move_to()
). A number of
supporting functions and data structures are defined which descover or describe
available devices and may be used to construct or configure a
SilhouetteDevice
(e.g. enumerate_devices()
).
Device Discovery & Connection¶
To construct a SilhouetteDevice
we must first discover a connected
device for it to control using enumerate_devices()
:
- py_silhouette.enumerate_devices(supported_device_parameters=SUPPORTED_DEVICE_PARAMETERS)¶
Generator which produces a series of
(device, device_params)
pairs for all currently connected devices.- Parameters
- supported_device_parameters[
DeviceParameters
, …] An optional list of
DeviceParameters
objects for the types of devices to include in the enumeration. By default this is all of the devices enumerated inSUPPORTED_DEVICE_PARAMETERS
.
- supported_device_parameters[
At this point you may wish to present your users with a prompt to select a
plotter (perhaps using DeviceParameters.name
as a hint) or select
one automatically according to your own logic (perhaps using
DeviceParameters.area_width_min
and friends to make an informed
choice).
Once a specific device has been chosen, pass the USB device and
DeviceParameters
object to the SilhouetteDevice
constructor:
- class py_silhouette.SilhouetteDevice(device=None, device_params=None)¶
Connect to and control the specified plotter/cutter.
See
enumerate_devices()
for discovering connected devices and their parameters.As a convenience, if no arguments are provided, this class will attempt to connect to the first device found by
enumerate_devices()
, throwing aNoDeviceFoundError
if no devices are found.- Parameters
- device
pyusb.core.Device
The USB device object for the plotter to control.
- device_params
DeviceParameters
Definition of the device’s key parameters.
- device
The DeviceParameters
used to configure the device can be obtained
from:
- SilhouetteDevice.params = None¶
The
DeviceParameters
object passed during construction (or selected automatically). Contains useful information about the shape and size of media and tools supported by this device.
For diagnostic purposes you can request the device name and state:
- SilhouetteDevice.get_name()¶
Return the human-readable name and version reported by the device (as a string).
- SilhouetteDevice.get_state()¶
Get the current state of the device returning a
DeviceState
.
- class py_silhouette.DeviceState(value)¶
What is the device currently doing?
- ready = b'0'¶
The device is ready to begin plotting.
- moving = b'1'¶
The plotter is busy plotting or moving.
- unloaded = b'2'¶
Idle with no media loaded.
- paused = b'3'¶
The pause button has been pressed.
- unknown = None¶
Unrecognised device state; probably an error.
Setting the plotter origin¶
Before beginning a plot it is important to decide how the coordinate axis is zeroed. This library presents you with two options:
Do nothing and the device’s ‘home’ position will be the origin for plotting coordinates.
Use
SilhouetteDevice.zero_on_registration_mark()
to zero the plotting axes on registration marks printed on the page.
- SilhouetteDevice.zero_on_registration_mark(width, height, box_size=5.0, line_thickness=0.5, line_length=20.0, search=True)¶
Zero coordinate system and compensate for small page misalignments using registration marks printed on the page.
If the registration marks are not found,
RegistrationMarkNotFoundError
is raised.This command will block until the registration marks have been found or not.
The registration settings will be retained until the current page is ejected from the machine.
Warning
As a side effect of calling this command, the tool speed will be set to its maximum.
Note
Registration marks should look as follows (without the red construction/dimension lines…):
The registration marks must be oriented as shown and white space must be left around all three marks to ensure they are found by the plotter.
The entire path to be plotted/cut must be within the bounds of the registration marks.
The top-left mark should be near the top-left of the page so that the plotter can find.
The width and height are measured from the outside of the corner bracket lines.
Most Silhouette plotters also support a second type of registration mark where the top-left square is replaced with another corner bracket. Use of this type of registration mark is not supported by this library.
- Parameters
- width, height: float
The size of the area the registration mark covers in mm.
Warning
Take care that the right-most registration mark is not too close to the right-hand extreme of the machine. The registration sensor is mounted at the very left side of the carriage so it will need to move further than it would when plotting on that corner of the page. The plotter does not have a ‘right’ end-stop and may hit the end of its axis.
- box_sizefloat
The size of the black square in the top-left registration mark (mm). Currently must be set to 5mm (the default).
- line_thicknessfloat
The thickness of the registration lines (mm). Default of 0.5 mm is known to work well.
- line_lengthfloat
The length the registration lines (mm). Default of 20 mm is known to work well.
- searchbool
If true, the device will start searching for the registration mark automatically, starting at the device home position. If False the device should first be positioned with the tool over the black square. In practice this is very difficult to achieve so most users will want to leave this setting in its default mode (True).
Setting tool parameters¶
Prior to plotting it is important to set the plotting speed, force and tool parameters according to the tool and material in use. There are no hard-and-fast rules for setting these parameters so experimentation is required.
- SilhouetteDevice.set_speed(speed)¶
Set the movement speed of the device in mm/sec.
This parameter will be automatically clamped to the range specified in the
SilhouetteDevice.params
.tool_speed_min
andSilhouetteDevice.params
.tool_speed_max
.
- SilhouetteDevice.set_force(force)¶
Set the amount of force to be applied (in grams (yes.)) when the tool is used.
This parameter will be automatically clamped to the range specified in the
SilhouetteDevice.params
.tool_force_min
andSilhouetteDevice.params
.tool_force_max
.
Depending on the type of tool used, the device will automatically tweak the toolpath supplied to compensate for the tool diameter. For this function to work correctly, the diameter of the tool must also be supplied.
- SilhouetteDevice.set_tool_diameter(diameter)¶
Inform the plotter of the diameter of a swivelling tool’s working point to allow it to adjust tool paths accordingly.
Tool diameters for the standard tools supplied with the current device can be obtained from
SilhouetteDevice.params
.tool_diameters
.Note
Cutting blade cartridges contain a blade on a swivelling attachment, a little like the casters on an office chair.
As such, the point of the blade’s position will lag behind the plotter’s position. Setting this parameter causes the device’s firmware to compensate for this automatically when turning corners by moving the plotter in an arc pattern towards the new line. During this move, the blade turns to face the new cut direction but does not actually cut.
When using a tool with a swivelling cutting implement (such as the included knife cartridge), setting this parameter correctly is strongly recommended for good results. If using a fixed implement (e.g. a pen), this setting should usually be set to 0.0 since the pen tip is fixed.
- Parameters
- diameterfloat
Tool swivel mounting diameter.
This parameter will be automatically clamped to the range specified in the
SilhouetteDevice.params
.tool_diameter_min
andSilhouetteDevice.params
.tool_diameter_max
.
Finally, for devices with an auto blade, the following function may be used to automatically set the blade depth.
- SilhouetteDevice.set_depth(depth)¶
Set the blade depth on devices supporting auto blade.
This parameter will be automatically clamped to the range specified in the
SilhouetteDevice.params
.tool_depth_min
andSilhouetteDevice.params
.tool_depth_max
.If the device does not have auto blade support (i.e.
SilhouetteDevice.params
.tool_depth_min
is None), anAutoBladeNotSupportedError
will be raised.
Plotting¶
Plotting is performed by making a series of SilhouetteDevice.move_to()
calls followed by SilhouetteDevice.move_home()
and
SilhouetteDevice.flush()
.
- SilhouetteDevice.move_to(x, y, use_tool)¶
Move the plotter, optionally with the tool engaged.
Facing the plotter, the X axis runs from left to right with strictly positive coordinates. The Y axis runs from top to bottom with strictly positive coordinates.
Call
flush()
to ensure this command has arrived at the device.After completing a sequence of move_to commands, always use
move_home()
to return the plotter to the home position and notify the device that plotting has finished.- Parameters
- x, y: float
Absolute page position in mm.
These values will be clamped between 0 and the maximum page width and height however this may not always be enough to prevent the machine hitting the end of the carrage (e.g. when zeroed on a registration mark). It is the caller’s responsibility to sensibly clip the input to prevent crashes.
If
zero_on_registration_mark()
has been used since the last paper load, coordinates will be relative to the top-left corner of the registration mark and should not go beyond the width and height of the registered area. Ifzero_on_registration_mark()
has not been used, coordinates start from the device’s home position.- use_tool: bool
If True, the tool will be applied during the movement. If False, the tool will be lifted.
- SilhouetteDevice.move_home()¶
Move the carriage to the home position (or to the top-left registration mark if zeroed) with the tool disengaged.
The plotter expects this to be the final command received at the end of a sequence of
move_to()
calls.Note
If this command is not used at the end of a series of
move_to()
calls, the final command sent will be delayed for a short while since the device likes to always have a look-ahead of at least one command (probably to support tool diameter compensation – seeset_tool_diameter()
).Call
flush()
to ensure this command has arrived at the device.
- SilhouetteDevice.flush()¶
Ensure all outstanding commands have been sent. Blocks until complete.
Device Parameters and Tools¶
Device parameters for widely used Silhouette devices are included in:
- py_silhouette.SUPPORTED_DEVICE_PARAMETERS¶
A list of
DeviceParameters
describing a particular supported device. At the time of writing, only support for the Silhouette Potrait v1 has been verified.
For each supported device type, information defining its USB interface identifiers, plotter dimensions and out-of-the-box tool support is included.
- class py_silhouette.DeviceParameters(product_name, usb_vendor_id, usb_product_id, area_width_min, area_width_max, area_height_min, area_height_max, tool_diameters=_Nothing.NOTHING, tool_force_min=7.0, tool_force_max=231.0, tool_speed_min=100.0, tool_speed_max=1000.0, tool_diameter_min=0.0, tool_diameter_max=2.3, tool_depth_min=None, tool_depth_max=None)¶
Method generated by attrs for class DeviceParameters.
- product_name¶
Human readable product name for the device supported by this class.
- usb_vendor_id¶
The USB Vendor ID used by device.
- usb_product_id¶
The USB Product ID used by device.
- area_width_min¶
Minimum width for valid plot areas (mm)
- area_width_max¶
Maximum width for valid plot areas (mm)
- area_height_min¶
Minimum height for valid plot areas (mm)
- area_height_max¶
Maximum height for valid plot areas (mm)
- tool_diameters¶
A dictionary mapping from tool name to tool diameter (in mm) for all tools which ship with or are available for this device for use with
SilhouetteDevice.set_tool_diameter()
. Generally'Pen'
and'Knife'
tools will be defined.
- tool_force_min¶
Lowest force which may be applied by the machine (in grams)
- tool_force_max¶
Highest force which may be applied by the machine (in grams)
- tool_speed_min¶
Lowest speed at which the machine can move (mm/sec)
- tool_speed_max¶
Highest speed at which the machine can move (mm/sec)
- tool_diameter_min¶
Lowest valid tool diameter (mm)
- tool_diameter_max¶
Highest valid tool diameter (mm)
- tool_depth_min¶
Shortest valid knife setting, or None if automatic depth setting is not possible for this device.
- tool_depth_max¶
Longest valid knife setting, or None if automatic depth setting is not possible for this device.
Exceptions¶
The following exceptions may be thrown by this library.
- exception py_silhouette.DeviceError¶
Baseclass for all py_silhouette hardware related errors.
- exception py_silhouette.NoDeviceFoundError¶
No connected devices were found.
- exception py_silhouette.RegistrationMarkNotFoundError¶
The registration mark could not be found.
- exception py_silhouette.AutoBladeNotSupportedError¶
Thrown when
SilhouetteDevice.set_depth()
is called on a device without auto blade support.
Origins and Acknowledgements¶
This software is primarily based on my own reverse engineering efforts targeting the Silhouette Portrait USB protocol based on observing the behaviour of the Silhouette Studio software running under Windows back in 2013. This first reverse-engineering pass uncovered an easy to use and understand subset of the Silhouette control protocol allowing the device to be satisfactorily used under Linux and other platforms.
Later, I discovered others’ efforts to reverse engineer and drive the Silhouette series of plotters (in particular Robocut and later Inkscape-Silhouette). Based hints in these other codebases I managed to document the remaining ‘unknowns’ within my minimal subset of the USB protocol used by Silhouette Studio.
I’m also greatful to derwassi on GitHub for reverse engineering and assisting with testing of the auto-blade feature.