Merge branch 'master' of github.com:Ultimaker/Cura

This commit is contained in:
Jaime van Kessel 2020-04-22 17:20:59 +02:00
commit 13105cfbd7
No known key found for this signature in database
GPG key ID: 3710727397403C91
7 changed files with 1459 additions and 358 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,26 +1,29 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
from typing import Dict, List, Optional
from PyQt5.QtCore import QTimer
from UM import i18nCatalog
from UM.Logger import Logger # To log errors talking to the API.
from UM.Message import Message
from UM.Signal import Signal
from cura.API import Account
from cura.CuraApplication import CuraApplication
from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.GlobalStack import GlobalStack
from .CloudApiClient import CloudApiClient
from .CloudOutputDevice import CloudOutputDevice
from ..Models.Http.CloudClusterResponse import CloudClusterResponse
## The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters.
# Keeping all cloud related logic in this class instead of the UM3OutputDevicePlugin results in more readable code.
# API spec is available on https://api.ultimaker.com/docs/connect/spec/.
class CloudOutputDeviceManager:
"""The cloud output device manager is responsible for using the Ultimaker Cloud APIs to manage remote clusters.
Keeping all cloud related logic in this class instead of the UM3OutputDevicePlugin results in more readable code.
API spec is available on https://api.ultimaker.com/docs/connect/spec/.
"""
META_CLUSTER_ID = "um_cloud_cluster_id"
META_NETWORK_KEY = "um_network_key"
@ -44,14 +47,16 @@ class CloudOutputDeviceManager:
# Create a timer to update the remote cluster list
self._update_timer = QTimer()
self._update_timer.setInterval(int(self.CHECK_CLUSTER_INTERVAL * 1000))
self._update_timer.setSingleShot(False)
# The timer is restarted explicitly after an update was processed. This prevents 2 concurrent updates
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._getRemoteClusters)
# Ensure we don't start twice.
self._running = False
## Starts running the cloud output device manager, thus periodically requesting cloud data.
def start(self):
"""Starts running the cloud output device manager, thus periodically requesting cloud data."""
if self._running:
return
if not self._account.isLoggedIn:
@ -61,8 +66,9 @@ class CloudOutputDeviceManager:
self._update_timer.start()
self._getRemoteClusters()
## Stops running the cloud output device manager.
def stop(self):
"""Stops running the cloud output device manager."""
if not self._running:
return
self._running = False
@ -70,47 +76,121 @@ class CloudOutputDeviceManager:
self._update_timer.stop()
self._onGetRemoteClustersFinished([]) # Make sure we remove all cloud output devices.
## Force refreshing connections.
def refreshConnections(self) -> None:
"""Force refreshing connections."""
self._connectToActiveMachine()
## Called when the uses logs in or out
def _onLoginStateChanged(self, is_logged_in: bool) -> None:
"""Called when the uses logs in or out"""
if is_logged_in:
self.start()
else:
self.stop()
## Gets all remote clusters from the API.
def _getRemoteClusters(self) -> None:
"""Gets all remote clusters from the API."""
self._api.getClusters(self._onGetRemoteClustersFinished)
## Callback for when the request for getting the clusters is finished.
def _onGetRemoteClustersFinished(self, clusters: List[CloudClusterResponse]) -> None:
"""Callback for when the request for getting the clusters is finished."""
new_clusters = []
online_clusters = {c.cluster_id: c for c in clusters if c.is_online} # type: Dict[str, CloudClusterResponse]
for device_id, cluster_data in online_clusters.items():
if device_id not in self._remote_clusters:
self._onDeviceDiscovered(cluster_data)
new_clusters.append(cluster_data)
else:
self._onDiscoveredDeviceUpdated(cluster_data)
self._onDevicesDiscovered(new_clusters)
removed_device_keys = set(self._remote_clusters.keys()) - set(online_clusters.keys())
for device_id in removed_device_keys:
self._onDiscoveredDeviceRemoved(device_id)
def _onDeviceDiscovered(self, cluster_data: CloudClusterResponse) -> None:
device = CloudOutputDevice(self._api, cluster_data)
CuraApplication.getInstance().getDiscoveredPrintersModel().addDiscoveredPrinter(
ip_address=device.key,
key=device.getId(),
name=device.getName(),
create_callback=self._createMachineFromDiscoveredDevice,
machine_type=device.printerType,
device=device
if new_clusters or removed_device_keys:
self.discoveredDevicesChanged.emit()
if removed_device_keys:
# If the removed device was active we should connect to the new active device
self._connectToActiveMachine()
# Schedule a new update
self._update_timer.start()
def _onDevicesDiscovered(self, clusters: List[CloudClusterResponse]) -> None:
"""**Synchronously** create machines for discovered devices
Any new machines are made available to the user.
May take a long time to complete. As this code needs access to the Application
and blocks the GIL, creating a Job for this would not make sense.
Shows a Message informing the user of progress.
"""
new_devices = []
for cluster_data in clusters:
device = CloudOutputDevice(self._api, cluster_data)
# Create a machine if we don't already have it. Do not make it the active machine.
machine_manager = CuraApplication.getInstance().getMachineManager()
# We only need to add it if it wasn't already added by "local" network or by cloud.
if machine_manager.getMachine(device.printerType, {self.META_CLUSTER_ID: device.key}) is None \
and machine_manager.getMachine(device.printerType, {self.META_NETWORK_KEY: cluster_data.host_name + "*"}) is None: # The host name is part of the network key.
new_devices.append(device)
if not new_devices:
return
new_devices.sort(key = lambda x: x.name.lower())
image_path = os.path.join(
CuraApplication.getInstance().getPluginRegistry().getPluginPath("UM3NetworkPrinting") or "",
"resources", "svg", "cloud-flow-completed.svg"
)
self._remote_clusters[device.getId()] = device
self.discoveredDevicesChanged.emit()
self._connectToActiveMachine()
message = Message(
title = self.I18N_CATALOG.i18ncp(
"info:status",
"New printer detected from your Ultimaker account",
"New printers detected from your Ultimaker account",
len(new_devices)
),
progress = 0,
lifetime = 0,
image_source = image_path
)
message.show()
for idx, device in enumerate(new_devices):
message_text = self.I18N_CATALOG.i18nc(
"info:status", "Adding printer {} ({}) from your account",
device.name,
device.printerTypeName
)
message.setText(message_text)
if len(new_devices) > 1:
message.setProgress((idx / len(new_devices)) * 100)
CuraApplication.getInstance().processEvents()
self._remote_clusters[device.getId()] = device
self._createMachineFromDiscoveredDevice(device.getId(), activate = False)
message.setProgress(None)
max_disp_devices = 3
if len(new_devices) > max_disp_devices:
num_hidden = len(new_devices) - max_disp_devices + 1
device_name_list = ["- {} ({})".format(device.name, device.printerTypeName) for device in new_devices[0:num_hidden]]
device_name_list.append(self.I18N_CATALOG.i18nc("info:hidden list items", "- and {} others", num_hidden))
device_names = "\n".join(device_name_list)
else:
device_names = "\n".join(["- {} ({})".format(device.name, device.printerTypeName) for device in new_devices])
message_text = self.I18N_CATALOG.i18nc(
"info:status",
"Cloud printers added from your account:\n{}",
device_names
)
message.setText(message_text)
def _onDiscoveredDeviceUpdated(self, cluster_data: CloudClusterResponse) -> None:
device = self._remote_clusters.get(cluster_data.cluster_id)
@ -128,29 +208,31 @@ class CloudOutputDeviceManager:
if not device:
return
device.close()
CuraApplication.getInstance().getDiscoveredPrintersModel().removeDiscoveredPrinter(device.key)
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
if device.key in output_device_manager.getOutputDeviceIds():
output_device_manager.removeOutputDevice(device.key)
self.discoveredDevicesChanged.emit()
def _createMachineFromDiscoveredDevice(self, key: str) -> None:
def _createMachineFromDiscoveredDevice(self, key: str, activate: bool = True) -> None:
device = self._remote_clusters[key]
if not device:
return
# Create a new machine and activate it.
# Create a new machine.
# We do not use use MachineManager.addMachine here because we need to set the cluster ID before activating it.
new_machine = CuraStackBuilder.createMachine(device.name, device.printerType)
if not new_machine:
Logger.log("e", "Failed creating a new machine")
return
new_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
CuraApplication.getInstance().getMachineManager().setActiveMachine(new_machine.getId())
if activate:
CuraApplication.getInstance().getMachineManager().setActiveMachine(new_machine.getId())
self._connectToOutputDevice(device, new_machine)
## Callback for when the active machine was changed by the user or a new remote cluster was found.
def _connectToActiveMachine(self) -> None:
"""Callback for when the active machine was changed by the user"""
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
if not active_machine:
return
@ -169,8 +251,9 @@ class CloudOutputDeviceManager:
# Remove device if it is not meant for the active machine.
output_device_manager.removeOutputDevice(device.key)
## Connects to an output device and makes sure it is registered in the output device manager.
def _connectToOutputDevice(self, device: CloudOutputDevice, machine: GlobalStack) -> None:
"""Connects to an output device and makes sure it is registered in the output device manager."""
machine.setName(device.name)
machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
machine.setMetaDataEntry("group_name", device.name)

View file

@ -42,7 +42,7 @@
"default_value": 1.75
},
"machine_start_gcode": {
"default_value": "G21\nG90\nM82\nM107 T0\nM190 S{material_bed_temperature}\nM109 S{material_print_temperature} T0\nG28\nG92 E0\nG0 E3 F200\nG92 E0\n"
"default_value": "G21\nG90\nM82\nM107 T0\nM190 S{material_bed_temperature_layer_0}\nM109 S{material_print_temperature_layer_0} T0\nG28\nG92 E0\nG0 E3 F200\nG92 E0\n"
},
"machine_end_gcode": {
"default_value": "M107 T0\nM104 S0\nM104 S0 T1\nM140 S0\nG92 E0\nG91\nG1 E-1 F300 \nG1 Z+0.5 E-5 X-20 Y-20 F9000\nG28 X0 Y0\nM84 ;steppers off\nG90 ;absolute positioning\n"

View file

@ -69,7 +69,7 @@
"material_bed_temp_wait": {"default_value": false },
"machine_max_feedrate_z": {"default_value": 10 },
"machine_acceleration": {"default_value": 180 },
"machine_start_gcode": {"default_value": "\n;Neither Hybrid AM Systems nor any of Hybrid AM Systems representatives has any liabilities or gives any warranties on this .gcode file, or on any or all objects made with this .gcode file.\n\nM140 S{material_bed_temperature_layer_0}\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\n\nG1 Z10 F900\nG1 X-30 Y100 F12000\n\nM190 S{material_bed_temperature_layer_0}\nM117 HMS434 Printing ...\n\nM42 P10 S255 ; chamberfans on" },
"machine_start_gcode": {"default_value": "\n;Neither Hybrid AM Systems nor any of Hybrid AM Systems representatives has any liabilities or gives any warranties on this .gcode file, or on any or all objects made with this .gcode file.\n\nM140 S{material_bed_temperature_layer_0}\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\n\nG1 Z10 F900\nG1 X-25 Y20 F12000\n\nM190 S{material_bed_temperature_layer_0}\nM117 HMS434 Printing ...\n\nM42 P10 S255 ; chamberfans on" },
"machine_end_gcode": {"default_value": "" },
"retraction_extra_prime_amount": {"minimum_value_warning": "-2.0" },
@ -81,18 +81,19 @@
"layer_height": {"maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
"layer_height_0": {"maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
"line_width": {"value": "(machine_nozzle_size + 0.2)" },
"wall_line_width_0": {"value": "(machine_nozzle_size - 0.05)" },
"wall_line_width_0": {"value": "(machine_nozzle_size)" },
"infill_line_width": {"value": "(line_width)" },
"initial_layer_line_width_factor": {"value": 110 },
"wall_thickness": {"value": "(line_width * 3) if infill_sparse_density < 95 else line_width" },
"roofing_layer_count": {"value": "4" },
"top_bottom_thickness": {"value": "(layer_height_0 + (layer_height * 3))" },
"top_layers": {"value": "4" },
"roofing_layer_count": {"value": "0" },
"top_bottom_thickness": {"value": "(layer_height_0 + (layer_height * (top_layers - 1)))" },
"top_layers": {"value": "4 if infill_sparse_density < 95 else 1" },
"bottom_layers": {"value": "(top_layers)" },
"wall_0_inset": {"value": "0" },
"outer_inset_first": {"value": true },
"alternate_extra_perimeter": {"value": false },
"travel_compensate_overlapping_walls_enabled": {"value": false },
"filter_out_tiny_gaps": {"value": true },
"fill_outline_gaps": {"value": true },
"z_seam_type": {"value": "'shortest'"},
@ -107,7 +108,7 @@
"infill_sparse_density": {"value": 30},
"infill_pattern": {"value": "'lines'"},
"infill_before_walls": {"value": false},
"infill_before_walls": {"value": true},
"material_print_temperature_layer_0": {"value": "material_print_temperature"},
"material_initial_print_temperature": {"value": "material_print_temperature",
@ -116,7 +117,7 @@
"material_bed_temperature_layer_0": {"value": "material_bed_temperature"},
"material_flow_layer_0": {"value": "material_flow"},
"retraction_enable": {"value": true },
"retract_at_layer_change": {"value": true },
"retract_at_layer_change": {"value": false },
"retraction_min_travel": {"value": "(round(line_width * 10))"},
"switch_extruder_retraction_speeds": {"value": "(retraction_speed)"},
"switch_extruder_prime_speed": {"value": "(retraction_prime_speed)"},

View file

@ -16,10 +16,10 @@
"machine_nozzle_offset_y": { "default_value": 0.0 },
"material_diameter": { "default_value": 1.75 },
"machine_extruder_start_code": {
"default_value": "\n;changing to tool1\nM83\nM109 T0 S{material_print_temperature}\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E-{switch_extruder_retraction_amount} F2400\nG1 Y120 F3000\nG1 X10 F12000\n\n"
"default_value": "\n;changing to tool1\nM83\nM109 T0 S{material_print_temperature}\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E-{switch_extruder_retraction_amount} F2400\nG1 Y40 F3000\nG1 X10 F12000\n\n"
},
"machine_extruder_end_code": {
"default_value": "\nG1 X10 Y120 F12000\nG1 X-40 F12000\nM109 T0 R{material_standby_temperature}\nG1 Y100 F3000\n; ending tool1\n\n"
"default_value": "\nG1 X10 Y120 F12000\nG1 X-25 F12000\nM109 T0 R{material_standby_temperature}\nG1 Y20 F3000\n; ending tool1\n\n"
}
}
}

View file

@ -16,10 +16,10 @@
"machine_nozzle_offset_y": { "default_value": 0.0 },
"material_diameter": { "default_value": 1.75 },
"machine_extruder_start_code": {
"default_value": "\n;changing to tool2\nM83\nM109 T1 S{material_print_temperature}\nG1 E{switch_retraction_prime_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E-{switch_extruder_retraction_amount} F2400\nG1 Y120 F3000\nG1 X10 F12000\n\n"
"default_value": "\n;changing to tool2\nM83\nM109 T1 S{material_print_temperature}\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E{switch_extruder_retraction_amount} F300\nG1 E-{switch_extruder_retraction_amount} F2400\nG1 Y40 F3000\nG1 X10 F12000\n\n"
},
"machine_extruder_end_code": {
"default_value": "\nG1 X10 Y120 F12000\nG1 X-40 F12000\nM109 T1 R{material_standby_temperature}\nG1 Y100 F3000\n; ending tool2\n\n"
"default_value": "\nG1 X10 Y120 F12000\nG1 X-25 F12000\nM109 T1 R{material_standby_temperature}\nG1 Y20 F3000\n; ending tool2\n\n"
}
}
}

