mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-10 15:25:09 -06:00
Merge branch 'master' into CURA-7455_Keep_printer_configurations_when_cloud_printer_removed_from_account
This commit is contained in:
commit
fdc555caf3
24 changed files with 278 additions and 218 deletions
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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__(
|
||||
|
|
|
@ -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, "
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue