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

This commit is contained in:
Jaime van Kessel 2019-04-19 13:58:58 +02:00
commit 9daa78a98f
9 changed files with 102 additions and 40 deletions

View File

@ -214,14 +214,14 @@ class CuraApplication(QtApplication):
self._cura_scene_controller = None self._cura_scene_controller = None
self._machine_error_checker = None self._machine_error_checker = None
self._machine_settings_manager = MachineSettingsManager(self) self._machine_settings_manager = MachineSettingsManager(self, parent = self)
self._discovered_printer_model = DiscoveredPrintersModel(self) self._discovered_printer_model = DiscoveredPrintersModel(parent = self)
self._first_start_machine_actions_model = FirstStartMachineActionsModel(self) self._first_start_machine_actions_model = FirstStartMachineActionsModel(self, parent = self)
self._welcome_pages_model = WelcomePagesModel(self) self._welcome_pages_model = WelcomePagesModel(self, parent = self)
self._add_printer_pages_model = AddPrinterPagesModel(self) self._add_printer_pages_model = AddPrinterPagesModel(self, parent = self)
self._whats_new_pages_model = WhatsNewPagesModel(self) self._whats_new_pages_model = WhatsNewPagesModel(self, parent = self)
self._text_manager = TextManager(self) self._text_manager = TextManager(parent = self)
self._quality_profile_drop_down_menu_model = None self._quality_profile_drop_down_menu_model = None
self._custom_quality_profile_drop_down_menu_model = None self._custom_quality_profile_drop_down_menu_model = None

View File

@ -40,4 +40,4 @@ cmake3 \
-DBUILD_TESTS=ON \ -DBUILD_TESTS=ON \
.. ..
make make
ctest3 --verbose --output-on-failure -T Test ctest3 --output-on-failure -T Test

View File

@ -58,6 +58,14 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
# Therefore we create a private signal used to trigger the printersChanged signal. # Therefore we create a private signal used to trigger the printersChanged signal.
_clusterPrintersChanged = pyqtSignal() _clusterPrintersChanged = pyqtSignal()
# Map of Cura Connect machine_variant field to Cura machine types.
# Needed for printer discovery stack creation.
_host_machine_variant_to_machine_type_map = {
"Ultimaker 3": "ultimaker3",
"Ultimaker 3 Extended": "ultimaker3_extended",
"Ultimaker S5": "ultimaker_s5"
}
## Creates a new cloud output device ## Creates a new cloud output device
# \param api_client: The client that will run the API calls # \param api_client: The client that will run the API calls
# \param cluster: The device response received from the cloud API. # \param cluster: The device response received from the cloud API.
@ -68,10 +76,10 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
# Because the cloud connection does not off all of these, we manually construct this version here. # Because the cloud connection does not off all of these, we manually construct this version here.
# An example of why this is needed is the selection of the compatible file type when exporting the tool path. # An example of why this is needed is the selection of the compatible file type when exporting the tool path.
properties = { properties = {
b"address": b"", b"address": cluster.host_internal_ip.encode() if cluster.host_internal_ip else b"",
b"name": cluster.host_name.encode() if cluster.host_name else b"", 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"firmware_version": cluster.host_version.encode() if cluster.host_version else b"",
b"printer_type": b"" b"cluster_size": b"1" # cloud devices are always clusters of at least one
} }
super().__init__(device_id = cluster.cluster_id, address = "", super().__init__(device_id = cluster.cluster_id, address = "",
@ -96,6 +104,7 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
# We keep track of which printer is visible in the monitor page. # We keep track of which printer is visible in the monitor page.
self._active_printer = None # type: Optional[PrinterOutputModel] self._active_printer = None # type: Optional[PrinterOutputModel]
self._host_machine_type = ""
# Properties to populate later on with received cloud data. # Properties to populate later on with received cloud data.
self._print_jobs = [] # type: List[UM3PrintJobOutputModel] self._print_jobs = [] # type: List[UM3PrintJobOutputModel]
@ -236,6 +245,10 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel] previous = {p.key: p for p in self._printers} # type: Dict[str, PrinterOutputModel]
received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinterStatus] received = {p.uuid: p for p in printers} # type: Dict[str, CloudClusterPrinterStatus]
if len(printers) > 0:
# We need the machine type of the host (1st list entry) to allow discovery to work.
self._host_machine_type = printers[0].machine_variant
removed_printers, added_printers, updated_printers = findChanges(previous, received) removed_printers, added_printers, updated_printers = findChanges(previous, received)
for removed_printer in removed_printers: for removed_printer in removed_printers:
@ -359,6 +372,19 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
).show() ).show()
self.writeFinished.emit() self.writeFinished.emit()
## Gets the printer type of the cluster host. Falls back to the printer type in the device properties.
@pyqtProperty(str, notify=_clusterPrintersChanged)
def printerType(self) -> str:
if self._printers and self._host_machine_type in self._host_machine_variant_to_machine_type_map:
return self._host_machine_variant_to_machine_type_map[self._host_machine_type]
return super().printerType
## Gets the number of printers in the cluster.
# We use a minimum of 1 because cloud devices are always a cluster and printer discovery needs it.
@pyqtProperty(int, notify = _clusterPrintersChanged)
def clusterSize(self) -> int:
return max(1, len(self._printers))
## Gets the remote printers. ## Gets the remote printers.
@pyqtProperty("QVariantList", notify=_clusterPrintersChanged) @pyqtProperty("QVariantList", notify=_clusterPrintersChanged)
def printers(self) -> List[PrinterOutputModel]: def printers(self) -> List[PrinterOutputModel]:
@ -376,10 +402,6 @@ class CloudOutputDevice(NetworkedPrinterOutputDevice):
self._active_printer = printer self._active_printer = printer
self.activePrinterChanged.emit() self.activePrinterChanged.emit()
@pyqtProperty(int, notify = _clusterPrintersChanged)
def clusterSize(self) -> int:
return len(self._printers)
## Get remote print jobs. ## Get remote print jobs.
@pyqtProperty("QVariantList", notify = printJobsChanged) @pyqtProperty("QVariantList", notify = printJobsChanged)
def printJobs(self) -> List[UM3PrintJobOutputModel]: def printJobs(self) -> List[UM3PrintJobOutputModel]:

