Skip to content

Commit

Permalink
Fix bottom display when pattern is near the edge
Browse files Browse the repository at this point in the history
Values of `row_multiplier` and `midline` were passed along
with `status` to UI update events, move them into `status`.

Pass `knit_start_needle` and `machine_width` properties
instead of `midline` in the hope of making needle number
computations clearer.
  • Loading branch information
jonathanperret authored and dl1com committed Dec 13, 2024
1 parent 22335aa commit 3baa653
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 80 deletions.
15 changes: 10 additions & 5 deletions src/main/python/main/ayab/engine/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ class Control(SignalSender):
initial_position: int
len_pat_expanded: int
line_block: int
midline: int
mode: Mode
mode_func: ModeFuncType
num_colors: int
Expand Down Expand Up @@ -110,10 +109,6 @@ def start(
)
self.start_pixel = self.start_needle - self.pattern.pat_start_needle
self.end_pixel = self.end_needle - self.pattern.pat_start_needle
if self.FLANKING_NEEDLES and self.mode != Mode.SINGLEBED:
self.midline = self.pattern.knit_end_needle - self.machine.width // 2
else:
self.midline = self.end_needle - self.machine.width // 2
self.initial_carriage = Carriage.Unknown
self.initial_position = -1
self.initial_direction = Direction.Unknown
Expand Down Expand Up @@ -155,6 +150,16 @@ def reset_status(self) -> None:
else:
self.status.alt_color = None

self.status.machine_width = self.machine.width

if self.FLANKING_NEEDLES and self.mode != Mode.SINGLEBED:
self.status.knit_start_needle = self.pattern.knit_start_needle
else:
# in single-bed mode, only the pattern bits are emitted, no extra needles
self.status.knit_start_needle = self.start_needle

self.status.passes_per_row = self.passes_per_row

def check_serial_API6(self) -> tuple[Token, int]:
msg, token, param = self.com.update_API6()
if msg is None:
Expand Down
13 changes: 4 additions & 9 deletions src/main/python/main/ayab/engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,15 +204,10 @@ def __handle_status(self) -> None:
# that holds up this thread until the knit progress window has finished
# updating, Otherwise if the knit progress window lags the status
# will change before the information is written to the UI.
data = Status()
data.copy(self.status)
self.emit_knit_progress_updater(
data,
self.control.passes_per_row,
self.control.midline,
self.config.auto_mirror,
)
self.emit_progress_bar_updater(data)
status_copy = Status()
status_copy.copy(self.status)
self.emit_knit_progress_updater(status_copy)
self.emit_progress_bar_updater(status_copy)

def cancel(self) -> None:
self.__canceled = True
14 changes: 10 additions & 4 deletions src/main/python/main/ayab/engine/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,15 @@ class Status(object):
active: bool
# data fields
alt_color: Optional[int]
machine_width: int
knit_start_needle: int
passes_per_row: int
bits: bitarray
color: int
color_symbol: ColorSymbolType
current_row: int
firmware_state: int
line_number: int
mirror: bool
repeats: int
total_rows: int
# carriage info
Expand All @@ -133,14 +135,16 @@ def __init__(self) -> None:
def reset(self) -> None:
self.active = True
# data fields
self.machine_width = -1
self.knit_start_needle = -1
self.passes_per_row = 1
self.alt_color = None
self.bits = bitarray()
self.color = -1
self.color_symbol = ""
self.current_row = -1
self.firmware_state = -1
self.line_number = -1
self.mirror = False
self.repeats = -1
self.total_rows = -1
# carriage info
Expand All @@ -159,14 +163,16 @@ def copy(self, status: Status) -> None:
self.color_symbol = status.color_symbol
self.color = status.color
self.alt_color = status.alt_color
self.total_rows = status.total_rows
self.machine_width = status.machine_width
self.knit_start_needle = status.knit_start_needle
self.passes_per_row = status.passes_per_row
self.bits = status.bits
self.hall_l = status.hall_l
self.hall_r = status.hall_r
self.carriage_type = status.carriage_type
self.carriage_position = status.carriage_position
self.carriage_direction = status.carriage_direction
self.total_rows = status.total_rows
self.mirror = status.mirror

