1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192 |
- from UM.i18n import i18nCatalog
- from UM.Application import Application
- from UM.Logger import Logger
- from UM.Signal import signalemitter
- from UM.Message import Message
- import UM.Settings.ContainerRegistry
- import UM.Version
- from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
- import cura.Settings.ExtruderManager
- from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply
- from PyQt5.QtCore import QUrl, QTimer, pyqtSignal, pyqtProperty, pyqtSlot, QCoreApplication
- from PyQt5.QtGui import QImage
- from PyQt5.QtWidgets import QMessageBox
- import json
- import os
- import gzip
- import zlib
- from time import time
- from time import sleep
- i18n_catalog = i18nCatalog("cura")
- from enum import IntEnum
- class AuthState(IntEnum):
- NotAuthenticated = 1
- AuthenticationRequested = 2
- Authenticated = 3
- AuthenticationDenied = 4
- @signalemitter
- class NetworkPrinterOutputDevice(PrinterOutputDevice):
- def __init__(self, key, address, properties, api_prefix):
- super().__init__(key)
- self._address = address
- self._key = key
- self._properties = properties
- self._api_prefix = api_prefix
- self._gcode = None
- self._print_finished = True
- self._use_gzip = True
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- self._json_printer_state = {}
-
-
- self._num_extruders = 2
-
- self._hotend_temperatures = [0] * self._num_extruders
- self._target_hotend_temperatures = [0] * self._num_extruders
- self._material_ids = [""] * self._num_extruders
- self._hotend_ids = [""] * self._num_extruders
- self._target_bed_temperature = 0
- self._processing_preheat_requests = True
- self.setPriority(2)
- self.setName(key)
- self.setShortDescription(i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network"))
- self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print over network"))
- self.setIconName("print")
- self._manager = None
- self._post_request = None
- self._post_reply = None
- self._post_multi_part = None
- self._post_part = None
- self._material_multi_part = None
- self._material_part = None
- self._progress_message = None
- self._error_message = None
- self._connection_message = None
- self._update_timer = QTimer()
- self._update_timer.setInterval(2000)
- self._update_timer.setSingleShot(False)
- self._update_timer.timeout.connect(self._update)
- self._camera_timer = QTimer()
- self._camera_timer.setInterval(500)
- self._camera_timer.setSingleShot(False)
- self._camera_timer.timeout.connect(self._updateCamera)
- self._image_request = None
- self._image_reply = None
- self._use_stream = True
- self._stream_buffer = b""
- self._stream_buffer_start_index = -1
- self._camera_image_id = 0
- self._authentication_counter = 0
- self._max_authentication_counter = 5 * 60
- self._authentication_timer = QTimer()
- self._authentication_timer.setInterval(1000)
- self._authentication_timer.setSingleShot(False)
- self._authentication_timer.timeout.connect(self._onAuthenticationTimer)
- self._authentication_request_active = False
- self._authentication_state = AuthState.NotAuthenticated
- self._authentication_id = None
- self._authentication_key = None
- self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", "Access to the printer requested. Please approve the request on the printer"), lifetime = 0, dismissable = False, progress = 0)
- self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", ""))
- self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry"), None, i18n_catalog.i18nc("@info:tooltip", "Re-send the access request"))
- self._authentication_failed_message.actionTriggered.connect(self.requestAuthentication)
- self._authentication_succeeded_message = Message(i18n_catalog.i18nc("@info:status", "Access to the printer accepted"))
- self._not_authenticated_message = Message(i18n_catalog.i18nc("@info:status", "No access to print with this printer. Unable to send print job."))
- self._not_authenticated_message.addAction("Request", i18n_catalog.i18nc("@action:button", "Request Access"), None, i18n_catalog.i18nc("@info:tooltip", "Send access request to the printer"))
- self._not_authenticated_message.actionTriggered.connect(self.requestAuthentication)
- self._camera_image = QImage()
- self._material_post_objects = {}
- self._connection_state_before_timeout = None
- self._last_response_time = time()
- self._last_request_time = None
- self._response_timeout_time = 10
- self._recreate_network_manager_time = 30
- self._recreate_network_manager_count = 1
- self._send_gcode_start = time()
- self._last_command = ""
- self._compressing_print = False
- self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")
- printer_type = self._properties.get(b"machine", b"").decode("utf-8")
- if printer_type.startswith("9511"):
- self._updatePrinterType("ultimaker3_extended")
- elif printer_type.startswith("9066"):
- self._updatePrinterType("ultimaker3")
- else:
- self._updatePrinterType("unknown")
- Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
- def _onNetworkAccesibleChanged(self, accessible):
- Logger.log("d", "Network accessible state changed to: %s", accessible)
-
-
-
- def _onOutputDevicesChanged(self):
- if self.getId() not in Application.getInstance().getOutputDeviceManager().getOutputDeviceIds():
- self.stopCamera()
- def _onAuthenticationTimer(self):
- self._authentication_counter += 1
- self._authentication_requested_message.setProgress(self._authentication_counter / self._max_authentication_counter * 100)
- if self._authentication_counter > self._max_authentication_counter:
- self._authentication_timer.stop()
- Logger.log("i", "Authentication timer ended. Setting authentication to denied")
- self.setAuthenticationState(AuthState.AuthenticationDenied)
- def _onAuthenticationRequired(self, reply, authenticator):
- if self._authentication_id is not None and self._authentication_key is not None:
- Logger.log("d", "Authentication was required. Setting up authenticator with ID %s and key %s", self._authentication_id, self._getSafeAuthKey())
- authenticator.setUser(self._authentication_id)
- authenticator.setPassword(self._authentication_key)
- else:
- Logger.log("d", "No authentication is available to use, but we did got a request for it.")
- def getProperties(self):
- return self._properties
- @pyqtSlot(str, result = str)
- def getProperty(self, key):
- key = key.encode("utf-8")
- if key in self._properties:
- return self._properties.get(key, b"").decode("utf-8")
- else:
- return ""
-
-
- @pyqtSlot(result = str)
- def getKey(self):
- return self._key
-
- @pyqtProperty(str, constant = True)
- def address(self):
- return self._properties.get(b"address", b"").decode("utf-8")
-
- @pyqtProperty(str, constant = True)
- def name(self):
- return self._properties.get(b"name", b"").decode("utf-8")
-
- @pyqtProperty(str, constant=True)
- def firmwareVersion(self):
- return self._properties.get(b"firmware_version", b"").decode("utf-8")
-
- @pyqtProperty(str, constant=True)
- def ipAddress(self):
- return self._address
-
-
-
-
-
- @pyqtSlot(float, float)
- def preheatBed(self, temperature, duration):
- temperature = round(temperature)
- duration = round(duration)
- if UM.Version.Version(self.firmwareVersion) < UM.Version.Version("3.5.92"):
- self.setTargetBedTemperature(temperature = temperature)
- return
- url = QUrl("http://" + self._address + self._api_prefix + "printer/bed/pre_heat")
- if duration > 0:
- data = """{"temperature": "%i", "timeout": "%i"}""" % (temperature, duration)
- else:
- data = """{"temperature": "%i"}""" % temperature
- Logger.log("i", "Pre-heating bed to %i degrees.", temperature)
- put_request = QNetworkRequest(url)
- put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
- self._processing_preheat_requests = False
- self._manager.put(put_request, data.encode())
- self._preheat_bed_timer.start(self._preheat_bed_timeout * 1000)
- self.preheatBedRemainingTimeChanged.emit()
-
-
-
- @pyqtSlot()
- def cancelPreheatBed(self):
- Logger.log("i", "Cancelling pre-heating of the bed.")
- self.preheatBed(temperature = 0, duration = 0)
- self._preheat_bed_timer.stop()
- self._preheat_bed_timer.setInterval(0)
- self.preheatBedRemainingTimeChanged.emit()
-
-
-
- def _setTargetBedTemperature(self, temperature):
- if not self._updateTargetBedTemperature(temperature):
- return
- url = QUrl("http://" + self._address + self._api_prefix + "printer/bed/temperature/target")
- data = str(temperature)
- put_request = QNetworkRequest(url)
- put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
- self._manager.put(put_request, data.encode())
-
-
-
-
- def _updateTargetBedTemperature(self, temperature):
- if self._target_bed_temperature == temperature:
- return False
- self._target_bed_temperature = temperature
- self.targetBedTemperatureChanged.emit()
- return True
- def _stopCamera(self):
- if self._camera_timer.isActive():
- self._camera_timer.stop()
- if self._image_reply:
- try:
- self._image_reply.abort()
- self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
- except RuntimeError:
- pass
- self._image_reply = None
- self._image_request = None
- def _startCamera(self):
- if self._use_stream:
- self._startCameraStream()
- else:
- self._camera_timer.start()
- def _startCameraStream(self):
-
- url = QUrl("http://" + self._address + ":8080/?action=stream")
- self._image_request = QNetworkRequest(url)
- self._image_reply = self._manager.get(self._image_request)
- self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
- def _updateCamera(self):
- if not self._manager.networkAccessible():
- return
-
- url = QUrl("http://" + self._address + ":8080/?action=snapshot")
- image_request = QNetworkRequest(url)
- self._manager.get(image_request)
- self._last_request_time = time()
-
-
- def setAuthenticationState(self, auth_state):
- if auth_state == self._authentication_state:
- return
- if auth_state == AuthState.AuthenticationRequested:
- Logger.log("d", "Authentication state changed to authentication requested.")
- self.setAcceptsCommands(False)
- self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network. Please approve the access request on the printer."))
- self._authentication_requested_message.show()
- self._authentication_request_active = True
- self._authentication_timer.start()
- elif auth_state == AuthState.Authenticated:
- Logger.log("d", "Authentication state changed to authenticated")
- self.setAcceptsCommands(True)
- self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network."))
- self._authentication_requested_message.hide()
- if self._authentication_request_active:
- self._authentication_succeeded_message.show()
-
- self._authentication_timer.stop()
- self._authentication_counter = 0
-
- self.sendMaterialProfiles()
- elif auth_state == AuthState.AuthenticationDenied:
- self.setAcceptsCommands(False)
- self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network. No access to control the printer."))
- self._authentication_requested_message.hide()
- if self._authentication_request_active:
- if self._authentication_timer.remainingTime() > 0:
- Logger.log("d", "Authentication state changed to authentication denied before the request timeout.")
- self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request was denied on the printer."))
- else:
- Logger.log("d", "Authentication state changed to authentication denied due to a timeout")
- self._authentication_failed_message.setText(i18n_catalog.i18nc("@info:status", "Access request failed due to a timeout."))
- self._authentication_failed_message.show()
- self._authentication_request_active = False
-
- self._authentication_timer.stop()
- self._authentication_counter = 0
- self._authentication_state = auth_state
- self.authenticationStateChanged.emit()
- authenticationStateChanged = pyqtSignal()
- @pyqtProperty(int, notify = authenticationStateChanged)
- def authenticationState(self):
- return self._authentication_state
- @pyqtSlot()
- def requestAuthentication(self, message_id = None, action_id = "Retry"):
- if action_id == "Request" or action_id == "Retry":
- self._authentication_failed_message.hide()
- self._not_authenticated_message.hide()
- self._authentication_state = AuthState.NotAuthenticated
- self._authentication_counter = 0
- self._authentication_requested_message.setProgress(0)
- self._authentication_id = None
- self._authentication_key = None
- self._createNetworkManager()
-
- def _update(self):
- if self._last_response_time:
- time_since_last_response = time() - self._last_response_time
- else:
- time_since_last_response = 0
- if self._last_request_time:
- time_since_last_request = time() - self._last_request_time
- else:
- time_since_last_request = float("inf")
-
-
-
- if self._last_response_time and self._connection_state_before_timeout:
- if time_since_last_response > self._recreate_network_manager_time * self._recreate_network_manager_count:
- self._recreate_network_manager_count += 1
- counter = 0
-
-
- while time_since_last_response - self._recreate_network_manager_time * self._recreate_network_manager_count > self._recreate_network_manager_time and counter < 10:
- counter += 1
- self._recreate_network_manager_count += 1
- Logger.log("d", "Timeout lasted over %.0f seconds (%.1fs), re-checking connection.", self._recreate_network_manager_time, time_since_last_response)
- self._createNetworkManager()
- return
-
- if not self._manager.networkAccessible():
- if not self._connection_state_before_timeout:
- Logger.log("d", "The network connection seems to be disabled. Going into timeout mode")
- self._connection_state_before_timeout = self._connection_state
- self.setConnectionState(ConnectionState.error)
- self._connection_message = Message(i18n_catalog.i18nc("@info:status",
- "The connection with the network was lost."))
- self._connection_message.show()
- if self._progress_message:
- self._progress_message.hide()
-
-
- try:
- if self._post_reply:
- Logger.log("d", "Stopping post upload because the connection was lost.")
- try:
- self._post_reply.uploadProgress.disconnect(self._onUploadProgress)
- except TypeError:
- pass
- self._post_reply.abort()
- self._post_reply = None
- except RuntimeError:
- self._post_reply = None
- return
- else:
- if not self._connection_state_before_timeout:
- self._recreate_network_manager_count = 1
-
- if self._last_response_time and self._last_request_time and not self._connection_state_before_timeout:
- if time_since_last_response > self._response_timeout_time and time_since_last_request <= self._response_timeout_time:
-
- Logger.log("d", "We did not receive a response for %0.1f seconds, so it seems the printer is no longer accessible.", time_since_last_response)
- self._connection_state_before_timeout = self._connection_state
- self._connection_message = Message(i18n_catalog.i18nc("@info:status", "The connection with the printer was lost. Check your printer to see if it is connected."))
- self._connection_message.show()
- if self._progress_message:
- self._progress_message.hide()
-
-
- try:
- if self._post_reply:
- Logger.log("d", "Stopping post upload because the connection was lost.")
- try:
- self._post_reply.uploadProgress.disconnect(self._onUploadProgress)
- except TypeError:
- pass
- self._post_reply.abort()
- self._post_reply = None
- except RuntimeError:
- self._post_reply = None
- self.setConnectionState(ConnectionState.error)
- return
- if self._authentication_state == AuthState.NotAuthenticated:
- self._verifyAuthentication()
- elif self._authentication_state == AuthState.AuthenticationRequested:
- self._checkAuthentication()
-
- url = QUrl("http://" + self._address + self._api_prefix + "printer")
- printer_request = QNetworkRequest(url)
- self._manager.get(printer_request)
-
- url = QUrl("http://" + self._address + self._api_prefix + "print_job")
- print_job_request = QNetworkRequest(url)
- self._manager.get(print_job_request)
- self._last_request_time = time()
- def _createNetworkManager(self):
- if self._manager:
- self._manager.finished.disconnect(self._onFinished)
- self._manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged)
- self._manager.authenticationRequired.disconnect(self._onAuthenticationRequired)
- self._manager = QNetworkAccessManager()
- self._manager.finished.connect(self._onFinished)
- self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
- self._manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged)
-
-
- def _spliceJSONData(self):
-
- for index in range(0, self._num_extruders):
- temperature = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["temperature"]["current"]
- self._setHotendTemperature(index, temperature)
- try:
- material_id = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"]
- except KeyError:
- material_id = ""
- self._setMaterialId(index, material_id)
- try:
- hotend_id = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]
- except KeyError:
- hotend_id = ""
- self._setHotendId(index, hotend_id)
- bed_temperature = self._json_printer_state["bed"]["temperature"]["current"]
- self._setBedTemperature(bed_temperature)
- target_bed_temperature = self._json_printer_state["bed"]["temperature"]["target"]
- self._updateTargetBedTemperature(target_bed_temperature)
- head_x = self._json_printer_state["heads"][0]["position"]["x"]
- head_y = self._json_printer_state["heads"][0]["position"]["y"]
- head_z = self._json_printer_state["heads"][0]["position"]["z"]
- self._updateHeadPosition(head_x, head_y, head_z)
- self._updatePrinterState(self._json_printer_state["status"])
- if self._processing_preheat_requests:
- try:
- is_preheating = self._json_printer_state["bed"]["pre_heat"]["active"]
- except KeyError:
- pass
- else:
- if is_preheating:
- try:
- remaining_preheat_time = self._json_printer_state["bed"]["pre_heat"]["remaining"]
- except KeyError:
- pass
- else:
-
-
- if abs(self._preheat_bed_timer.remainingTime() - remaining_preheat_time * 1000) > 5000:
- self._preheat_bed_timer.setInterval(remaining_preheat_time * 1000)
- self._preheat_bed_timer.start()
- self.preheatBedRemainingTimeChanged.emit()
- else:
- if self._preheat_bed_timer.isActive():
- self._preheat_bed_timer.setInterval(0)
- self._preheat_bed_timer.stop()
- self.preheatBedRemainingTimeChanged.emit()
- def close(self):
- Logger.log("d", "Closing connection of printer %s with ip %s", self._key, self._address)
- self._updateJobState("")
- self.setConnectionState(ConnectionState.closed)
- if self._progress_message:
- self._progress_message.hide()
-
- self._authentication_requested_message.hide()
- self._authentication_state = AuthState.NotAuthenticated
- self._authentication_counter = 0
- self._authentication_timer.stop()
- self._authentication_requested_message.hide()
- self._authentication_failed_message.hide()
- self._authentication_succeeded_message.hide()
-
- self._material_ids = [""] * self._num_extruders
- self._hotend_ids = [""] * self._num_extruders
- if self._error_message:
- self._error_message.hide()
-
- self._connection_state_before_timeout = None
- self._last_response_time = time()
- self._last_request_time = None
-
- self._update_timer.stop()
- self.stopCamera()
-
-
-
-
-
-
-
-
- def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
- if self._printer_state not in ["idle", ""]:
- self._error_message = Message(
- i18n_catalog.i18nc("@info:status", "Unable to start a new print job, printer is busy. Current printer status is %s.") % self._printer_state)
- self._error_message.show()
- return
- elif self._authentication_state != AuthState.Authenticated:
- self._not_authenticated_message.show()
- Logger.log("d", "Attempting to perform an action without authentication. Auth state is %s", self._authentication_state)
- return
- Application.getInstance().showPrintMonitor.emit(True)
- self._print_finished = True
- self.writeStarted.emit(self)
- self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
- print_information = Application.getInstance().getPrintInformation()
- warnings = []
-
- if print_information.materialLengths:
-
- for index in range(0, self._num_extruders):
- if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
- if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "":
- Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1)
- self._error_message = Message(
- i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No Printcore loaded in slot {0}".format(index + 1)))
- self._error_message.show()
- return
- if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "":
- Logger.log("e", "No material loaded in slot %s, unable to start print", index + 1)
- self._error_message = Message(
- i18n_catalog.i18nc("@info:status",
- "Unable to start a new print job. No material loaded in slot {0}".format(index + 1)))
- self._error_message.show()
- return
- for index in range(0, self._num_extruders):
-
- material_length = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["length_remaining"]
- if material_length != -1 and index < len(print_information.materialLengths) and print_information.materialLengths[index] > material_length:
- Logger.log("w", "Printer reports that there is not enough material left for extruder %s. We need %s and the printer has %s", index + 1, print_information.materialLengths[index], material_length)
- warnings.append(i18n_catalog.i18nc("@label", "Not enough material for spool {0}.").format(index+1))
-
- extruder_manager = cura.Settings.ExtruderManager.ExtruderManager.getInstance()
- if index < len(print_information.materialLengths) and print_information.materialLengths[index] != 0:
- variant = extruder_manager.getExtruderStack(index).findContainer({"type": "variant"})
- core_name = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"]
- if variant:
- if variant.getName() != core_name:
- Logger.log("w", "Extruder %s has a different Cartridge (%s) as Cura (%s)", index + 1, core_name, variant.getName())
- warnings.append(i18n_catalog.i18nc("@label", "Different PrintCore (Cura: {0}, Printer: {1}) selected for extruder {2}".format(variant.getName(), core_name, index + 1)))
- material = extruder_manager.getExtruderStack(index).findContainer({"type": "material"})
- if material:
- remote_material_guid = self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"]
- if material.getMetaDataEntry("GUID") != remote_material_guid:
- Logger.log("w", "Extruder %s has a different material (%s) as Cura (%s)", index + 1,
- remote_material_guid,
- material.getMetaDataEntry("GUID"))
- remote_materials = UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().findInstanceContainers(type = "material", GUID = remote_material_guid, read_only = True)
- remote_material_name = "Unknown"
- if remote_materials:
- remote_material_name = remote_materials[0].getName()
- warnings.append(i18n_catalog.i18nc("@label", "Different material (Cura: {0}, Printer: {1}) selected for extruder {2}").format(material.getName(), remote_material_name, index + 1))
- try:
- is_offset_calibrated = self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["offset"]["state"] == "valid"
- except KeyError:
- is_offset_calibrated = True
- if not is_offset_calibrated:
- warnings.append(i18n_catalog.i18nc("@label", "PrintCore {0} is not properly calibrated. XY calibration needs to be performed on the printer.").format(index + 1))
- else:
- Logger.log("w", "There was no material usage found. No check to match used material with machine is done.")
- if warnings:
- text = i18n_catalog.i18nc("@label", "Are you sure you wish to print with the selected configuration?")
- informative_text = i18n_catalog.i18nc("@label", "There is a mismatch between the configuration or calibration of the printer and Cura. "
- "For the best result, always slice for the PrintCores and materials that are inserted in your printer.")
- detailed_text = ""
- for warning in warnings:
- detailed_text += warning + "\n"
- Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
- text,
- informative_text,
- detailed_text,
- buttons=QMessageBox.Yes + QMessageBox.No,
- icon=QMessageBox.Question,
- callback=self._configurationMismatchMessageCallback
- )
- return
- self.startPrint()
- def _configurationMismatchMessageCallback(self, button):
- def delayedCallback():
- if button == QMessageBox.Yes:
- self.startPrint()
- else:
- Application.getInstance().showPrintMonitor.emit(False)
-
-
- QTimer.singleShot(100, delayedCallback)
- def isConnected(self):
- return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error
-
- def connect(self):
- if self.isConnected():
- self.close()
- self._createNetworkManager()
- self.setConnectionState(ConnectionState.connecting)
- self._update()
- if not self._use_stream:
- self._updateCamera()
- Logger.log("d", "Connection with printer %s with ip %s started", self._key, self._address)
-
- self._authentication_id = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_id", None)
- self._authentication_key = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("network_authentication_key", None)
- if self._authentication_id is None and self._authentication_key is None:
- Logger.log("d", "No authentication found in metadata.")
- else:
- Logger.log("d", "Loaded authentication id %s and key %s from the metadata entry", self._authentication_id, self._getSafeAuthKey())
- self._update_timer.start()
-
- def disconnect(self):
- Logger.log("d", "Connection with printer %s with ip %s stopped", self._key, self._address)
- self.close()
- newImage = pyqtSignal()
- @pyqtProperty(QUrl, notify = newImage)
- def cameraImage(self):
- self._camera_image_id += 1
-
-
-
- temp = "image://camera/" + str(self._camera_image_id)
- return QUrl(temp, QUrl.TolerantMode)
- def getCameraImage(self):
- return self._camera_image
- def _setJobState(self, job_state):
- self._last_command = job_state
- url = QUrl("http://" + self._address + self._api_prefix + "print_job/state")
- put_request = QNetworkRequest(url)
- put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
- data = "{\"target\": \"%s\"}" % job_state
- self._manager.put(put_request, data.encode())
-
-
- def _getUserName(self):
- for name in ("LOGNAME", "USER", "LNAME", "USERNAME"):
- user = os.environ.get(name)
- if user:
- return user
- return "Unknown User"
- def _progressMessageActionTrigger(self, message_id = None, action_id = None):
- if action_id == "Abort":
- Logger.log("d", "User aborted sending print to remote.")
- self._progress_message.hide()
- self._compressing_print = False
- if self._post_reply:
- self._post_reply.abort()
- self._post_reply = None
- Application.getInstance().showPrintMonitor.emit(False)
-
-
-
- def startPrint(self):
- try:
- self._send_gcode_start = time()
- self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1)
- self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
- self._progress_message.actionTriggered.connect(self._progressMessageActionTrigger)
- self._progress_message.show()
- Logger.log("d", "Started sending g-code to remote printer.")
- self._compressing_print = True
-
- max_chars_per_line = 1024 * 1024 / 4
- byte_array_file_data = b""
- batched_line = ""
- def _compress_data_and_notify_qt(data_to_append):
- compressed_data = gzip.compress(data_to_append.encode("utf-8"))
- QCoreApplication.processEvents()
-
- self._last_response_time = time()
- return compressed_data
- for line in self._gcode:
- if not self._compressing_print:
- self._progress_message.hide()
- return
- if self._use_gzip:
- batched_line += line
-
-
- if len(batched_line) < max_chars_per_line:
- continue
- byte_array_file_data += _compress_data_and_notify_qt(batched_line)
- batched_line = ""
- else:
- byte_array_file_data += line.encode("utf-8")
-
- if self._use_gzip:
- if batched_line:
- byte_array_file_data += _compress_data_and_notify_qt(batched_line)
- if self._use_gzip:
- file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName
- else:
- file_name = "%s.gcode" % Application.getInstance().getPrintInformation().jobName
- self._compressing_print = False
-
- self._post_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
-
- self._post_part = QHttpPart()
- self._post_part.setHeader(QNetworkRequest.ContentDispositionHeader,
- "form-data; name=\"file\"; filename=\"%s\"" % file_name)
- self._post_part.setBody(byte_array_file_data)
- self._post_multi_part.append(self._post_part)
- url = QUrl("http://" + self._address + self._api_prefix + "print_job")
-
- self._post_request = QNetworkRequest(url)
-
- self._post_reply = self._manager.post(self._post_request, self._post_multi_part)
- self._post_reply.uploadProgress.connect(self._onUploadProgress)
- except IOError:
- self._progress_message.hide()
- self._error_message = Message(i18n_catalog.i18nc("@info:status", "Unable to send data to printer. Is another job still active?"))
- self._error_message.show()
- except Exception as e:
- self._progress_message.hide()
- Logger.log("e", "An exception occurred in network connection: %s" % str(e))
-
- def _verifyAuthentication(self):
- url = QUrl("http://" + self._address + self._api_prefix + "auth/verify")
- request = QNetworkRequest(url)
- self._manager.get(request)
-
- def _checkAuthentication(self):
- Logger.log("d", "Checking if authentication is correct for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
- self._manager.get(QNetworkRequest(QUrl("http://" + self._address + self._api_prefix + "auth/check/" + str(self._authentication_id))))
-
- def _requestAuthentication(self):
- url = QUrl("http://" + self._address + self._api_prefix + "auth/request")
- request = QNetworkRequest(url)
- request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
- self._authentication_key = None
- self._authentication_id = None
- self._manager.post(request, json.dumps({"application": "Cura-" + Application.getInstance().getVersion(), "user": self._getUserName()}).encode())
- self.setAuthenticationState(AuthState.AuthenticationRequested)
-
- def sendMaterialProfiles(self):
- for container in UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().findInstanceContainers(type = "material"):
- try:
- xml_data = container.serialize()
- if xml_data == "" or xml_data is None:
- continue
- material_multi_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
- material_part = QHttpPart()
- file_name = "none.xml"
- material_part.setHeader(QNetworkRequest.ContentDispositionHeader, "form-data; name=\"file\";filename=\"%s\"" % file_name)
- material_part.setBody(xml_data.encode())
- material_multi_part.append(material_part)
- url = QUrl("http://" + self._address + self._api_prefix + "materials")
- material_post_request = QNetworkRequest(url)
- reply = self._manager.post(material_post_request, material_multi_part)
-
- self._material_post_objects[id(reply)] = (material_part, material_multi_part, reply)
- except NotImplementedError:
-
-
- pass
-
- def _onFinished(self, reply):
- if reply.error() == QNetworkReply.TimeoutError:
- Logger.log("w", "Received a timeout on a request to the printer")
- self._connection_state_before_timeout = self._connection_state
-
-
- if self._post_reply:
- self._post_reply.abort()
- self._post_reply.uploadProgress.disconnect(self._onUploadProgress)
- Logger.log("d", "Uploading of print failed after %s", time() - self._send_gcode_start)
- self._post_reply = None
- self._progress_message.hide()
- self.setConnectionState(ConnectionState.error)
- return
- if self._connection_state_before_timeout and reply.error() == QNetworkReply.NoError:
- Logger.log("d", "We got a response (%s) from the server after %0.1f of silence. Going back to previous state %s", reply.url().toString(), time() - self._last_response_time, self._connection_state_before_timeout)
-
- if self._camera_active:
- self._startCamera()
- self.setConnectionState(self._connection_state_before_timeout)
- self._connection_state_before_timeout = None
- if reply.error() == QNetworkReply.NoError:
- self._last_response_time = time()
- status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
- if not status_code:
- if self._connection_state != ConnectionState.error:
- Logger.log("d", "A reply from %s did not have status code.", reply.url().toString())
-
- return
- reply_url = reply.url().toString()
- if reply.operation() == QNetworkAccessManager.GetOperation:
- if "printer" in reply_url:
- if status_code == 200:
- if self._connection_state == ConnectionState.connecting:
- self.setConnectionState(ConnectionState.connected)
- try:
- self._json_printer_state = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid printer state message: Not valid JSON.")
- return
- self._spliceJSONData()
-
- if self._connection_message:
- self._connection_message.hide()
- self._connection_message = None
- else:
- Logger.log("w", "We got an unexpected status (%s) while requesting printer state", status_code)
- pass
- elif "print_job" in reply_url:
- if status_code == 200:
- try:
- json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
- return
- progress = json_data["progress"]
-
- if progress == 0:
- progress += 0.001
- elif progress == 1:
- self._print_finished = True
- else:
- self._print_finished = False
- self.setProgress(progress * 100)
- state = json_data["state"]
-
-
-
-
- if state == "none" or state == "":
- if self._last_command == "abort":
- self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Aborting print..."))
- state = "error"
- else:
- state = "printing"
- if state == "wait_cleanup" and self._last_command == "abort":
-
- self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Print aborted. Please check the printer"))
- state = "error"
-
-
- if state == "!pausing":
- self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Pausing print..."))
- if state == "!resuming":
- self.setErrorText(i18n_catalog.i18nc("@label:MonitorStatus", "Resuming print..."))
- self._updateJobState(state)
- self.setTimeElapsed(json_data["time_elapsed"])
- self.setTimeTotal(json_data["time_total"])
- self.setJobName(json_data["name"])
- elif status_code == 404:
- self.setProgress(0)
- self._updateJobState("")
- self.setErrorText("")
- self.setTimeElapsed(0)
- self.setTimeTotal(0)
- self.setJobName("")
- else:
- Logger.log("w", "We got an unexpected status (%s) while requesting print job state", status_code)
- elif "snapshot" in reply_url:
- if status_code == 200:
- self._camera_image.loadFromData(reply.readAll())
- self.newImage.emit()
- elif "auth/verify" in reply_url:
- if status_code == 401:
- if self._authentication_state != AuthState.AuthenticationRequested:
-
- Logger.log("i", "Not authenticated (Current auth state is %s). Attempting to request authentication", self._authentication_state )
- self._requestAuthentication()
- elif status_code == 403:
-
- if self._authentication_state != AuthState.AuthenticationRequested:
- Logger.log("d", "While trying to verify the authentication state, we got a forbidden response. Our own auth state was %s", self._authentication_state)
- self.setAuthenticationState(AuthState.AuthenticationDenied)
- elif status_code == 200:
- self.setAuthenticationState(AuthState.Authenticated)
- global_container_stack = Application.getInstance().getGlobalContainerStack()
-
- if global_container_stack:
- if "network_authentication_key" in global_container_stack.getMetaData():
- global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key)
- else:
- global_container_stack.addMetaDataEntry("network_authentication_key", self._authentication_key)
- if "network_authentication_id" in global_container_stack.getMetaData():
- global_container_stack.setMetaDataEntry("network_authentication_id", self._authentication_id)
- else:
- global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id)
- Application.getInstance().saveStack(global_container_stack)
- Logger.log("i", "Authentication succeeded for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
- else:
- Logger.log("e", "While trying to authenticate, we got an unexpected response: %s", reply.attribute(QNetworkRequest.HttpStatusCodeAttribute))
- self.setAuthenticationState(AuthState.NotAuthenticated)
- elif "auth/check" in reply_url:
- try:
- data = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid authentication check from printer: Not valid JSON.")
- return
- if data.get("message", "") == "authorized":
- Logger.log("i", "Authentication was approved")
- self._verifyAuthentication()
- elif data.get("message", "") == "unauthorized":
- Logger.log("i", "Authentication was denied.")
- self.setAuthenticationState(AuthState.AuthenticationDenied)
- else:
- pass
- elif reply.operation() == QNetworkAccessManager.PostOperation:
- if "/auth/request" in reply_url:
-
- try:
- data = json.loads(bytes(reply.readAll()).decode("utf-8"))
- except json.decoder.JSONDecodeError:
- Logger.log("w", "Received an invalid authentication request reply from printer: Not valid JSON.")
- return
- global_container_stack = Application.getInstance().getGlobalContainerStack()
- if global_container_stack:
- Logger.log("d", "Removing old network authentication data as a new one was requested.")
- global_container_stack.removeMetaDataEntry("network_authentication_key")
- global_container_stack.removeMetaDataEntry("network_authentication_id")
- Application.getInstance().saveStack(global_container_stack)
- self._authentication_key = data["key"]
- self._authentication_id = data["id"]
- Logger.log("i", "Got a new authentication ID (%s) and KEY (%s). Waiting for authorization.", self._authentication_id, self._getSafeAuthKey())
-
- self._checkAuthentication()
- elif "materials" in reply_url:
-
- del self._material_post_objects[id(reply)]
- elif "print_job" in reply_url:
- reply.uploadProgress.disconnect(self._onUploadProgress)
- Logger.log("d", "Uploading of print succeeded after %s", time() - self._send_gcode_start)
-
- if reply == self._post_reply:
- self._post_reply = None
- self._progress_message.hide()
- elif reply.operation() == QNetworkAccessManager.PutOperation:
- if "printer/bed/pre_heat" in reply_url:
- self._processing_preheat_requests = True
- if status_code in [200, 201, 202, 204]:
- pass
- else:
- Logger.log("d", "Something went wrong when trying to update data of API (%s). Message: %s Statuscode: %s", reply_url, reply.readAll(), status_code)
- else:
- Logger.log("d", "NetworkPrinterOutputDevice got an unhandled operation %s", reply.operation())
- def _onStreamDownloadProgress(self, bytes_received, bytes_total):
-
-
- if self._image_reply is None:
- return
- self._stream_buffer += self._image_reply.readAll()
- if self._stream_buffer_start_index == -1:
- self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
- stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')
-
-
- if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
- jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
- self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
- self._stream_buffer_start_index = -1
- self._camera_image.loadFromData(jpg_data)
- self.newImage.emit()
- def _onUploadProgress(self, bytes_sent, bytes_total):
- if bytes_total > 0:
- new_progress = bytes_sent / bytes_total * 100
-
-
- self._last_response_time = time()
- if new_progress > self._progress_message.getProgress():
- self._progress_message.show()
- self._progress_message.setProgress(bytes_sent / bytes_total * 100)
- else:
- self._progress_message.setProgress(0)
- self._progress_message.hide()
-
- def materialHotendChangedMessage(self, callback):
- Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Sync with your printer"),
- i18n_catalog.i18nc("@label",
- "Would you like to use your current printer configuration in Cura?"),
- i18n_catalog.i18nc("@label",
- "The PrintCores and/or materials on your printer differ from those within your current project. For the best result, always slice for the PrintCores and materials that are inserted in your printer."),
- buttons=QMessageBox.Yes + QMessageBox.No,
- icon=QMessageBox.Question,
- callback=callback
- )
-
-
- def _getSafeAuthKey(self):
- if self._authentication_key is not None:
- result = self._authentication_key[-5:]
- result = "********" + result
- return result
- return self._authentication_key
|