View file

@ -68,11 +68,9 @@ fragment =
finalColor = (-normal.y > u_overhangAngle) ? u_overhangColor : finalColor;
if(u_renderError > 0.5)
{
vec3 grid = vec3(f_vertex.x - round(f_vertex.x), f_vertex.y - round(f_vertex.y), f_vertex.z - round(f_vertex.z));
finalColor.a = dot(grid, grid) < 0.245 ? 0.667 : 1.0;
}
vec3 grid = vec3(f_vertex.x - floor(f_vertex.x - 0.5), f_vertex.y - floor(f_vertex.y - 0.5), f_vertex.z - floor(f_vertex.z - 0.5));
finalColor.a = (u_renderError > 0.5) && dot(grid, grid) < 0.245 ? 0.667 : 1.0;
gl_FragColor = finalColor;
}
@ -144,11 +142,8 @@ fragment41core =
finalColor = (u_faceId != gl_PrimitiveID) ? ((-normal.y > u_overhangAngle) ? u_overhangColor : finalColor) : u_faceColor;
frag_color = finalColor;
if(u_renderError > 0.5)
{
vec3 grid = f_vertex - round(f_vertex);
frag_color.a = dot(grid, grid) < 0.245 ? 0.667 : 1.0;
}
vec3 grid = f_vertex - round(f_vertex);
frag_color.a = (u_renderError > 0.5) && dot(grid, grid) < 0.245 ? 0.667 : 1.0;
}
[defaults]