Files
lego-instructions-manager/app/routes/labels_dymo.py
T

183 lines
6.6 KiB
Python

"""
Dymo LabelWriter printing - admin only.
The tape printer does one label at a time on a continuous roll. A
LabelWriter does die-cut stock, so several drawer labels are ganged onto
one sticker and you cut them apart. Same drawer labels, same 40 x 15mm
house size, different carrier.
Routes (all under /admin/labels/dymo):
/ choose labels, pick the stock, see the sheet
/preview.png one rendered sticker
/download.pdf the print-ready PDF, page size = the stock size
There is no direct printing here on purpose: the LabelWriter 4XL is a USB
printer, not a network one, so the PDF goes through whatever driver the
machine already has. Print at 100% / Actual size.
Created by: Jess Rogerson (yelling commands at Claude.AI)
"""
import logging
from flask import (Blueprint, abort, current_app, flash, redirect,
render_template, request, send_file, url_for)
from flask_login import login_required
from app.models.site_setting import SiteSetting
from app.routes.admin import admin_required
from app.routes.labels_tape import _group_spec, _selected_labels
from app.services import dymo_sheet, tape_printer
logger = logging.getLogger(__name__)
dymo_bp = Blueprint('dymo', __name__, url_prefix='/admin/labels/dymo')
def _tape_options(source):
"""What goes ON each drawer label - shared with tape printing, so a
label looks the same whichever printer it comes off."""
options = tape_printer.options_from_form(
source,
size_default=SiteSetting.get('tape_size_mode',
tape_printer.DEFAULT_SIZE_MODE))
# 'Fit the tape width' is meaningless on die-cut stock: there is no
# tape to fit. Fall back to the house sizes.
if not options.is_fixed:
options.size_mode = tape_printer.DEFAULT_SIZE_MODE
return options
def _query(sheet, options, labels, group_spec=None):
"""Every option as query args, so preview images and the PDF link show
exactly what the page shows."""
query = {
'ids': ','.join(str(l.id) for l in labels),
tape_printer.FORM_MARKER: '1',
'stock': sheet.stock,
'layout': sheet.layout,
'margin_mm': sheet.margin_mm,
'gap_mm': sheet.gap_mm,
'start_at': sheet.start_at,
'ink': sheet.ink,
'size_mode': options.size_mode,
'copies': options.copies,
'font_scale': options.font_scale,
'image_percent': options.image_percent,
'padding_mm': options.padding_mm,
'align': options.align,
'fields': options.fields,
}
for name, on in (('cut_guides', sheet.cut_guides),
('centre', sheet.centre),
('group_drawers', options.group_drawers),
('outline', options.outline)):
if on:
query[name] = '1'
if group_spec:
query['groups'] = group_spec
return query
@dymo_bp.route('/', methods=['GET', 'POST'])
@login_required
@admin_required
def index():
labels = _selected_labels()
if not labels:
flash('Select some labels to print first.', 'warning')
return redirect(url_for('labels.index'))
source = request.form if request.method == 'POST' else request.args
options = _tape_options(source)
sheet = dymo_sheet.sheet_options_from_form(
source, stock_default=SiteSetting.get('dymo_stock',
dymo_sheet.DEFAULT_STOCK))
group_spec = _group_spec()
plan = tape_printer.plan_groups(labels, options, group_spec)
pages = dymo_sheet.plan_sheets(plan, sheet, copies=options.copies)
per_sheet, cols, rows = dymo_sheet.capacity(sheet)
return render_template(
'admin/labels/dymo.html',
labels=labels,
plan=plan,
pages=pages,
ids=','.join(str(l.id) for l in labels),
options=options,
sheet=sheet,
stocks=dymo_sheet.STOCKS,
stock=dymo_sheet.get_stock(sheet.stock),
layouts=dymo_sheet.LAYOUTS,
ink_modes=dymo_sheet.INK_MODES,
font_info=tape_printer.font_report(),
size_modes=[(key, text) for key, text in tape_printer.SIZE_MODES
if key in tape_printer.FIXED_MODES],
per_sheet=per_sheet,
grid=f'{cols} across, {rows} down',
query=_query(sheet, options, labels, group_spec),
field_options=[
('image', 'Part image'), ('name', 'Part name'),
('dimensions', 'Dimensions'), ('part_number', 'Part number'),
('alt_numbers', 'Alternate numbers'), ('category', 'Category'),
('category_code', 'Category code'), ('location', 'Drawer'),
('colour', 'Colour name'), ('accent', 'Colour stripe'),
],
)
def _rendered_pages():
"""The sticker images for the current request's options."""
labels = _selected_labels()
if not labels:
abort(400, 'No labels selected.')
options = _tape_options(request.args)
sheet = dymo_sheet.sheet_options_from_form(request.args)
plan = tape_printer.plan_groups(labels, options, _group_spec())
return dymo_sheet.render_pages(
plan, sheet, options, current_app.config['UPLOAD_FOLDER'],
copies=options.copies)
@dymo_bp.route('/preview.png')
@login_required
@admin_required
def preview():
"""One sticker, scaled for the screen."""
pages = _rendered_pages()
index_ = request.args.get('page', type=int) or 1
if index_ < 1 or index_ > len(pages):
abort(404)
return send_file(dymo_sheet.preview_png(pages[index_ - 1]),
mimetype='image/png')
@dymo_bp.route('/download.pdf', methods=['GET', 'POST'])
@login_required
@admin_required
def download_pdf():
"""The print-ready PDF: one page per sticker, exactly the stock size."""
labels = _selected_labels()
if not labels:
flash('Nothing selected.', 'warning')
return redirect(url_for('labels.index'))
source = request.form if request.method == 'POST' else request.args
options = _tape_options(source)
sheet = dymo_sheet.sheet_options_from_form(source)
plan = tape_printer.plan_groups(labels, options, _group_spec())
buffer = dymo_sheet.render_pdf(
plan, sheet, options, current_app.config['UPLOAD_FOLDER'],
copies=options.copies)
if buffer is None:
flash('Nothing to render.', 'warning')
return redirect(url_for('labels.index'))
stock = dymo_sheet.get_stock(sheet.stock)
# Truncated, not rounded: 101.6mm stock is sold as "101 x 54".
name = f'lego-labels-{int(stock.width_mm)}x{int(stock.height_mm)}mm.pdf'
return send_file(buffer, mimetype='application/pdf', as_attachment=True,
download_name=name)