From 2f8bda69396fdb34de82b5e75fd54433fa3a2856 Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 10:46:23 +0200 Subject: [PATCH 01/12] Remove unnecessary _functions --- src/labelle/lib/devices/dymo_labeler.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/labelle/lib/devices/dymo_labeler.py b/src/labelle/lib/devices/dymo_labeler.py index 4b6d6445..0183f827 100755 --- a/src/labelle/lib/devices/dymo_labeler.py +++ b/src/labelle/lib/devices/dymo_labeler.py @@ -273,15 +273,6 @@ def get_label_height_px(self): """Get the (usable) tape height in pixels.""" return self.tape_print_properties.usable_tape_height_px - @property - def _functions(self) -> DymoLabelerFunctions: - assert self._device is not None - return DymoLabelerFunctions( - devout=self._device.devout, - devin=self._device.devin, - synwait=64, - ) - @property def minimum_horizontal_margin_mm(self): # Return distance between printhead and cutter @@ -431,7 +422,13 @@ def print( try: LOG.debug("Printing label..") - self._functions.print_label(label_matrix) + assert self._device is not None + functions = DymoLabelerFunctions( + devout=self._device.devout, + devin=self._device.devin, + synwait=64, + ) + functions.print_label(label_matrix) LOG.debug("Done printing.") if self._device is not None: self._device.dispose() From a4493260e29b677d6a03a04d49027584f63d0b12 Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 13:05:20 +0200 Subject: [PATCH 02/12] Use integer pixel margins with NamedTuple --- src/labelle/cli/cli.py | 4 +- src/labelle/gui/q_labels_list.py | 4 +- src/labelle/lib/devices/dymo_labeler.py | 10 +-- src/labelle/lib/render_engines/margins.py | 62 ++++++++++++------- .../lib/render_engines/print_payload.py | 4 +- .../lib/render_engines/print_preview.py | 16 ++--- 6 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/labelle/cli/cli.py b/src/labelle/cli/cli.py index 7f16b0ca..839886a0 100755 --- a/src/labelle/cli/cli.py +++ b/src/labelle/cli/cli.py @@ -535,7 +535,7 @@ def default( render_engine=render_engine, justify=justify, visible_horizontal_margin_px=margin_px, - labeler_margin_px=dymo_labeler.labeler_margin_px, + labeler_margin_px=dymo_labeler.get_labeler_margin_px(), max_width_px=max_payload_len_px, min_width_px=min_payload_len_px, ) @@ -547,7 +547,7 @@ def default( dymo_labeler=dymo_labeler, justify=justify, visible_horizontal_margin_px=margin_px, - labeler_margin_px=dymo_labeler.labeler_margin_px, + labeler_margin_px=dymo_labeler.get_labeler_margin_px(), max_width_px=max_payload_len_px, min_width_px=min_payload_len_px, ) diff --git a/src/labelle/gui/q_labels_list.py b/src/labelle/gui/q_labels_list.py index bbaa133e..5b0635cf 100644 --- a/src/labelle/gui/q_labels_list.py +++ b/src/labelle/gui/q_labels_list.py @@ -161,7 +161,7 @@ def render_preview(self) -> None: dymo_labeler=self.dymo_labeler, justify=self.justify, visible_horizontal_margin_px=self.dymo_labeler.mm_to_px(self.h_margin_mm), - labeler_margin_px=self.dymo_labeler.labeler_margin_px, + labeler_margin_px=self.dymo_labeler.get_labeler_margin_px(), max_width_px=None, min_width_px=self.dymo_labeler.mm_to_px(self.min_label_width_mm), ) @@ -180,7 +180,7 @@ def render_print(self) -> None: render_engine=self._payload_render_engine, justify=self.justify, visible_horizontal_margin_px=self.dymo_labeler.mm_to_px(self.h_margin_mm), - labeler_margin_px=self.dymo_labeler.labeler_margin_px, + labeler_margin_px=self.dymo_labeler.get_labeler_margin_px(), max_width_px=None, min_width_px=self.dymo_labeler.mm_to_px(self.min_label_width_mm), ) diff --git a/src/labelle/lib/devices/dymo_labeler.py b/src/labelle/lib/devices/dymo_labeler.py index 0183f827..88445478 100755 --- a/src/labelle/lib/devices/dymo_labeler.py +++ b/src/labelle/lib/devices/dymo_labeler.py @@ -20,6 +20,7 @@ from labelle.lib.devices.device_config import DeviceConfig from labelle.lib.devices.device_manager import get_device_config_by_id from labelle.lib.devices.usb_device import UsbDevice, UsbDeviceError +from labelle.lib.render_engines.margins import LabelMarginsPx LOG = logging.getLogger(__name__) POSSIBLE_USB_ERRORS = (UsbDeviceError, NoBackendError, USBError) @@ -281,11 +282,10 @@ def minimum_horizontal_margin_mm(self): self.device_config.distance_between_print_head_and_cutter_px ) - @property - def labeler_margin_px(self) -> tuple[float, float]: - return ( - self.device_config.distance_between_print_head_and_cutter_px, - self.tape_print_properties.top_margin_px, + def get_labeler_margin_px(self) -> LabelMarginsPx: + return LabelMarginsPx( + horizontal=self.device_config.distance_between_print_head_and_cutter_px, + vertical=self.tape_print_properties.top_margin_px, ) @property diff --git a/src/labelle/lib/render_engines/margins.py b/src/labelle/lib/render_engines/margins.py index a634920d..538695d8 100644 --- a/src/labelle/lib/render_engines/margins.py +++ b/src/labelle/lib/render_engines/margins.py @@ -1,7 +1,6 @@ from __future__ import annotations -import math -from typing import Literal +from typing import Literal, NamedTuple from PIL import Image @@ -14,6 +13,10 @@ ) +class LabelMarginsPx(NamedTuple): + horizontal: int + vertical: int + class BitmapTooBigError(RenderEngineException): def __init__(self, width_px, max_width_px): msg = f"width_px: {width_px}, max_width_px: {max_width_px}" @@ -21,21 +24,28 @@ def __init__(self, width_px, max_width_px): class MarginsRenderEngine(RenderEngine): + label_margins_px: LabelMarginsPx + min_width_px: int + max_width_px: int + visible_horizontal_margin_px: int + render_engine: RenderEngine + def __init__( self, render_engine: RenderEngine, mode: Literal["print", "preview"], justify: Direction = Direction.CENTER, - visible_horizontal_margin_px: float = 0, - labeler_margin_px: tuple[float, float] = (0, 0), - max_width_px: float | None = None, - min_width_px: float | None = 0, + visible_horizontal_margin_px: int = 0, + labeler_margin_px: LabelMarginsPx | None = None, + max_width_px: int | None = None, + min_width_px: int | None = None, ): super().__init__() - labeler_horizontal_margin_px, labeler_vertical_margin_px = labeler_margin_px + if labeler_margin_px is None: + labeler_margin_px = LabelMarginsPx(horizontal=0, vertical=0) assert visible_horizontal_margin_px >= 0 - assert labeler_horizontal_margin_px >= 0 - assert labeler_vertical_margin_px >= 0 + assert labeler_margin_px.horizontal >= 0 + assert labeler_margin_px.vertical >= 0 assert not max_width_px or max_width_px >= 0 if min_width_px is None: min_width_px = 0 @@ -43,20 +53,24 @@ def __init__( self.mode = mode self.justify = justify if is_dev_mode_no_margins(): - self.visible_horizontal_margin_px = 0.0 - self.labeler_horizontal_margin_px = 0.0 - self.min_width_px = 0.0 + self.visible_horizontal_margin_px = 0 + labeler_horizontal_margin_px = 0 + self.min_width_px = 0 else: self.visible_horizontal_margin_px = visible_horizontal_margin_px - self.labeler_horizontal_margin_px = labeler_horizontal_margin_px + labeler_horizontal_margin_px = labeler_margin_px.horizontal self.min_width_px = min_width_px - self.labeler_vertical_margin_px = labeler_vertical_margin_px + labeler_vertical_margin_px = labeler_margin_px.vertical + self.label_margins_px = LabelMarginsPx( + horizontal=labeler_horizontal_margin_px, + vertical=labeler_vertical_margin_px, + ) self.max_width_px = max_width_px self.render_engine = render_engine - def _calculate_visible_width(self, payload_width_px: int) -> float: + def _calculate_visible_width(self, payload_width_px: int) -> int: minimal_label_width_px = ( - payload_width_px + self.visible_horizontal_margin_px * 2.0 + payload_width_px + self.visible_horizontal_margin_px * 2 ) if self.max_width_px is not None and minimal_label_width_px > self.max_width_px: raise BitmapTooBigError(minimal_label_width_px, self.max_width_px) @@ -72,7 +86,7 @@ def render(self, _: RenderContext) -> Image.Image: def render_with_meta( self, context: RenderContext - ) -> tuple[Image.Image, dict[str, float]]: + ) -> tuple[Image.Image, dict[str, int]]: payload_bitmap = self.render_engine.render(context) payload_width_px = payload_bitmap.width label_width_px = self._calculate_visible_width(payload_width_px) @@ -81,9 +95,11 @@ def render_with_meta( if self.justify == Direction.LEFT: horizontal_offset_px = self.visible_horizontal_margin_px elif self.justify == Direction.CENTER: - horizontal_offset_px = padding_px / 2 + horizontal_offset_px = padding_px // 2 elif self.justify == Direction.RIGHT: horizontal_offset_px = padding_px - self.visible_horizontal_margin_px + else: + raise ValueError(f"Invalid justify value: {self.justify}") assert horizontal_offset_px >= self.visible_horizontal_margin_px # In print mode: @@ -107,20 +123,20 @@ def render_with_meta( if self.mode == "print": # print head is already in offset from label's edge under the cutter - horizontal_offset_px -= self.labeler_horizontal_margin_px + horizontal_offset_px -= self.label_margins_px.horizontal # add vertical margins to bitmap bitmap_height = ( - float(payload_bitmap.height) + self.labeler_vertical_margin_px * 2.0 + payload_bitmap.height + self.label_margins_px.vertical * 2 ) - bitmap = Image.new("1", (math.ceil(label_width_px), math.ceil(bitmap_height))) + bitmap = Image.new("1", (label_width_px, bitmap_height)) bitmap.paste( payload_bitmap, - box=(round(horizontal_offset_px), round(self.labeler_vertical_margin_px)), + box=(horizontal_offset_px, self.label_margins_px.vertical), ) meta = { "horizontal_offset_px": horizontal_offset_px, - "vertical_offset_px": self.labeler_vertical_margin_px, + "vertical_offset_px": self.label_margins_px.vertical, } return bitmap, meta diff --git a/src/labelle/lib/render_engines/print_payload.py b/src/labelle/lib/render_engines/print_payload.py index a8388aff..254fbd83 100644 --- a/src/labelle/lib/render_engines/print_payload.py +++ b/src/labelle/lib/render_engines/print_payload.py @@ -3,7 +3,7 @@ from PIL import Image from labelle.lib.constants import Direction -from labelle.lib.render_engines.margins import MarginsRenderEngine +from labelle.lib.render_engines.margins import LabelMarginsPx, MarginsRenderEngine from labelle.lib.render_engines.render_context import RenderContext from labelle.lib.render_engines.render_engine import RenderEngine @@ -14,7 +14,7 @@ def __init__( render_engine: RenderEngine, justify: Direction = Direction.CENTER, visible_horizontal_margin_px: float = 0, - labeler_margin_px: tuple[float, float] = (0, 0), + labeler_margin_px: LabelMarginsPx | None = None, max_width_px: float | None = None, min_width_px: float | None = 0, ): diff --git a/src/labelle/lib/render_engines/print_preview.py b/src/labelle/lib/render_engines/print_preview.py index 4cbc32cc..ecda8d5f 100644 --- a/src/labelle/lib/render_engines/print_preview.py +++ b/src/labelle/lib/render_engines/print_preview.py @@ -1,20 +1,22 @@ from __future__ import annotations +import math + from darkdetect import isDark from PIL import Image, ImageColor, ImageDraw, ImageOps from labelle.lib.constants import Direction from labelle.lib.devices.dymo_labeler import DymoLabeler -from labelle.lib.render_engines.margins import MarginsRenderEngine +from labelle.lib.render_engines.margins import LabelMarginsPx, MarginsRenderEngine from labelle.lib.render_engines.render_context import RenderContext from labelle.lib.render_engines.render_engine import RenderEngine class PrintPreviewRenderEngine(RenderEngine): - X_MARGIN_PX = 80 - Y_MARGIN_PX = 30 - DX = X_MARGIN_PX * 0.3 - DY = Y_MARGIN_PX * 0.3 + X_MARGIN_PX: int = 80 + Y_MARGIN_PX: int = 30 + DX: int = math.floor(X_MARGIN_PX * 0.3) + DY: int = math.floor(Y_MARGIN_PX * 0.3) dymo_labeler: DymoLabeler def __init__( @@ -23,10 +25,10 @@ def __init__( dymo_labeler: DymoLabeler, justify: Direction = Direction.CENTER, visible_horizontal_margin_px: float = 0, - labeler_margin_px: tuple[float, float] = (0, 0), + labeler_margin_px: LabelMarginsPx | None = None, max_width_px: float | None = None, min_width_px: float = 0, - ): + ) -> None: super().__init__() self.dymo_labeler = dymo_labeler self.render_engine = MarginsRenderEngine( From 46497833dee0cf00057e9a6d886fc43b022a2ae7 Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 13:30:06 +0200 Subject: [PATCH 03/12] Fix types for integer pixels --- src/labelle/cli/cli.py | 7 +++--- src/labelle/gui/q_labels_list.py | 4 ++++ src/labelle/lib/devices/dymo_labeler.py | 6 ++--- src/labelle/lib/render_engines/margins.py | 7 +++--- .../lib/render_engines/print_payload.py | 8 +++---- .../lib/render_engines/print_preview.py | 22 +++++++++++++------ .../lib/render_engines/render_engine.py | 2 +- 7 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/labelle/cli/cli.py b/src/labelle/cli/cli.py index 839886a0..a96446e9 100755 --- a/src/labelle/cli/cli.py +++ b/src/labelle/cli/cli.py @@ -6,6 +6,7 @@ # this notice are preserved. # === END LICENSE STATEMENT === import logging +import math from pathlib import Path from typing import List, NoReturn, Optional @@ -53,12 +54,12 @@ LOG = logging.getLogger(__name__) -def mm_to_payload_px(labeler: DymoLabeler, mm: float, margin: float) -> float: +def mm_to_payload_px(labeler: DymoLabeler, mm: float, margin: int) -> int: """Convert a length in mm to a number of pixels of payload. Margin is subtracted from each side. """ - return max(0, (mm * labeler.pixels_per_mm()) - margin * 2) + return max(0, math.ceil(mm * labeler.pixels_per_mm()) - margin * 2) def version_callback(value: bool) -> None: @@ -220,7 +221,7 @@ def default( Optional[Path], typer.Option(help="Picture", rich_help_panel="Elements") ] = None, margin_px: Annotated[ - float, + int, typer.Option( help="Horizontal margins [px]", rich_help_panel="Label Dimensions" ), diff --git a/src/labelle/gui/q_labels_list.py b/src/labelle/gui/q_labels_list.py index 5b0635cf..5bcc33d1 100644 --- a/src/labelle/gui/q_labels_list.py +++ b/src/labelle/gui/q_labels_list.py @@ -156,6 +156,8 @@ def _payload_render_engine(self): def render_preview(self) -> None: assert self.dymo_labeler is not None assert self.render_context is not None + assert self.h_margin_mm is not None + assert self.min_label_width_mm is not None render_engine = PrintPreviewRenderEngine( render_engine=self._payload_render_engine, dymo_labeler=self.dymo_labeler, @@ -176,6 +178,8 @@ def render_preview(self) -> None: def render_print(self) -> None: assert self.dymo_labeler is not None assert self.render_context is not None + assert self.h_margin_mm is not None + assert self.min_label_width_mm is not None render_engine = PrintPayloadRenderEngine( render_engine=self._payload_render_engine, justify=self.justify, diff --git a/src/labelle/lib/devices/dymo_labeler.py b/src/labelle/lib/devices/dymo_labeler.py index 88445478..10064197 100755 --- a/src/labelle/lib/devices/dymo_labeler.py +++ b/src/labelle/lib/devices/dymo_labeler.py @@ -375,15 +375,15 @@ def pixels_per_mm(self) -> float: # Makes 7.11 pixels/mm return self.device_config.print_head_px / self.device_config.print_head_mm - def px_to_mm(self, px) -> float: + def px_to_mm(self, px: int) -> float: """Convert pixels to millimeters for the current printer.""" mm = px / self.pixels_per_mm() # Round up to nearest 0.1mm return math.ceil(mm * 10) / 10 - def mm_to_px(self, mm) -> float: + def mm_to_px(self, mm: float) -> int: """Convert millimeters to pixels for the current printer.""" - return mm * self.pixels_per_mm() + return math.ceil(mm * self.pixels_per_mm()) def print( self, diff --git a/src/labelle/lib/render_engines/margins.py b/src/labelle/lib/render_engines/margins.py index 538695d8..faa42600 100644 --- a/src/labelle/lib/render_engines/margins.py +++ b/src/labelle/lib/render_engines/margins.py @@ -17,6 +17,7 @@ class LabelMarginsPx(NamedTuple): horizontal: int vertical: int + class BitmapTooBigError(RenderEngineException): def __init__(self, width_px, max_width_px): msg = f"width_px: {width_px}, max_width_px: {max_width_px}" @@ -26,7 +27,7 @@ def __init__(self, width_px, max_width_px): class MarginsRenderEngine(RenderEngine): label_margins_px: LabelMarginsPx min_width_px: int - max_width_px: int + max_width_px: int | None visible_horizontal_margin_px: int render_engine: RenderEngine @@ -126,9 +127,7 @@ def render_with_meta( horizontal_offset_px -= self.label_margins_px.horizontal # add vertical margins to bitmap - bitmap_height = ( - payload_bitmap.height + self.label_margins_px.vertical * 2 - ) + bitmap_height = payload_bitmap.height + self.label_margins_px.vertical * 2 bitmap = Image.new("1", (label_width_px, bitmap_height)) bitmap.paste( diff --git a/src/labelle/lib/render_engines/print_payload.py b/src/labelle/lib/render_engines/print_payload.py index 254fbd83..1ab029e8 100644 --- a/src/labelle/lib/render_engines/print_payload.py +++ b/src/labelle/lib/render_engines/print_payload.py @@ -13,10 +13,10 @@ def __init__( self, render_engine: RenderEngine, justify: Direction = Direction.CENTER, - visible_horizontal_margin_px: float = 0, + visible_horizontal_margin_px: int = 0, labeler_margin_px: LabelMarginsPx | None = None, - max_width_px: float | None = None, - min_width_px: float | None = 0, + max_width_px: int | None = None, + min_width_px: int | None = 0, ): super().__init__() self.render_engine = MarginsRenderEngine( @@ -34,5 +34,5 @@ def render(self, _: RenderContext) -> Image.Image: def render_with_meta( self, context: RenderContext - ) -> tuple[Image.Image, dict[str, float]]: + ) -> tuple[Image.Image, dict[str, int]]: return self.render_engine.render_with_meta(context) diff --git a/src/labelle/lib/render_engines/print_preview.py b/src/labelle/lib/render_engines/print_preview.py index ecda8d5f..a0f8c9cd 100644 --- a/src/labelle/lib/render_engines/print_preview.py +++ b/src/labelle/lib/render_engines/print_preview.py @@ -24,10 +24,10 @@ def __init__( render_engine: RenderEngine, dymo_labeler: DymoLabeler, justify: Direction = Direction.CENTER, - visible_horizontal_margin_px: float = 0, + visible_horizontal_margin_px: int = 0, labeler_margin_px: LabelMarginsPx | None = None, - max_width_px: float | None = None, - min_width_px: float = 0, + max_width_px: int | None = None, + min_width_px: int = 0, ) -> None: super().__init__() self.dymo_labeler = dymo_labeler @@ -167,32 +167,40 @@ def _show_margins(self, label_bitmap, preview_bitmap, meta, context): fill=mark_color, ) + full_width_mm: float = self.dymo_labeler.px_to_mm(label_width) + full_height_mm: float = self.dymo_labeler.px_to_mm(label_height) + printable_width_mm: float = self.dymo_labeler.px_to_mm( + label_width - x_margin * 2 + ) + printable_height_mm: float = self.dymo_labeler.px_to_mm( + label_height - y_margin * 2 + ) labels = [ # payload width { "xy": (self.X_MARGIN_PX + label_width / 2, preview_width_mark_y), - "text": f"{self.dymo_labeler.px_to_mm(label_width - x_margin * 2)} mm", + "text": f"{printable_width_mm} mm", "anchor": "mm", "align": "center", }, # label width { "xy": (self.X_MARGIN_PX + label_width / 2, label_width_mark_y), - "text": f"{self.dymo_labeler.px_to_mm(label_width)} mm", + "text": f"{full_width_mm} mm", "anchor": "mm", "align": "center", }, # payload height { "xy": (preview_width_mark_x, self.DY + label_height / 2 - self.DY), - "text": f"{self.dymo_labeler.px_to_mm(label_height - y_margin * 2)} mm", + "text": f"{printable_height_mm} mm", "anchor": "mm", "align": "center", }, # label height { "xy": (label_width_mark_x, self.DY + label_height / 2 + self.DY), - "text": f"{self.dymo_labeler.px_to_mm(label_height)} mm", + "text": f"{full_height_mm} mm", "anchor": "mm", "align": "center", }, diff --git a/src/labelle/lib/render_engines/render_engine.py b/src/labelle/lib/render_engines/render_engine.py index 71aea987..d7687fb4 100644 --- a/src/labelle/lib/render_engines/render_engine.py +++ b/src/labelle/lib/render_engines/render_engine.py @@ -18,5 +18,5 @@ def render(self, context: RenderContext) -> Image.Image: def render_with_meta( self, context: RenderContext - ) -> tuple[Image.Image, dict[str, float] | None]: + ) -> tuple[Image.Image, dict[str, int] | None]: return self.render(context), None From 851c72ab39108fd8b8eb507ab465b34d0736aabe Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 13:35:18 +0200 Subject: [PATCH 04/12] Fix import cycle --- src/labelle/lib/devices/dymo_labeler.py | 2 +- src/labelle/lib/margins.py | 6 ++++++ src/labelle/lib/render_engines/margins.py | 7 +------ src/labelle/lib/render_engines/print_payload.py | 3 ++- src/labelle/lib/render_engines/print_preview.py | 3 ++- 5 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 src/labelle/lib/margins.py diff --git a/src/labelle/lib/devices/dymo_labeler.py b/src/labelle/lib/devices/dymo_labeler.py index 10064197..44d7498f 100755 --- a/src/labelle/lib/devices/dymo_labeler.py +++ b/src/labelle/lib/devices/dymo_labeler.py @@ -20,7 +20,7 @@ from labelle.lib.devices.device_config import DeviceConfig from labelle.lib.devices.device_manager import get_device_config_by_id from labelle.lib.devices.usb_device import UsbDevice, UsbDeviceError -from labelle.lib.render_engines.margins import LabelMarginsPx +from labelle.lib.margins import LabelMarginsPx LOG = logging.getLogger(__name__) POSSIBLE_USB_ERRORS = (UsbDeviceError, NoBackendError, USBError) diff --git a/src/labelle/lib/margins.py b/src/labelle/lib/margins.py new file mode 100644 index 00000000..37575f04 --- /dev/null +++ b/src/labelle/lib/margins.py @@ -0,0 +1,6 @@ +from typing import NamedTuple + + +class LabelMarginsPx(NamedTuple): + horizontal: int + vertical: int diff --git a/src/labelle/lib/render_engines/margins.py b/src/labelle/lib/render_engines/margins.py index faa42600..017c2a3c 100644 --- a/src/labelle/lib/render_engines/margins.py +++ b/src/labelle/lib/render_engines/margins.py @@ -11,12 +11,7 @@ RenderEngine, RenderEngineException, ) - - -class LabelMarginsPx(NamedTuple): - horizontal: int - vertical: int - +from labelle.lib.margins import LabelMarginsPx class BitmapTooBigError(RenderEngineException): def __init__(self, width_px, max_width_px): diff --git a/src/labelle/lib/render_engines/print_payload.py b/src/labelle/lib/render_engines/print_payload.py index 1ab029e8..c51fd083 100644 --- a/src/labelle/lib/render_engines/print_payload.py +++ b/src/labelle/lib/render_engines/print_payload.py @@ -3,7 +3,8 @@ from PIL import Image from labelle.lib.constants import Direction -from labelle.lib.render_engines.margins import LabelMarginsPx, MarginsRenderEngine +from labelle.lib.margins import LabelMarginsPx +from labelle.lib.render_engines.margins import MarginsRenderEngine from labelle.lib.render_engines.render_context import RenderContext from labelle.lib.render_engines.render_engine import RenderEngine diff --git a/src/labelle/lib/render_engines/print_preview.py b/src/labelle/lib/render_engines/print_preview.py index a0f8c9cd..3e49baee 100644 --- a/src/labelle/lib/render_engines/print_preview.py +++ b/src/labelle/lib/render_engines/print_preview.py @@ -7,7 +7,8 @@ from labelle.lib.constants import Direction from labelle.lib.devices.dymo_labeler import DymoLabeler -from labelle.lib.render_engines.margins import LabelMarginsPx, MarginsRenderEngine +from labelle.lib.margins import LabelMarginsPx +from labelle.lib.render_engines.margins import MarginsRenderEngine from labelle.lib.render_engines.render_context import RenderContext from labelle.lib.render_engines.render_engine import RenderEngine From f8deed2110126d2c720bc86d63f7e636fecd8e58 Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 14:35:29 +0200 Subject: [PATCH 05/12] Use 12mm default when no device present --- src/labelle/lib/devices/dymo_labeler.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/labelle/lib/devices/dymo_labeler.py b/src/labelle/lib/devices/dymo_labeler.py index 44d7498f..b6a80b06 100755 --- a/src/labelle/lib/devices/dymo_labeler.py +++ b/src/labelle/lib/devices/dymo_labeler.py @@ -259,8 +259,12 @@ def __init__( raise ValueError("No device config") if tape_size_mm is None: - # Select highest supported tape size as default, if not set - tape_size_mm = max(self.device_config.supported_tape_sizes_mm) + if device is None: + # If there's no device, then use the most common tape size + tape_size_mm = 12 + else: + # Select highest supported tape size as default, if not set + tape_size_mm = max(self.device_config.supported_tape_sizes_mm) # Check if selected tape size is supported if tape_size_mm not in self.device_config.supported_tape_sizes_mm: From c3350d8045cdcee24807c328ed30926357d36773 Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 14:56:10 +0200 Subject: [PATCH 06/12] Only disable the print button when no device attached --- src/labelle/gui/gui.py | 7 +++---- src/labelle/lib/devices/dymo_labeler.py | 4 ---- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/labelle/gui/gui.py b/src/labelle/gui/gui.py index 8bc6ce1c..34ff8a43 100644 --- a/src/labelle/gui/gui.py +++ b/src/labelle/gui/gui.py @@ -116,10 +116,9 @@ def _on_settings_changed(self, settings: Settings) -> None: justify=settings.justify, ) - is_ready = self._dymo_labeler.is_ready - self._settings_toolbar.setEnabled(is_ready) - self._label_list.setEnabled(is_ready) - self._render_widget.setEnabled(is_ready) + self._settings_toolbar.setEnabled(True) + self._label_list.setEnabled(True) + self._render_widget.setEnabled(self._dymo_labeler.device is not None) def _update_preview_render(self, preview_bitmap: Image.Image) -> None: self._render.update_preview_render(preview_bitmap) diff --git a/src/labelle/lib/devices/dymo_labeler.py b/src/labelle/lib/devices/dymo_labeler.py index b6a80b06..3129a4e4 100755 --- a/src/labelle/lib/devices/dymo_labeler.py +++ b/src/labelle/lib/devices/dymo_labeler.py @@ -369,10 +369,6 @@ def set_device(self, device: UsbDevice | None): LOG.error(e) self._device = device - @property - def is_ready(self) -> bool: - return self.device is not None - def pixels_per_mm(self) -> float: # Calculate the pixels per mm for this printer # Example: printhead of 128 Pixels, distributed over 18 mm of active area. From 77249bfa2ad145015e6a1a8b82eded7f964e46a3 Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 14:56:39 +0200 Subject: [PATCH 07/12] Turn compute_tape_print_properties into a function --- src/labelle/lib/devices/dymo_labeler.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/labelle/lib/devices/dymo_labeler.py b/src/labelle/lib/devices/dymo_labeler.py index 3129a4e4..e9897564 100755 --- a/src/labelle/lib/devices/dymo_labeler.py +++ b/src/labelle/lib/devices/dymo_labeler.py @@ -276,7 +276,7 @@ def __init__( def get_label_height_px(self): """Get the (usable) tape height in pixels.""" - return self.tape_print_properties.usable_tape_height_px + return self.compute_tape_print_properties().usable_tape_height_px @property def minimum_horizontal_margin_mm(self): @@ -287,13 +287,13 @@ def minimum_horizontal_margin_mm(self): ) def get_labeler_margin_px(self) -> LabelMarginsPx: + tape_print_properties = self.compute_tape_print_properties() return LabelMarginsPx( horizontal=self.device_config.distance_between_print_head_and_cutter_px, - vertical=self.tape_print_properties.top_margin_px, + vertical=tape_print_properties.top_margin_px, ) - @property - def tape_print_properties(self) -> TapePrintProperties: + def compute_tape_print_properties(self) -> TapePrintProperties: # Check if selected tape size supported if self.tape_size_mm not in self.device_config.supported_tape_sizes_mm: raise ValueError( From eb052887f70da631ae99f12559e7ee34e97474ef Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 16:24:08 +0200 Subject: [PATCH 08/12] Cleanup --- src/labelle/lib/render_engines/margins.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/labelle/lib/render_engines/margins.py b/src/labelle/lib/render_engines/margins.py index 017c2a3c..68e8bb34 100644 --- a/src/labelle/lib/render_engines/margins.py +++ b/src/labelle/lib/render_engines/margins.py @@ -1,17 +1,18 @@ from __future__ import annotations -from typing import Literal, NamedTuple +from typing import Literal from PIL import Image from labelle.lib.constants import Direction from labelle.lib.env_config import is_dev_mode_no_margins +from labelle.lib.margins import LabelMarginsPx from labelle.lib.render_engines.render_context import RenderContext from labelle.lib.render_engines.render_engine import ( RenderEngine, RenderEngineException, ) -from labelle.lib.margins import LabelMarginsPx + class BitmapTooBigError(RenderEngineException): def __init__(self, width_px, max_width_px): From 0a6e008bc15d00b84a920c3c893a4921baebef3f Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 17:34:05 +0200 Subject: [PATCH 09/12] Eliminate redundant property --- src/labelle/lib/devices/online_device_manager.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/labelle/lib/devices/online_device_manager.py b/src/labelle/lib/devices/online_device_manager.py index a8e0394c..ef0fa489 100644 --- a/src/labelle/lib/devices/online_device_manager.py +++ b/src/labelle/lib/devices/online_device_manager.py @@ -15,7 +15,7 @@ class OnlineDeviceManager(QWidget): - _last_scan_error: DeviceManagerError | None + last_scan_error: DeviceManagerError | None _status_time: QTimer _device_manager: DeviceManager last_scan_error_changed_signal = QtCore.pyqtSignal( @@ -26,20 +26,20 @@ class OnlineDeviceManager(QWidget): def __init__(self) -> None: super().__init__() self._device_manager = DeviceManager() - self._last_scan_error = None + self.last_scan_error = None self._init_timers() def _refresh_devices(self) -> None: - prev = self._last_scan_error + prev = self.last_scan_error try: changed = self._device_manager.scan() - self._last_scan_error = None + self.last_scan_error = None if changed: self.devices_changed_signal.emit() except DeviceManagerError as e: - self._last_scan_error = e + self.last_scan_error = e - if str(prev) != str(self._last_scan_error): + if str(prev) != str(self.last_scan_error): self.devices_changed_signal.emit() self.last_scan_error_changed_signal.emit() @@ -49,10 +49,6 @@ def _init_timers(self) -> None: self._status_time.start(2000) self._refresh_devices() - @property - def last_scan_error(self) -> DeviceManagerError | None: - return self._last_scan_error - @property def devices(self) -> list[UsbDevice]: return self._device_manager.devices From 1438d9cc5793a8b2dbf0c3a391ec8c5acd588875 Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 17:35:38 +0200 Subject: [PATCH 10/12] =?UTF-8?q?devices=20=E2=86=92=20get=5Fdevices=5Ffro?= =?UTF-8?q?m=5Flast=5Fscan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/labelle/cli/cli.py | 2 +- src/labelle/lib/devices/device_manager.py | 3 +-- src/labelle/lib/devices/online_device_manager.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/labelle/cli/cli.py b/src/labelle/cli/cli.py index a96446e9..15fab811 100755 --- a/src/labelle/cli/cli.py +++ b/src/labelle/cli/cli.py @@ -97,7 +97,7 @@ def list_devices() -> NoReturn: console = Console() headers = ["Manufacturer", "Product", "Serial Number", "USB"] table = Table(*headers, show_header=True) - for device in device_manager.devices: + for device in device_manager.get_devices_from_last_scan(): table.add_row( device.manufacturer, device.product, device.serial_number, device.usb_id ) diff --git a/src/labelle/lib/devices/device_manager.py b/src/labelle/lib/devices/device_manager.py index 1a66a2fd..104168f3 100644 --- a/src/labelle/lib/devices/device_manager.py +++ b/src/labelle/lib/devices/device_manager.py @@ -51,8 +51,7 @@ def scan(self) -> bool: changed = prev_set != cur_set return changed - @property - def devices(self) -> list[UsbDevice]: + def get_devices_from_last_scan(self) -> list[UsbDevice]: try: return sorted(self._devices.values(), key=lambda dev: dev.hash) except POSSIBLE_USB_ERRORS: diff --git a/src/labelle/lib/devices/online_device_manager.py b/src/labelle/lib/devices/online_device_manager.py index ef0fa489..73482248 100644 --- a/src/labelle/lib/devices/online_device_manager.py +++ b/src/labelle/lib/devices/online_device_manager.py @@ -51,4 +51,4 @@ def _init_timers(self) -> None: @property def devices(self) -> list[UsbDevice]: - return self._device_manager.devices + return self._device_manager.get_devices_from_last_scan() From 192dfb59e555fcc292d9bb12bf8214a8709978aa Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 17:39:22 +0200 Subject: [PATCH 11/12] =?UTF-8?q?supported=5Fdevices=20=E2=86=92=20find=5F?= =?UTF-8?q?supported=5Fdevices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/labelle/lib/devices/device_manager.py | 4 +++- src/labelle/lib/devices/usb_device.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/labelle/lib/devices/device_manager.py b/src/labelle/lib/devices/device_manager.py index 104168f3..bfcd1dec 100644 --- a/src/labelle/lib/devices/device_manager.py +++ b/src/labelle/lib/devices/device_manager.py @@ -32,7 +32,9 @@ def __init__(self) -> None: def scan(self) -> bool: prev = self._devices try: - cur = {dev.hash: dev for dev in UsbDevice.supported_devices() if dev.hash} + cur = { + dev.hash: dev for dev in UsbDevice.find_supported_devices() if dev.hash + } except POSSIBLE_USB_ERRORS as e: self._devices.clear() raise DeviceManagerError(f"Failed scanning devices: {e}") from e diff --git a/src/labelle/lib/devices/usb_device.py b/src/labelle/lib/devices/usb_device.py index 113d5a7d..7e9751d3 100644 --- a/src/labelle/lib/devices/usb_device.py +++ b/src/labelle/lib/devices/usb_device.py @@ -98,7 +98,7 @@ def is_supported(self) -> bool: return False @staticmethod - def supported_devices() -> set[UsbDevice]: + def find_supported_devices() -> set[UsbDevice]: return { UsbDevice(dev) for dev in usb.core.find( From bdf8d38631af6a6394b0945a303d3576f15c0b10 Mon Sep 17 00:00:00 2001 From: Ben Mares Date: Thu, 4 Jul 2024 17:39:51 +0200 Subject: [PATCH 12/12] Comments/docstrings --- src/labelle/gui/q_device_selector.py | 1 + src/labelle/lib/devices/device_manager.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/labelle/gui/q_device_selector.py b/src/labelle/gui/q_device_selector.py index 0465cb7e..2565cbd1 100644 --- a/src/labelle/gui/q_device_selector.py +++ b/src/labelle/gui/q_device_selector.py @@ -48,6 +48,7 @@ def _init_connections(self) -> None: self._devices.currentIndexChanged.connect(self._index_changed) def repopulate(self) -> None: + """Update the device selector.""" old_hashes = {device.hash for device in self.device_manager.devices} self._devices.clear() for idx, device in enumerate(self.device_manager.devices): diff --git a/src/labelle/lib/devices/device_manager.py b/src/labelle/lib/devices/device_manager.py index bfcd1dec..1375c313 100644 --- a/src/labelle/lib/devices/device_manager.py +++ b/src/labelle/lib/devices/device_manager.py @@ -24,12 +24,22 @@ class DeviceManagerNoDevices(DeviceManagerError): class DeviceManager: + """Incrementally maintain a list of connected USB devices. + + The list begins empty. It is updated whenever scan() is called. The list is + accessible via get_devices_from_last_scan(). + """ + _devices: dict[str, UsbDevice] def __init__(self) -> None: self._devices = {} def scan(self) -> bool: + """Check for devices being connected or disconnected. + + Returns true if the list of devices has changed. + """ prev = self._devices try: cur = { @@ -46,8 +56,10 @@ def scan(self) -> bool: cur_set = set(cur) for dev in prev_set - cur_set: + # Pop removed devices self._devices.pop(dev) for dev in cur_set - prev_set: + # Add new devices self._devices[dev] = cur[dev] changed = prev_set != cur_set @@ -95,7 +107,6 @@ def get_device_config_by_id(product_id: int) -> DeviceConfig: :param idValue: USB ID value :return: Device config, None if not found """ - # for device in SUPPORTED_PRODUCTS: if device.matches_device_id(product_id): return device