Merge branch 'master' into CURA-7455_Keep_printer_configurations_when_cloud_printer_removed_from_account

This commit is contained in:
Kostas Karmas 2020-06-10 11:51:50 +02:00
commit fdc555caf3
24 changed files with 278 additions and 218 deletions

View file

@ -466,10 +466,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_info.intent_info = instance_container_info_dict[intent_id]
if not machine_conflict and containers_found_dict["machine"]:
if position not in global_stack.extruders:
if int(position) >= len(global_stack.extrurderList):
continue
existing_extruder_stack = global_stack.extruders[position]
existing_extruder_stack = global_stack.extruderList[int(position)]
# check if there are any changes at all in any of the container stacks.
id_list = self._getContainerIdListFromSerialized(serialized)
for index, container_id in enumerate(id_list):

View file

@ -1,4 +1,4 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
@ -114,7 +114,7 @@ class FirmwareUpdateCheckerJob(Job):
# notify the user when no new firmware version is available.
if (checked_version != "") and (checked_version != current_version):
Logger.log("i", "Showing firmware update message for new version: {version}".format(version = current_version))
message = FirmwareUpdateCheckerMessage(machine_id, self._machine_name,
message = FirmwareUpdateCheckerMessage(machine_id, self._machine_name, current_version,
self._lookups.getRedirectUserUrl())
message.actionTriggered.connect(self._callback)
message.show()

View file

@ -1,4 +1,4 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.i18n import i18nCatalog
@ -11,11 +11,12 @@ i18n_catalog = i18nCatalog("cura")
class FirmwareUpdateCheckerMessage(Message):
STR_ACTION_DOWNLOAD = "download"
def __init__(self, machine_id: int, machine_name: str, download_url: str) -> None:
def __init__(self, machine_id: int, machine_name: str, latest_version: str, download_url: str) -> None:
super().__init__(i18n_catalog.i18nc(
"@info Don't translate {machine_name}, since it gets replaced by a printer name!",
"New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format(
machine_name = machine_name),
"New features or bug-fixes may be available for your {machine_name}! If not already at the latest version, "
"it is recommended to update the firmware on your printer to version {latest_version}.").format(
machine_name = machine_name, latest_version = latest_version),
title = i18n_catalog.i18nc(
"@info:title The %s gets replaced with the printer name.",
"New %s firmware available") % machine_name)

View file