def parse_device_state_API6(self, state: Any, msg: bytes) -> None:
if not (self.active):
Expand Down
143 changes: 88 additions & 55 deletions src/main/python/main/ayab/knitprogress.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@

from __future__ import annotations
from PySide6.QtCore import QCoreApplication, QRect, Qt
from PySide6.QtWidgets import QTableWidget, QTableWidgetItem, QHeaderView, QAbstractItemView
from PySide6.QtWidgets import (
QTableWidget,
QTableWidgetItem,
QHeaderView,
QAbstractItemView,
)
from PySide6.QtGui import QBrush, QColor
from typing import TYPE_CHECKING, Optional, cast, List
from math import floor
Expand Down Expand Up @@ -50,16 +55,14 @@ def __init__(self, parent: GuiMain):
self.__progbar = parent.progbar
self.setGeometry(QRect(0, 0, 700, 220))
self.setContentsMargins(1, 1, 1, 1)
self.verticalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.Fixed
)
self.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Fixed)
self.verticalHeader().setSectionsClickable(False)
self.horizontalHeader().setMinimumSectionSize(0)
self.horizontalHeader().setDefaultSectionSize(self.__prefs.value("lower_display_stitch_width"))
self.horizontalHeader().setSectionsClickable(False)
self.horizontalHeader().setSectionResizeMode(
QHeaderView.ResizeMode.Fixed
self.horizontalHeader().setDefaultSectionSize(
self.__prefs.value("lower_display_stitch_width")
)
self.horizontalHeader().setSectionsClickable(False)
self.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Fixed)
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems)
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
Expand Down Expand Up @@ -90,16 +93,15 @@ def uiStateChanged(self, status: Status) -> bool:
or status.carriage_direction != self.previousStatus.carriage_direction
or status.bits != self.previousStatus.bits
or status.alt_color != self.previousStatus.alt_color
or status.knit_start_needle != self.previousStatus.knit_start_needle
or status.machine_width != self.previousStatus.machine_width
or status.passes_per_row != self.previousStatus.passes_per_row
):
return True

return False

def update_progress(
self, status: Status, row_multiplier: int, midline: int, auto_mirror: bool
) -> None:
# FIXME auto_mirror not used

def update_progress(self, status: Status) -> None:
if not self.uiStateChanged(status):
return