View File

@ -7,7 +7,7 @@ from PyQt5.QtCore import QTimer
from UM import i18nCatalog from UM import i18nCatalog
from UM.Logger import Logger from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.Signal import Signal, signalemitter from UM.Signal import Signal
from cura.API import Account from cura.API import Account
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
@ -81,26 +81,62 @@ class CloudOutputDeviceManager:
Logger.log("d", "Removed: %s, added: %s, updates: %s", len(removed_devices), len(added_clusters), len(updates)) Logger.log("d", "Removed: %s, added: %s, updates: %s", len(removed_devices), len(added_clusters), len(updates))
# Remove output devices that are gone # Remove output devices that are gone
for removed_cluster in removed_devices: for device in removed_devices:
if removed_cluster.isConnected(): if device.isConnected():
removed_cluster.disconnect() device.disconnect()
removed_cluster.close() device.close()
self._output_device_manager.removeOutputDevice(removed_cluster.key) self._output_device_manager.removeOutputDevice(device.key)
self.removedCloudCluster.emit(removed_cluster) self._application.getDiscoveredPrintersModel().removeDiscoveredPrinter(device.key)
del self._remote_clusters[removed_cluster.key] self.removedCloudCluster.emit(device)
del self._remote_clusters[device.key]
# Add an output device for each new remote cluster. # Add an output device for each new remote cluster.
# We only add when is_online as we don't want the option in the drop down if the cluster is not online. # We only add when is_online as we don't want the option in the drop down if the cluster is not online.
for added_cluster in added_clusters: for cluster in added_clusters:
device = CloudOutputDevice(self._api, added_cluster) device = CloudOutputDevice(self._api, cluster)
self._remote_clusters[added_cluster.cluster_id] = device self._remote_clusters[cluster.cluster_id] = device
self.addedCloudCluster.emit(added_cluster) self._application.getDiscoveredPrintersModel().addDiscoveredPrinter(
cluster.cluster_id,
device.key,
cluster.friendly_name,
self._createMachineFromDiscoveredPrinter,
device.printerType,
device
)
self.addedCloudCluster.emit(cluster)
# Update the output devices
for device, cluster in updates: for device, cluster in updates:
device.clusterData = cluster device.clusterData = cluster
self._application.getDiscoveredPrintersModel().updateDiscoveredPrinter(
cluster.cluster_id,
cluster.friendly_name,
device.printerType,
)
self._connectToActiveMachine() self._connectToActiveMachine()
def _createMachineFromDiscoveredPrinter(self, key: str) -> None:
device = self._remote_clusters[key] # type: CloudOutputDevice
if not device:
Logger.log("e", "Could not find discovered device with key [%s]", key)
return
group_name = device.clusterData.friendly_name
machine_type_id = device.printerType
Logger.log("i", "Creating machine from cloud device with key = [%s], group name = [%s], printer type = [%s]",
key, group_name, machine_type_id)
# The newly added machine is automatically activated.
self._application.getMachineManager().addMachine(machine_type_id, group_name)
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
if not active_machine:
return
active_machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
self._connectToOutputDevice(device, active_machine)
## Callback for when the active machine was changed by the user or a new remote cluster was found. ## Callback for when the active machine was changed by the user or a new remote cluster was found.
def _connectToActiveMachine(self) -> None: def _connectToActiveMachine(self) -> None:
active_machine = CuraApplication.getInstance().getGlobalContainerStack() active_machine = CuraApplication.getInstance().getGlobalContainerStack()

