""" 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)