@ -32,10 +32,7 @@ Item
var type = currentMeshType
// set checked state of mesh type buttons
normalButton.checked = type === normalMeshType
supportMeshButton.checked = type === supportMeshType
overhangMeshButton.checked = type === infillMeshType || type === cuttingMeshType
antiOverhangMeshButton.checked = type === antiOverhangMeshType
updateMeshTypeCheckedState(type)
// update active type label
for (var button in meshTypeButtons.children)
@ -49,9 +46,19 @@ Item
visibility_handler.addSkipResetSetting(currentMeshType)
}
function updateMeshTypeCheckedState(type)
{
// set checked state of mesh type buttons
normalButton.checked = type === normalMeshType
supportMeshButton.checked = type === supportMeshType
overlapMeshButton.checked = type === infillMeshType || type === cuttingMeshType
antiOverhangMeshButton.checked = type === antiOverhangMeshType
}
function setMeshType(type)
{
UM.ActiveTool.setProperty("MeshType", type)
updateMeshTypeCheckedState(type)
}
UM.I18nCatalog { id: catalog; name: "cura"}
@ -95,7 +102,7 @@ Item
Button
{
id: overhangMeshButton
id: overlapMeshButton
text: catalog.i18nc("@label", "Modify settings for overlaps")
iconSource: UM.Theme.getIcon("pos_modify_overlaps");
property bool needBorder: true

View file

@ -289,6 +289,13 @@ class Stretcher:
self.layergcode = self.layergcode + sout + "\n"
ipos = ipos + 1
else:
# The command is intended to be passed through unmodified via
# the comment field. In the case of an extruder only move, though,
# the extruder and potentially the feed rate are modified.
# We need to update self.outpos accordingly so that subsequent calls
# to stepToGcode() knows about the extruder and feed rate change.
self.outpos.step_e = layer_steps[i].step_e
self.outpos.step_f = layer_steps[i].step_f
self.layergcode = self.layergcode + layer_steps[i].comment + "\n"
def workOnSequence(self, orig_seq, modif_seq):

View file

@ -101,7 +101,7 @@ class SliceInfo(QObject, Extension):
user_modified_setting_keys = set() # type: Set[str]
for stack in [global_stack] + list(global_stack.extruders.values()):
for stack in [global_stack] + global_stack.extruderList:
# Get all settings in user_changes and quality_changes
all_keys = stack.userChanges.getAllKeys() | stack.qualityChanges.getAllKeys()
user_modified_setting_keys |= all_keys
@ -152,7 +152,7 @@ class SliceInfo(QObject, Extension):
# add extruder specific data to slice info
data["extruders"] = []
extruders = list(global_stack.extruders.values())
extruders = global_stack.extruderList
extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position"))
for extruder in extruders:

View file

@ -97,7 +97,7 @@ class UFPWriter(MeshWriter):
Logger.log("w", "The material extension: %s was already added", material_extension)
added_materials = []
for extruder_stack in global_stack.extruders.values():
for extruder_stack in global_stack.extruderList:
material = extruder_stack.material
try:
material_file_name = material.getMetaData()["base_file"] + ".xml.fdm_material"

View file

@ -74,7 +74,7 @@ class CloudOutputDevice(UltimakerNetworkedPrinterOutputDevice):
b"name": cluster.friendly_name.encode() if cluster.friendly_name else b"",
b"firmware_version": cluster.host_version.encode() if cluster.host_version else b"",
b"printer_type": cluster.printer_type.encode() if cluster.printer_type else b"",
b"cluster_size": b"1" # cloud devices are always clusters of at least one
b"cluster_size": str(cluster.printer_count).encode() if cluster.printer_count else b"1"
}
super().__init__(

View file

@ -97,8 +97,6 @@ class CloudOutputDeviceManager:
if self._syncing:
return
Logger.info("Syncing cloud printer clusters")
self._syncing = True
self._account.setSyncState(self.SYNC_SERVICE_NAME, SyncState.SYNCING)
self._api.getClusters(self._onGetRemoteClustersFinished, self._onGetRemoteClusterFailed)
@ -187,7 +185,11 @@ class CloudOutputDeviceManager:
self._connectToActiveMachine()
return
new_devices.sort(key = lambda x: x.name.lower())
# Sort new_devices on online status first, alphabetical second.
# Since the first device might be activated in case there is no active printer yet,
# it would be nice to prioritize online devices
online_cluster_names = {c.friendly_name.lower() for c in clusters if c.is_online and not c.friendly_name is None}
new_devices.sort(key = lambda x: ("a{}" if x.name.lower() in online_cluster_names else "b{}").format(x.name.lower()))
image_path = os.path.join(
CuraApplication.getInstance().getPluginRegistry().getPluginPath("UM3NetworkPrinting") or "",
@ -365,6 +367,7 @@ class CloudOutputDeviceManager:
machine.setName(device.name)
machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
machine.setMetaDataEntry("group_name", device.name)
machine.setMetaDataEntry("group_size", device.clusterSize)
machine.setMetaDataEntry("removal_warning", self.I18N_CATALOG.i18nc(
"@label ({} is printer name)",
"{} will be removed until the next account sync. <br> To remove {} permanently, "

View file

@ -20,9 +20,6 @@ class ToolPathUploader:
# The HTTP codes that should trigger a retry.
RETRY_HTTP_CODES = {500, 502, 503, 504}
# The amount of bytes to send per request
BYTES_PER_REQUEST = 256 * 1024
def __init__(self, http: HttpRequestManager, print_job: CloudPrintJobResponse, data: bytes,
on_finished: Callable[[], Any], on_progress: Callable[[int], Any], on_error: Callable[[], Any]
) -> None:
@ -44,7 +41,6 @@ class ToolPathUploader:
self._on_progress = on_progress
self._on_error = on_error
self._sent_bytes = 0
self._retries = 0
self._finished = False
@ -54,50 +50,34 @@ class ToolPathUploader:
return self._print_job
def _chunkRange(self) -> Tuple[int, int]:
"""Determines the bytes that should be uploaded next.
:return: A tuple with the first and the last byte to upload.
"""
last_byte = min(len(self._data), self._sent_bytes + self.BYTES_PER_REQUEST)
return self._sent_bytes, last_byte
def start(self) -> None:
"""Starts uploading the mesh."""
if self._finished:
# reset state.
self._sent_bytes = 0
self._retries = 0
self._finished = False
self._uploadChunk()
self._upload()
def stop(self):
"""Stops uploading the mesh, marking it as finished."""
Logger.log("i", "Stopped uploading")
self._finished = True
def _uploadChunk(self) -> None:
"""Uploads a chunk of the mesh to the cloud."""
Logger.log("i", "Finished uploading")
self._finished = True # Signal to any ongoing retries that we should stop retrying.
self._on_finished()
def _upload(self) -> None:
"""
Uploads the print job to the cloud printer.
"""
if self._finished:
raise ValueError("The upload is already finished")
first_byte, last_byte = self._chunkRange()
content_range = "bytes {}-{}/{}".format(first_byte, last_byte - 1, len(self._data))
headers = {
"Content-Type": cast(str, self._print_job.content_type),
"Content-Range": content_range
} # type: Dict[str, str]
Logger.log("i", "Uploading %s to %s", content_range, self._print_job.upload_url)
Logger.log("i", "Uploading print to {upload_url}".format(upload_url = self._print_job.upload_url))
self._http.put(
url = cast(str, self._print_job.upload_url),
headers_dict = headers,
data = self._data[first_byte:last_byte],
headers_dict = {"Content-Type": cast(str, self._print_job.content_type)},
data = self._data,
callback = self._finishedCallback,
error_callback = self._errorCallback,
upload_progress_callback = self._progressCallback
@ -109,10 +89,9 @@ class ToolPathUploader:
:param bytes_sent: The amount of bytes sent in the current request.
:param bytes_total: The amount of bytes to send in the current request.
"""
Logger.log("i", "Progress callback %s / %s", bytes_sent, bytes_total)
Logger.debug("Cloud upload progress %s / %s", bytes_sent, bytes_total)
if bytes_total:
total_sent = self._sent_bytes + bytes_sent
self._on_progress(int(total_sent / len(self._data) * 100))
self._on_progress(int(bytes_sent / len(self._data) * 100))
## Handles an error uploading.
def _errorCallback(self, reply: QNetworkReply, error: QNetworkReply.NetworkError) -> None:
@ -136,7 +115,7 @@ class ToolPathUploader:
self._retries += 1
Logger.log("i", "Retrying %s/%s request %s", self._retries, self.MAX_RETRIES, reply.url().toString())
try:
self._uploadChunk()
self._upload()
except ValueError: # Asynchronously it could have completed in the meanwhile.
pass
return
@ -148,16 +127,5 @@ class ToolPathUploader:
Logger.log("d", "status_code: %s, Headers: %s, body: %s", status_code,
[bytes(header).decode() for header in reply.rawHeaderList()], bytes(reply.readAll()).decode())
self._chunkUploaded()
def _chunkUploaded(self) -> None:
"""Handles a chunk of data being uploaded, starting the next chunk if needed."""
# We got a successful response. Let's start the next chunk or report the upload is finished.
first_byte, last_byte = self._chunkRange()
self._sent_bytes += last_byte - first_byte
if self._sent_bytes >= len(self._data):
self.stop()
self._on_finished()
else:
self._uploadChunk()
self.stop()

View file

@ -11,7 +11,7 @@ class CloudClusterResponse(BaseModel):
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str,
host_internal_ip: Optional[str] = None, host_version: Optional[str] = None,
friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", **kwargs) -> None:
friendly_name: Optional[str] = None, printer_type: str = "ultimaker3", printer_count: int = 1, **kwargs) -> None:
"""Creates a new cluster response object.
:param cluster_id: The secret unique ID, e.g. 'kBEeZWEifXbrXviO8mRYLx45P8k5lHVGs43XKvRniPg='.
@ -23,6 +23,7 @@ class CloudClusterResponse(BaseModel):
:param host_internal_ip: The internal IP address of the host printer.
:param friendly_name: The human readable name of the host printer.
:param printer_type: The machine type of the host printer.
:param printer_count: The amount of printers in the print cluster. 1 for a single printer
"""
self.cluster_id = cluster_id
@ -34,6 +35,7 @@ class CloudClusterResponse(BaseModel):
self.host_internal_ip = host_internal_ip
self.friendly_name = friendly_name
self.printer_type = printer_type
self.printer_count = printer_count
super().__init__(**kwargs)
# Validates the model, raising an exception if the model is invalid.