View File

@ -16,7 +16,8 @@ class CloudClusterResponse(BaseCloudModel):
# \param status: The status of the cluster authentication (active or inactive). # \param status: The status of the cluster authentication (active or inactive).
# \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on. # \param host_version: The firmware version of the cluster host. This is where the Stardust client is running on.
def __init__(self, cluster_id: str, host_guid: str, host_name: str, is_online: bool, status: str, 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, **kwargs) -> None: host_internal_ip: Optional[str] = None, host_version: Optional[str] = None,
friendly_name: Optional[str] = None, **kwargs) -> None:
self.cluster_id = cluster_id self.cluster_id = cluster_id
self.host_guid = host_guid self.host_guid = host_guid
self.host_name = host_name self.host_name = host_name
@ -24,6 +25,7 @@ class CloudClusterResponse(BaseCloudModel):
self.is_online = is_online self.is_online = is_online
self.host_version = host_version self.host_version = host_version
self.host_internal_ip = host_internal_ip self.host_internal_ip = host_internal_ip
self.friendly_name = friendly_name
super().__init__(**kwargs) super().__init__(**kwargs)
# Validates the model, raising an exception if the model is invalid. # Validates the model, raising an exception if the model is invalid.

View File

@ -22,6 +22,7 @@ class TestCloudOutputDevice(TestCase):
HOST_NAME = "ultimakersystem-ccbdd30044ec" HOST_NAME = "ultimakersystem-ccbdd30044ec"
HOST_GUID = "e90ae0ac-1257-4403-91ee-a44c9b7e8050" HOST_GUID = "e90ae0ac-1257-4403-91ee-a44c9b7e8050"
HOST_VERSION = "5.2.0" HOST_VERSION = "5.2.0"
FRIENDLY_NAME = "My Friendly Printer"
STATUS_URL = "{}/connect/v1/clusters/{}/status".format(CuraCloudAPIRoot, CLUSTER_ID) STATUS_URL = "{}/connect/v1/clusters/{}/status".format(CuraCloudAPIRoot, CLUSTER_ID)
PRINT_URL = "{}/connect/v1/clusters/{}/print/{}".format(CuraCloudAPIRoot, CLUSTER_ID, JOB_ID) PRINT_URL = "{}/connect/v1/clusters/{}/print/{}".format(CuraCloudAPIRoot, CLUSTER_ID, JOB_ID)
@ -37,7 +38,8 @@ class TestCloudOutputDevice(TestCase):
patched_method.start() patched_method.start()
self.cluster = CloudClusterResponse(self.CLUSTER_ID, self.HOST_GUID, self.HOST_NAME, is_online=True, self.cluster = CloudClusterResponse(self.CLUSTER_ID, self.HOST_GUID, self.HOST_NAME, is_online=True,
status="active", host_version=self.HOST_VERSION) status="active", host_version=self.HOST_VERSION,
friendly_name=self.FRIENDLY_NAME)
self.network = NetworkManagerMock() self.network = NetworkManagerMock()
self.account = MagicMock(isLoggedIn=True, accessToken="TestAccessToken") self.account = MagicMock(isLoggedIn=True, accessToken="TestAccessToken")
@ -60,7 +62,7 @@ class TestCloudOutputDevice(TestCase):
# We test for these in order to make sure the correct file type is selected depending on the firmware version. # We test for these in order to make sure the correct file type is selected depending on the firmware version.
def test_properties(self): def test_properties(self):
self.assertEqual(self.device.firmwareVersion, self.HOST_VERSION) self.assertEqual(self.device.firmwareVersion, self.HOST_VERSION)
self.assertEqual(self.device.name, self.HOST_NAME) self.assertEqual(self.device.name, self.FRIENDLY_NAME)
def test_status(self): def test_status(self):
self.device._update() self.device._update()

