From b3ff08e5d17da4b1a08f12b2441ada270328b88d Mon Sep 17 00:00:00 2001 From: t0mpr1c3 Date: Thu, 6 Aug 2020 14:24:05 -0400 Subject: [PATCH] Add test button to GUI --- src/main/python/ayab/ayab.py | 25 +++- src/main/python/ayab/engine/__init__.py | 2 +- src/main/python/ayab/engine/communication.py | 21 ++- .../ayab/engine/communication_mockup.py | 22 ++- src/main/python/ayab/engine/control.py | 34 ++--- src/main/python/ayab/engine/engine.py | 52 ++++--- src/main/python/ayab/engine/mode.py | 6 +- src/main/python/ayab/engine/options.py | 24 ++-- src/main/python/ayab/engine/output.py | 6 +- src/main/python/ayab/engine/state.py | 127 +++++++++--------- src/main/python/ayab/fsm.py | 59 +++++--- src/main/python/ayab/main_gui.ui | 12 +- src/main/python/ayab/observer.py | 2 + src/main/python/ayab/preferences.py | 4 +- .../python/ayab/tests/test_communication.py | 33 +++-- .../ayab/tests/test_communication_mockup.py | 28 ++-- src/main/python/ayab/tests/test_control.py | 121 +++++++++-------- .../translations/ayab-translation-master.tsv | 3 +- 18 files changed, 320 insertions(+), 261 deletions(-) diff --git a/src/main/python/ayab/ayab.py b/src/main/python/ayab/ayab.py index 7b83748a..5cbc2bcf 100644 --- a/src/main/python/ayab/ayab.py +++ b/src/main/python/ayab/ayab.py @@ -39,7 +39,8 @@ from .progressbar import ProgressBar from .about import About from .knitprogress import KnitProgress -from .engine import KnitEngine +from .engine import Engine +from .engine.state import Operation from .engine.options import Alignment from .machine import Machine @@ -73,11 +74,12 @@ def __init__(self, app_context): self.about = About(self) self.scene = Scene(self) self.knitprog = KnitProgress(self) - self.engine = KnitEngine(self) + self.engine = Engine(self) self.progbar = ProgressBar(self) self.flash = FirmwareFlash(self) self.audio = AudioPlayer(self) - self.engine_thread = GenericThread(self.engine.knit) + self.knit_thread = GenericThread(self.engine.run, Operation.KNIT) + self.test_thread = GenericThread(self.engine.run, Operation.TEST) # show UI self.showMaximized() @@ -124,7 +126,7 @@ def start_knitting(self): self.ui.filename_lineedit.setEnabled(False) self.ui.load_file_button.setEnabled(False) # start thread for knit engine - self.engine_thread.start() + self.knit_thread.start() def finish_knitting(self, beep: bool): """(Re-)enable UI elements after knitting finishes.""" @@ -134,6 +136,21 @@ def finish_knitting(self, beep: bool): if beep: self.audio.play("finish") + def start_testing(self): + """Start the testing process.""" + # disable UI elements at start of testing + self.menu.depopulate() + self.ui.filename_lineedit.setEnabled(False) + self.ui.load_file_button.setEnabled(False) + # start thread for test engine + self.test_thread.start() + + def finish_testing(self, beep: bool): + """(Re-)enable UI elements after testing finishes.""" + self.menu.repopulate() + self.ui.filename_lineedit.setEnabled(True) + self.ui.load_file_button.setEnabled(True) + def set_image_dimensions(self): """Set dimensions of image.""" width, height = self.scene.ayabimage.image.size diff --git a/src/main/python/ayab/engine/__init__.py b/src/main/python/ayab/engine/__init__.py index 4b2fa7a5..7c2b0d98 100644 --- a/src/main/python/ayab/engine/__init__.py +++ b/src/main/python/ayab/engine/__init__.py @@ -1,4 +1,4 @@ from . import engine # This adds the engine to the upper namespace of the module. -KnitEngine = engine.KnitEngine +Engine = engine.Engine diff --git a/src/main/python/ayab/engine/communication.py b/src/main/python/ayab/engine/communication.py index 47e24b3a..a6a7cf44 100644 --- a/src/main/python/ayab/engine/communication.py +++ b/src/main/python/ayab/engine/communication.py @@ -32,7 +32,7 @@ import pprint -class MessageToken(Enum): +class Token(Enum): unknown = -2 none = -1 reqInfo = 0x03 @@ -47,7 +47,7 @@ class MessageToken(Enum): slipFrameEnd = 0xc0 -class AyabCommunication(object): +class Communication(object): """Class Handling the serial communication protocol.""" def __init__(self, serial=None): """Create an AyabCommunication object, @@ -98,20 +98,19 @@ def close_serial(self): # NB this method must be the same for all API versions def req_info(self): """Send a request for information to the device.""" - data = self.__driver.send(bytes([MessageToken.reqInfo.value])) + data = self.__driver.send(bytes([Token.reqInfo.value])) self.__ser.write(data) def req_test_API6(self, machine_val): """Send a request for testing to the device.""" - data = self.__driver.send( - bytes([MessageToken.reqTest.value, machine_val])) + data = self.__driver.send(bytes([Token.reqTest.value, machine_val])) self.__ser.write(data) def req_start_API6(self, machine_val, start_needle, stop_needle, continuous_reporting): """Send a start message to the device.""" data = bytearray() - data.append(MessageToken.reqStart.value) + data.append(Token.reqStart.value) data.append(machine_val) data.append(start_needle) data.append(stop_needle) @@ -137,7 +136,7 @@ def cnf_line_API6(self, line_number, color, flags, line_data): """ data = bytearray() - data.append(MessageToken.cnfLine.value) + data.append(Token.cnfLine.value) data.append(line_number) data.append(color) data.append(flags) @@ -158,13 +157,13 @@ def update_API6(self): if len(self.__rx_msg_list) > 0: return self.parse_update_API6(self.__rx_msg_list.pop(0)) - return None, MessageToken.none, 0 + return None, Token.none, 0 def parse_update_API6(self, msg): if msg is None: - return None, MessageToken.none, 0 + return None, Token.none, 0 - for t in list(MessageToken): + for t in list(Token): if msg[0] == t.value: return msg, t, msg[1] @@ -172,7 +171,7 @@ def parse_update_API6(self, msg): self.__logger.debug("unknown message: ") # drop crlf pp = pprint.PrettyPrinter(indent=4) pp.pprint(msg) - return msg, MessageToken.unknown, 0 + return msg, Token.unknown, 0 # CRC algorithm after Maxim/Dallas diff --git a/src/main/python/ayab/engine/communication_mockup.py b/src/main/python/ayab/engine/communication_mockup.py index 9d2d07f7..5352e147 100644 --- a/src/main/python/ayab/engine/communication_mockup.py +++ b/src/main/python/ayab/engine/communication_mockup.py @@ -17,7 +17,7 @@ # Copyright 2013 Christian Obersteiner, Andreas Müller, Christian Gerbrandt # https://github.com/AllYarnsAreBeautiful/ayab-desktop """ -Mockup Class of AYABCommunication for Test/Simulation purposes +Mockup Class of Communication for Test/Simulation purposes """ import logging @@ -26,10 +26,10 @@ from PyQt5 import QtWidgets from PyQt5.QtWidgets import QMessageBox -from .communication import AyabCommunication, MessageToken +from .communication import Communication, Token -class AyabCommunicationMockup(AyabCommunication): +class CommunicationMockup(Communication): """Class Handling the serial communication protocol.""" def __init__(self, delay=True, step=False) -> None: logging.basicConfig(level=logging.DEBUG) @@ -60,21 +60,20 @@ def open_serial(self, portname=None) -> bool: return True def req_info(self) -> None: - cnfInfo = bytearray([MessageToken.cnfInfo.value, 0x5, 0xFF, 0xFF]) + cnfInfo = bytearray([Token.cnfInfo.value, 0x5, 0xFF, 0xFF]) self.__rx_msg_list.append(cnfInfo) - indState = bytearray([ - MessageToken.indState.value, 0x1, 0xFF, 0xFF, 0xFF, 0xFF, 0x1, 0x7F - ]) + indState = bytearray( + [Token.indState.value, 0x1, 0xFF, 0xFF, 0xFF, 0xFF, 0x1, 0x7F]) self.__rx_msg_list.append(indState) def req_test_API6(self, machine_val) -> None: - cnfTest = bytearray([MessageToken.cnfTest.value, 0x1]) + cnfTest = bytearray([Token.cnfTest.value, 0x1]) self.__rx_msg_list.append(cnfTest) def req_start_API6(self, machine_val, start_needle, stop_needle, continuous_reporting) -> None: self.__is_started = True - cnfStart = bytearray([MessageToken.cnfStart.value, 0x1]) + cnfStart = bytearray([Token.cnfStart.value, 0x1]) self.__rx_msg_list.append(cnfStart) def cnf_line_API6(self, line_number, color, flags, line_data) -> bool: @@ -82,8 +81,7 @@ def cnf_line_API6(self, line_number, color, flags, line_data) -> bool: def update_API6(self) -> tuple: if self.__is_open and self.__is_started: - reqLine = bytearray( - [MessageToken.reqLine.value, self.__line_count]) + reqLine = bytearray([Token.reqLine.value, self.__line_count]) self.__line_count += 1 self.__line_count %= 256 self.__rx_msg_list.append(reqLine) @@ -105,4 +103,4 @@ def update_API6(self) -> tuple: if len(self.__rx_msg_list) > 0: return self.parse_update_API6(self.__rx_msg_list.pop(0)) - return None, MessageToken.none, 0 + return None, Token.none, 0 diff --git a/src/main/python/ayab/engine/control.py b/src/main/python/ayab/engine/control.py index 5db310e1..7e8dc4ad 100644 --- a/src/main/python/ayab/engine/control.py +++ b/src/main/python/ayab/engine/control.py @@ -24,16 +24,16 @@ from PyQt5.QtCore import QCoreApplication -from .communication import AyabCommunication, MessageToken -from .communication_mockup import AyabCommunicationMockup +from .communication import Communication, Token +from .communication_mockup import CommunicationMockup from .options import Alignment -from .mode import KnitMode, KnitModeFunc -from .state import KnitState, KnitStateMachine -from .output import KnitOutput +from .mode import Mode, ModeFunc +from .state import State, StateMachine +from .output import Output from ayab.machine import Machine -class KnitControl(object): +class Control(object): """ Class governing information flow with the shield. """ @@ -48,7 +48,7 @@ def __init__(self, parent): def start(self, machine): self.machine_width = machine.width - self.state = KnitState.SETUP + self.state = State.SETUP def stop(self): try: @@ -69,17 +69,17 @@ def func_selector(self): return False # else func_name = self.mode.knit_func(self.num_colors) - if not hasattr(KnitModeFunc, func_name): + if not hasattr(ModeFunc, func_name): self.logger.error( - "Unrecognized value returned from KnitMode.knit_func()") + "Unrecognized value returned from Mode.knit_func()") return False # else - self.mode_func = getattr(KnitModeFunc, func_name) + self.mode_func = getattr(ModeFunc, func_name) return True def reset_status(self): self.status.reset() - if self.mode == KnitMode.SINGLEBED: + if self.mode == Mode.SINGLEBED: self.status.alt_color = self.pattern.palette[1] self.status.color_symbol = "" # "A/B" else: @@ -88,9 +88,9 @@ def reset_status(self): def check_serial_API6(self): msg, token, param = self.com.update_API6() - if token == MessageToken.cnfInfo: + if token == Token.cnfInfo: self.__log_cnfInfo(msg) - elif token == MessageToken.indState: + elif token == Token.indState: self.status.parse_device_state_API6(param, msg) return token, param @@ -154,7 +154,7 @@ def __update_status(self, line_number, color, bits): self.status.line_number = line_number if self.inf_repeat: self.status.repeats = self.pattern_repeats - if self.mode != KnitMode.SINGLEBED: + if self.mode != Mode.SINGLEBED: self.status.color_symbol = self.COLOR_SYMBOLS[color] self.status.color = self.pattern.palette[color] if self.FLANKING_NEEDLES: @@ -190,8 +190,8 @@ def select_needles_API6(self, color, row_index, blank_line): def operate(self, pattern, options, operation, API_version=6): """Finite State Machine governing serial communication""" method = "_API" + str(API_version) + "_" + self.state.name.lower() - if not hasattr(KnitStateMachine, method): - return KnitOutput.NONE - dispatch = getattr(KnitStateMachine, method) + if not hasattr(StateMachine, method): + return Output.NONE + dispatch = getattr(StateMachine, method) result = dispatch(self, pattern, options, operation) return result diff --git a/src/main/python/ayab/engine/engine.py b/src/main/python/ayab/engine/engine.py index 42d96272..da4750a6 100644 --- a/src/main/python/ayab/engine/engine.py +++ b/src/main/python/ayab/engine/engine.py @@ -27,17 +27,17 @@ from ayab import utils from ayab.observable import Observable -from .control import KnitControl -from .state import KnitOperation +from .control import Control +from .state import Operation from .pattern import Pattern from .options import OptionsTab, Alignment, NeedleColor from .status import Status, StatusTab -from .mode import KnitMode -from .output import KnitOutput, KnitFeedbackHandler +from .mode import Mode +from .output import Output, FeedbackHandler from .dock_gui import Ui_DockWidget -class KnitEngine(Observable, QDockWidget): +class Engine(Observable, QDockWidget): """ Top-level class for the slave thread that communicates with the shield. @@ -52,9 +52,10 @@ def __init__(self, parent): self.status = StatusTab() self.setup_ui() parent.ui.dock_container_layout.addWidget(self) - self.__control = KnitControl(self) + self.__control = Control(self) self.__logger = logging.getLogger(type(self).__name__) - self.__feedback = KnitFeedbackHandler(parent) + self.__feedback = FeedbackHandler(parent) + self.__pattern = None def __del__(self): self.__control.stop() @@ -149,9 +150,6 @@ def knit_config(self, image): # if self.config.continuous_reporting: # self.__status_tab.activate() - # start knitting controller - self.__control.start(self.config.machine) - # send signal to start knitting self.emit_knitting_starter() @@ -161,25 +159,42 @@ def validate(self): # else return self.config.validate() - def knit(self): + def run(self, operation): self.__canceled = False + # start knitting controller + self.__control.start(self.config.machine) + while True: - # continue knitting + # continue operating # typically each step involves some communication with the shield - # FIXME pattern and config are only used by KnitControl.knit() - # in the KnitState.SETUP step and do not need to be sent otherwise. + # FIXME pattern and config are only used by Control.operate() + # in the State.SETUP step and do not need to be sent otherwise. result = self.__control.operate(self.__pattern, self.config, - KnitOperation.KNIT) + operation) self.__feedback.handle(result) - self.__status_handler() - if self.__canceled or result is KnitOutput.FINISHED: + if operation == Operation.KNIT: + self.__status_handler() + if self.__canceled or result is Output.FINISHED: break - self.__logger.info("Finished knitting.") self.__control.stop() + if operation == Operation.KNIT: + if self.__canceled: + self.emit_notification("Knitting canceled.") + self.__logger.info("Knitting canceled.") + else: + self.__logger.info("Finished knitting.") + else: + # TODO: provide translations for these messages + if self.__canceled: + self.emit_notification("Testing canceled.") + self.__logger.info("Testing canceled.") + else: + self.__logger.info("Finished testing.") + # small delay to finish printing to knit progress window # before "finish.wav" sound plays sleep(1) @@ -205,5 +220,4 @@ def __status_handler(self): data.repeats, data.color_symbol) def cancel(self): - self.emit_notification("Knitting canceled.") self.__canceled = True diff --git a/src/main/python/ayab/engine/mode.py b/src/main/python/ayab/engine/mode.py index f380272b..b647deb8 100644 --- a/src/main/python/ayab/engine/mode.py +++ b/src/main/python/ayab/engine/mode.py @@ -26,7 +26,7 @@ from ayab.utils import odd, even -class KnitMode(Enum): +class Mode(Enum): SINGLEBED = 0 CLASSIC_RIBBER = 1 MIDDLECOLORSTWICE_RIBBER = 2 @@ -79,12 +79,12 @@ def add_items(box): box.addItem(tr_("KnitMode", "Ribber: Circular")) -class KnitModeFunc(object): +class ModeFunc(object): """ Methods available to `AyabControl.func_selector()`. @author Tom Price - @datei June 2020 + @date June 2020 """ # singlebed, 2 color diff --git a/src/main/python/ayab/engine/options.py b/src/main/python/ayab/engine/options.py index f0b4520c..bca7379c 100644 --- a/src/main/python/ayab/engine/options.py +++ b/src/main/python/ayab/engine/options.py @@ -25,7 +25,7 @@ from ayab.observable import Observable from .options_gui import Ui_OptionsWidget -from .mode import KnitMode +from .mode import Mode from ayab.machine import Machine @@ -50,7 +50,7 @@ def __setup_ui(self): self.ui.setupUi(self) # Combo boxes - KnitMode.add_items(self.ui.knitting_mode_box) + Mode.add_items(self.ui.knitting_mode_box) Alignment.add_items(self.ui.alignment_combo_box) NeedleColor.add_items(self.ui.start_needle_color) NeedleColor.add_items(self.ui.stop_needle_color) @@ -66,8 +66,10 @@ def __activate_ui(self): lambda: self.emit_start_row_updater(self.__read_start_row())) self.ui.start_needle_edit.valueChanged.connect(self.update_needles) self.ui.stop_needle_edit.valueChanged.connect(self.update_needles) - self.ui.start_needle_color.currentIndexChanged.connect(self.update_needles) - self.ui.stop_needle_color.currentIndexChanged.connect(self.update_needles) + self.ui.start_needle_color.currentIndexChanged.connect( + self.update_needles) + self.ui.stop_needle_color.currentIndexChanged.connect( + self.update_needles) self.ui.alignment_combo_box.currentIndexChanged.connect( lambda: self.emit_alignment_updater(self.__read_alignment())) @@ -92,7 +94,7 @@ def __update_machine(self): def __reset(self): """Reset configuration options to default settings.""" self.__update_machine() - self.mode = KnitMode(self.prefs.value("default_knitting_mode")) + self.mode = Mode(self.prefs.value("default_knitting_mode")) self.num_colors = 2 self.start_row = 0 self.inf_repeat = self.prefs.value("default_infinite_repeat") @@ -131,8 +133,7 @@ def refresh(self): def as_dict(self): return dict([("portname", self.portname), - ("machine", self.machine.name), - ("mode", self.mode.name), + ("machine", self.machine.name), ("mode", self.mode.name), ("num_colors", self.num_colors), ("start_row", self.start_row), ("inf_repeat", self.inf_repeat), @@ -146,11 +147,12 @@ def read(self, portname): """Get configuration options from the UI elements.""" self.__update_machine() self.portname = portname - self.mode = KnitMode(self.ui.knitting_mode_box.currentIndex()) + self.mode = Mode(self.ui.knitting_mode_box.currentIndex()) self.num_colors = int(self.ui.color_edit.value()) self.start_row = self.__read_start_row() self.inf_repeat = self.ui.inf_repeat_checkbox.isChecked() - self.start_needle = NeedleColor.read_start_needle(self.ui, self.machine) + self.start_needle = NeedleColor.read_start_needle( + self.ui, self.machine) self.stop_needle = NeedleColor.read_stop_needle(self.ui, self.machine) self.alignment = self.__read_alignment() self.auto_mirror = self.ui.auto_mirror_checkbox.isChecked() @@ -177,11 +179,11 @@ def validate(self): if self.start_needle > self.stop_needle: return False, "Invalid needle start and end." # else - if self.mode == KnitMode.SINGLEBED \ + if self.mode == Mode.SINGLEBED \ and self.num_colors >= 3: return False, "Singlebed knitting currently supports only 2 colors." # else - if self.mode == KnitMode.CIRCULAR_RIBBER \ + if self.mode == Mode.CIRCULAR_RIBBER \ and self.num_colors >= 3: return False, "Circular knitting supports only 2 colors." # else diff --git a/src/main/python/ayab/engine/output.py b/src/main/python/ayab/engine/output.py index c6a3b5cd..2bcd10be 100644 --- a/src/main/python/ayab/engine/output.py +++ b/src/main/python/ayab/engine/output.py @@ -23,7 +23,7 @@ from ayab.observable import Observable -class KnitOutput(Enum): +class Output(Enum): NONE = auto() ERROR_INVALID_SETTINGS = auto() ERROR_SERIAL_PORT = auto() @@ -36,11 +36,11 @@ class KnitOutput(Enum): FINISHED = auto() -class KnitFeedbackHandler(Observable): +class FeedbackHandler(Observable): """Polymorphic dispatch of notification signals on KnitOutput. @author Tom Price - @data July 2020 + @date July 2020 """ def __init__(self, parent): super().__init__(parent.seer) diff --git a/src/main/python/ayab/engine/state.py b/src/main/python/ayab/engine/state.py index c2de6bac..820feb37 100644 --- a/src/main/python/ayab/engine/state.py +++ b/src/main/python/ayab/engine/state.py @@ -22,17 +22,17 @@ from PyQt5.QtCore import QCoreApplication -from .communication import AyabCommunication, MessageToken -from .communication_mockup import AyabCommunicationMockup -from .output import KnitOutput +from .communication import Communication, Token +from .communication_mockup import CommunicationMockup +from .output import Output -class KnitOperation(Enum): +class Operation(Enum): KNIT = auto() TEST = auto() -class KnitState(Enum): +class State(Enum): SETUP = auto() INIT = auto() REQUEST_START = auto() @@ -43,7 +43,7 @@ class KnitState(Enum): RUN_TEST = auto() -class KnitStateMachine(object): +class StateMachine(object): """ Each method is a step in the finite state machine that governs communication with the shield and is called only by `AyabControl.knit()` @@ -52,132 +52,133 @@ class KnitStateMachine(object): @date June 2020 """ def _API6_setup(control, pattern, options, operation): - control.logger.debug("KnitState SETUP") - control.former_request = 0 - control.line_block = 0 - control.pattern_repeats = 0 - control.pattern = pattern - control.pat_height = pattern.pat_height - control.num_colors = options.num_colors - control.start_row = options.start_row - control.mode = options.mode - control.inf_repeat = options.inf_repeat - control.len_pat_expanded = control.pat_height * control.num_colors - control.passes_per_row = control.mode.row_multiplier( - control.num_colors) - control.reset_status() - if not control.func_selector(): - return KnitOutput.ERROR_INVALID_SETTINGS + control.logger.debug("State SETUP") + if operation == Operation.KNIT: + control.former_request = 0 + control.line_block = 0 + control.pattern_repeats = 0 + control.pattern = pattern + control.pat_height = pattern.pat_height + control.num_colors = options.num_colors + control.start_row = options.start_row + control.mode = options.mode + control.inf_repeat = options.inf_repeat + control.len_pat_expanded = control.pat_height * control.num_colors + control.passes_per_row = control.mode.row_multiplier( + control.num_colors) + control.reset_status() + if not control.func_selector(): + return Output.ERROR_INVALID_SETTINGS # else control.logger.debug(options.portname) if options.portname == QCoreApplication.translate( "AyabPlugin", "Simulation"): - control.com = AyabCommunicationMockup() + control.com = CommunicationMockup() else: - control.com = AyabCommunication() + control.com = Communication() if not control.com.open_serial(options.portname): control.logger.error("Could not open serial port") - return KnitOutput.ERROR_SERIAL_PORT + return Output.ERROR_SERIAL_PORT # else # setup complete - control.state = KnitState.INIT - return KnitOutput.NONE + control.state = State.INIT + return Output.NONE def _API6_init(control, pattern, options, operation): - control.logger.debug("KnitState INIT") + control.logger.debug("State INIT") rcvMsg, rcvParam = control.check_serial() - if rcvMsg == MessageToken.cnfInfo: + if rcvMsg == Token.cnfInfo: if rcvParam >= control.FIRST_SUPPORTED_API_VERSION: control.api_version = rcvParam - if operation == KnitOperation.TEST: - control.state = KnitState.REQUEST_TEST + if operation == Operation.TEST: + control.state = State.REQUEST_TEST else: - control.state = KnitState.REQUEST_START - return KnitOutput.WAIT_FOR_INIT + control.state = State.REQUEST_START + return Output.WAIT_FOR_INIT else: control.logger.error("Wrong API version: " + str(rcvParam) + ", expected >= " + str(control.FIRST_SUPPORTED_API_VERSION)) - return KnitOutput.ERROR_WRONG_API + return Output.ERROR_WRONG_API # else control.com.req_info() - return KnitOutput.CONNECTING_TO_MACHINE + return Output.CONNECTING_TO_MACHINE def _API6_request_start(control, pattern, options, operation): - control.logger.debug("KnitState REQUEST_START") + control.logger.debug("State REQUEST_START") rcvMsg, rcvParam = control.check_serial() - if rcvMsg == MessageToken.indState: + if rcvMsg == Token.indState: if rcvParam == 1: control.com.req_start_API6(options.machine.value, control.pattern.knit_start_needle, control.pattern.knit_stop_needle, options.continuous_reporting) - control.state = KnitState.CONFIRM_START + control.state = State.CONFIRM_START else: # any value of rcvParam other than 1 is some kind of error code control.logger.debug("Knit init failed") # TODO: more output to describe error # fallthrough - return KnitOutput.NONE + return Output.NONE def _API6_confirm_start(control, pattern, options, operation): - control.logger.debug("KnitState CONFIRM_START") + control.logger.debug("State CONFIRM_START") rcvMsg, rcvParam = control.check_serial() - if rcvMsg == MessageToken.cnfStart: + if rcvMsg == Token.cnfStart: if rcvParam == 1: - control.state = KnitState.RUN_KNIT - return KnitOutput.PLEASE_KNIT + control.state = State.RUN_KNIT + return Output.PLEASE_KNIT else: # any value of rcvParam other than 1 is some kind of error code control.logger.error("Device not ready") # TODO: more output to describe error - return KnitOutput.DEVICE_NOT_READY + return Output.DEVICE_NOT_READY # fallthrough - return KnitOutput.NONE + return Output.NONE def _API6_run_knit(control, pattern, options, operation): - control.logger.debug("KnitState RUN_KNIT") + control.logger.debug("State RUN_KNIT") rcvMsg, rcvParam = control.check_serial() - if rcvMsg == MessageToken.reqLine: + if rcvMsg == Token.reqLine: pattern_finished = control.cnf_line_API6(rcvParam) if pattern_finished: - control.state = KnitState.SETUP - return KnitOutput.FINISHED + control.state = State.SETUP + return Output.FINISHED # else - return KnitOutput.NEXT_LINE + return Output.NEXT_LINE # fallthrough - return KnitOutput.NONE + return Output.NONE def _API6_request_test(control, pattern, options, operation): - control.logger.debug("KnitState REQUEST_TEST") + control.logger.debug("State REQUEST_TEST") rcvMsg, rcvParam = control.check_serial() - if rcvMsg == MessageToken.indState: + if rcvMsg == Token.indState: if rcvParam == 1: control.com.req_test_API6(options.machine.value) - control.state = KnitState.CONFIRM_TEST + control.state = State.CONFIRM_TEST else: # any value of rcvParam other than 1 is some kind of error code control.logger.debug("Test init failed") # TODO: more output to describe error # fallthrough - return KnitOutput.NONE + return Output.NONE def _API6_confirm_test(control, pattern, options, operation): - control.logger.debug("KnitState CONFIRM_TEST") + control.logger.debug("State CONFIRM_TEST") rcvMsg, rcvParam = control.check_serial() - if rcvMsg == MessageToken.cnfTest: + if rcvMsg == Token.cnfTest: if rcvParam == 1: - control.state = KnitState.RUN_TEST - return KnitOutput.NONE + control.state = State.RUN_TEST + return Output.NONE else: # any value of rcvParam other than 1 is some kind of error code control.logger.error("Device not ready") # TODO: more output to describe error - return KnitOutput.DEVICE_NOT_READY + return Output.DEVICE_NOT_READY # fallthrough - return KnitOutput.NONE + return Output.NONE def _API6_run_test(control, pattern, options, operation): - control.logger.debug("KnitState RUN_TEST") + control.logger.debug("State RUN_TEST") # TODO: open serial monitor - return KnitOutput.NONE + return Output.NONE diff --git a/src/main/python/ayab/fsm.py b/src/main/python/ayab/fsm.py index 378b1915..0a30052c 100644 --- a/src/main/python/ayab/fsm.py +++ b/src/main/python/ayab/fsm.py @@ -37,14 +37,15 @@ def __init__(self): # Finite State Machine self.machine = QStateMachine() - # Image states + # Machine states self.NO_IMAGE = QState(self.machine) + self.TESTING_NO_IMAGE = QState(self.machine) self.CONFIGURING = QState(self.machine) self.CHECKING = QState(self.machine) self.KNITTING = QState(self.machine) - # self.TESTING = QState(self.machine) + self.TESTING = QState(self.machine) - # Set machine states + # Set machine state self.machine.setInitialState(self.NO_IMAGE) def set_transitions(self, parent): @@ -53,10 +54,14 @@ def set_transitions(self, parent): # Events that trigger state changes self.NO_IMAGE.addTransition(parent.seer.got_image_flag, self.CONFIGURING) + self.NO_IMAGE.addTransition(parent.ui.test_button.clicked, + self.TESTING_NO_IMAGE) + self.TESTING_NO_IMAGE.addTransition(parent.test_thread.finished, + self.NO_IMAGE) self.CONFIGURING.addTransition(parent.ui.knit_button.clicked, self.CHECKING) - # self.CONFIGURING.addTransition(parent.ui.test_button.clicked, - # self.TESTING) + self.CONFIGURING.addTransition(parent.ui.test_button.clicked, + self.TESTING) self.CHECKING.addTransition(parent.seer.got_image_flag, self.CONFIGURING) self.CHECKING.addTransition(parent.seer.new_image_flag, @@ -65,27 +70,34 @@ def set_transitions(self, parent): self.CONFIGURING) self.CHECKING.addTransition(parent.seer.knitting_starter, self.KNITTING) - self.KNITTING.addTransition(parent.engine_thread.finished, + self.KNITTING.addTransition(parent.knit_thread.finished, self.CONFIGURING) + self.TESTING.addTransition(parent.test_thread.finished, + self.CONFIGURING) # Actions triggered by state changes self.NO_IMAGE.entered.connect( lambda: logging.debug("Entered state NO_IMAGE")) + self.TESTING_NO_IMAGE.entered.connect( + lambda: logging.debug("Entered state TESTING_NO_IMAGE")) self.CONFIGURING.entered.connect( lambda: logging.debug("Entered state CONFIGURING")) self.CHECKING.entered.connect( lambda: logging.debug("Entered state CHECKING")) self.KNITTING.entered.connect( lambda: logging.debug("Entered state KNITTING")) - # self.TESTING.entered.connect( - # lambda: logging.debug("Entered state TESTING")) + self.TESTING.entered.connect( + lambda: logging.debug("Entered state TESTING")) self.NO_IMAGE.exited.connect(parent.engine.config.refresh) + self.TESTING_NO_IMAGE.entered.connect(parent.start_testing) self.CONFIGURING.entered.connect(parent.menu.add_image_actions) self.CONFIGURING.entered.connect(parent.progbar.reset) self.CHECKING.entered.connect( lambda: parent.engine.knit_config(parent.scene.ayabimage.image)) self.KNITTING.entered.connect(parent.start_knitting) + self.TESTING.entered.connect(parent.start_testing) + self.TESTING_NO_IMAGE.entered.connect(parent.start_testing) def set_properties(self, parent): """ @@ -96,28 +108,35 @@ def set_properties(self, parent): self.NO_IMAGE.assignProperty(parent.engine, "enabled", "False") self.CONFIGURING.assignProperty(parent.engine, "enabled", "True") self.KNITTING.assignProperty(parent.engine, "enabled", "False") - # self.TESTING.assignProperty(parent.engine, "enabled", "False") # Status tab in options dock should be activated only when knitting # self.NO_IMAGE.assignProperty(ui.status_tab, "enabled", "False") # self.CONFIGURING.assignProperty(ui.status_tab, "enabled", "False") # self.KNITTING.assignProperty(ui.status_tab, "enabled", "True") - # self.TESTING.assignProperty(ui.status_tab, "enabled", "False") # ? # Knit button self.NO_IMAGE.assignProperty(parent.ui.knit_button, "enabled", "False") - self.CONFIGURING.assignProperty(parent.ui.knit_button, "enabled", "True") + self.CONFIGURING.assignProperty(parent.ui.knit_button, "enabled", + "True") self.KNITTING.assignProperty(parent.ui.knit_button, "enabled", "False") - # self.TESTING.assignProperty(parent.ui.knit_button, "enabled", "False") + self.TESTING.assignProperty(parent.ui.knit_button, "enabled", "False") # Test button - # self.NO_IMAGE.assignProperty(parent.ui.test_button, "enabled", "True") - # self.CONFIGURING.assignProperty(parent.ui.test_button, "enabled", "True") - # self.KNITTING.assignProperty(parent.ui.knit_button, "enabled", "False") - # self.TESTING.assignProperty(parent.ui.knit_button, "enabled", "False") + self.NO_IMAGE.assignProperty(parent.ui.test_button, "enabled", "True") + self.TESTING_NO_IMAGE.assignProperty(parent.ui.knit_button, "enabled", + "False") + self.CONFIGURING.assignProperty(parent.ui.test_button, "enabled", + "True") + self.KNITTING.assignProperty(parent.ui.knit_button, "enabled", "False") + self.TESTING.assignProperty(parent.ui.knit_button, "enabled", "False") # Cancel button - self.NO_IMAGE.assignProperty(parent.ui.cancel_button, "enabled", "False") - self.CONFIGURING.assignProperty(parent.ui.cancel_button, "enabled", "False") - self.KNITTING.assignProperty(parent.ui.cancel_button, "enabled", "True") - # self.TESTING.assignProperty(parent.ui.cancel_button, "enabled", "True") + self.NO_IMAGE.assignProperty(parent.ui.cancel_button, "enabled", + "False") + self.TESTING_NO_IMAGE.assignProperty(parent.ui.cancel_button, + "enabled", "True") + self.CONFIGURING.assignProperty(parent.ui.cancel_button, "enabled", + "False") + self.KNITTING.assignProperty(parent.ui.cancel_button, "enabled", + "True") + self.TESTING.assignProperty(parent.ui.cancel_button, "enabled", "True") diff --git a/src/main/python/ayab/main_gui.ui b/src/main/python/ayab/main_gui.ui index f70d2373..ef734cf4 100644 --- a/src/main/python/ayab/main_gui.ui +++ b/src/main/python/ayab/main_gui.ui @@ -253,10 +253,20 @@ + + + + Test + + + T + + + - Cancel Knitting + Cancel Esc diff --git a/src/main/python/ayab/observer.py b/src/main/python/ayab/observer.py index ffbd7692..4663fb91 100644 --- a/src/main/python/ayab/observer.py +++ b/src/main/python/ayab/observer.py @@ -49,6 +49,7 @@ class Observer(QObject): bad_config_flag = pyqtSignal() knitting_starter = pyqtSignal() knitting_finisher = pyqtSignal(bool) + testing_finisher = pyqtSignal() def __init__(self): super().__init__() @@ -73,3 +74,4 @@ def activate_signals(self, parent): self.alignment_updater.connect(parent.scene.update_alignment) self.image_resizer.connect(parent.set_image_dimensions) self.knitting_finisher.connect(parent.finish_knitting) + self.testing_finisher.connect(parent.finish_testing) diff --git a/src/main/python/ayab/preferences.py b/src/main/python/ayab/preferences.py index 3c209115..79becb61 100644 --- a/src/main/python/ayab/preferences.py +++ b/src/main/python/ayab/preferences.py @@ -31,7 +31,7 @@ from .prefs_gui import Ui_PrefsDialog from .observable import Observable from .engine.options import Alignment -from .engine.mode import KnitMode +from .engine.mode import Mode from .machine import Machine from .language import Language @@ -58,7 +58,7 @@ class Preferences(Observable): """ variables = { 'machine': Machine, - 'default_knitting_mode': KnitMode, + 'default_knitting_mode': Mode, 'default_infinite_repeat': bool, 'default_alignment': Alignment, 'default_mirroring': bool, diff --git a/src/main/python/ayab/tests/test_communication.py b/src/main/python/ayab/tests/test_communication.py index 754383d8..f3d671be 100644 --- a/src/main/python/ayab/tests/test_communication.py +++ b/src/main/python/ayab/tests/test_communication.py @@ -23,7 +23,7 @@ import unittest from mock import patch -from ayab.engine.communication import AyabCommunication, MessageToken +from ayab.engine.communication import Communication, Token from ayab.machine import Machine @@ -31,7 +31,7 @@ class TestCommunication(unittest.TestCase): def setUp(self): self.dummy_serial = serial.serial_for_url("loop://logging=debug", timeout=0.1) - self.comm_dummy = AyabCommunication(self.dummy_serial) + self.comm_dummy = Communication(self.dummy_serial) def test_close_serial(self): before = self.dummy_serial.is_open @@ -43,7 +43,7 @@ def test_close_serial(self): def test_open_serial(self): with patch.object(serial, 'Serial') as mock_method: mock_method.return_value = object() - self.ayabCom = AyabCommunication() + self.ayabCom = Communication() openStatus = self.ayabCom.open_serial('dummyPortname') assert openStatus mock_method.assert_called_once_with('dummyPortname', @@ -53,7 +53,7 @@ def test_open_serial(self): with patch.object(serial, 'Serial') as mock_method: with pytest.raises(Exception) as excinfo: mock_method.side_effect = serial.SerialException() - self.ayabCom = AyabCommunication() + self.ayabCom = Communication() openStatus = self.ayabCom.open_serial('dummyPortname') assert "CommunicationException" in str(excinfo.type) mock_method.assert_called_once_with('dummyPortname', @@ -61,11 +61,10 @@ def test_open_serial(self): timeout=0.1) def test_update_API6(self): - byte_array = bytearray([0xc0, MessageToken.cnfStart.value, 1, 0xc0]) + byte_array = bytearray([0xc0, Token.cnfStart.value, 1, 0xc0]) self.dummy_serial.write(byte_array) result = self.comm_dummy.update_API6() - expected_result = (bytes([MessageToken.cnfStart.value, - 1]), MessageToken.cnfStart, 1) + expected_result = (bytes([Token.cnfStart.value, 1]), Token.cnfStart, 1) assert result == expected_result def test_req_start_API6(self): @@ -73,9 +72,9 @@ def test_req_start_API6(self): self.comm_dummy.req_start_API6(machine_val, start_val, end_val, continuous_reporting) byte_array = bytearray([ - MessageToken.slipFrameEnd.value, MessageToken.reqStart.value, - machine_val, start_val, end_val, continuous_reporting, crc8, - MessageToken.slipFrameEnd.value + Token.slipFrameEnd.value, Token.reqStart.value, machine_val, + start_val, end_val, continuous_reporting, crc8, + Token.slipFrameEnd.value ]) bytes_read = self.dummy_serial.read(len(byte_array)) self.assertEqual(bytes_read, byte_array) @@ -83,8 +82,8 @@ def test_req_start_API6(self): def test_req_info(self): self.comm_dummy.req_info() byte_array = bytearray([ - MessageToken.slipFrameEnd.value, MessageToken.reqInfo.value, - MessageToken.slipFrameEnd.value + Token.slipFrameEnd.value, Token.reqInfo.value, + Token.slipFrameEnd.value ]) bytes_read = self.dummy_serial.read(len(byte_array)) assert bytes_read == byte_array @@ -93,8 +92,8 @@ def test_req_test_API6(self): machine_val = Machine.KH910_KH950.value self.comm_dummy.req_test_API6(machine_val) byte_array = bytearray([ - MessageToken.slipFrameEnd.value, MessageToken.reqTest.value, - machine_val, MessageToken.slipFrameEnd.value + Token.slipFrameEnd.value, Token.reqTest.value, machine_val, + Token.slipFrameEnd.value ]) bytes_read = self.dummy_serial.read(len(byte_array)) assert bytes_read == byte_array @@ -107,10 +106,10 @@ def test_cnf_line_API6(self): crc8 = 0xa7 self.comm_dummy.cnf_line_API6(line_number, color, flags, line_data) byte_array = bytearray([ - MessageToken.slipFrameEnd.value, MessageToken.cnfLine.value, - line_number, color, flags + Token.slipFrameEnd.value, Token.cnfLine.value, line_number, color, + flags ]) byte_array.extend(line_data) - byte_array.extend(bytes([crc8, MessageToken.slipFrameEnd.value])) + byte_array.extend(bytes([crc8, Token.slipFrameEnd.value])) bytes_read = self.dummy_serial.read(len(byte_array)) assert bytes_read == byte_array diff --git a/src/main/python/ayab/tests/test_communication_mockup.py b/src/main/python/ayab/tests/test_communication_mockup.py index eeaf66c4..3e310a54 100644 --- a/src/main/python/ayab/tests/test_communication_mockup.py +++ b/src/main/python/ayab/tests/test_communication_mockup.py @@ -20,14 +20,14 @@ import unittest -from ayab.engine.communication import MessageToken -from ayab.engine.communication_mockup import AyabCommunicationMockup +from ayab.engine.communication import Token +from ayab.engine.communication_mockup import CommunicationMockup from ayab.machine import Machine -class TestAyabCommunicationMockup(unittest.TestCase): +class TestCommunicationMockup(unittest.TestCase): def setUp(self): - self.comm_dummy = AyabCommunicationMockup(delay=False) + self.comm_dummy = CommunicationMockup(delay=False) def test_close_serial(self): self.comm_dummy.close_serial() @@ -39,34 +39,32 @@ def test_open_serial(self): assert self.comm_dummy.is_open() def test_update_API6(self): - assert self.comm_dummy.update_API6() == (None, MessageToken.none, 0) + assert self.comm_dummy.update_API6() == (None, Token.none, 0) def test_req_start_API6(self): machine_val, start_val, end_val, continuous_reporting, crc8 = 0, 0, 10, True, 0xb9 - expected_result = (bytes([MessageToken.cnfStart.value, - 1]), MessageToken.cnfStart, 1) + expected_result = (bytes([Token.cnfStart.value, 1]), Token.cnfStart, 1) self.comm_dummy.req_start_API6(machine_val, start_val, end_val, continuous_reporting) bytes_read = self.comm_dummy.update_API6() assert bytes_read == expected_result def test_req_info(self): - expected_result = (bytes([MessageToken.cnfInfo.value, 5, 0xff, - 0xff]), MessageToken.cnfInfo, 5) + expected_result = (bytes([Token.cnfInfo.value, 5, 0xff, + 0xff]), Token.cnfInfo, 5) self.comm_dummy.req_info() bytes_read = self.comm_dummy.update_API6() assert bytes_read == expected_result # indState shall be sent automatically, also expected_result = (bytes( - [MessageToken.indState.value, 1, 0xff, 0xff, 0xff, 0xff, 1, - 0x7f]), MessageToken.indState, 1) + [Token.indState.value, 1, 0xff, 0xff, 0xff, 0xff, 1, + 0x7f]), Token.indState, 1) bytes_read = self.comm_dummy.update_API6() assert bytes_read == expected_result def test_req_test_API6(self): - expected_result = (bytes([MessageToken.cnfTest.value, - 1]), MessageToken.cnfTest, 1) + expected_result = (bytes([Token.cnfTest.value, 1]), Token.cnfTest, 1) self.comm_dummy.req_test_API6(Machine.KH910_KH950) bytes_read = self.comm_dummy.update_API6() assert bytes_read == expected_result @@ -89,5 +87,5 @@ def test_req_line_API6(self): for i in range(0, 256): bytes_read = self.comm_dummy.update_API6() - assert bytes_read == (bytearray([MessageToken.reqLine.value, - i]), MessageToken.reqLine, i) + assert bytes_read == (bytearray([Token.reqLine.value, + i]), Token.reqLine, i) diff --git a/src/main/python/ayab/tests/test_control.py b/src/main/python/ayab/tests/test_control.py index c41a6ec0..c5ff6529 100644 --- a/src/main/python/ayab/tests/test_control.py +++ b/src/main/python/ayab/tests/test_control.py @@ -23,9 +23,9 @@ from PIL import Image from bitarray import bitarray -from ayab.engine.control import KnitControl +from ayab.engine.control import Control from ayab.engine.options import Alignment -from ayab.engine.mode import KnitMode, KnitModeFunc +from ayab.engine.mode import Mode, ModeFunc from ayab.engine.pattern import Pattern from ayab.engine.status import Status from ayab.machine import Machine @@ -36,18 +36,18 @@ def __init__(self): self.status = Status() -class TestKnitControl(unittest.TestCase): +class TestControl(unittest.TestCase): def setUp(self): self.parent = Parent() def test__singlebed(self): - control = KnitControl(self.parent) + control = Control(self.parent) control.pattern = Pattern(Image.new('P', (1, 3)), Machine(0), 2) control.num_colors = 2 control.start_row = 0 control.inf_repeat = False control.pat_height = control.pattern.pat_height - control.func = getattr(KnitModeFunc, "_singlebed") + control.func = getattr(ModeFunc, "_singlebed") assert control.func(control, 0) == (0, 0, False, False) assert control.func(control, 1) == (0, 2, False, False) assert control.func(control, 2) == (0, 4, False, True) @@ -58,14 +58,14 @@ def test__singlebed(self): assert control.func(control, 2) == (0, 0, False, False) def test__classic_ribber_2col(self): - control = KnitControl(self.parent) + control = Control(self.parent) control.pattern = Pattern(Image.new('P', (1, 5)), Machine(0), 2) control.num_colors = 2 control.start_row = 0 control.inf_repeat = False control.pat_height = control.pattern.pat_height control.len_pat_expanded = control.pat_height * control.num_colors - control.func = getattr(KnitModeFunc, "_classic_ribber_2col") + control.func = getattr(ModeFunc, "_classic_ribber_2col") assert control.func(control, 0) == (0, 0, False, False) assert control.func(control, 1) == (1, 1, False, False) assert control.func(control, 2) == (1, 3, False, False) @@ -85,14 +85,14 @@ def test__classic_ribber_2col(self): assert control.func(control, 8) == (1, 1, False, False) def test__classic_ribber_multicol(self): - control = KnitControl(self.parent) + control = Control(self.parent) control.pattern = Pattern(Image.new('P', (1, 3)), Machine(0), 3) control.num_colors = 3 control.start_row = 0 control.inf_repeat = False control.pat_height = control.pattern.pat_height control.len_pat_expanded = control.pat_height * control.num_colors - control.func = getattr(KnitModeFunc, "_classic_ribber_multicol") + control.func = getattr(ModeFunc, "_classic_ribber_multicol") assert control.func(control, 0) == (0, 0, False, False) assert control.func(control, 1) == (0, 0, True, False) assert control.func(control, 2) == (1, 1, False, False) @@ -117,9 +117,9 @@ def test__classic_ribber_multicol(self): assert control.func(control, 12) == (0, 0, False, False) def test__middlecolorstwice_ribber(self): - control = KnitControl(self.parent) + control = Control(self.parent) control.pattern = Pattern(Image.new('P', (1, 5)), Machine(0), 3) - control.mode = KnitMode.MIDDLECOLORSTWICE_RIBBER + control.mode = Mode.MIDDLECOLORSTWICE_RIBBER control.num_colors = 3 control.start_row = 0 control.inf_repeat = False @@ -127,7 +127,7 @@ def test__middlecolorstwice_ribber(self): control.len_pat_expanded = control.pat_height * control.num_colors control.passes_per_row = control.mode.row_multiplier( control.num_colors) - control.func = getattr(KnitModeFunc, "_middlecolorstwice_ribber") + control.func = getattr(ModeFunc, "_middlecolorstwice_ribber") assert control.func(control, 0) == (0, 0, False, False) assert control.func(control, 1) == (2, 2, True, False) assert control.func(control, 2) == (2, 2, False, False) @@ -158,9 +158,9 @@ def test__middlecolorstwice_ribber(self): assert control.func(control, 16) == (1, 1, False, False) def test__heartofpluto_ribber(self): - control = KnitControl(self.parent) + control = Control(self.parent) control.pattern = Pattern(Image.new('P', (1, 5)), Machine(0), 3) - control.mode = KnitMode.HEARTOFPLUTO_RIBBER + control.mode = Mode.HEARTOFPLUTO_RIBBER control.num_colors = 3 control.start_row = 0 control.inf_repeat = False @@ -168,7 +168,7 @@ def test__heartofpluto_ribber(self): control.len_pat_expanded = control.pat_height * control.num_colors control.passes_per_row = control.mode.row_multiplier( control.num_colors) - control.func = getattr(KnitModeFunc, "_heartofpluto_ribber") + control.func = getattr(ModeFunc, "_heartofpluto_ribber") assert control.func(control, 0) == (2, 2, False, False) assert control.func(control, 1) == (1, 1, False, False) assert control.func(control, 2) == (1, 1, True, False) @@ -199,14 +199,14 @@ def test__heartofpluto_ribber(self): assert control.func(control, 16) == (1, 1, False, False) def test__circular_ribber(self): - control = KnitControl(self.parent) + control = Control(self.parent) control.pattern = Pattern(Image.new('P', (1, 3)), Machine(0), 3) control.num_colors = 3 control.start_row = 0 control.inf_repeat = False control.pat_height = control.pattern.pat_height control.len_pat_expanded = control.pat_height * control.num_colors - control.func = getattr(KnitModeFunc, "_circular_ribber") + control.func = getattr(ModeFunc, "_circular_ribber") assert control.func(control, 0) == (0, 0, False, False) assert control.func(control, 1) == (0, 0, True, False) assert control.func(control, 2) == (1, 1, False, False) @@ -231,13 +231,13 @@ def test__circular_ribber(self): assert control.func(control, 12) == (0, 0, False, False) def test_select_needles_API6(self): - control = KnitControl(self.parent) + control = Control(self.parent) control.start(Machine(0)) control.num_colors = 2 control.start_row = 0 # 40 pixel image set to the left - control.mode = KnitMode.SINGLEBED + control.mode = Mode.SINGLEBED im = Image.new('P', (40, 3), 0) im1 = Image.new('P', (40, 1), 1) im.paste(im1, (0, 0)) @@ -264,60 +264,59 @@ def test_select_needles_API6(self): # 40 pixel image set in the center # blank line so central 40 pixels unset # flanking pixels set (2 different options) - control.mode = KnitMode.CLASSIC_RIBBER + control.mode = Mode.CLASSIC_RIBBER assert control.select_needles_API6(2, 1, False) == ~bits1 - control.mode = KnitMode.MIDDLECOLORSTWICE_RIBBER + control.mode = Mode.MIDDLECOLORSTWICE_RIBBER assert control.select_needles_API6(2, 1, False) == ~bits1 # image is wider than machine width # all pixels set control.pattern = Pattern(Image.new('P', (202, 1)), Machine(0), 2) - assert control.select_needles_API6(0, 0, False) == bitarray([True] * Machine(0).width) + assert control.select_needles_API6(0, 0, False) == bitarray( + [True] * Machine(0).width) def test_row_multiplier(self): - assert KnitMode.SINGLEBED.row_multiplier(2) == 1 - assert KnitMode.CLASSIC_RIBBER.row_multiplier(2) == 2 - assert KnitMode.CLASSIC_RIBBER.row_multiplier(3) == 6 - assert KnitMode.MIDDLECOLORSTWICE_RIBBER.row_multiplier(3) == 4 - assert KnitMode.HEARTOFPLUTO_RIBBER.row_multiplier(4) == 6 - assert KnitMode.CIRCULAR_RIBBER.row_multiplier(2) == 4 + assert Mode.SINGLEBED.row_multiplier(2) == 1 + assert Mode.CLASSIC_RIBBER.row_multiplier(2) == 2 + assert Mode.CLASSIC_RIBBER.row_multiplier(3) == 6 + assert Mode.MIDDLECOLORSTWICE_RIBBER.row_multiplier(3) == 4 + assert Mode.HEARTOFPLUTO_RIBBER.row_multiplier(4) == 6 + assert Mode.CIRCULAR_RIBBER.row_multiplier(2) == 4 def test_good_ncolors(self): - assert KnitMode.SINGLEBED.good_ncolors(2) - assert not KnitMode.SINGLEBED.good_ncolors(3) - assert KnitMode.CLASSIC_RIBBER.good_ncolors(2) - assert KnitMode.CLASSIC_RIBBER.good_ncolors(3) - assert KnitMode.MIDDLECOLORSTWICE_RIBBER.good_ncolors(2) - assert KnitMode.MIDDLECOLORSTWICE_RIBBER.good_ncolors(3) - assert KnitMode.HEARTOFPLUTO_RIBBER.good_ncolors(2) - assert KnitMode.HEARTOFPLUTO_RIBBER.good_ncolors(3) - assert KnitMode.CIRCULAR_RIBBER.good_ncolors(2) - assert not KnitMode.CIRCULAR_RIBBER.good_ncolors(3) + assert Mode.SINGLEBED.good_ncolors(2) + assert not Mode.SINGLEBED.good_ncolors(3) + assert Mode.CLASSIC_RIBBER.good_ncolors(2) + assert Mode.CLASSIC_RIBBER.good_ncolors(3) + assert Mode.MIDDLECOLORSTWICE_RIBBER.good_ncolors(2) + assert Mode.MIDDLECOLORSTWICE_RIBBER.good_ncolors(3) + assert Mode.HEARTOFPLUTO_RIBBER.good_ncolors(2) + assert Mode.HEARTOFPLUTO_RIBBER.good_ncolors(3) + assert Mode.CIRCULAR_RIBBER.good_ncolors(2) + assert not Mode.CIRCULAR_RIBBER.good_ncolors(3) def test_knit_func(self): - assert KnitMode.SINGLEBED.knit_func(2) == "_singlebed" - assert KnitMode.CLASSIC_RIBBER.knit_func(2) == "_classic_ribber_2col" - assert KnitMode.CLASSIC_RIBBER.knit_func( - 3) == "_classic_ribber_multicol" - assert KnitMode.MIDDLECOLORSTWICE_RIBBER.knit_func( + assert Mode.SINGLEBED.knit_func(2) == "_singlebed" + assert Mode.CLASSIC_RIBBER.knit_func(2) == "_classic_ribber_2col" + assert Mode.CLASSIC_RIBBER.knit_func(3) == "_classic_ribber_multicol" + assert Mode.MIDDLECOLORSTWICE_RIBBER.knit_func( 3) == "_middlecolorstwice_ribber" - assert KnitMode.HEARTOFPLUTO_RIBBER.knit_func( - 4) == "_heartofpluto_ribber" - assert KnitMode.CIRCULAR_RIBBER.knit_func(2) == "_circular_ribber" + assert Mode.HEARTOFPLUTO_RIBBER.knit_func(4) == "_heartofpluto_ribber" + assert Mode.CIRCULAR_RIBBER.knit_func(2) == "_circular_ribber" def test_flanking_needles(self): - assert KnitMode.SINGLEBED.flanking_needles(0, 2) - assert not KnitMode.SINGLEBED.flanking_needles(1, 2) - assert KnitMode.CLASSIC_RIBBER.flanking_needles(0, 2) - assert not KnitMode.CLASSIC_RIBBER.flanking_needles(1, 2) - assert KnitMode.CLASSIC_RIBBER.flanking_needles(0, 3) - assert not KnitMode.CLASSIC_RIBBER.flanking_needles(1, 3) - assert not KnitMode.CLASSIC_RIBBER.flanking_needles(2, 3) - assert KnitMode.MIDDLECOLORSTWICE_RIBBER.flanking_needles(0, 3) - assert not KnitMode.MIDDLECOLORSTWICE_RIBBER.flanking_needles(1, 3) - assert not KnitMode.MIDDLECOLORSTWICE_RIBBER.flanking_needles(2, 3) - assert KnitMode.HEARTOFPLUTO_RIBBER.flanking_needles(0, 3) - assert not KnitMode.HEARTOFPLUTO_RIBBER.flanking_needles(1, 3) - assert not KnitMode.HEARTOFPLUTO_RIBBER.flanking_needles(2, 3) - assert KnitMode.CIRCULAR_RIBBER.flanking_needles(0, 2) - assert not KnitMode.CIRCULAR_RIBBER.flanking_needles(1, 2) + assert Mode.SINGLEBED.flanking_needles(0, 2) + assert not Mode.SINGLEBED.flanking_needles(1, 2) + assert Mode.CLASSIC_RIBBER.flanking_needles(0, 2) + assert not Mode.CLASSIC_RIBBER.flanking_needles(1, 2) + assert Mode.CLASSIC_RIBBER.flanking_needles(0, 3) + assert not Mode.CLASSIC_RIBBER.flanking_needles(1, 3) + assert not Mode.CLASSIC_RIBBER.flanking_needles(2, 3) + assert Mode.MIDDLECOLORSTWICE_RIBBER.flanking_needles(0, 3) + assert not Mode.MIDDLECOLORSTWICE_RIBBER.flanking_needles(1, 3) + assert not Mode.MIDDLECOLORSTWICE_RIBBER.flanking_needles(2, 3) + assert Mode.HEARTOFPLUTO_RIBBER.flanking_needles(0, 3) + assert not Mode.HEARTOFPLUTO_RIBBER.flanking_needles(1, 3) + assert not Mode.HEARTOFPLUTO_RIBBER.flanking_needles(2, 3) + assert Mode.CIRCULAR_RIBBER.flanking_needles(0, 2) + assert not Mode.CIRCULAR_RIBBER.flanking_needles(1, 2) diff --git a/src/main/resources/base/ayab/translations/ayab-translation-master.tsv b/src/main/resources/base/ayab/translations/ayab-translation-master.tsv index cd5c4553..d8aaef08 100644 --- a/src/main/resources/base/ayab/translations/ayab-translation-master.tsv +++ b/src/main/resources/base/ayab/translations/ayab-translation-master.tsv @@ -44,7 +44,8 @@ Dest Src Context en_US en_GB de_DE is_IS it_IT ru_RU uk_UA fr_FR es_Es 42 35 MainWindow 1. Load Image File 1. Load Image File 1. Muster öffnen 1. Hlaðið myndskrá 1. Carica l'immagine 1. Загрузить изображение 1. Завантажити зображення 1. Charger une image 1. Cargar archivo de imagen 43 36 MainWindow Knitting Options Knitting Options Strickeinstellungen Prjóna valkostir Opzioni Lavorazione Варианты вязания Варіанти в'язання Options de tricot Opciones de tejido 44 37 MainWindow 2. Knit 2. Knit 2. Stricken 2. Prjónið 2. Lavora 2. Вязание 2. В'язання 2. Tricoter 2. Tejer -45 38 MainWindow Cancel Knitting Cancel Knitting Stricken abbrechen Hætta við að prjóna Annulla la lavorazione Отменить вязание Скасувати в’язання Annuler Cancelar tejido +45 38 MainWindow Cancel Cancel Abbrechen Hætta við Annulla Отмена Скасувати Annuler Cancelar +45 38 MainWindow Test Test Testen Prófa Test Тест Тест Tester Test 46 5 MenuBar File File Datei Skrá File Файл Файл Fichier Archivo 47 6 MenuBar Help Help Hilfe Hjálp Aiuto Помощь Допомога Aide Ayuda 48 7 MenuBar Tools Tools Werkzeuge Verkfæri Strumenti Инструменты Інструменти Outils Utiles