mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-19 17:59:13 +08:00
Removed old & unused code
CL-541
This commit is contained in:
parent
6ad82ee1b0
commit
8bc9663294
@ -1,716 +0,0 @@
|
||||
import datetime
|
||||
import getpass
|
||||
import gzip
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import time
|
||||
|
||||
from enum import Enum
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QHttpPart, QHttpMultiPart
|
||||
from PyQt5.QtCore import QUrl, pyqtSlot, pyqtProperty, QCoreApplication, QTimer, pyqtSignal, QObject
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
from UM.OutputDevice import OutputDeviceError
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Qt.Duration import Duration, DurationFormat
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
from . import NetworkPrinterOutputDevice
|
||||
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class OutputStage(Enum):
|
||||
ready = 0
|
||||
uploading = 2
|
||||
|
||||
|
||||
class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinterOutputDevice):
|
||||
printJobsChanged = pyqtSignal()
|
||||
printersChanged = pyqtSignal()
|
||||
selectedPrinterChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, key, address, properties, api_prefix):
|
||||
super().__init__(key, address, properties, api_prefix)
|
||||
# Store the address of the master.
|
||||
self._master_address = address
|
||||
name_property = properties.get(b"name", b"")
|
||||
if name_property:
|
||||
name = name_property.decode("utf-8")
|
||||
else:
|
||||
name = key
|
||||
|
||||
self._authentication_state = NetworkPrinterOutputDevice.AuthState.Authenticated # The printer is always authenticated
|
||||
|
||||
self.setName(name)
|
||||
description = i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network")
|
||||
self.setShortDescription(description)
|
||||
self.setDescription(description)
|
||||
|
||||
self._stage = OutputStage.ready
|
||||
host_override = os.environ.get("CLUSTER_OVERRIDE_HOST", "")
|
||||
if host_override:
|
||||
Logger.log(
|
||||
"w",
|
||||
"Environment variable CLUSTER_OVERRIDE_HOST is set to [%s], cluster hosts are now set to this host",
|
||||
host_override)
|
||||
self._host = "http://" + host_override
|
||||
else:
|
||||
self._host = "http://" + address
|
||||
|
||||
# is the same as in NetworkPrinterOutputDevicePlugin
|
||||
self._cluster_api_version = "1"
|
||||
self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
|
||||
self._api_base_uri = self._host + self._cluster_api_prefix
|
||||
|
||||
self._file_name = None
|
||||
self._progress_message = None
|
||||
self._request = None
|
||||
self._reply = None
|
||||
|
||||
# The main reason to keep the 'multipart' form data on the object
|
||||
# is to prevent the Python GC from claiming it too early.
|
||||
self._multipart = None
|
||||
|
||||
self._print_view = None
|
||||
self._request_job = []
|
||||
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
|
||||
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")
|
||||
|
||||
self._print_jobs = []
|
||||
self._print_job_by_printer_uuid = {}
|
||||
self._print_job_by_uuid = {} # Print jobs by their own uuid
|
||||
self._printers = []
|
||||
self._printers_dict = {} # by unique_name
|
||||
|
||||
self._connected_printers_type_count = []
|
||||
self._automatic_printer = {"unique_name": "", "friendly_name": "Automatic"} # empty unique_name IS automatic selection
|
||||
self._selected_printer = self._automatic_printer
|
||||
|
||||
self._cluster_status_update_timer = QTimer()
|
||||
self._cluster_status_update_timer.setInterval(5000)
|
||||
self._cluster_status_update_timer.setSingleShot(False)
|
||||
self._cluster_status_update_timer.timeout.connect(self._requestClusterStatus)
|
||||
|
||||
self._can_pause = True
|
||||
self._can_abort = True
|
||||
self._can_pre_heat_bed = False
|
||||
self._can_control_manually = False
|
||||
self._cluster_size = int(properties.get(b"cluster_size", 0))
|
||||
|
||||
self._cleanupRequest()
|
||||
|
||||
#These are texts that are to be translated for future features.
|
||||
temporary_translation = i18n_catalog.i18n("This printer is not set up to host a group of connected Ultimaker 3 printers.")
|
||||
temporary_translation2 = i18n_catalog.i18nc("Count is number of printers.", "This printer is the host for a group of {count} connected Ultimaker 3 printers.").format(count = 3)
|
||||
temporary_translation3 = i18n_catalog.i18n("{printer_name} has finished printing '{job_name}'. Please collect the print and confirm clearing the build plate.") #When finished.
|
||||
temporary_translation4 = i18n_catalog.i18n("{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing.") #When configuration changed.
|
||||
|
||||
## No authentication, so requestAuthentication should do exactly nothing
|
||||
@pyqtSlot()
|
||||
def requestAuthentication(self, message_id = None, action_id = "Retry"):
|
||||
pass # Cura Connect doesn't do any authorization
|
||||
|
||||
def setAuthenticationState(self, auth_state):
|
||||
self._authentication_state = NetworkPrinterOutputDevice.AuthState.Authenticated # The printer is always authenticated
|
||||
|
||||
def _verifyAuthentication(self):
|
||||
pass
|
||||
|
||||
def _checkAuthentication(self):
|
||||
Logger.log("d", "_checkAuthentication Cura Connect - nothing to be done")
|
||||
|
||||
@pyqtProperty(QObject, notify=selectedPrinterChanged)
|
||||
def controlItem(self):
|
||||
# TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
|
||||
if not self._control_item:
|
||||
self._createControlViewFromQML()
|
||||
name = self._selected_printer.get("friendly_name")
|
||||
if name == self._automatic_printer.get("friendly_name") or name == "":
|
||||
return self._control_item
|
||||
# Let cura use the default.
|
||||
return None
|
||||
|
||||
@pyqtSlot(int, result = str)
|
||||
def getTimeCompleted(self, time_remaining):
|
||||
current_time = time.time()
|
||||
datetime_completed = datetime.datetime.fromtimestamp(current_time + time_remaining)
|
||||
return "{hour:02d}:{minute:02d}".format(hour = datetime_completed.hour, minute = datetime_completed.minute)
|
||||
|
||||
@pyqtSlot(int, result = str)
|
||||
def getDateCompleted(self, time_remaining):
|
||||
current_time = time.time()
|
||||
datetime_completed = datetime.datetime.fromtimestamp(current_time + time_remaining)
|
||||
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
|
||||
|
||||
@pyqtProperty(int, constant = True)
|
||||
def clusterSize(self):
|
||||
return self._cluster_size
|
||||
|
||||
@pyqtProperty(str, notify=selectedPrinterChanged)
|
||||
def name(self):
|
||||
# Show the name of the selected printer.
|
||||
# This is not the nicest way to do this, but changes to the Cura UI are required otherwise.
|
||||
name = self._selected_printer.get("friendly_name")
|
||||
if name != self._automatic_printer.get("friendly_name"):
|
||||
return name
|
||||
# Return name of cluster master.
|
||||
return self._properties.get(b"name", b"").decode("utf-8")
|
||||
|
||||
def connect(self):
|
||||
super().connect()
|
||||
self._cluster_status_update_timer.start()
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
self._cluster_status_update_timer.stop()
|
||||
|
||||
def _setJobState(self, job_state):
|
||||
if not self._selected_printer:
|
||||
return
|
||||
|
||||
selected_printer_uuid = self._printers_dict[self._selected_printer["unique_name"]]["uuid"]
|
||||
if selected_printer_uuid not in self._print_job_by_printer_uuid:
|
||||
return
|
||||
|
||||
print_job_uuid = self._print_job_by_printer_uuid[selected_printer_uuid]["uuid"]
|
||||
|
||||
url = QUrl(self._api_base_uri + "print_jobs/" + print_job_uuid + "/action")
|
||||
put_request = QNetworkRequest(url)
|
||||
put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
|
||||
data = '{"action": "' + job_state + '"}'
|
||||
self._manager.put(put_request, data.encode())
|
||||
|
||||
def _requestClusterStatus(self):
|
||||
# TODO: Handle timeout. We probably want to know if the cluster is still reachable or not.
|
||||
url = QUrl(self._api_base_uri + "printers/")
|
||||
printers_request = QNetworkRequest(url)
|
||||
self._addUserAgentHeader(printers_request)
|
||||
self._manager.get(printers_request)
|
||||
# See _finishedPrintersRequest()
|
||||
|
||||
if self._printers: # if printers is not empty
|
||||
url = QUrl(self._api_base_uri + "print_jobs/")
|
||||
print_jobs_request = QNetworkRequest(url)
|
||||
self._addUserAgentHeader(print_jobs_request)
|
||||
self._manager.get(print_jobs_request)
|
||||
# See _finishedPrintJobsRequest()
|
||||
|
||||
def _finishedPrintJobsRequest(self, reply):
|
||||
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
|
||||
self.setPrintJobs(json_data)
|
||||
|
||||
def _finishedPrintersRequest(self, reply):
|
||||
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
|
||||
self.setPrinters(json_data)
|
||||
|
||||
def materialHotendChangedMessage(self, callback):
|
||||
# When there is just one printer, the activate configuration option is enabled
|
||||
if (self._cluster_size == 1):
|
||||
super().materialHotendChangedMessage(callback = callback)
|
||||
|
||||
def _startCameraStream(self):
|
||||
## Request new image
|
||||
url = QUrl("http://" + self._printers_dict[self._selected_printer["unique_name"]]["ip_address"] + ":8080/?action=stream")
|
||||
self._image_request = QNetworkRequest(url)
|
||||
self._addUserAgentHeader(self._image_request)
|
||||
self._image_reply = self._manager.get(self._image_request)
|
||||
self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
|
||||
|
||||
def spawnPrintView(self):
|
||||
if self._print_view is None:
|
||||
path = os.path.join(self._plugin_path, "PrintWindow.qml")
|
||||
self._print_view = Application.getInstance().createQmlComponent(path, {"OutputDevice", self})
|
||||
if self._print_view is not None:
|
||||
self._print_view.show()
|
||||
|
||||
## Store job info, show Print view for settings
|
||||
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
|
||||
self._selected_printer = self._automatic_printer # reset to default option
|
||||
self._request_job = [nodes, file_name, filter_by_machine, file_handler, kwargs]
|
||||
|
||||
if self._stage != OutputStage.ready:
|
||||
if self._error_message:
|
||||
self._error_message.hide()
|
||||
self._error_message = Message(
|
||||
i18n_catalog.i18nc("@info:status",
|
||||
"Sending new jobs (temporarily) blocked, still sending the previous print job."))
|
||||
self._error_message.show()
|
||||
return
|
||||
|
||||
self.writeStarted.emit(self) # Allow postprocessing before sending data to the printer
|
||||
|
||||
if len(self._printers) > 1:
|
||||
self.spawnPrintView() # Ask user how to print it.
|
||||
elif len(self._printers) == 1:
|
||||
# If there is only one printer, don't bother asking.
|
||||
self.selectAutomaticPrinter()
|
||||
self.sendPrintJob()
|
||||
else:
|
||||
# Cluster has no printers, warn the user of this.
|
||||
if self._error_message:
|
||||
self._error_message.hide()
|
||||
self._error_message = Message(
|
||||
i18n_catalog.i18nc("@info:status",
|
||||
"Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."))
|
||||
self._error_message.show()
|
||||
|
||||
## Actually send the print job, called from the dialog
|
||||
# :param: require_printer_name: name of printer, or ""
|
||||
@pyqtSlot()
|
||||
def sendPrintJob(self):
|
||||
nodes, file_name, filter_by_machine, file_handler, kwargs = self._request_job
|
||||
require_printer_name = self._selected_printer["unique_name"]
|
||||
|
||||
self._send_gcode_start = time.time()
|
||||
Logger.log("d", "Sending print job [%s] to host..." % file_name)
|
||||
|
||||
if self._stage != OutputStage.ready:
|
||||
Logger.log("d", "Unable to send print job as the state is %s", self._stage)
|
||||
raise OutputDeviceError.DeviceBusyError()
|
||||
self._stage = OutputStage.uploading
|
||||
|
||||
self._file_name = "%s.gcode.gz" % file_name
|
||||
self._showProgressMessage()
|
||||
|
||||
new_request = self._buildSendPrintJobHttpRequest(require_printer_name)
|
||||
if new_request is None or self._stage != OutputStage.uploading:
|
||||
return
|
||||
self._request = new_request
|
||||
self._reply = self._manager.post(self._request, self._multipart)
|
||||
self._reply.uploadProgress.connect(self._onUploadProgress)
|
||||
# See _finishedPostPrintJobRequest()
|
||||
|
||||
def _buildSendPrintJobHttpRequest(self, require_printer_name):
|
||||
api_url = QUrl(self._api_base_uri + "print_jobs/")
|
||||
request = QNetworkRequest(api_url)
|
||||
# Create multipart request and add the g-code.
|
||||
self._multipart = QHttpMultiPart(QHttpMultiPart.FormDataType)
|
||||
|
||||
# Add gcode
|
||||
part = QHttpPart()
|
||||
part.setHeader(QNetworkRequest.ContentDispositionHeader,
|
||||
'form-data; name="file"; filename="%s"' % self._file_name)
|
||||
|
||||
gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
|
||||
compressed_gcode = self._compressGcode(gcode)
|
||||
if compressed_gcode is None:
|
||||
return None # User aborted print, so stop trying.
|
||||
|
||||
part.setBody(compressed_gcode)
|
||||
self._multipart.append(part)
|
||||
|
||||
# require_printer_name "" means automatic
|
||||
if require_printer_name:
|
||||
self._multipart.append(self.__createKeyValueHttpPart("require_printer_name", require_printer_name))
|
||||
user_name = self.__get_username()
|
||||
if user_name is None:
|
||||
user_name = "unknown"
|
||||
self._multipart.append(self.__createKeyValueHttpPart("owner", user_name))
|
||||
|
||||
self._addUserAgentHeader(request)
|
||||
return request
|
||||
|
||||
def _compressGcode(self, gcode):
|
||||
self._compressing_print = True
|
||||
batched_line = ""
|
||||
max_chars_per_line = int(1024 * 1024 / 4) # 1 / 4 MB
|
||||
|
||||
byte_array_file_data = b""
|
||||
|
||||
def _compressDataAndNotifyQt(data_to_append):
|
||||
compressed_data = gzip.compress(data_to_append.encode("utf-8"))
|
||||
self._progress_message.setProgress(-1) # Tickle the message so that it's clear that it's still being used.
|
||||
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
|
||||
# Pretend that this is a response, as zipping might take a bit of time.
|
||||
self._last_response_time = time.time()
|
||||
return compressed_data
|
||||
|
||||
if gcode is None:
|
||||
Logger.log("e", "Unable to find sliced gcode, returning empty.")
|
||||
return byte_array_file_data
|
||||
|
||||
for line in gcode:
|
||||
if not self._compressing_print:
|
||||
self._progress_message.hide()
|
||||
return None # Stop trying to zip, abort was called.
|
||||
batched_line += line
|
||||
# if the gcode was read from a gcode file, self._gcode will be a list of all lines in that file.
|
||||
# Compressing line by line in this case is extremely slow, so we need to batch them.
|
||||
if len(batched_line) < max_chars_per_line:
|
||||
continue
|
||||
byte_array_file_data += _compressDataAndNotifyQt(batched_line)
|
||||
batched_line = ""
|
||||
|
||||
# Also compress the leftovers.
|
||||
if batched_line:
|
||||
byte_array_file_data += _compressDataAndNotifyQt(batched_line)
|
||||
|
||||
return byte_array_file_data
|
||||
|
||||
def __createKeyValueHttpPart(self, key, value):
|
||||
metadata_part = QHttpPart()
|
||||
metadata_part.setHeader(QNetworkRequest.ContentTypeHeader, 'text/plain')
|
||||
metadata_part.setHeader(QNetworkRequest.ContentDispositionHeader, 'form-data; name="%s"' % (key))
|
||||
metadata_part.setBody(bytearray(value, "utf8"))
|
||||
return metadata_part
|
||||
|
||||
def __get_username(self):
|
||||
try:
|
||||
return getpass.getuser()
|
||||
except:
|
||||
Logger.log("d", "Could not get the system user name, returning 'unknown' instead.")
|
||||
return None
|
||||
|
||||
def _finishedPrintJobPostRequest(self, reply):
|
||||
self._stage = OutputStage.ready
|
||||
if self._progress_message:
|
||||
self._progress_message.hide()
|
||||
self._progress_message = None
|
||||
self.writeFinished.emit(self)
|
||||
|
||||
if reply.error():
|
||||
self._showRequestFailedMessage(reply)
|
||||
self.writeError.emit(self)
|
||||
else:
|
||||
self._showRequestSucceededMessage()
|
||||
self.writeSuccess.emit(self)
|
||||
|
||||
self._cleanupRequest()
|
||||
|
||||
def _showRequestFailedMessage(self, reply):
|
||||
if reply is not None:
|
||||
Logger.log("w", "Unable to send print job to group {cluster_name}: {error_string} ({error})".format(
|
||||
cluster_name = self.getName(),
|
||||
error_string = str(reply.errorString()),
|
||||
error = str(reply.error())))
|
||||
error_message_template = i18n_catalog.i18nc("@info:status", "Unable to send print job to group {cluster_name}.")
|
||||
message = Message(text=error_message_template.format(
|
||||
cluster_name = self.getName()))
|
||||
message.show()
|
||||
|
||||
def _showRequestSucceededMessage(self):
|
||||
confirmation_message_template = i18n_catalog.i18nc(
|
||||
"@info:status",
|
||||
"Sent {file_name} to group {cluster_name}."
|
||||
)
|
||||
file_name = os.path.basename(self._file_name).split(".")[0]
|
||||
message_text = confirmation_message_template.format(cluster_name = self.getName(), file_name = file_name)
|
||||
message = Message(text=message_text)
|
||||
button_text = i18n_catalog.i18nc("@action:button", "Show print jobs")
|
||||
button_tooltip = i18n_catalog.i18nc("@info:tooltip", "Opens the print jobs interface in your browser.")
|
||||
message.addAction("open_browser", button_text, "globe", button_tooltip)
|
||||
message.actionTriggered.connect(self._onMessageActionTriggered)
|
||||
message.show()
|
||||
|
||||
def setPrintJobs(self, print_jobs):
|
||||
#TODO: hack, last seen messes up the check, so drop it.
|
||||
for job in print_jobs:
|
||||
del job["last_seen"]
|
||||
# Strip any extensions
|
||||
job["name"] = self._removeGcodeExtension(job["name"])
|
||||
|
||||
if self._print_jobs != print_jobs:
|
||||
old_print_jobs = self._print_jobs
|
||||
self._print_jobs = print_jobs
|
||||
|
||||
self._notifyFinishedPrintJobs(old_print_jobs, print_jobs)
|
||||
self._notifyConfigurationChangeRequired(old_print_jobs, print_jobs)
|
||||
|
||||
# Yes, this is a hacky way of doing it, but it's quick and the API doesn't give the print job per printer
|
||||
# for some reason. ugh.
|
||||
self._print_job_by_printer_uuid = {}
|
||||
self._print_job_by_uuid = {}
|
||||
for print_job in print_jobs:
|
||||
if "printer_uuid" in print_job and print_job["printer_uuid"] is not None:
|
||||
self._print_job_by_printer_uuid[print_job["printer_uuid"]] = print_job
|
||||
self._print_job_by_uuid[print_job["uuid"]] = print_job
|
||||
self.printJobsChanged.emit()
|
||||
|
||||
def _removeGcodeExtension(self, name):
|
||||
parts = name.split(".")
|
||||
if parts[-1].upper() == "GZ":
|
||||
parts = parts[:-1]
|
||||
if parts[-1].upper() == "GCODE":
|
||||
parts = parts[:-1]
|
||||
return ".".join(parts)
|
||||
|
||||
def _notifyFinishedPrintJobs(self, old_print_jobs, new_print_jobs):
|
||||
"""Notify the user when any of their print jobs have just completed.
|
||||
|
||||
Arguments:
|
||||
|
||||
old_print_jobs -- the previous list of print job status information as returned by the cluster REST API.
|
||||
new_print_jobs -- the current list of print job status information as returned by the cluster REST API.
|
||||
"""
|
||||
if old_print_jobs is None:
|
||||
return
|
||||
|
||||
username = self.__get_username()
|
||||
if username is None:
|
||||
return
|
||||
|
||||
our_old_print_jobs = self.__filterOurPrintJobs(old_print_jobs)
|
||||
our_old_not_finished_print_jobs = [pj for pj in our_old_print_jobs if pj["status"] != "wait_cleanup"]
|
||||
|
||||
our_new_print_jobs = self.__filterOurPrintJobs(new_print_jobs)
|
||||
our_new_finished_print_jobs = [pj for pj in our_new_print_jobs if pj["status"] == "wait_cleanup"]
|
||||
|
||||
old_not_finished_print_job_uuids = set([pj["uuid"] for pj in our_old_not_finished_print_jobs])
|
||||
|
||||
for print_job in our_new_finished_print_jobs:
|
||||
if print_job["uuid"] in old_not_finished_print_job_uuids:
|
||||
|
||||
printer_name = self.__getPrinterNameFromUuid(print_job["printer_uuid"])
|
||||
if printer_name is None:
|
||||
printer_name = i18n_catalog.i18nc("@label Printer name", "Unknown")
|
||||
|
||||
message_text = (i18n_catalog.i18nc("@info:status",
|
||||
"Printer '{printer_name}' has finished printing '{job_name}'.")
|
||||
.format(printer_name=printer_name, job_name=print_job["name"]))
|
||||
message = Message(text=message_text, title=i18n_catalog.i18nc("@info:status", "Print finished"))
|
||||
Application.getInstance().showMessage(message)
|
||||
Application.getInstance().showToastMessage(
|
||||
i18n_catalog.i18nc("@info:status", "Print finished"),
|
||||
message_text)
|
||||
|
||||
def __filterOurPrintJobs(self, print_jobs):
|
||||
username = self.__get_username()
|
||||
return [print_job for print_job in print_jobs if print_job["owner"] == username]
|
||||
|
||||
def _notifyConfigurationChangeRequired(self, old_print_jobs, new_print_jobs):
|
||||
if old_print_jobs is None:
|
||||
return
|
||||
|
||||
old_change_required_print_jobs = self.__filterConfigChangePrintJobs(self.__filterOurPrintJobs(old_print_jobs))
|
||||
new_change_required_print_jobs = self.__filterConfigChangePrintJobs(self.__filterOurPrintJobs(new_print_jobs))
|
||||
old_change_required_print_job_uuids = set([pj["uuid"] for pj in old_change_required_print_jobs])
|
||||
|
||||
for print_job in new_change_required_print_jobs:
|
||||
if print_job["uuid"] not in old_change_required_print_job_uuids:
|
||||
|
||||
printer_name = self.__getPrinterNameFromUuid(print_job["assigned_to"])
|
||||
if printer_name is None:
|
||||
# don't report on yet unknown printers
|
||||
continue
|
||||
|
||||
message_text = (i18n_catalog.i18n("{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing.")
|
||||
.format(printer_name=printer_name, job_name=print_job["name"]))
|
||||
message = Message(text=message_text, title=i18n_catalog.i18nc("@label:status", "Action required"))
|
||||
Application.getInstance().showMessage(message)
|
||||
Application.getInstance().showToastMessage(
|
||||
i18n_catalog.i18nc("@label:status", "Action required"),
|
||||
message_text)
|
||||
|
||||
def __filterConfigChangePrintJobs(self, print_jobs):
|
||||
return filter(self.__isConfigurationChangeRequiredPrintJob, print_jobs)
|
||||
|
||||
def __isConfigurationChangeRequiredPrintJob(self, print_job):
|
||||
if print_job["status"] == "queued":
|
||||
changes_required = print_job.get("configuration_changes_required", [])
|
||||
return len(changes_required) != 0
|
||||
return False
|
||||
|
||||
def __getPrinterNameFromUuid(self, printer_uuid):
|
||||
for printer in self._printers:
|
||||
if printer["uuid"] == printer_uuid:
|
||||
return printer["friendly_name"]
|
||||
return None
|
||||
|
||||
def setPrinters(self, printers):
|
||||
if self._printers != printers:
|
||||
self._connected_printers_type_count = []
|
||||
printers_count = {}
|
||||
self._printers = printers
|
||||
self._printers_dict = dict((p["unique_name"], p) for p in printers) # for easy lookup by unique_name
|
||||
|
||||
for printer in printers:
|
||||
variant = printer["machine_variant"]
|
||||
if variant in printers_count:
|
||||
printers_count[variant] += 1
|
||||
else:
|
||||
printers_count[variant] = 1
|
||||
for type in printers_count:
|
||||
self._connected_printers_type_count.append({"machine_type": type, "count": printers_count[type]})
|
||||
self.printersChanged.emit()
|
||||
|
||||
@pyqtProperty("QVariantList", notify=printersChanged)
|
||||
def connectedPrintersTypeCount(self):
|
||||
return self._connected_printers_type_count
|
||||
|
||||
@pyqtProperty("QVariantList", notify=printersChanged)
|
||||
def connectedPrinters(self):
|
||||
return self._printers
|
||||
|
||||
@pyqtProperty(int, notify=printJobsChanged)
|
||||
def numJobsPrinting(self):
|
||||
num_jobs_printing = 0
|
||||
for job in self._print_jobs:
|
||||
if job["status"] in ["printing", "wait_cleanup", "sent_to_printer", "pre_print", "post_print"]:
|
||||
num_jobs_printing += 1
|
||||
return num_jobs_printing
|
||||
|
||||
@pyqtProperty(int, notify=printJobsChanged)
|
||||
def numJobsQueued(self):
|
||||
num_jobs_queued = 0
|
||||
for job in self._print_jobs:
|
||||
if job["status"] == "queued":
|
||||
num_jobs_queued += 1
|
||||
return num_jobs_queued
|
||||
|
||||
@pyqtProperty("QVariantMap", notify=printJobsChanged)
|
||||
def printJobsByUUID(self):
|
||||
return self._print_job_by_uuid
|
||||
|
||||
@pyqtProperty("QVariantMap", notify=printJobsChanged)
|
||||
def printJobsByPrinterUUID(self):
|
||||
return self._print_job_by_printer_uuid
|
||||
|
||||
@pyqtProperty("QVariantList", notify=printJobsChanged)
|
||||
def printJobs(self):
|
||||
return self._print_jobs
|
||||
|
||||
@pyqtProperty("QVariantList", notify=printersChanged)
|
||||
def printers(self):
|
||||
return [self._automatic_printer, ] + self._printers
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def selectPrinter(self, unique_name, friendly_name):
|
||||
self.stopCamera()
|
||||
self._selected_printer = {"unique_name": unique_name, "friendly_name": friendly_name}
|
||||
Logger.log("d", "Selected printer: %s %s", friendly_name, unique_name)
|
||||
# TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
|
||||
if unique_name == "":
|
||||
self._address = self._master_address
|
||||
else:
|
||||
self._address = self._printers_dict[self._selected_printer["unique_name"]]["ip_address"]
|
||||
|
||||
self.selectedPrinterChanged.emit()
|
||||
|
||||
def _updateJobState(self, job_state):
|
||||
name = self._selected_printer.get("friendly_name")
|
||||
if name == "" or name == "Automatic":
|
||||
# TODO: This is now a bit hacked; If no printer is selected, don't show job state.
|
||||
if self._job_state != "":
|
||||
self._job_state = ""
|
||||
self.jobStateChanged.emit()
|
||||
else:
|
||||
if self._job_state != job_state:
|
||||
self._job_state = job_state
|
||||
self.jobStateChanged.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def selectAutomaticPrinter(self):
|
||||
self.stopCamera()
|
||||
self._selected_printer = self._automatic_printer
|
||||
self.selectedPrinterChanged.emit()
|
||||
|
||||
@pyqtProperty("QVariant", notify=selectedPrinterChanged)
|
||||
def selectedPrinterName(self):
|
||||
return self._selected_printer.get("unique_name", "")
|
||||
|
||||
def getPrintJobsUrl(self):
|
||||
return self._host + "/print_jobs"
|
||||
|
||||
def getPrintersUrl(self):
|
||||
return self._host + "/printers"
|
||||
|
||||
def _showProgressMessage(self):
|
||||
progress_message_template = i18n_catalog.i18nc("@info:progress",
|
||||
"Sending <filename>{file_name}</filename> to group {cluster_name}")
|
||||
file_name = os.path.basename(self._file_name).split(".")[0]
|
||||
self._progress_message = Message(progress_message_template.format(file_name = file_name, cluster_name = self.getName()), 0, False, -1)
|
||||
self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
|
||||
self._progress_message.actionTriggered.connect(self._onMessageActionTriggered)
|
||||
self._progress_message.show()
|
||||
|
||||
def _addUserAgentHeader(self, request):
|
||||
request.setRawHeader(b"User-agent", b"CuraPrintClusterOutputDevice Plugin")
|
||||
|
||||
def _cleanupRequest(self):
|
||||
self._request = None
|
||||
self._stage = OutputStage.ready
|
||||
self._file_name = None
|
||||
|
||||
def _onFinished(self, reply):
|
||||
super()._onFinished(reply)
|
||||
reply_url = reply.url().toString()
|
||||
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||
if status_code == 500:
|
||||
Logger.log("w", "Request to {url} returned a 500.".format(url = reply_url))
|
||||
return
|
||||
if reply.error() == QNetworkReply.ContentOperationNotPermittedError:
|
||||
# It was probably "/api/v1/materials" for legacy UM3
|
||||
return
|
||||
if reply.error() == QNetworkReply.ContentNotFoundError:
|
||||
# It was probably "/api/v1/print_job" for legacy UM3
|
||||
return
|
||||
|
||||
if reply.operation() == QNetworkAccessManager.PostOperation:
|
||||
if self._cluster_api_prefix + "print_jobs" in reply_url:
|
||||
self._finishedPrintJobPostRequest(reply)
|
||||
return
|
||||
|
||||
# We need to do this check *after* we process the post operation!
|
||||
# If the sending of g-code is cancelled by the user it will result in an error, but we do need to handle this.
|
||||
if reply.error() != QNetworkReply.NoError:
|
||||
Logger.log("e", "After requesting [%s] we got a network error [%s]. Not processing anything...", reply_url, reply.error())
|
||||
return
|
||||
|
||||
elif reply.operation() == QNetworkAccessManager.GetOperation:
|
||||
if self._cluster_api_prefix + "print_jobs" in reply_url:
|
||||
self._finishedPrintJobsRequest(reply)
|
||||
elif self._cluster_api_prefix + "printers" in reply_url:
|
||||
self._finishedPrintersRequest(reply)
|
||||
|
||||
@pyqtSlot()
|
||||
def openPrintJobControlPanel(self):
|
||||
Logger.log("d", "Opening print job control panel...")
|
||||
QDesktopServices.openUrl(QUrl(self.getPrintJobsUrl()))
|
||||
|
||||
@pyqtSlot()
|
||||
def openPrinterControlPanel(self):
|
||||
Logger.log("d", "Opening printer control panel...")
|
||||
QDesktopServices.openUrl(QUrl(self.getPrintersUrl()))
|
||||
|
||||
def _onMessageActionTriggered(self, message, action):
|
||||
if action == "open_browser":
|
||||
QDesktopServices.openUrl(QUrl(self.getPrintJobsUrl()))
|
||||
|
||||
if action == "Abort":
|
||||
Logger.log("d", "User aborted sending print to remote.")
|
||||
self._progress_message.hide()
|
||||
self._compressing_print = False
|
||||
if self._reply:
|
||||
self._reply.abort()
|
||||
self._stage = OutputStage.ready
|
||||
Application.getInstance().getController().setActiveStage("PrepareStage")
|
||||
|
||||
@pyqtSlot(int, result=str)
|
||||
def formatDuration(self, seconds):
|
||||
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
|
||||
|
||||
## For cluster below
|
||||
def _get_plugin_directory_name(self):
|
||||
current_file_absolute_path = os.path.realpath(__file__)
|
||||
directory_path = os.path.dirname(current_file_absolute_path)
|
||||
_, directory_name = os.path.split(directory_path)
|
||||
return directory_name
|
||||
|
||||
@property
|
||||
def _plugin_path(self):
|
||||
return PluginRegistry.getInstance().getPluginPath(self._get_plugin_directory_name())
|
File diff suppressed because it is too large
Load Diff
@ -1,357 +0,0 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import time
|
||||
import json
|
||||
from queue import Queue
|
||||
from threading import Event, Thread
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||
from UM.Preferences import Preferences
|
||||
from UM.Signal import Signal, signalemitter
|
||||
from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo # type: ignore
|
||||
|
||||
from . import NetworkPrinterOutputDevice, NetworkClusterPrinterOutputDevice
|
||||
|
||||
|
||||
## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
|
||||
# Zero-Conf is used to detect printers, which are saved in a dict.
|
||||
# If we discover a printer that has the same key as the active machine instance a connection is made.
|
||||
@signalemitter
|
||||
class NetworkPrinterOutputDevicePlugin(QObject, OutputDevicePlugin):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._zero_conf = None
|
||||
self._browser = None
|
||||
self._printers = {}
|
||||
self._cluster_printers_seen = {} # do not forget a cluster printer when we have seen one, to not 'downgrade' from Connect to legacy printer
|
||||
|
||||
self._api_version = "1"
|
||||
self._api_prefix = "/api/v" + self._api_version + "/"
|
||||
self._cluster_api_version = "1"
|
||||
self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
|
||||
|
||||
self._network_manager = QNetworkAccessManager()
|
||||
self._network_manager.finished.connect(self._onNetworkRequestFinished)
|
||||
|
||||
# List of old printer names. This is used to ensure that a refresh of zeroconf does not needlessly forces
|
||||
# authentication requests.
|
||||
self._old_printers = []
|
||||
|
||||
# Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
|
||||
self.addPrinterSignal.connect(self.addPrinter)
|
||||
self.removePrinterSignal.connect(self.removePrinter)
|
||||
Application.getInstance().globalContainerStackChanged.connect(self.reCheckConnections)
|
||||
|
||||
# Get list of manual printers from preferences
|
||||
self._preferences = Preferences.getInstance()
|
||||
self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames
|
||||
self._manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",")
|
||||
|
||||
self._network_requests_buffer = {} # store api responses until data is complete
|
||||
|
||||
# The zeroconf service changed requests are handled in a separate thread, so we can re-schedule the requests
|
||||
# which fail to get detailed service info.
|
||||
# Any new or re-scheduled requests will be appended to the request queue, and the handling thread will pick
|
||||
# them up and process them.
|
||||
self._service_changed_request_queue = Queue()
|
||||
self._service_changed_request_event = Event()
|
||||
self._service_changed_request_thread = Thread(target = self._handleOnServiceChangedRequests,
|
||||
daemon = True)
|
||||
self._service_changed_request_thread.start()
|
||||
|
||||
addPrinterSignal = Signal()
|
||||
removePrinterSignal = Signal()
|
||||
printerListChanged = Signal()
|
||||
|
||||
## Start looking for devices on network.
|
||||
def start(self):
|
||||
self.startDiscovery()
|
||||
|
||||
def startDiscovery(self):
|
||||
self.stop()
|
||||
if self._browser:
|
||||
self._browser.cancel()
|
||||
self._browser = None
|
||||
self._old_printers = [printer_name for printer_name in self._printers]
|
||||
self._printers = {}
|
||||
self.printerListChanged.emit()
|
||||
# After network switching, one must make a new instance of Zeroconf
|
||||
# On windows, the instance creation is very fast (unnoticable). Other platforms?
|
||||
self._zero_conf = Zeroconf()
|
||||
self._browser = ServiceBrowser(self._zero_conf, u'_ultimaker._tcp.local.', [self._appendServiceChangedRequest])
|
||||
|
||||
# Look for manual instances from preference
|
||||
for address in self._manual_instances:
|
||||
if address:
|
||||
self.addManualPrinter(address)
|
||||
|
||||
def addManualPrinter(self, address):
|
||||
if address not in self._manual_instances:
|
||||
self._manual_instances.append(address)
|
||||
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
|
||||
|
||||
instance_name = "manual:%s" % address
|
||||
properties = {
|
||||
b"name": address.encode("utf-8"),
|
||||
b"address": address.encode("utf-8"),
|
||||
b"manual": b"true",
|
||||
b"incomplete": b"true"
|
||||
}
|
||||
|
||||
if instance_name not in self._printers:
|
||||
# Add a preliminary printer instance
|
||||
self.addPrinter(instance_name, address, properties)
|
||||
|
||||
self.checkManualPrinter(address)
|
||||
self.checkClusterPrinter(address)
|
||||
|
||||
def removeManualPrinter(self, key, address = None):
|
||||
if key in self._printers:
|
||||
if not address:
|
||||
address = self._printers[key].ipAddress
|
||||
self.removePrinter(key)
|
||||
|
||||
if address in self._manual_instances:
|
||||
self._manual_instances.remove(address)
|
||||
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
|
||||
|
||||
def checkManualPrinter(self, address):
|
||||
# Check if a printer exists at this address
|
||||
# If a printer responds, it will replace the preliminary printer created above
|
||||
# origin=manual is for tracking back the origin of the call
|
||||
url = QUrl("http://" + address + self._api_prefix + "system?origin=manual_name")
|
||||
name_request = QNetworkRequest(url)
|
||||
self._network_manager.get(name_request)
|
||||
|
||||
def checkClusterPrinter(self, address):
|
||||
cluster_url = QUrl("http://" + address + self._cluster_api_prefix + "printers/?origin=check_cluster")
|
||||
cluster_request = QNetworkRequest(cluster_url)
|
||||
self._network_manager.get(cluster_request)
|
||||
|
||||
## Handler for all requests that have finished.
|
||||
def _onNetworkRequestFinished(self, reply):
|
||||
reply_url = reply.url().toString()
|
||||
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||
|
||||
if reply.operation() == QNetworkAccessManager.GetOperation:
|
||||
address = reply.url().host()
|
||||
if "origin=manual_name" in reply_url: # Name returned from printer.
|
||||
if status_code == 200:
|
||||
|
||||
try:
|
||||
system_info = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
Logger.log("e", "Printer returned invalid JSON.")
|
||||
return
|
||||
except UnicodeDecodeError:
|
||||
Logger.log("e", "Printer returned incorrect UTF-8.")
|
||||
return
|
||||
|
||||
if address not in self._network_requests_buffer:
|
||||
self._network_requests_buffer[address] = {}
|
||||
self._network_requests_buffer[address]["system"] = system_info
|
||||
elif "origin=check_cluster" in reply_url:
|
||||
if address not in self._network_requests_buffer:
|
||||
self._network_requests_buffer[address] = {}
|
||||
if status_code == 200:
|
||||
# We know it's a cluster printer
|
||||
Logger.log("d", "Cluster printer detected: [%s]", reply.url())
|
||||
|
||||
try:
|
||||
cluster_printers_list = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
Logger.log("e", "Printer returned invalid JSON.")
|
||||
return
|
||||
except UnicodeDecodeError:
|
||||
Logger.log("e", "Printer returned incorrect UTF-8.")
|
||||
return
|
||||
|
||||
self._network_requests_buffer[address]["cluster"] = True
|
||||
self._network_requests_buffer[address]["cluster_size"] = len(cluster_printers_list)
|
||||
else:
|
||||
Logger.log("d", "This url is not from a cluster printer: [%s]", reply.url())
|
||||
self._network_requests_buffer[address]["cluster"] = False
|
||||
|
||||
# Both the system call and cluster call are finished
|
||||
if (address in self._network_requests_buffer and
|
||||
"system" in self._network_requests_buffer[address] and
|
||||
"cluster" in self._network_requests_buffer[address]):
|
||||
|
||||
instance_name = "manual:%s" % address
|
||||
system_info = self._network_requests_buffer[address]["system"]
|
||||
machine = "unknown"
|
||||
if "variant" in system_info:
|
||||
variant = system_info["variant"]
|
||||
if variant == "Ultimaker 3":
|
||||
machine = "9066"
|
||||
elif variant == "Ultimaker 3 Extended":
|
||||
machine = "9511"
|
||||
|
||||
properties = {
|
||||
b"name": system_info["name"].encode("utf-8"),
|
||||
b"address": address.encode("utf-8"),
|
||||
b"firmware_version": system_info["firmware"].encode("utf-8"),
|
||||
b"manual": b"true",
|
||||
b"machine": machine.encode("utf-8")
|
||||
}
|
||||
|
||||
if self._network_requests_buffer[address]["cluster"]:
|
||||
properties[b"cluster_size"] = self._network_requests_buffer[address]["cluster_size"]
|
||||
|
||||
if instance_name in self._printers:
|
||||
# Only replace the printer if it is still in the list of (manual) printers
|
||||
self.removePrinter(instance_name)
|
||||
self.addPrinter(instance_name, address, properties)
|
||||
|
||||
del self._network_requests_buffer[address]
|
||||
|
||||
## Stop looking for devices on network.
|
||||
def stop(self):
|
||||
if self._zero_conf is not None:
|
||||
Logger.log("d", "zeroconf close...")
|
||||
self._zero_conf.close()
|
||||
|
||||
def getPrinters(self):
|
||||
return self._printers
|
||||
|
||||
def reCheckConnections(self):
|
||||
active_machine = Application.getInstance().getGlobalContainerStack()
|
||||
if not active_machine:
|
||||
return
|
||||
|
||||
for key in self._printers:
|
||||
if key == active_machine.getMetaDataEntry("um_network_key"):
|
||||
if not self._printers[key].isConnected():
|
||||
Logger.log("d", "Connecting [%s]..." % key)
|
||||
self._printers[key].connect()
|
||||
self._printers[key].connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
|
||||
else:
|
||||
if self._printers[key].isConnected():
|
||||
Logger.log("d", "Closing connection [%s]..." % key)
|
||||
self._printers[key].close()
|
||||
self._printers[key].connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
|
||||
|
||||
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
|
||||
def addPrinter(self, name, address, properties):
|
||||
cluster_size = int(properties.get(b"cluster_size", -1))
|
||||
if cluster_size >= 0:
|
||||
printer = NetworkClusterPrinterOutputDevice.NetworkClusterPrinterOutputDevice(
|
||||
name, address, properties, self._api_prefix)
|
||||
else:
|
||||
printer = NetworkPrinterOutputDevice.NetworkPrinterOutputDevice(name, address, properties, self._api_prefix)
|
||||
self._printers[printer.getKey()] = printer
|
||||
self._cluster_printers_seen[printer.getKey()] = name # Cluster printers that may be temporary unreachable or is rebooted keep being stored here
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack and printer.getKey() == global_container_stack.getMetaDataEntry("um_network_key"):
|
||||
if printer.getKey() not in self._old_printers: # Was the printer already connected, but a re-scan forced?
|
||||
Logger.log("d", "addPrinter, connecting [%s]..." % printer.getKey())
|
||||
self._printers[printer.getKey()].connect()
|
||||
printer.connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
|
||||
self.printerListChanged.emit()
|
||||
|
||||
def removePrinter(self, name):
|
||||
printer = self._printers.pop(name, None)
|
||||
if printer:
|
||||
if printer.isConnected():
|
||||
printer.disconnect()
|
||||
printer.connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
|
||||
Logger.log("d", "removePrinter, disconnecting [%s]..." % name)
|
||||
self.printerListChanged.emit()
|
||||
|
||||
## Handler for when the connection state of one of the detected printers changes
|
||||
def _onPrinterConnectionStateChanged(self, key):
|
||||
if key not in self._printers:
|
||||
return
|
||||
if self._printers[key].isConnected():
|
||||
self.getOutputDeviceManager().addOutputDevice(self._printers[key])
|
||||
else:
|
||||
self.getOutputDeviceManager().removeOutputDevice(key)
|
||||
|
||||
## Handler for zeroConf detection.
|
||||
# Return True or False indicating if the process succeeded.
|
||||
def _onServiceChanged(self, zeroconf, service_type, name, state_change):
|
||||
if state_change == ServiceStateChange.Added:
|
||||
Logger.log("d", "Bonjour service added: %s" % name)
|
||||
|
||||
# First try getting info from zeroconf cache
|
||||
info = ServiceInfo(service_type, name, properties = {})
|
||||
for record in zeroconf.cache.entries_with_name(name.lower()):
|
||||
info.update_record(zeroconf, time.time(), record)
|
||||
|
||||
for record in zeroconf.cache.entries_with_name(info.server):
|
||||
info.update_record(zeroconf, time.time(), record)
|
||||
if info.address:
|
||||
break
|
||||
|
||||
# Request more data if info is not complete
|
||||
if not info.address:
|
||||
Logger.log("d", "Trying to get address of %s", name)
|
||||
info = zeroconf.get_service_info(service_type, name)
|
||||
|
||||
if info:
|
||||
type_of_device = info.properties.get(b"type", None)
|
||||
if type_of_device:
|
||||
if type_of_device == b"printer":
|
||||
address = '.'.join(map(lambda n: str(n), info.address))
|
||||
self.addPrinterSignal.emit(str(name), address, info.properties)
|
||||
else:
|
||||
Logger.log("w", "The type of the found device is '%s', not 'printer'! Ignoring.." % type_of_device )
|
||||
else:
|
||||
Logger.log("w", "Could not get information about %s" % name)
|
||||
return False
|
||||
|
||||
elif state_change == ServiceStateChange.Removed:
|
||||
Logger.log("d", "Bonjour service removed: %s" % name)
|
||||
self.removePrinterSignal.emit(str(name))
|
||||
|
||||
return True
|
||||
|
||||
## Appends a service changed request so later the handling thread will pick it up and processes it.
|
||||
def _appendServiceChangedRequest(self, zeroconf, service_type, name, state_change):
|
||||
# append the request and set the event so the event handling thread can pick it up
|
||||
item = (zeroconf, service_type, name, state_change)
|
||||
self._service_changed_request_queue.put(item)
|
||||
self._service_changed_request_event.set()
|
||||
|
||||
def _handleOnServiceChangedRequests(self):
|
||||
while True:
|
||||
# wait for the event to be set
|
||||
self._service_changed_request_event.wait(timeout = 5.0)
|
||||
# stop if the application is shutting down
|
||||
if Application.getInstance().isShuttingDown():
|
||||
return
|
||||
|
||||
self._service_changed_request_event.clear()
|
||||
|
||||
# handle all pending requests
|
||||
reschedule_requests = [] # a list of requests that have failed so later they will get re-scheduled
|
||||
while not self._service_changed_request_queue.empty():
|
||||
request = self._service_changed_request_queue.get()
|
||||
zeroconf, service_type, name, state_change = request
|
||||
try:
|
||||
result = self._onServiceChanged(zeroconf, service_type, name, state_change)
|
||||
if not result:
|
||||
reschedule_requests.append(request)
|
||||
except Exception:
|
||||
Logger.logException("e", "Failed to get service info for [%s] [%s], the request will be rescheduled",
|
||||
service_type, name)
|
||||
reschedule_requests.append(request)
|
||||
|
||||
# re-schedule the failed requests if any
|
||||
if reschedule_requests:
|
||||
for request in reschedule_requests:
|
||||
self._service_changed_request_queue.put(request)
|
||||
|
||||
@pyqtSlot()
|
||||
def openControlPanel(self):
|
||||
Logger.log("d", "Opening print jobs web UI...")
|
||||
selected_device = self.getOutputDeviceManager().getActiveDevice()
|
||||
if isinstance(selected_device, NetworkClusterPrinterOutputDevice.NetworkClusterPrinterOutputDevice):
|
||||
QDesktopServices.openUrl(QUrl(selected_device.getPrintJobsUrl()))
|
@ -1,6 +1,6 @@
|
||||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from . import NetworkPrinterOutputDevicePlugin
|
||||
|
||||
from . import DiscoverUM3Action
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
Loading…
x
Reference in New Issue
Block a user