diff --git a/cura/Backups/Backup.py b/cura/Backups/Backup.py index f935aa6af5..cc47df770e 100644 --- a/cura/Backups/Backup.py +++ b/cura/Backups/Backup.py @@ -1,12 +1,13 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + import io import os import re import shutil -from typing import Optional +from typing import Dict, Optional from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile from UM import i18nCatalog @@ -28,9 +29,9 @@ class Backup: # Re-use translation catalog. catalog = i18nCatalog("cura") - def __init__(self, zip_file: bytes = None, meta_data: dict = None) -> None: + def __init__(self, zip_file: bytes = None, meta_data: Dict[str, str] = None) -> None: self.zip_file = zip_file # type: Optional[bytes] - self.meta_data = meta_data # type: Optional[dict] + self.meta_data = meta_data # type: Optional[Dict[str, str]] ## Create a back-up from the current user config folder. def makeFromCurrent(self) -> None: diff --git a/cura/Backups/BackupsManager.py b/cura/Backups/BackupsManager.py index bc560a8dd9..67e2a222f1 100644 --- a/cura/Backups/BackupsManager.py +++ b/cura/Backups/BackupsManager.py @@ -1,6 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Optional, Tuple + +from typing import Dict, Optional, Tuple from UM.Logger import Logger from cura.Backups.Backup import Backup @@ -18,7 +19,7 @@ class BackupsManager: ## Get a back-up of the current configuration. # \return A tuple containing a ZipFile (the actual back-up) and a dict # containing some metadata (like version). - def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]: + def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, str]]]: self._disableAutoSave() backup = Backup() backup.makeFromCurrent() @@ -30,7 +31,7 @@ class BackupsManager: # \param zip_file A bytes object containing the actual back-up. # \param meta_data A dict containing some metadata that is needed to # restore the back-up correctly. - def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None: + def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, str]) -> None: if not meta_data.get("cura_release", None): # If there is no "cura_release" specified in the meta data, we don't execute a backup restore. Logger.log("w", "Tried to restore a backup without specifying a Cura version number.") @@ -43,13 +44,13 @@ class BackupsManager: if restored: # At this point, Cura will need to restart for the changes to take effect. # We don't want to store the data at this point as that would override the just-restored backup. - self._application.windowClosed(save_data=False) + self._application.windowClosed(save_data = False) ## Here we try to disable the auto-save plug-in as it might interfere with # restoring a back-up. - def _disableAutoSave(self): + def _disableAutoSave(self) -> None: self._application.setSaveDataEnabled(False) ## Re-enable auto-save after we're done. - def _enableAutoSave(self): + def _enableAutoSave(self) -> None: self._application.setSaveDataEnabled(True) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index d0563a5352..87f0eb543e 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -47,10 +47,10 @@ class BuildVolume(SceneNode): self._disallowed_area_color = None self._error_area_color = None - self._width = 0 - self._height = 0 - self._depth = 0 - self._shape = "" + self._width = 0 #type: float + self._height = 0 #type: float + self._depth = 0 #type: float + self._shape = "" #type: str self._shader = None @@ -154,19 +154,19 @@ class BuildVolume(SceneNode): if active_extruder_changed is not None: active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild) - def setWidth(self, width): + def setWidth(self, width: float) -> None: if width is not None: self._width = width - def setHeight(self, height): + def setHeight(self, height: float) -> None: if height is not None: self._height = height - def setDepth(self, depth): + def setDepth(self, depth: float) -> None: if depth is not None: self._depth = depth - def setShape(self, shape: str): + def setShape(self, shape: str) -> None: if shape: self._shape = shape @@ -294,7 +294,7 @@ class BuildVolume(SceneNode): if not self._width or not self._height or not self._depth: return - if not self._application._qml_engine: + if not self._engine_ready: return if not self._volume_outline_color: diff --git a/cura_app.py b/cura_app.py index a7059a3940..c3c766fdb1 100755 --- a/cura_app.py +++ b/cura_app.py @@ -12,14 +12,14 @@ from UM.Platform import Platform parser = argparse.ArgumentParser(prog = "cura", add_help = False) -parser.add_argument('--debug', - action='store_true', +parser.add_argument("--debug", + action="store_true", default = False, help = "Turn on the debug mode by setting this option." ) -parser.add_argument('--trigger-early-crash', - dest = 'trigger_early_crash', - action = 'store_true', +parser.add_argument("--trigger-early-crash", + dest = "trigger_early_crash", + action = "store_true", default = False, help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog." ) diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index ead527a61f..10f841fc43 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -286,9 +286,11 @@ class FlavorParser: self._cancelled = False # We obtain the filament diameter from the selected extruder to calculate line widths global_stack = CuraApplication.getInstance().getGlobalContainerStack() + + if not global_stack: + return None - if global_stack: - self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value") + self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value") scene_node = CuraSceneNode() diff --git a/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py index ad83aa2a24..c78351909d 100644 --- a/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py +++ b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py @@ -247,57 +247,57 @@ class PauseAtHeight(Script): prepend_gcode += ";added code by post processing\n" prepend_gcode += ";script: PauseAtHeight.py\n" if pause_at == "height": - prepend_gcode += ";current z: {z}\n".format(z=current_z) - prepend_gcode += ";current height: {height}\n".format(height=current_height) + prepend_gcode += ";current z: {z}\n".format(z = current_z) + prepend_gcode += ";current height: {height}\n".format(height = current_height) else: - prepend_gcode += ";current layer: {layer}\n".format(layer=current_layer) + prepend_gcode += ";current layer: {layer}\n".format(layer = current_layer) # Retraction - prepend_gcode += self.putValue(M=83) + "\n" + prepend_gcode += self.putValue(M = 83) + "\n" if retraction_amount != 0: - prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n" + prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n" # Move the head away - prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n" + prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n" # This line should be ok - prepend_gcode += self.putValue(G=1, X=park_x, Y=park_y, F=9000) + "\n" + prepend_gcode += self.putValue(G = 1, X = park_x, Y = park_y, F = 9000) + "\n" if current_z < 15: - prepend_gcode += self.putValue(G=1, Z=15, F=300) + "\n" + prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + "\n" # Set extruder standby temperature - prepend_gcode += self.putValue(M=104, S=standby_temperature) + "; standby temperature\n" + prepend_gcode += self.putValue(M = 104, S = standby_temperature) + "; standby temperature\n" # Wait till the user continues printing - prepend_gcode += self.putValue(M=0) + ";Do the actual pause\n" + prepend_gcode += self.putValue(M = 0) + ";Do the actual pause\n" # Set extruder resume temperature prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + "; resume temperature\n" # Push the filament back, if retraction_amount != 0: - prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n" + prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n" # Optionally extrude material if extrude_amount != 0: - prepend_gcode += self.putValue(G=1, E=extrude_amount, F=extrude_speed * 60) + "\n" + prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = extrude_speed * 60) + "\n" # and retract again, the properly primes the nozzle # when changing filament. if retraction_amount != 0: - prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n" + prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n" # Move the head back - prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n" - prepend_gcode += self.putValue(G=1, X=x, Y=y, F=9000) + "\n" + prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n" + prepend_gcode += self.putValue(G = 1, X = x, Y = y, F = 9000) + "\n" if retraction_amount != 0: - prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n" - prepend_gcode += self.putValue(G=1, F=9000) + "\n" - prepend_gcode += self.putValue(M=82) + "\n" + prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n" + prepend_gcode += self.putValue(G = 1, F = 9000) + "\n" + prepend_gcode += self.putValue(M = 82) + "\n" # reset extrude value to pre pause value - prepend_gcode += self.putValue(G=92, E=current_e) + "\n" + prepend_gcode += self.putValue(G = 92, E = current_e) + "\n" layer = prepend_gcode + layer diff --git a/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml b/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml index b4219d53bf..4978af6168 100644 --- a/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml +++ b/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml @@ -76,11 +76,26 @@ Item } } + Component + { + id: columnTextDelegate + Label + { + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + text: styleData.value || "" + elide: Text.ElideRight + color: UM.Theme.getColor("text_medium") + font: UM.Theme.getFont("default") + } + } + TableViewColumn { role: "machine" title: "Machine" width: Math.floor(table.width * 0.25) + delegate: columnTextDelegate } TableViewColumn { diff --git a/plugins/Toolbox/resources/qml/ToolboxFooter.qml b/plugins/Toolbox/resources/qml/ToolboxFooter.qml index 976ff46da6..5c2a6577ad 100644 --- a/plugins/Toolbox/resources/qml/ToolboxFooter.qml +++ b/plugins/Toolbox/resources/qml/ToolboxFooter.qml @@ -15,6 +15,7 @@ Item Label { text: catalog.i18nc("@info", "You will need to restart Cura before changes in packages have effect.") + color: UM.Theme.getColor("text") height: Math.floor(UM.Theme.getSize("toolbox_footer_button").height) verticalAlignment: Text.AlignVCenter anchors @@ -25,7 +26,7 @@ Item right: restartButton.right rightMargin: UM.Theme.getSize("default_margin").width } - color: UM.Theme.getColor("text") + } Button { diff --git a/plugins/Toolbox/src/Toolbox.py b/plugins/Toolbox/src/Toolbox.py index d3b9b37bc6..762e7027fb 100644 --- a/plugins/Toolbox/src/Toolbox.py +++ b/plugins/Toolbox/src/Toolbox.py @@ -224,6 +224,11 @@ class Toolbox(QObject, Extension): if not self._dialog: self._dialog = self._createDialog("Toolbox.qml") + + if not self._dialog: + Logger.log("e", "Unexpected error trying to create the 'Toolbox' dialog.") + return + self._dialog.show() # Apply enabled/disabled state to installed plugins @@ -231,9 +236,11 @@ class Toolbox(QObject, Extension): def _createDialog(self, qml_name: str) -> QObject: Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name) - path = os.path.join(cast(str, PluginRegistry.getInstance().getPluginPath(self.getPluginId())), "resources", "qml", qml_name) - if not path: - raise Exception("Failed to create toolbox QML path") + plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) + if not plugin_path: + return None + path = os.path.join(plugin_path, "resources", "qml", qml_name) + dialog = self._application.createQmlComponent(path, {"toolbox": self}) if not dialog: raise Exception("Failed to create toolbox dialog") diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index 6a5da04429..185430f3f3 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -128,6 +128,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): else: writer = CuraApplication.getInstance().getMeshFileHandler().getWriterByMimeType(cast(str, preferred_format["mime_type"])) + if not writer: + Logger.log("e", "Unexpected error when trying to get the FileWriter") + return + #This function pauses with the yield, waiting on instructions on which printer it needs to print with. if not writer: Logger.log("e", "Missing file or mesh writer!") diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py index 988559a3f5..b48a57e0a2 100644 --- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.py +++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.py @@ -170,11 +170,10 @@ class DiscoverUM3Action(MachineAction): Logger.log("d", "Creating additional ui components for UM3.") # Create networking dialog - path = os.path.join(cast(str, PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting")), "UM3InfoComponents.qml") - if not path: - Logger.log("w", "Could not get QML path for UM3 network printing UI.") + plugin_path = PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting") + if not plugin_path: return - + path = os.path.join(plugin_path, "UM3InfoComponents.qml") self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) if not self.__additional_components_view: Logger.log("w", "Could not create ui components for UM3.") diff --git a/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py b/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py new file mode 100644 index 0000000000..22df0d6487 --- /dev/null +++ b/plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py @@ -0,0 +1,35 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import configparser #To parse the resulting config files. +import pytest #To register tests with. + +import VersionUpgrade34to40 #The module we're testing. + +## Creates an instance of the upgrader to test with. +@pytest.fixture +def upgrader(): + return VersionUpgrade34to40.VersionUpgrade34to40() + +test_upgrade_version_nr_data = [ + ("Empty config file", + """[general] + version = 5 + [metadata] + setting_version = 4 +""" + ) +] + +## Tests whether the version numbers are updated. +@pytest.mark.parametrize("test_name, file_data", test_upgrade_version_nr_data) +def test_upgradeVersionNr(test_name, file_data, upgrader): + #Perform the upgrade. + _, upgraded_instances = upgrader.upgradePreferences(file_data, "") + upgraded_instance = upgraded_instances[0] + parser = configparser.ConfigParser(interpolation = None) + parser.read_string(upgraded_instance) + + #Check the new version. + assert parser["general"]["version"] == "6" + assert parser["metadata"]["setting_version"] == "5" \ No newline at end of file diff --git a/plugins/X3DReader/X3DReader.py b/plugins/X3DReader/X3DReader.py index f529f76509..da0502a7ec 100644 --- a/plugins/X3DReader/X3DReader.py +++ b/plugins/X3DReader/X3DReader.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from math import pi, sin, cos, sqrt +from typing import Dict import numpy @@ -42,8 +43,7 @@ class X3DReader(MeshReader): def __init__(self) -> None: super().__init__() self._supported_extensions = [".x3d"] - # TODO: Remove after testing because it appears to be unused - # self._namespaces = {} + self._namespaces = {} # type: Dict[str, str] # Main entry point # Reads the file, returns a SceneNode (possibly with nested ones), or None