View File

@ -5853,10 +5853,10 @@
"description": "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway.", "description": "The minimum size of a line segment after slicing. If you increase this, the mesh will have a lower resolution. This may allow the printer to keep up with the speed it has to process g-code and will increase slice speed by removing details of the mesh that it can't process anyway.",
"type": "float", "type": "float",
"unit": "mm", "unit": "mm",
"default_value": 0.20, "default_value": 0.5,
"minimum_value": "0.001", "minimum_value": "0.001",
"minimum_value_warning": "0.02", "minimum_value_warning": "0.01",
"maximum_value_warning": "2", "maximum_value_warning": "3",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"meshfix_maximum_travel_resolution": "meshfix_maximum_travel_resolution":
@ -5865,8 +5865,8 @@
"description": "The minimum size of a travel line segment after slicing. If you increase this, the travel moves will have less smooth corners. This may allow the printer to keep up with the speed it has to process g-code, but it may cause model avoidance to become less accurate.", "description": "The minimum size of a travel line segment after slicing. If you increase this, the travel moves will have less smooth corners. This may allow the printer to keep up with the speed it has to process g-code, but it may cause model avoidance to become less accurate.",
"type": "float", "type": "float",
"unit": "mm", "unit": "mm",
"default_value": 0.5, "default_value": 1.0,
"value": "meshfix_maximum_resolution * speed_travel / speed_print", "value": "min(meshfix_maximum_resolution * speed_travel / speed_print, 2 * line_width)",
"minimum_value": "0.001", "minimum_value": "0.001",
"minimum_value_warning": "0.05", "minimum_value_warning": "0.05",
"maximum_value_warning": "10", "maximum_value_warning": "10",
@ -5879,10 +5879,10 @@
"description": "The maximum deviation allowed when reducing the resolution for the Maximum Resolution setting. If you increase this, the print will be less accurate, but the g-code will be smaller.", "description": "The maximum deviation allowed when reducing the resolution for the Maximum Resolution setting. If you increase this, the print will be less accurate, but the g-code will be smaller.",
"type": "float", "type": "float",
"unit": "mm", "unit": "mm",
"default_value": 0.005, "default_value": 0.05,
"minimum_value": "0.001", "minimum_value": "0.001",
"minimum_value_warning": "0.003", "minimum_value_warning": "0.01",
"maximum_value_warning": "0.1", "maximum_value_warning": "0.3",
"settable_per_mesh": true "settable_per_mesh": true
}, },
"support_skip_some_zags": "support_skip_some_zags":

View File

@ -118,7 +118,6 @@
"material_bed_temperature": { "maximum_value": "115" }, "material_bed_temperature": { "maximum_value": "115" },
"material_bed_temperature_layer_0": { "maximum_value": "115" }, "material_bed_temperature_layer_0": { "maximum_value": "115" },
"material_standby_temperature": { "value": "100" }, "material_standby_temperature": { "value": "100" },
"meshfix_maximum_resolution": { "value": "0.04" },
"multiple_mesh_overlap": { "value": "0" }, "multiple_mesh_overlap": { "value": "0" },
"optimize_wall_printing_order": { "value": "True" }, "optimize_wall_printing_order": { "value": "True" },
"prime_tower_enable": { "default_value": true }, "prime_tower_enable": { "default_value": true },

View File

@ -156,7 +156,8 @@
"wall_0_inset": { "value": "0" }, "wall_0_inset": { "value": "0" },
"wall_line_width_x": { "value": "round(line_width * 0.3 / 0.35, 2)" }, "wall_line_width_x": { "value": "round(line_width * 0.3 / 0.35, 2)" },
"wall_thickness": { "value": "1" }, "wall_thickness": { "value": "1" },
"meshfix_maximum_resolution": { "value": "0.04" }, "meshfix_maximum_resolution": { "value": "(speed_wall_0 + speed_wall_x) / 60" },
"meshfix_maximum_deviation": { "value": "layer_height / 2" },
"optimize_wall_printing_order": { "value": "True" }, "optimize_wall_printing_order": { "value": "True" },
"retraction_combing": { "default_value": "all" }, "retraction_combing": { "default_value": "all" },
"initial_layer_line_width_factor": { "value": "120" }, "initial_layer_line_width_factor": { "value": "120" },