Skip to content

Commit

Permalink
Remove constant DPI values and calculations
Browse files Browse the repository at this point in the history
As the DPI or PPM (pixels per mm) is now a property of the printer. Move all calculations requiring this value to the labeler object.
This will improve future support of different DPI printers
  • Loading branch information
FaBjE committed Jun 23, 2024
1 parent 0fa7ad5 commit 7246473
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 51 deletions.
25 changes: 13 additions & 12 deletions src/labelle/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from labelle.lib.constants import (
DEFAULT_BARCODE_TYPE,
DEFAULT_MARGIN_PX,
PIXELS_PER_MM,
USE_QR,
BarcodeType,
Direction,
Expand Down Expand Up @@ -54,12 +53,12 @@
LOG = logging.getLogger(__name__)


def mm_to_payload_px(mm: float, margin: float) -> float:
def mm_to_payload_px(labeler: DymoLabeler, mm: float, margin: float) -> float:
"""Convert a length in mm to a number of pixels of payload.
The print resolution is 7 pixels/mm, and margin is subtracted from each side.
Margin is subtracted from each side.
"""
return max(0, (mm * PIXELS_PER_MM) - margin * 2)
return max(0, (mm * labeler.pixels_per_mm) - margin * 2)


def version_callback(value: bool) -> None:
Expand Down Expand Up @@ -503,13 +502,6 @@ def default(
min_label_mm_len = fixed_length
max_label_mm_len = fixed_length

min_payload_len_px = mm_to_payload_px(min_label_mm_len, margin_px)
max_payload_len_px = (
mm_to_payload_px(max_label_mm_len, margin_px)
if max_label_mm_len is not None
else None
)

if output == Output.PRINTER:
device_manager = get_device_manager()
device = device_manager.find_and_select_device(patterns=device_pattern)
Expand All @@ -518,13 +510,21 @@ def default(
device = None

dymo_labeler = DymoLabeler(tape_size_mm=tape_size_mm, device=device)

min_payload_len_px = mm_to_payload_px(dymo_labeler, min_label_mm_len, margin_px)
max_payload_len_px = (
mm_to_payload_px(dymo_labeler, max_label_mm_len, margin_px)
if max_label_mm_len is not None
else None
)

if not render_engines:
raise typer.BadParameter("No elements to print")
render_engine = HorizontallyCombinedRenderEngine(render_engines)
render_context = RenderContext(
background_color="white",
foreground_color="black",
height_px=dymo_labeler.height_px,
height_px=dymo_labeler.label_height_px,
preview_show_margins=False,
)

Expand All @@ -544,6 +544,7 @@ def default(
else:
render = PrintPreviewRenderEngine(
render_engine=render_engine,
dymo_labeler=dymo_labeler,
justify=justify,
visible_horizontal_margin_px=margin_px,
labeler_margin_px=dymo_labeler.labeler_margin_px,
Expand Down
2 changes: 1 addition & 1 deletion src/labelle/gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def _on_settings_changed(self, settings: Settings) -> None:
self._render_context = RenderContext(
foreground_color=settings.foreground_color,
background_color=settings.background_color,
height_px=self._dymo_labeler.height_px,
height_px=self._dymo_labeler.label_height_px,
preview_show_margins=settings.preview_show_margins,
)
self._label_list.update_params(
Expand Down
10 changes: 5 additions & 5 deletions src/labelle/gui/q_labels_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
RenderEngine,
RenderEngineException,
)
from labelle.lib.utils import mm_to_px

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -159,11 +158,12 @@ def render_preview(self) -> None:
assert self.render_context is not None
render_engine = PrintPreviewRenderEngine(
render_engine=self._payload_render_engine,
dymo_labeler=self.dymo_labeler,
justify=self.justify,
visible_horizontal_margin_px=mm_to_px(self.h_margin_mm),
visible_horizontal_margin_px=self.dymo_labeler.mm_to_px(self.h_margin_mm),
labeler_margin_px=self.dymo_labeler.labeler_margin_px,
max_width_px=None,
min_width_px=mm_to_px(self.min_label_width_mm),
min_width_px=self.dymo_labeler.mm_to_px(self.min_label_width_mm),
)
try:
bitmap = render_engine.render(self.render_context)
Expand All @@ -179,10 +179,10 @@ def render_print(self) -> None:
render_engine = PrintPayloadRenderEngine(
render_engine=self._payload_render_engine,
justify=self.justify,
visible_horizontal_margin_px=mm_to_px(self.h_margin_mm),
visible_horizontal_margin_px=self.dymo_labeler.mm_to_px(self.h_margin_mm),
labeler_margin_px=self.dymo_labeler.labeler_margin_px,
max_width_px=None,
min_width_px=mm_to_px(self.min_label_width_mm),
min_width_px=self.dymo_labeler.mm_to_px(self.min_label_width_mm),
)
try:
bitmap, _ = render_engine.render_with_meta(self.render_context)
Expand Down
2 changes: 1 addition & 1 deletion src/labelle/gui/q_settings_toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def _init_elements(self) -> None:

def update_labeler_context(
self,
supported_tape_sizes: tuple[int, ...],
supported_tape_sizes: list[int],
installed_tape_size: int,
minimum_horizontal_margin_mm: float,
) -> None:
Expand Down
4 changes: 0 additions & 4 deletions src/labelle/lib/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,6 @@
DEFAULT_MARGIN_PX = 56
VERTICAL_PREVIEW_MARGIN_PX = 13

DPI = 180
MM_PER_INCH = 25.4
PIXELS_PER_MM = DPI / MM_PER_INCH

ICON_DIR = Path(labelle.resources.icons.__file__).parent


Expand Down
36 changes: 25 additions & 11 deletions src/labelle/lib/devices/dymo_labeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
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.utils import mm_to_px

LOG = logging.getLogger(__name__)
POSSIBLE_USB_ERRORS = (UsbDeviceError, NoBackendError, USBError)
Expand Down Expand Up @@ -257,6 +256,9 @@ def __init__(
):
self.device = device

if self._device_config is None:
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)
Expand All @@ -270,7 +272,7 @@ def __init__(
self.tape_size_mm = tape_size_mm

@property
def height_px(self):
def label_height_px(self):
"""Get the (usable) tape height in pixels."""
return self.tape_print_properties.usable_tape_height_px

Expand All @@ -285,12 +287,14 @@ def _functions(self) -> DymoLabelerFunctions:

@property
def minimum_horizontal_margin_mm(self):
# Return distance between printhead and cutter
# as we don't want to cut though our printed label
return self.device_config.LABELER_DISTANCE_BETWEEN_PRINT_HEAD_AND_CUTTER_MM

@property
def labeler_margin_px(self) -> tuple[float, float]:
return (
mm_to_px(self.minimum_horizontal_margin_mm),
self.mm_to_px(self.minimum_horizontal_margin_mm),
self.tape_print_properties.top_margin_px,
)

Expand All @@ -303,13 +307,6 @@ def tape_print_properties(self) -> TapePrintProperties:
f"Supported sizes: {self.device_config.supported_tape_sizes_mm}mm"
)

# Calculate the pixels per mm for this printer
# Example: printhead of 128 Pixels, distributed over 18 mm of active area.
# Makes 7.11 pixels/mm
print_pixels_per_mm: float = (
self.device_config.print_head_px / self.device_config.print_head_mm
)

# Calculate usable tape height (*2 for top and bottom)
usable_tape_height_mm: float = self.tape_size_mm - (
2 * self.device_config.tape_alignment_inaccuracy_mm
Expand All @@ -323,7 +320,7 @@ def tape_print_properties(self) -> TapePrintProperties:
else:
# Calculate the amount of active pixels we are able to use
# (taking the placement inaccuracy into account)
usable_tape_height_pixels = print_pixels_per_mm * usable_tape_height_mm
usable_tape_height_pixels = self.pixels_per_mm * usable_tape_height_mm

# Round down to nearest whole number as we can't use half a pixels ;)
usable_tape_height_pixels = math.floor(usable_tape_height_pixels)
Expand Down Expand Up @@ -385,6 +382,23 @@ def device(self, device: UsbDevice | None):
def is_ready(self) -> bool:
return self.device is not None

@property
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.
# 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:
"""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:
"""Convert millimeters to pixels for the current printer."""
return mm * self.pixels_per_mm

def print(
self,
bitmap: Image.Image,
Expand Down
13 changes: 8 additions & 5 deletions src/labelle/lib/render_engines/print_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,31 @@
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.render_context import RenderContext
from labelle.lib.render_engines.render_engine import RenderEngine
from labelle.lib.utils import px_to_mm


class PrintPreviewRenderEngine(RenderEngine):
X_MARGIN_PX = 80
Y_MARGIN_PX = 30
DX = X_MARGIN_PX * 0.3
DY = Y_MARGIN_PX * 0.3
dymo_labeler: DymoLabeler

def __init__(
self,
render_engine: RenderEngine,
dymo_labeler: DymoLabeler,
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 = 0,
):
super().__init__()
self.dymo_labeler = dymo_labeler
self.render_engine = MarginsRenderEngine(
render_engine=render_engine,
mode="preview",
Expand Down Expand Up @@ -166,28 +169,28 @@ def _show_margins(self, label_bitmap, preview_bitmap, meta, context):
# payload width
{
"xy": (self.X_MARGIN_PX + label_width / 2, preview_width_mark_y),
"text": f"{px_to_mm(label_width - x_margin * 2)} mm",
"text": f"{self.dymo_labeler.px_to_mm(label_width - x_margin * 2)} mm",
"anchor": "mm",
"align": "center",
},
# label width
{
"xy": (self.X_MARGIN_PX + label_width / 2, label_width_mark_y),
"text": f"{px_to_mm(label_width)} mm",
"text": f"{self.dymo_labeler.px_to_mm(label_width)} mm",
"anchor": "mm",
"align": "center",
},
# payload height
{
"xy": (preview_width_mark_x, self.DY + label_height / 2 - self.DY),
"text": f"{px_to_mm(label_height - y_margin * 2)} mm",
"text": f"{self.dymo_labeler.px_to_mm(label_height - y_margin * 2)} mm",
"anchor": "mm",
"align": "center",
},
# label height
{
"xy": (label_width_mark_x, self.DY + label_height / 2 + self.DY),
"text": f"{px_to_mm(label_height)} mm",
"text": f"{self.dymo_labeler.px_to_mm(label_height)} mm",
"anchor": "mm",
"align": "center",
},
Expand Down
12 changes: 0 additions & 12 deletions src/labelle/lib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@
# === END LICENSE STATEMENT ===
import contextlib
import logging
import math
import sys
from typing import Generator, List, Tuple

from PIL import ImageDraw

from labelle.lib.constants import PIXELS_PER_MM
from labelle.lib.logger import print_exception

LOG = logging.getLogger(__name__)
Expand All @@ -34,16 +32,6 @@ def draw_image(bitmap) -> Generator[ImageDraw.ImageDraw, None, None]:
del drawobj


def px_to_mm(px) -> float:
mm = px / PIXELS_PER_MM
# Round up to nearest 0.1mm
return math.ceil(mm * 10) / 10


def mm_to_px(mm) -> float:
return mm * PIXELS_PER_MM


@contextlib.contextmanager
def system_run() -> Generator[None, None, None]:
try:
Expand Down

0 comments on commit 7246473

Please sign in to comment.