Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vunz4VLxAYDBnU5tLciLDC
313 lines
14 KiB
Markdown
313 lines
14 KiB
Markdown
# Parts Drawer Labels
|
||
|
||
Admin-only labelling system for sorting drawers and bags. Browse a part
|
||
taxonomy, define a label per drawer, and print them onto sticker sheets,
|
||
continuous tape, or Dymo die-cut stock.
|
||
|
||
Lives at **`/admin/labels`** — in the admin menu as *Parts Labels*.
|
||
|
||
---
|
||
|
||
## Deploying
|
||
|
||
The feature is already wired into `app/__init__.py`, `schema_upgrade.py` and
|
||
`base.html`, so a normal pull is enough:
|
||
|
||
```
|
||
git pull
|
||
```
|
||
|
||
Restart LIM. The tables are created automatically at boot by
|
||
`app/services/schema_upgrade.py`, and the taxonomy seeds itself the first time
|
||
an admin opens the page.
|
||
|
||
If you'd rather make the database change deliberately before restarting:
|
||
|
||
```
|
||
python migrate_add_part_labels.py
|
||
python migrate_add_parts_catalogue.py
|
||
```
|
||
|
||
Optional extras:
|
||
|
||
| What | Why | How |
|
||
|------|-----|-----|
|
||
| Rebrickable API key | Part photos when importing a label | `REBRICKABLE_API_KEY=...` in `.env`, or paste it into *Labels → Settings* |
|
||
| ReportLab | PDF export button for sheets | `pip install reportlab` |
|
||
|
||
Neither is required. Without them you type labels in by hand, upload your own
|
||
images, and print from the browser. The part catalogue works without a key.
|
||
|
||
---
|
||
|
||
## Why not Brickset?
|
||
|
||
Brickset's API v3 covers sets, minifigs and instructions. It has **no parts
|
||
endpoint**, so it can't supply part numbers, names or part images.
|
||
[Rebrickable](https://rebrickable.com/api/) can, and a key is free. Brickset
|
||
stays where it is, doing sets.
|
||
|
||
## Why not Brick Architect's labels?
|
||
|
||
Tom Alphin's label PDFs and his category numbering are his own copyrighted
|
||
work. This feature doesn't scrape, copy or redistribute them. What ships is a
|
||
starter taxonomy built from standard LEGO/BrickLink part terminology, arranged
|
||
in the same *style* of hierarchy, and completely editable — rename, renumber,
|
||
recolour and restructure it from *Labels → Categories* so it matches how your
|
||
drawers actually run. If you want his exact labels, download them from his site
|
||
and use them; this is for generating your own.
|
||
|
||
---
|
||
|
||
## Day-to-day
|
||
|
||
**Adding labels**
|
||
|
||
- *New Label* — name, dimensions, part number, alternates, category, drawer,
|
||
colour, image.
|
||
- Or pick them off the *Part Catalogue* tab (see below), which is much faster
|
||
for more than a handful.
|
||
- Or, with a Rebrickable key, use the quick lookup box on the labels page.
|
||
|
||
**The `name` + `dimensions` split matters.** Name is the big bold line
|
||
("Brick", "Plate, Modified with Clip"); dimensions is the geometry ("2 x 4").
|
||
They're printed together but stored apart so you can turn dimensions off for
|
||
mixed drawers.
|
||
|
||
**Alternate part numbers** are for old moulds that live in the same drawer —
|
||
`3001a, 3001b`. Comma separated, searchable, optionally printed.
|
||
|
||
**Printing to sheets**
|
||
|
||
1. Tick labels (or *Print whole category*), press **Sheet**.
|
||
2. Pick a sheet, set the options, hit *Update preview*.
|
||
3. Print from the browser: **margins None, scale 100%** — not "Fit to page",
|
||
which is what ruins most label sheets.
|
||
|
||
Options worth knowing:
|
||
|
||
- **Skip positions** — start partway down a part-used sheet instead of wasting it.
|
||
- **Copies each** — same label on several drawers.
|
||
- **Offset X/Y** — per-printer alignment nudge in millimetres. Set your usual
|
||
value once in *Settings*.
|
||
- **Outline each label** — hairlines for cutting, and for test-printing on plain
|
||
paper before you commit a sheet of Averys.
|
||
- **Show on each label** — image, name, dimensions, part number, alternates,
|
||
category, category code, drawer, colour, category colour stripe.
|
||
|
||
**Sheet presets**: Avery L7651 (65/sheet, good for bags), L7160 (21/sheet, the
|
||
all-rounder), L7163 (14/sheet, wide), L7159 (24/sheet), L7169 (8/sheet, large),
|
||
Avery 5160 (US Letter), plus two plain-A4 grids for cut-your-own sticker paper
|
||
or paper-and-tape.
|
||
|
||
---
|
||
|
||
## The part catalogue (the Update button)
|
||
|
||
*Labels → Part Catalogue*, then **Update catalogue**.
|
||
|
||
This pulls Rebrickable's published catalogue dumps into a local table so that
|
||
making a label is picking a part off a list rather than typing it out:
|
||
|
||
https://cdn.rebrickable.com/media/downloads/part_categories.csv.gz
|
||
https://cdn.rebrickable.com/media/downloads/parts.csv.gz
|
||
|
||
Two gzipped CSVs, about 60,000 parts, regenerated monthly by Rebrickable and
|
||
free to use with a credit. **No API key is needed for this** — the key only
|
||
adds the real part photo at the moment a catalogue row becomes a label.
|
||
|
||
The sync runs on a background thread and reports progress to the page, so you
|
||
can close the tab. Re-running it is cheap and idempotent: it only writes rows
|
||
that are new or actually changed (a second run on an unchanged catalogue
|
||
writes nothing).
|
||
|
||
**Category mapping.** Rebrickable's categories are theirs; the drawer
|
||
taxonomy is yours. Open a Rebrickable category and set *"Parts in this
|
||
category become labels in …"* once, and from then on importing any part from
|
||
it drops the label straight into the right drawer group. Unmapped categories
|
||
just ask you each time.
|
||
|
||
**Making labels.** Tick parts, optionally override the drawer category, press
|
||
*Make labels*. Parts already labelled are flagged and skipped, so you can
|
||
re-import a category without creating duplicates. `Brick 2 x 4` is split into
|
||
name `Brick` + dimensions `2 x 4` on the way in, because the label stores them
|
||
separately so the print options can show or hide the geometry.
|
||
|
||
Thumbnails in the browse list use Rebrickable's predictable LDraw render path
|
||
and quietly disappear for parts that have no LDraw model. That is expected —
|
||
the real image is fetched per-part when you create the label.
|
||
|
||
---
|
||
|
||
## Printing to the Brother VC-500W
|
||
|
||
*Labels → tick some labels → **Tape***, or the tape icon on any row.
|
||
|
||
The VC-500W is AirPrint-enabled, which means it speaks **IPP on port 631** —
|
||
an open, documented protocol. So LIM talks to it directly over the network:
|
||
no Brother driver, no P-touch Editor, no exporting a PDF and printing it by
|
||
hand. Brother publish no raw command protocol for this printer and none is
|
||
reverse-engineered here; it is plain standard IPP (RFC 8010/8011).
|
||
|
||
**Setup.** Put the printer's IP address into *Labels → Settings → Tape
|
||
printer*. Give it a DHCP reservation first or the address will wander. Then
|
||
hit **Test connection** on the tape page: it reports the model, state, and
|
||
exactly which document formats the printer will accept.
|
||
|
||
**Format negotiation.** LIM renders at the printer's native 313 dpi and sends
|
||
whichever format the printer lists that LIM can produce — JPEG, PNG or PDF, in
|
||
that order. If your firmware only offers `image/urf` or raster formats, the
|
||
test will say so and you use *Download PDF* instead. You can pin a format in
|
||
Settings if auto-negotiation ever picks wrong.
|
||
|
||
**Tape widths.** CZ tape in 9, 12, 19, 25 and 50 mm. Each preset knows its
|
||
printable width; labels are sized to the content, or to a fixed length if you
|
||
set one. Maximum length is about 430 mm.
|
||
|
||
> **Check this on your first print:** labels are rotated 90° by default,
|
||
> because the tape feeds lengthwise. If your first one comes out sideways,
|
||
> untick *Rotate for tape feed* and print again. It is the one setting worth
|
||
> testing on a fresh printer, so print one before queueing fifty.
|
||
|
||
Copies are sent as repeated jobs rather than via the IPP `copies` attribute,
|
||
because tape printers vary wildly in whether they honour it.
|
||
|
||
**One job at a time.** The VC-500W prints at about 7.5 mm/s and accepts a
|
||
single job at a time: anything that arrives while the tape is still moving
|
||
comes back `server-error-busy`, which is why firing a batch at it used to
|
||
print the first label and fail the rest. LIM now waits for the printer to
|
||
report *idle* before each job and retries while it reports busy, so a batch
|
||
goes through in order. The trade is that printing is synchronous — a long run
|
||
holds the page until it finishes. **Don't refresh mid-run**, or you'll send
|
||
the batch twice and spend the tape twice.
|
||
|
||
### Combining parts onto one label
|
||
|
||
Under the preview: tick two or more labels and press **Combine onto one
|
||
label**. *Split apart* undoes it for the ticked ones, and *Back to drawers*
|
||
throws the manual grouping away and goes back to grouping on the drawer field.
|
||
|
||
Automatic grouping matches on the *drawer / location* field, which is what you
|
||
want for a sorted drawer. Combining by hand is for everything that has nothing
|
||
in common to match on — a custom build, a MOC bag, the odds and ends tray.
|
||
|
||
A hand-made group is never broken up: it is kept whatever *Size* says, and the
|
||
label grows 5 mm for each part past the second (two parts 20 mm, three 25 mm,
|
||
four 30 mm). The grouping lives in the URL, so it survives printing and you
|
||
can bookmark a set of combined labels.
|
||
|
||
### Text size
|
||
|
||
*Tape & layout → Text size*, 0.5× to 2×, with Normal / Large / Largest presets
|
||
and a slider. The same control is on the Dymo page.
|
||
|
||
The name is sized first and everything else gives way to it: the part image
|
||
shrinks (and is dropped entirely if that is what it takes) before the type
|
||
does. The whole name always prints — it wraps to two lines and settles at the
|
||
largest size that fits.
|
||
|
||
**40mm is 40mm.** Once the name spans the label, turning the dial further
|
||
changes nothing, and most names reach that by about 1.4×. What the setting
|
||
really buys you is the trade against the artwork: with a part image on the
|
||
label, 1.4× roughly halves the picture and makes the name half again as big.
|
||
Untick *Part image* to give the text the whole width from the start.
|
||
|
||
### What to do when it won't print
|
||
|
||
| Symptom | Likely cause |
|
||
|---|---|
|
||
| *Could not reach the printer* | Wrong IP, printer asleep, or on its own ad-hoc Wi-Fi rather than your LAN |
|
||
| *Printer rejected the job: …not-supported* | It won't take JPEG/PNG/PDF. Use Download PDF |
|
||
| Prints sideways | Untick *Rotate for tape feed* |
|
||
| Text clipped at the edges | Raise *Padding*, or lower *Text size* |
|
||
| Blank or faint | Cleaning cassette (CK-1000), or the tape is in backwards |
|
||
| *Printer stayed busy after 6 attempts* | Something else is printing to it, or a job is stuck. Power-cycle the printer |
|
||
| Text smaller than you asked for | A long name fits at that size and no larger. Untick *Part image*, or shorten the name |
|
||
|
||
---
|
||
|
||
## Printing on a Dymo LabelWriter
|
||
|
||
*Labels → tick some labels → **Dymo***, or the sticker icon on any row.
|
||
|
||
Tape is a roll; a LabelWriter is die-cut stock — one fixed rectangle at a
|
||
time. A 40 × 15mm drawer label is small enough that several fit on one
|
||
sticker, so LIM gangs them up with dashed cut guides and gives you a PDF.
|
||
Print, cut, stick. The labels are drawn by the same code as the tape ones, so
|
||
a drawer labelled from the Dymo looks identical to one off the VC-500W.
|
||
|
||
**Stock presets** (Dymo's own part numbers and sizes):
|
||
|
||
| Stock | Size | Standard labels per sticker |
|
||
|---|---|---|
|
||
| 99014 shipping | 101 × 54mm | 6 (2 across, 3 down) |
|
||
| 30256 shipping | 59 × 102mm | 5 (1 across, 5 down) |
|
||
| 1744907 (4XL, 4 × 6in) | 104 × 159mm | 18 |
|
||
| 99012 address | 89 × 36mm | 4 |
|
||
| 99010 address | 89 × 28mm | 2 |
|
||
|
||
*Labels per sticker* is auto by default — as many as physically fit — or pin
|
||
it to 4-up (2 × 2), 6-up (2 × 3) or one at a time. A two-part drawer label is
|
||
20mm instead of 15, so a mixed run fits fewer; labels stack down a column and
|
||
then across, which wastes less of the sticker than filling it in rows.
|
||
|
||
**Printing.** Each PDF page is exactly the stock size, so set the Dymo driver
|
||
to that label and print at **100% / Actual size** — never "Fit to page", which
|
||
quietly shrinks the 40mm labels by a few percent. There is no direct network
|
||
printing here because the LabelWriter 4XL is a USB printer; the PDF goes
|
||
through whatever driver the machine already has.
|
||
|
||
**It is a thermal printer**, so there is no colour and no greys of its own:
|
||
black dots or nothing. The preview converts to monochrome to show you what
|
||
will actually come out — untick *Monochrome* if you would rather judge the
|
||
colour version. Category colour stripes still print, as black bars.
|
||
|
||
Other options worth knowing: *Skip slots* starts partway into a part-used
|
||
sticker, *Margin* keeps ink off the die-cut edge where feed tolerance lives,
|
||
*Gap* is cutting room between labels, and *Copies each* repeats every label.
|
||
|
||
Set the stock you usually keep loaded in *Labels → Settings → Dymo
|
||
LabelWriter* and the page starts there.
|
||
|
||
---
|
||
|
||
## The catalogue look
|
||
|
||
The labelling pages use a denser, cooler-toned skin than the rest of LIM
|
||
(`app/static/css/labels-bricklink.css`, scoped to `.bl`). That is deliberate:
|
||
this is a reference catalogue where you scan hundreds of rows, not a library
|
||
you browse by cover art — so small type, tight rows, thin rules and counts on
|
||
everything. It is our own stylesheet, not a copy of anyone's site, and it does
|
||
not leak into the red LEGO-themed pages.
|
||
|
||
---
|
||
|
||
## Where things live
|
||
|
||
```
|
||
app/models/part_label.py PartCategory (tree) + PartLabel
|
||
app/models/part_catalog_item.py local Rebrickable mirror + sync history
|
||
app/services/part_catalog.py taxonomy seeding, Rebrickable API client
|
||
app/services/catalog_sync.py the Update button: CSV dumps -> database
|
||
app/services/label_render.py sheet geometry, layout, optional PDF
|
||
app/services/tape_printer.py 313dpi tape rendering, CZ tape presets
|
||
app/services/ipp_client.py minimal IPP client (RFC 8010/8011)
|
||
app/routes/labels.py /admin/labels (all admin_required)
|
||
app/routes/labels_catalog.py /admin/labels/catalog
|
||
app/routes/labels_tape.py /admin/labels/tape
|
||
app/services/dymo_sheet.py Dymo stock presets, ganging, 300dpi PDF
|
||
app/routes/labels_dymo.py /admin/labels/dymo
|
||
app/data/part_taxonomy.json the starter taxonomy
|
||
app/static/css/labels-bricklink.css the catalogue skin
|
||
app/templates/admin/labels/ index, form, categories, settings, print,
|
||
catalog, tape, dymo, _nav
|
||
<UPLOAD_FOLDER>/parts/ cached and uploaded part images
|
||
```
|
||
|
||
Labels are **not** an inventory. There's no quantity field on purpose — the
|
||
drawer tells you how many you have.
|
||
|
||
---
|
||
|
||
Created by: Jess Rogerson (yelling commands at Claude.AI)
|