Label Printers
Label printers are useful in electronics work for labelling PCBs, cables, production items and more.
Printing From Python in Windows
The pywin32
Python library can be used to print to most label printers on Windows. This library makes it easy to interact with the Win32 API, which includes printer operations. You can install it using pip
:
pip install pywin32
Here is an example of how to print a image to the label printer using the pywin32
library. In this case I was connected to a Brady M511 label printer.
import datetimeimport osimport sys
from PIL import Image, ImageWinimport win32conimport win32printimport win32ui
file_name = os.path.join(SCRIPT_DIR, "test.png")# Create device contexthDC = win32ui.CreateDC()hDC.CreatePrinterDC(printer_name)
# Get printer dimensions in device unitsprinter_width = hDC.GetDeviceCaps(win32con.PHYSICALWIDTH)printer_height = hDC.GetDeviceCaps(win32con.PHYSICALHEIGHT)
# Open and prepare the imagebmp = Image.open(file_name)
# Calculate scaling factor to fit the image within printer dimensions# while maintaining aspect ratiowidth_ratio = printer_width / bmp.size[0]height_ratio = printer_height / bmp.size[1]scale_factor = min(width_ratio, height_ratio)
# Calculate new dimensionsnew_width = int(bmp.size[0] * scale_factor)new_height = int(bmp.size[1] * scale_factor)
# Resize the imagebmp = bmp.resize((new_width, new_height), Image.Resampling.LANCZOS)
# Calculate centering offsetsx_offset = (printer_width - new_width) // 2y_offset = (printer_height - new_height) // 2
# Start printinghDC.StartDoc(file_name)hDC.StartPage()
# Draw the image centered on the pagedib = ImageWin.Dib(bmp)dib.draw(hDC.GetHandleOutput(), (0, 0, new_width, new_height))
hDC.EndPage()hDC.EndDoc()hDC.DeleteDC()
GetDeviceCaps()
can be used to retrieve a number of different properties of the printer. Some of the things you can request are:
HORZRES
: Printable area width in pixelsVERTRES
: Printable area height in pixelsLOGPIXELS
: Dots per inchPHYSICALWIDTH
: Total area width in device unitsPHYSICALHEIGHT
: Total area height in device unitsPHYSICALOFFSETX
: Left margin in device unitsPHYSICALOFFSETY
: Top margin in device units
These constants are defined in the `win32con.py` file as part of the `pywin32` library.
```pythonimport win32con
print(win32con.PHYSICALWIDTH)
If you are wanting to change the label size, you need to set the paper width and length attributes. To do this, you need to open the printer with the ACCESS_MASK
set to win32print.PRINTER_ALL_ACCESS
:
PRINTER_DEFAULTS = {"DesiredAccess": win32print.PRINTER_ALL_ACCESS}printer_handle = win32print.OpenPrinter(printer_name, PRINTER_DEFAULTS)
Here is an example which set a number of different attributes for a printer.
# PRINTER_ALL_ACCESS is required to set paper sizePRINTER_DEFAULTS = {"DesiredAccess": win32print.PRINTER_ALL_ACCESS}printer_handle = win32print.OpenPrinter(printer_name, PRINTER_DEFAULTS)
# 2 : A PRINTER_INFO_2 structure containing detailed information about the printer.# See https://learn.microsoft.com/en-us/windows/win32/printdocs/setprinterlevel = 2attributes = win32print.GetPrinter(printer_handle, level)# Width is perpendicular to the label feed direction# Length is parallel with the label feed direction# Both of these are in tenths of a millimeterattributes['pDevMode'].PaperWidth = 90attributes['pDevMode'].PaperLength = 80
# For printer devices only, selects the size of the paper to print on.# This member can be set to zero if the length and width of the paper are# both set by the dmPaperLength and dmPaperWidth members# (https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-devmodea)attributes['pDevMode'].PaperSize = 0
# Print 2 copies of the labelattributes['pDevMode'].Copies = 2
win32print.SetPrinter(printer_handle, level, attributes, 0)
I have found that setting changing the printer length can change the position a label is cut on a continuous cartridge.
Be careful when changing the printer paper size. As fair I have found, this will apply the new paper size to all pending print jobs, including ones you have previously submitted but have not yet been printed.
This code example also uses Copies
to set the number of copies to print. I found this to be more reliable than submitting the print job multiple times.
Detecting If A Printer Is Connected
It can be useful to be able to detect if a printer is connected and ready to print. If you don’t check this, your printing efforts could just end up queuing up a load of print jobs but not actually printing anything. This is not ideal in a production line environment.
The GetPrinter(<printer_handle>, 2)
function can be used to get printer information. I would have thought the status field would have been the variable to check, but in my testing it was always 0
, whether the printer was actually connected or not. Instead I used the PRINTER_ATTRIBUTE_WORK_OFFLINE
attribute. This seems to be set to 1024
when the printer is not connected, and 0
when it is.
Below is an example function which returns True
if the printer is connected (online).
def is_printer_connected(printer_name: str) -> bool: """ Checks if the specified printer is connected and ready to print.
Args: printer_name (str): The name of the printer to check.
Returns: bool: True if the printer is connected and ready, False otherwise. """ try: # Check if printer exists in the list of available printers available_printers = list_windows_printers() if printer_name not in available_printers: print(f"Printer '{printer_name}' not found in available printers") return False
# Try to open the printer to check if it's accessible PRINTER_DEFAULTS = {"DesiredAccess": win32print.PRINTER_ACCESS_USE} printer_handle = win32print.OpenPrinter(printer_name, PRINTER_DEFAULTS)
if printer_handle: # Get printer status level = 2 printer_info = win32print.GetPrinter(printer_handle, level) attributes = printer_info['Attributes'] # Status did not change from 0, so we are using this WORK_OFFLINE attribute instead. # Is seems to be set to 1024 when not connected, and 0 when connected. is_ready = (attributes & win32print.PRINTER_ATTRIBUTE_WORK_OFFLINE) == 0 win32print.ClosePrinter(printer_handle) return is_ready except Exception as e: print(f"Error checking printer status: {e}.") return False return False