Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop cleanup 2 #82

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
13 changes: 7 additions & 6 deletions src/labelle/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -96,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
)
Expand Down Expand Up @@ -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"
),
Expand Down Expand Up @@ -535,7 +536,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,
)
Expand All @@ -547,7 +548,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,
)
Expand Down
7 changes: 3 additions & 4 deletions src/labelle/gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/labelle/gui/q_device_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
8 changes: 6 additions & 2 deletions src/labelle/gui/q_labels_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,14 @@ 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,
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),
)
Expand All @@ -176,11 +178,13 @@ 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,
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),
)
Expand Down
20 changes: 16 additions & 4 deletions src/labelle/lib/devices/device_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,27 @@ 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 = {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
Expand All @@ -44,15 +56,16 @@ 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
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:
Expand Down Expand Up @@ -94,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
Expand Down
51 changes: 24 additions & 27 deletions src/labelle/lib/devices/dymo_labeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.margins import LabelMarginsPx

LOG = logging.getLogger(__name__)
POSSIBLE_USB_ERRORS = (UsbDeviceError, NoBackendError, USBError)
Expand Down Expand Up @@ -258,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:
Expand All @@ -271,16 +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

@property
def _functions(self) -> DymoLabelerFunctions:
assert self._device is not None
return DymoLabelerFunctions(
devout=self._device.devout,
devin=self._device.devin,
synwait=64,
)
return self.compute_tape_print_properties().usable_tape_height_px

@property
def minimum_horizontal_margin_mm(self):
Expand All @@ -290,15 +286,14 @@ 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:
tape_print_properties = self.compute_tape_print_properties()
return LabelMarginsPx(
horizontal=self.device_config.distance_between_print_head_and_cutter_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(
Expand Down Expand Up @@ -374,25 +369,21 @@ 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.
# 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,
Expand Down Expand Up @@ -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()
Expand Down
18 changes: 7 additions & 11 deletions src/labelle/lib/devices/online_device_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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()

Expand All @@ -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
return self._device_manager.get_devices_from_last_scan()
2 changes: 1 addition & 1 deletion src/labelle/lib/devices/usb_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
6 changes: 6 additions & 0 deletions src/labelle/lib/margins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from typing import NamedTuple


class LabelMarginsPx(NamedTuple):
horizontal: int
vertical: int
Loading
Loading