Expand All @@ -112,45 +114,44 @@ def update_progress(
else:
self.color = True

midline = self.load_columns_from_status(status, midline, columns)
self.load_columns_from_status(status, columns)

# For the top row (row idx 0), we show the row header as "To Be Selected",
# When we show a new row, we recover the header info and recombine it with its row (now row idx 2)
# When we show a new row, we recover the header info and recombine it
# with its row (now row idx 2)
self.make_row_with_spacer()

if self.columnCount() != len(columns):
self.setColumnCount(len(columns))
n_cols = len(columns)
if n_cols < 4:
self.hideColumn(5)
self.instantiate_row_from_columns(midline, columns)
self.instantiate_row_from_columns(status, columns)

self.previousStatus = status
self.previous_row_mulitplier = row_multiplier

# update bar in Scene
self.scene.row_progress = status.current_row

def load_columns_from_status(self, status: Status, midline: int, columns: List[QTableWidgetItem]) -> int:
midline = len(status.bits) - midline

for c in range(0, midline):
columns.append(self.__stitch(
status.color, cast(bool, status.bits[c]), status.alt_color, self.__alternate_bg_colors(midline-c, self.orange)
))

# if we are only working on the right side, midline is negative.
green_start = midline
if green_start < 0:
green_start = 0
for c in range(green_start, len(status.bits)):
columns.append(self.__stitch(
status.color, cast(bool, status.bits[c]), status.alt_color, self.__alternate_bg_colors(c-green_start, self.green)
))

return midline

def instantiate_row_from_columns(self, midline: int, columns: List[QTableWidgetItem]) -> None:
def load_columns_from_status(
self, status: Status, columns: List[QTableWidgetItem]
) -> None:
for c in range(0, len(status.bits)):
needle = status.knit_start_needle + c
needle_number_from_r1 = needle - status.machine_width // 2
if needle_number_from_r1 < 0:
color = self.__alternate_bg_colors(needle_number_from_r1, self.orange)
else:
color = self.__alternate_bg_colors(needle_number_from_r1, self.green)
columns.append(
self.__stitch(
status.color, cast(bool, status.bits[c]), status.alt_color, color
)
)

def instantiate_row_from_columns(
self, status: Status, columns: List[QTableWidgetItem]
) -> None:
self.setVerticalHeaderItem(0, QTableWidgetItem("To Be Selected"))
for i, col in enumerate(columns):
self.setItem(0, i, col)
Expand All @@ -160,14 +161,16 @@ def instantiate_row_from_columns(self, midline: int, columns: List[QTableWidgetI
self.horizontalHeader().setVisible(False)
else:
self.horizontalHeader().setVisible(True)
if i < midline:
header = QTableWidgetItem(f"{(midline)-(i)}")
needle = status.knit_start_needle + i
needle_number_from_r1 = needle - status.machine_width // 2
if needle_number_from_r1 < 0:
header = QTableWidgetItem(f"{-needle_number_from_r1}")
header.font().setBold(True)
header.setForeground(QBrush(QColor(f"#{self.orange:06x}")))
header.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.setHorizontalHeaderItem(i, header)
else:
header = QTableWidgetItem(f"{(i+1)-(midline)}")
header = QTableWidgetItem(f"{1 + needle_number_from_r1}")
header.setForeground(QBrush(QColor(f"#{self.green:06x}")))
header.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
self.setHorizontalHeaderItem(i, header)
Expand All @@ -178,49 +181,74 @@ def make_row_with_spacer(self) -> None:
self.insertRow(1)
self.setVerticalHeaderItem(1, QTableWidgetItem(""))
if self.rowCount() > 2:
self.setVerticalHeaderItem(2, self.format_row_header_text(self.previousStatus, self.previous_row_mulitplier))
self.setVerticalHeaderItem(
2,
self.format_row_header_text(self.previousStatus),
)
self.verticalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
self.verticalHeader().setMinimumSectionSize(0)
self.verticalHeader().resizeSection(1, 5)

def format_row_header_text(self, status: Optional[Status], row_multiplier: int) -> QTableWidgetItem:
def format_row_header_text(self, status: Optional[Status]) -> QTableWidgetItem:
if status is None:
return QTableWidgetItem("")
tr_ = QCoreApplication.translate
info_header = QTableWidgetItem()
info_text = ""
row, swipe = divmod(status.line_number, row_multiplier)
swipe = status.line_number % status.passes_per_row
# row "Row [1]"
info_text = (tr_("KnitProgress", "Row") + " " + str(status.current_row))
info_text = tr_("KnitProgress", "Row") + " " + str(status.current_row)

# pass, see Mode object. "Pass [1,2,3]"
if row_multiplier == 1:
info_text = info_text+(" "+tr_("KnitProgress", "Pass") + " " + str(swipe + 1))
if status.passes_per_row > 1:
info_text = info_text + (
f' {tr_("KnitProgress", "Pass")} {swipe + 1}/{status.passes_per_row}'
)

# color "Color [A,B,C,D]"
if self.color is True:
info_text = info_text + " " + tr_("KnitProgress", "Color") + " " + status.color_symbol
info_text = (
info_text
+ " "
+ tr_("KnitProgress", "Color")
+ " "
+ status.color_symbol
)
background_color = QColor(f"#{status.color:06x}")
# Ensure text is readable
if background_color.lightness() > 128:
background_color.setHsl(background_color.hslHue(), background_color.hslSaturation(), 128)
background_color.setHsl(
background_color.hslHue(), background_color.hslSaturation(), 128
)
info_header.setForeground(QBrush(background_color))

# Carriage & Direction "[K,L,G] [<-,->]"
carriage = status.carriage_type
direction = status.carriage_direction
info_text = info_text + (" "+carriage.symbol + " " + direction.symbol)
info_text = info_text + (" " + carriage.symbol + " " + direction.symbol)
info_header.setText(info_text)
return info_header

def __alternate_bg_colors(self, position: int, color: int, frequency: int = 10) -> QColor:
def __alternate_bg_colors(
self, position: int, color: int, frequency: int = 10
) -> QColor:
background_color = QColor(f"#{color:06x}")
bg_color_alternate = floor(position/frequency) % 2
bg_color_alternate = (position // frequency) % 2
if bg_color_alternate > 0:
background_color.setHsl(floor(background_color.hslHue()*.85), floor(background_color.hslSaturation()*.85), background_color.lightness())
background_color.setHsl(
floor(background_color.hslHue() * 0.85),
floor(background_color.hslSaturation() * 0.85),
background_color.lightness(),
)
return background_color

def __stitch(self, color: int, bit: bool, alt_color: Optional[int] = None, bg_color: Optional[QColor] = None) -> QTableWidgetItem:
def __stitch(
self,
color: int,
bit: bool,
alt_color: Optional[int] = None,
bg_color: Optional[QColor] = None,
) -> QTableWidgetItem:
stitch = QTableWidgetItem()
if bit:
background_color = QColor(f"#{color:06x}")
Expand All @@ -236,9 +264,14 @@ def onStitchSelect(self, current: QTableWidgetItem | None) -> None:
if current is None:
self.__progbar.set_selection_label("")
return
if self.horizontalHeaderItem(current.column()).foreground().color().red() == 187:
if (
self.horizontalHeaderItem(current.column()).foreground().color().red()
== 187
):
side = "Right"
else:
side = "Left"
selection_string = f"Selection: {self.verticalHeaderItem(current.row()).text()} , stitch {side}-{self.horizontalHeaderItem(current.column()).text()}"
selection_string = f"""Selection: {
self.verticalHeaderItem(current.row()).text()} stitch {side}-{
self.horizontalHeaderItem(current.column()).text()}"""
self.__progbar.set_selection_label(selection_string)
2 changes: 1 addition & 1 deletion src/main/python/main/ayab/signal_receiver.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class SignalReceiver(QObject):
# over-ridden by instance attributes with the same name
start_row_updater = Signal(int)
progress_bar_updater = Signal(Status)
knit_progress_updater = Signal(Status, int, int, bool)
knit_progress_updater = Signal(Status)
notifier = Signal(str, bool)
# statusbar_updater = Signal('QString', bool)
popup_displayer = Signal(str, str)
Expand Down
8 changes: 2 additions & 6 deletions src/main/python/main/ayab/signal_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,8 @@ def emit_start_row_updater(self, start_row: int) -> None:
def emit_progress_bar_updater(self, status: Status) -> None:
self.__signal_receiver.progress_bar_updater.emit(status)

def emit_knit_progress_updater(
self, status: Status, row_multiplier: int, midline: int, auto_mirror: bool
) -> None:
self.__signal_receiver.knit_progress_updater.emit(
status, row_multiplier, midline, auto_mirror
)
def emit_knit_progress_updater(self, status: Status) -> None:
self.__signal_receiver.knit_progress_updater.emit(status)

def emit_notifier(self, text: str, log: bool) -> None:
self.__signal_receiver.notifier.emit(text, log)
Expand Down

0 comments on commit 3baa653

Please sign in to comment.