1# Copyright (c) 2018 Ultimaker B.V.
2# Cura is released under the terms of the LGPLv3 or higher.
3
4from typing import TYPE_CHECKING, Set, Union, Optional
5
6from PyQt5.QtCore import QTimer
7
8from .PrinterOutputController import PrinterOutputController
9
10if TYPE_CHECKING:
11    from .Models.PrintJobOutputModel import PrintJobOutputModel
12    from .Models.PrinterOutputModel import PrinterOutputModel
13    from .PrinterOutputDevice import PrinterOutputDevice
14    from .Models.ExtruderOutputModel import ExtruderOutputModel
15
16
17class GenericOutputController(PrinterOutputController):
18    def __init__(self, output_device: "PrinterOutputDevice") -> None:
19        super().__init__(output_device)
20
21        self._preheat_bed_timer = QTimer()
22        self._preheat_bed_timer.setSingleShot(True)
23        self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
24        self._preheat_printer = None  # type: Optional[PrinterOutputModel]
25
26        self._preheat_hotends_timer = QTimer()
27        self._preheat_hotends_timer.setSingleShot(True)
28        self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished)
29        self._preheat_hotends = set()  # type: Set[ExtruderOutputModel]
30
31        self._output_device.printersChanged.connect(self._onPrintersChanged)
32        self._active_printer = None  # type: Optional[PrinterOutputModel]
33
34    def _onPrintersChanged(self) -> None:
35        if self._active_printer:
36            self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged)
37            self._active_printer.targetBedTemperatureChanged.disconnect(self._onTargetBedTemperatureChanged)
38            for extruder in self._active_printer.extruders:
39                extruder.targetHotendTemperatureChanged.disconnect(self._onTargetHotendTemperatureChanged)
40
41        self._active_printer = self._output_device.activePrinter
42        if self._active_printer:
43            self._active_printer.stateChanged.connect(self._onPrinterStateChanged)
44            self._active_printer.targetBedTemperatureChanged.connect(self._onTargetBedTemperatureChanged)
45            for extruder in self._active_printer.extruders:
46                extruder.targetHotendTemperatureChanged.connect(self._onTargetHotendTemperatureChanged)
47
48    def _onPrinterStateChanged(self) -> None:
49        if self._active_printer and self._active_printer.state != "idle":
50            if self._preheat_bed_timer.isActive():
51                self._preheat_bed_timer.stop()
52                if self._preheat_printer:
53                    self._preheat_printer.updateIsPreheating(False)
54            if self._preheat_hotends_timer.isActive():
55                self._preheat_hotends_timer.stop()
56                for extruder in self._preheat_hotends:
57                    extruder.updateIsPreheating(False)
58                self._preheat_hotends = set()
59
60    def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
61        self._output_device.sendCommand("G91")
62        self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
63        self._output_device.sendCommand("G90")
64
65    def homeHead(self, printer: "PrinterOutputModel") -> None:
66        self._output_device.sendCommand("G28 X Y")
67
68    def homeBed(self, printer: "PrinterOutputModel") -> None:
69        self._output_device.sendCommand("G28 Z")
70
71    def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None:
72        self._output_device.sendCommand(command.upper()) #Most printers only understand uppercase g-code commands.
73
74    def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
75        if state == "pause":
76            self._output_device.pausePrint()
77            job.updateState("paused")
78        elif state == "print":
79            self._output_device.resumePrint()
80            job.updateState("printing")
81        elif state == "abort":
82            self._output_device.cancelPrint()
83            pass
84
85    def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: float) -> None:
86        self._output_device.sendCommand("M140 S%s" % round(temperature)) # The API doesn't allow floating point.
87
88    def _onTargetBedTemperatureChanged(self) -> None:
89        if self._preheat_bed_timer.isActive() and self._preheat_printer and self._preheat_printer.targetBedTemperature == 0:
90            self._preheat_bed_timer.stop()
91            self._preheat_printer.updateIsPreheating(False)
92
93    def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None:
94        try:
95            temperature = round(temperature)  # The API doesn't allow floating point.
96            duration = round(duration)
97        except ValueError:
98            return  # Got invalid values, can't pre-heat.
99
100        self.setTargetBedTemperature(printer, temperature = temperature)
101        self._preheat_bed_timer.setInterval(duration * 1000)
102        self._preheat_bed_timer.start()
103        self._preheat_printer = printer
104        printer.updateIsPreheating(True)
105
106    def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None:
107        self.setTargetBedTemperature(printer, temperature = 0)
108        self._preheat_bed_timer.stop()
109        printer.updateIsPreheating(False)
110
111    def _onPreheatBedTimerFinished(self) -> None:
112        if not self._preheat_printer:
113            return
114        self.setTargetBedTemperature(self._preheat_printer, 0)
115        self._preheat_printer.updateIsPreheating(False)
116
117    def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None:
118        self._output_device.sendCommand("M104 S%s T%s" % (temperature, position))
119
120    def _onTargetHotendTemperatureChanged(self) -> None:
121        if not self._preheat_hotends_timer.isActive():
122            return
123        if not self._active_printer:
124            return
125
126        for extruder in self._active_printer.extruders:
127            if extruder in self._preheat_hotends and extruder.targetHotendTemperature == 0:
128                extruder.updateIsPreheating(False)
129                self._preheat_hotends.remove(extruder)
130        if not self._preheat_hotends:
131            self._preheat_hotends_timer.stop()
132
133    def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None:
134        position = extruder.getPosition()
135        number_of_extruders = len(extruder.getPrinter().extruders)
136        if position >= number_of_extruders:
137            return  # Got invalid extruder nr, can't pre-heat.
138
139        try:
140            temperature = round(temperature)  # The API doesn't allow floating point.
141            duration = round(duration)
142        except ValueError:
143            return  # Got invalid values, can't pre-heat.
144
145        self.setTargetHotendTemperature(extruder.getPrinter(), position, temperature=temperature)
146        self._preheat_hotends_timer.setInterval(duration * 1000)
147        self._preheat_hotends_timer.start()
148        self._preheat_hotends.add(extruder)
149        extruder.updateIsPreheating(True)
150
151    def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None:
152        self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0)
153        if extruder in self._preheat_hotends:
154            extruder.updateIsPreheating(False)
155            self._preheat_hotends.remove(extruder)
156        if not self._preheat_hotends and self._preheat_hotends_timer.isActive():
157            self._preheat_hotends_timer.stop()
158
159    def _onPreheatHotendsTimerFinished(self) -> None:
160        for extruder in self._preheat_hotends:
161            self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
162        self._preheat_hotends = set()
163
164    # Cancel any ongoing preheating timers, without setting back the temperature to 0
165    # This can be used eg at the start of a print
166    def stopPreheatTimers(self) -> None:
167        if self._preheat_hotends_timer.isActive():
168            for extruder in self._preheat_hotends:
169                extruder.updateIsPreheating(False)
170            self._preheat_hotends = set()
171
172            self._preheat_hotends_timer.stop()
173
174        if self._preheat_bed_timer.isActive():
175            if self._preheat_printer:
176                self._preheat_printer.updateIsPreheating(False)
177            self._preheat_bed_timer.stop()
178