284 lines
10 KiB
Python
284 lines
10 KiB
Python
#!/usr/bin/python3
|
||
"""
|
||
|
||
Скрипт с графическим интерфейсом
|
||
для печати этикеток на принтере серий brother p-touch
|
||
|
||
(форк) на основе скрипта Анны Полковниковой (https://gitea.basealt.ru/polkovnikovaav/ptouch-print-labels)
|
||
подробности и настройки в README.md
|
||
|
||
зи
|
||
|
||
"""
|
||
|
||
from PySide6.QtWidgets import QApplication
|
||
from PySide6 import QtGui
|
||
from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QPushButton, QComboBox, QTableWidget, QHBoxLayout, QMessageBox, QTableWidgetItem
|
||
|
||
import subprocess
|
||
import time
|
||
import os
|
||
import sys
|
||
import glob
|
||
import re
|
||
|
||
from PIL import Image, ImageOps, ImageDraw, ImageFont
|
||
from barcode import EAN13
|
||
from barcode.writer import ImageWriter
|
||
|
||
|
||
class MainWindow(QMainWindow):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.version = "v1.3"
|
||
|
||
self.device_names = ['компьютер', 'монитор', 'сетевое об.',
|
||
'токен', 'принтер/МФУ', 'флешка', 'HID', 'UPS', 'другое']
|
||
|
||
self.setWindowTitle(f"Печать стикеров инвентаризации {self.version}")
|
||
self.resize(700, 500)
|
||
|
||
# таблица
|
||
self.q_table = QTableWidget()
|
||
self.init_table()
|
||
|
||
# кнопка печати
|
||
self.q_print_button = QPushButton()
|
||
self.q_print_button.setText("печать")
|
||
self.q_print_button.clicked.connect(self.start_print)
|
||
self.q_print_button.setMinimumSize(15, 50)
|
||
|
||
# кнопка очистки
|
||
self.q_clear_button = QPushButton()
|
||
self.q_clear_button.setText("очистка")
|
||
self.q_clear_button.clicked.connect(self.init_table)
|
||
self.q_clear_button.setMinimumSize(15, 50)
|
||
|
||
self.qh_layout2 = QHBoxLayout()
|
||
self.qh_layout2.addWidget(self.q_print_button)
|
||
self.qh_layout2.addWidget(self.q_clear_button)
|
||
|
||
# компоновщик1 вертикальный
|
||
self.qv_layout1 = QVBoxLayout()
|
||
self.qv_layout1.setContentsMargins(15, 15, 15, 15)
|
||
self.qv_layout1.setSpacing(15)
|
||
self.qv_layout1.addWidget(self.q_table)
|
||
self.qv_layout1.addLayout(self.qh_layout2)
|
||
|
||
# компоновщик горизонтальный
|
||
self.qh_layout = QHBoxLayout()
|
||
self.qh_layout.addLayout(self.qv_layout1)
|
||
|
||
# обобщающий виджет
|
||
self.q_central_widget = QWidget()
|
||
self.q_central_widget.setLayout(self.qh_layout)
|
||
self.setCentralWidget(self.q_central_widget)
|
||
|
||
# выпадающий список типов и кодовые соответствия
|
||
self.combo_type_items = {
|
||
|
||
"компьютер": "21",
|
||
"монитор": "22",
|
||
"сетевое об.": "23",
|
||
"токен": "24",
|
||
"принтер/МФУ": "25",
|
||
"флешка": "26",
|
||
"HID": "27",
|
||
"UPS": "28",
|
||
"другое": "29",
|
||
}
|
||
|
||
def gen_message_box(self, message_str):
|
||
msg_box = QMessageBox()
|
||
msg_box.setWindowTitle("Внимание")
|
||
msg_box.setText(message_str)
|
||
msg_box.setIcon(QMessageBox.Information)
|
||
msg_box.exec()
|
||
|
||
# проверка строки на соответствие строго 12 цифр
|
||
def check_inventory_number(self, inventoryN):
|
||
pattern=r'^\d{12,17}$'
|
||
if re.match(pattern, inventoryN):
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
# инициализация таблицы
|
||
|
||
def init_table(self):
|
||
self.q_table.clear()
|
||
|
||
self.q_table.setColumnCount(3)
|
||
self.q_table.setHorizontalHeaderLabels(
|
||
['Текст', 'Тип', 'Инв.Н (пустое - генерация)'])
|
||
self.q_table.setRowCount(10)
|
||
self.q_table.setColumnWidth(0, 200)
|
||
self.q_table.setColumnWidth(1, 110)
|
||
self.q_table.setColumnWidth(2, 200)
|
||
|
||
# кладем комбобоксы в ячейки
|
||
for i in range(0, 10):
|
||
self.q_combo = QComboBox(self)
|
||
self.q_combo.addItems(self.device_names)
|
||
self.q_table.setCellWidget(i, 1, self.q_combo)
|
||
|
||
# удаляем временные Png файлы
|
||
|
||
def clear_temp_png_files(self):
|
||
self.set_current_location()
|
||
|
||
fileList = glob.glob('*.png')
|
||
# Iterate over the list of filepaths & remove each file.
|
||
for filePath in fileList:
|
||
try:
|
||
os.remove(filePath)
|
||
except Exception:
|
||
self.gen_message_box("Ошибка удаления файла : ", filePath)
|
||
|
||
# получаем текущий каталог и переходим в него
|
||
def set_current_location(self):
|
||
path_there = os.path.dirname(os.path.realpath(__file__))
|
||
os.chdir(path_there)
|
||
|
||
# генерация штрих-кода с контрльной суммой
|
||
def gen_ean13(self, product_code: str):
|
||
|
||
# добавляем псевдослучаное значение (от времени к коду продукта)
|
||
# при пакетной генерации между генерациями необходим интервал
|
||
time.sleep(1)
|
||
digits_s = product_code + str(round(time.time()))
|
||
|
||
# строка продукта в список чисел
|
||
digits = [int(i) for i in digits_s]
|
||
|
||
# контрольная сумма по алгоритму ЛУна
|
||
checksum = (10 - (sum(digits[1::2]) * 3 + sum(digits[::2])) % 10) % 10
|
||
return "".join(str(i) for i in (digits + [checksum]))
|
||
|
||
# генерация картинки
|
||
def create_code_png(self, filename, code, text):
|
||
self.set_current_location()
|
||
ean13_code = EAN13(code, writer=ImageWriter())
|
||
|
||
image = ean13_code.render(
|
||
writer_options={
|
||
"module_height": 11.0,
|
||
"module_width": 0.3,
|
||
"dpi": 169,
|
||
"font_size": 0,
|
||
"text_distance": 1,
|
||
"write_text": None,
|
||
}
|
||
)
|
||
|
||
image = image.convert("1", dither=Image.Dither.NONE)
|
||
image = ImageOps.expand(image, border=17, fill=1)
|
||
draw = ImageDraw.Draw(image)
|
||
|
||
# загружаем шрифт
|
||
dst = "terminus.pil"
|
||
font = ImageFont.load(dst)
|
||
|
||
text_length = draw.textlength(text, font=font)
|
||
code_text_length = draw.textlength(code, font=font)
|
||
draw.text(((image.width - text_length) / 2, 3),
|
||
text, fill="black", font=font)
|
||
draw.text(
|
||
((image.width - code_text_length) / 2, 100),
|
||
code,
|
||
fill="black",
|
||
font=font,
|
||
)
|
||
|
||
image.save(filename)
|
||
|
||
# запуск всего процесса (генерация и печать)
|
||
def start_print(self):
|
||
|
||
# удаляем старые png-файлы
|
||
self.clear_temp_png_files()
|
||
|
||
for row in range(0, self.q_table.rowCount()):
|
||
#print(row)
|
||
|
||
# проверка Инв. Номера на корректность
|
||
if self.q_table.item(row, 2) is not None and self.check_inventory_number(self.q_table.item(row, 2).text()) is False:
|
||
self.gen_message_box(
|
||
"инвентарный номер должен содержать минимум 12 цифр")
|
||
self.q_table.setItem(row, 2, QTableWidgetItem(""))
|
||
return None
|
||
|
||
# если ТЕКСТ и ИН заполнены
|
||
if self.q_table.item(row, 0) is not None and self.q_table.item(row, 2) is not None:
|
||
#print(self.q_table.item(row, 0).text())
|
||
combo_widget = self.q_table.cellWidget(row, 1)
|
||
type_device = self.combo_type_items[combo_widget.currentText()]
|
||
barcode = self.q_table.item(row, 2).text()
|
||
self.create_code_png(
|
||
f"temp-{row}.png", barcode, self.q_table.item(row, 0).text())
|
||
|
||
# если только ТЕКСТ заполнен
|
||
if self.q_table.item(row, 0) is not None and self.q_table.item(row, 2) is None:
|
||
#print(self.q_table.item(row, 0).text())
|
||
combo_widget = self.q_table.cellWidget(row, 1)
|
||
type_device = self.combo_type_items[combo_widget.currentText()]
|
||
barcode = self.gen_ean13(type_device)
|
||
self.create_code_png(
|
||
f"temp-{row}.png", barcode, self.q_table.item(row, 0).text())
|
||
|
||
|
||
|
||
# запуск утилиты печати сфомированного изображения
|
||
self.start_process()
|
||
|
||
# удаляем временные файлы
|
||
self.clear_temp_png_files()
|
||
|
||
|
||
|
||
def start_process(self):
|
||
|
||
# получаем список файлов
|
||
file_list_to_print = glob.glob('*.png')
|
||
|
||
# подготовка строки параметров для запуска
|
||
prepare_cmd = ""
|
||
for file_name in file_list_to_print:
|
||
prepare_cmd = prepare_cmd+" --image "+file_name+" --pad 5"
|
||
prepare_cmd=prepare_cmd[:-8]
|
||
#print(prepare_cmd)
|
||
|
||
|
||
subprocess.run("ptouch-print "+prepare_cmd,shell=True)
|
||
|
||
|
||
|
||
if __name__ == '__main__':
|
||
app = QApplication(sys.argv)
|
||
configparser = ChildProcessError
|
||
|
||
# таблица стилей (задаем размер шрифта)
|
||
style_sheet01 = """
|
||
* {
|
||
font-size: 10pt;
|
||
}
|
||
"""
|
||
|
||
# Применение таблицы стилей (размер шрифта) ко всему приложению
|
||
app.setStyleSheet(style_sheet01)
|
||
|
||
window = MainWindow()
|
||
|
||
# Установить размер окна
|
||
window.resize(600, 500)
|
||
|
||
# Получить допустимое разрешение
|
||
SrcSize = QtGui.QScreen.availableGeometry(QApplication.primaryScreen())
|
||
|
||
frmX = (SrcSize.width() - window.width())/2
|
||
frmY = (SrcSize.height() - window.height())/2
|
||
window.move(frmX, frmY)
|
||
|
||
window.show()
|
||
sys.exit(app.exec_())
|