diff options
author | Lipu Fei <lipu.fei815@gmail.com> | 2018-05-01 12:56:34 +0300 |
---|---|---|
committer | Lipu Fei <lipu.fei815@gmail.com> | 2018-05-01 12:56:34 +0300 |
commit | 9a5fb47a6e5b63e4acea0e2f50620c65e6a4e66c (patch) | |
tree | 8668eb7b7c3041c772facfe5fa9a981e2038a5e4 /plugins | |
parent | cc207a3f9246a589d5db1be409b776730fbcd77c (diff) | |
parent | 0d8674eceb736290a5f980184e77adc097e47f13 (diff) |
Merge master into material marketplace
Diffstat (limited to 'plugins')
21 files changed, 575 insertions, 143 deletions
diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 4baa930cba..5cd0ef5ced 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -16,6 +16,7 @@ from UM.Application import Application from UM.Logger import Logger from UM.i18n import i18nCatalog from UM.Signal import postponeSignals, CompressTechnique +from UM.Settings.ContainerFormatError import ContainerFormatError from UM.Settings.ContainerStack import ContainerStack from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.InstanceContainer import InstanceContainer @@ -303,7 +304,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader): containers_found_dict["quality_changes"] = True # Check if there really is a conflict by comparing the values instance_container = InstanceContainer(container_id) - instance_container.deserialize(serialized, file_name = instance_container_file_name) + try: + instance_container.deserialize(serialized, file_name = instance_container_file_name) + except ContainerFormatError: + Logger.logException("e", "Failed to deserialize InstanceContainer %s from project file %s", + instance_container_file_name, file_name) + return ThreeMFWorkspaceReader.PreReadResult.failed if quality_changes[0] != instance_container: quality_changes_conflict = True elif container_type == "quality": @@ -610,8 +616,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader): definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id) if not definitions: definition_container = DefinitionContainer(container_id) - definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"), - file_name = definition_container_file) + try: + definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"), + file_name = definition_container_file) + except ContainerFormatError: + # We cannot just skip the definition file because everything else later will just break if the + # machine definition cannot be found. + Logger.logException("e", "Failed to deserialize definition file %s in project file %s", + definition_container_file, file_name) + definition_container = self._container_registry.findDefinitionContainers(id = "fdmprinter")[0] #Fall back to defaults. self._container_registry.addContainer(definition_container) Job.yieldThread() @@ -650,8 +663,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if to_deserialize_material: material_container = xml_material_profile(container_id) - material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"), - file_name = container_id + "." + self._material_container_suffix) + try: + material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"), + file_name = container_id + "." + self._material_container_suffix) + except ContainerFormatError: + Logger.logException("e", "Failed to deserialize material file %s in project file %s", + material_container_file, file_name) + continue if need_new_name: new_name = ContainerRegistry.getInstance().uniqueName(material_container.getName()) material_container.setName(new_name) @@ -675,7 +693,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # To solve this, we schedule _updateActiveMachine() for later so it will have the latest data. self._updateActiveMachine(global_stack) - # Load all the nodes / meshdata of the workspace + # Load all the nodes / mesh data of the workspace nodes = self._3mf_mesh_reader.read(file_name) if nodes is None: nodes = [] diff --git a/plugins/ChangeLogPlugin/ChangeLog.py b/plugins/ChangeLogPlugin/ChangeLog.py index c31d1337ef..030d854d3f 100644 --- a/plugins/ChangeLogPlugin/ChangeLog.py +++ b/plugins/ChangeLogPlugin/ChangeLog.py @@ -23,9 +23,9 @@ class ChangeLog(Extension, QObject,): self._changelog_context = None version_string = Application.getInstance().getVersion() if version_string is not "master": - self._version = Version(version_string) + self._current_app_version = Version(version_string) else: - self._version = None + self._current_app_version = None self._change_logs = None Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) @@ -76,7 +76,7 @@ class ChangeLog(Extension, QObject,): self._change_logs[open_version][open_header].append(line) def _onEngineCreated(self): - if not self._version: + if not self._current_app_version: return #We're on dev branch. if Preferences.getInstance().getValue("general/latest_version_changelog_shown") == "master": @@ -91,7 +91,7 @@ class ChangeLog(Extension, QObject,): if not Application.getInstance().getGlobalContainerStack(): return - if self._version > latest_version_shown: + if self._current_app_version > latest_version_shown: self.showChangelog() def showChangelog(self): diff --git a/plugins/ChangeLogPlugin/ChangeLog.txt b/plugins/ChangeLogPlugin/ChangeLog.txt index 2b7f1682db..d132f17b5c 100755 --- a/plugins/ChangeLogPlugin/ChangeLog.txt +++ b/plugins/ChangeLogPlugin/ChangeLog.txt @@ -1,94 +1,122 @@ [3.3.0] -*Configuration/sync button -This new feature for network connected printers allows you to easily synchronize with all available configurations in your Cura Connect group. The name of the Host of the group is automatically pulled from the API, and network printers and local printers are now separated in the list for clarity. -*Single extrusion mode Ultimaker 3 -Single extrusion mode allows to disable the unused extruder. This has the following positive effects: -- Printing profiles are optimized to print with a single extruder, increasing print quality. -- Global settings, like build plate temperature, are set for the active extruder. -- The ‘print one at a time’ feature is now available. +*Profile for the Ultimaker S5 +New printer profile for the Ultimaker S5, our latest 3D printer. -*Plugin browser updates -The plugin browser has been updated to look and feel similar to the other UI elements. The author name is clickable, which opens email for support. The plugins can now be uninstalled with an uninstall button. +*Profile for Tough PLA +New material profile for Tough PLA, a material that prints with the convenience of PLA but with toughness and impact strength similar to ABS. -*Block support - fieldOfView -This new feature allows you to easily add small cubic areas which prevent support material from being generated. Select the ‘block support’ tool and click the model to place a cube. This cube can be scaled, rotated and moved with the adjustment tools to modify it. +*Configuration/sync button +Configuration and synchronization button now available for Ultimaker network-connected printers to easily synchronize all possible available configurations in your Cura Connect group. The name of the group host is automatically pulled from the API, and network printers and local printers are separated in the list for clarity. -*Setting visibility preset - fieldOfView -A convenient way to select a preset of settings to be visible. These presets guide you to find the most important Cura settings in an incremental way. A small menu is located next to the ‘search bar’ to easily access these new setting visibility presets. +*Setting visibility preset +Presets guide you to find the most important settings incrementally. A small menu is located next to the search bar to easily access these new setting visibility presets. Contributed by fieldOfView. -*Model assistant -This feature provides useful information to the user based on their model. For now, it informs the user when printing models with a footprint larger than 15x15x10cm, printed with ABS, PC, PP or CPE+, that they may want to make changes to the model such as filleting sharp edges. +*Print/save hotkey +Send a print to the queue using Ctrl + P (Windows/Linux) or Cmd + P (Mac). If no printer is present on the network, it will save to file instead. -*Circular prime tower -The prime tower shape has changed from rectangular to circular. This shape should increase the adhesion to the build plate, overall strength, and prevent delamination of the layers. +*3D model assistant +Models sliced to print with ABS, PC, PP or CPE+ that have a larger footprint than 150 x 150 x 150 mm will notify the user with an icon and popup of how they can achieve the best possible print quality and reliability. -*Connected infill lines -Grid and triangular infill lines are now connected. This strengthens the internal structure of the infill and results in a more constant flow of filament while printing. +*Refactored machine manager +Refactored machine manager resulted in less manager classes. Changing settings, materials, variants and machines is now clearer. This results in an overall speed up when making changes. -*Real bridging - smartavionics -This new experimental feature adds bridge detection which adjusts the print speed, flow and fan speed to enhance print quality on bridging parts. +*Multiply models faster +Significant speed increase when multiplying models. -*Initial layer flow -The flow of the initial layer can now be adjusted separately. The new setting is found in the ‘material’ category. +*Auto slicing disabled by default +The auto slice tool is now disabled by default. Users can still enable the feature in the user preferences dialog. -*Initial travel move retraction - smartavionics -The initial travel move–before printing the brim/skirt–is now retracted. This reduces the chance of the prime blob dragging into the printed part. +*Updated fonts +Default font changed to NotoSans to increase readability and consistency with Cura Connect. -*Updated CuraEngine executable - thopiekar -The CuraEngine executable now contains a dedicated icon, author information and a license. +*Plugin browser look and feel +The plugin browser has been updated with a better look and feel to bring it in line with other UI elements. The author name is clickable, which opens email for support. Plugins can now be uninstalled with an uninstall button. -*Removed unnecessary retractions in spiralize - smartavionics -This feature removes retractions on layer change in spiralize mode, improving model quality. +*Show tooltip for unavailable profile +Tooltips have been added to incompatible settings, to give explanations why they are incompatible. -*Improve travel paths - smartavionics -This feature optimizes the travel paths in a print, reducing print times and oozing of the material. +*Empty material slots Ultimaker 3 +When a material is not loaded in the Ultimaker 3, it is now displayed as ‘Empty’ rather than ‘Unknown’. -*Refactor machine manager -The refactor of the machine manager resulted in fewer manager classes and made changing settings, materials, variants and machines more streamlined. This results in a significant overall improvement in speed when changing any of the aforementioned properties. +*Send over network confirmation +When a print job is sent to a networked printer, a popup will confirm the job was sent, with a button to redirect the user to the monitor in Cura Connect. -*Speed up ‘multiply model’ -The viewport frame rate is significantly increased when working with multiple models. +*Post processing scripts +Fixed an issue where post processing scripts could be lost between sessions. Post processing scripts are now persistent between sessions. -*New font: Noto Sans -The font of Ultimaker Cura has been changed to Noto Sans. This improves readability and consistency with Cura Connect. The font update also fixes some rendering issues on macOS. +*Single extrusion mode +Disable an extruder on a dual extrusion printer, so you are not limited by the other extruder’s parameters. To disable an extruder, right click it in the right panel, and select ‘Disable extruder’ to disable it. Re-enable by right clicking and selecting ‘enable extruder’. Printing profiles are optimized for the active extruder, as well as global settings, such as build plate temperature, to achieve better print quality. Using single extrusion mode also makes the ‘print one at a time’ feature available for the Ultimaker 3 and Ultimaker S5. -*Plugin updates - Pheneeny -Three new plugins were added to Ultimaker Cura; Scalable extra prime, Print temperature offset and Enclosure fan. -Also Alexander Roessler made a new NGC writer plugin so you can export files in NGC format. +*New UFP extension +UFP (Ultimaker format package) is a new file extension that contains compressed gcode and a preview thumbnail. Using this extension enables a model preview (similar to the solid view) on the Ultimaker S5 touchscreen and in Cura Connect. -*Pre-heat extruders - fieldOfView -This new feature allows to preheat the extruders in the printer monitor. +*Compressed Gcode +Gcode saved from Ultimaker Cura using the Ultimaker 3 profile is compressed (using gzip) to save space on printers. + +*Circular prime tower +Prime towers are now circular, resulting in a less jerky print head action, a more robust structure, better layer adhesion, and better build plate adhesion compared to square prime towers, reducing the chance of prime tower failure mid-print. + +*Connected infill lines +Grid and triangular infill patterns now have connected lines for a more constant flow, better model rigidity, and reduced impact on the quality of the outer wall. -*Persistent post-processing scripts -Scripts are no longer erased after restarting Ultimaker Cura. +*Support blocker - fieldOfView +Generate a cube mesh to prevent support material generation in specific areas of a model. Each cube can be scaled, rotated, and moved with the standard adjustment tools to fit your requirements. When the support blocker tool is selected, single click in the area you want to block support to generate a mesh. If it is positioned by accident, click it again to remove it. -*GZ Reader -By default, G-code for Ultimaker 3 machines is now saved as gzipped G-Code. +*Real bridging - smartavionics +New experimental feature that detects bridges, adjusting the print speed, slow and fan speed to enhance print quality on bridging parts. -*Print preview image -Adds a preview image of the gcode to the slice information. This can be shown in Cura Connect. +*Updated CuraEngine executable - thopiekar +The CuraEngine executable now contains a dedicated icon, author information and a license. *Use RapidJSON and ClipperLib from system libraries -This reduces both the amount of trouble required to keep these libraries up to date (the operating system is now responsible) as well as reducing the amount of code we’re shipping. +Application updated to use verified copies of libraries, reducing maintenance time keeping them up to date (the operating system is now responsible), as well as reducing the amount of code shipped (as necessary code is already on the user’s system). + +*Initial layer flow +New setting in the ‘material’ category where the initial layer flow can be adjusted. + +*Initial travel move retraction - smartavionics +Retraction has been added to the initial travel move, reducing the chance of prime blobs getting dragged into parts before brim/skirts are printed. + +*Unnecessary retractions in spiralize - smartavionics +Removes retractions on layer change in spiralize mode, improving model quality. -*TweakAtZ renamed to ChangeAtZ -The TweakAtZ script has been renamed to ChangeAtZ to be more consistent with the rest of the scripts. +*Faster travel paths - smartavionics +Until now, the path order optimizer worked on the basis that the shortest possible route could be taken from any one point to another. When combing is used, any route may longer, due to the need to route around obstacles. Now, it can use the combed distances to give more consistent results. -*XML material profile improvements -XML material profiles are now checked before importing into Ultimaker Cura. ALso, XML material profiles can now contain Ultimaker Cura-specific properties (by fieldOfView). +*New plugins - Pheneeny +Three new plugins have been added to Ultimaker Cura: Scalable extra prime, Print temperature offset, and Enclosure fan. -*Ultimaker 3 network status improvements -- When a material is not loaded in the Ultimaker 3, Cura now displays it as ‘Empty’ rather than ‘Unknown’. -- When an Ultimaker 3 is in maintenance mode, Cura now displays it as ‘Unavailable’ rather than ‘Unknown’. +*Pre-heat extruders - fieldOfView +This new feature allows to preheat the extruders in the printer monitor. -*Bug Fixes -- Cura Engine no longer crashes when slicing with a material at 0°C. -- Cura reconnects to networked printers after connectivity loss. -- Pause at height ‘redo layers’ broken no longer results in failed prints. -- Setting reset icon no longer remains visible after resetting. -- The infill density in the recommended mode now applies to all extruders instead of extruder 1. -- The maximum number of allowed extrusions for all 0.25mm Polypropylene profile prints has been fixed. +*Renamed TweakAtZ to ‘ChangeAtZ’ +This script has been renamed to be more consistent with other scripts. + +*Import XML material profile checks +XML material profile files are now checked before import in Ultimaker Cura to avoid potential issues. Contributed by fieldOfView. + +*Bug fixes +- Slice engine crash default temperature 0. Fixed an issue where the slicing engine could crash when slicing with a material at 0°C. +- Network printer reconnect. Fixed an issue where the user could not connect to the printer after losing connection. +- Pause at height ‘redo layers’ broken. Fixed an issue where setting ‘pause at height redo layers’ to 1 or more would cause failed prints. +- Reset icon fix. Fixed an issue where manually reverting a default print profile value instead of using the reset button would cause the reset icon to remain. +- Infill density for all extruders. The infill density in the recommended mode now applies to all extruders instead of extruder 1. +- Polypropylene 0.25mm print profile. Fixed the maximum number of allowed extrusions for all 0.25mm Polypropylene profile prints. +- SolidWorks plugin. Replaced comtypes module with win32com to fix issues. +- Font rendering issues. Fixed font rendering issues on Max OSX. +- Slice engine avoids broken wall segments. Fixed an issue where narrow walls created broken line segments by testing for such situations and using slightly narrow lines in those cases. + +*Third party printers + +- FABtotum TPU profiles. Added third-party material profiles for TPU. Contributed by krios-fabteam. +- Dagoma profiles. Updated printer profiles contributed by dagoma3d. +- uBuild profile. Updated printer profiles contributed by uBuild-3D. +- Cartesio printer updates. Updated profiles contributed by maukcc. +- Printrbot Simple Maker's Kit 1405. Profiles contributed by timur-tabi. +- Added SeeMeCNC. Profiles contributed by pouncingiguana. +- Velleman Vertex. Updated printer and quality profiles contributed by velbn. +- gMax 1.5. Profiles contributed by gordo3di. [3.2.1] *Bug fixes diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py index 12434d2c3f..d7370326e4 100644 --- a/plugins/CuraProfileReader/CuraProfileReader.py +++ b/plugins/CuraProfileReader/CuraProfileReader.py @@ -1,9 +1,10 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import configparser from UM.PluginRegistry import PluginRegistry from UM.Logger import Logger +from UM.Settings.ContainerFormatError import ContainerFormatError from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make. from cura.ReaderWriters.ProfileReader import ProfileReader @@ -77,7 +78,10 @@ class CuraProfileReader(ProfileReader): profile.addMetaDataEntry("type", "quality_changes") try: profile.deserialize(serialized) - except Exception as e: # Parsing error. This is not a (valid) Cura profile then. + except ContainerFormatError as e: + Logger.log("e", "Error in the format of a container: %s", str(e)) + return None + except Exception as e: Logger.log("e", "Error while trying to parse profile: %s", str(e)) return None return profile diff --git a/plugins/GCodeProfileReader/GCodeProfileReader.py b/plugins/GCodeProfileReader/GCodeProfileReader.py index f255950c36..4b50a600ba 100644 --- a/plugins/GCodeProfileReader/GCodeProfileReader.py +++ b/plugins/GCodeProfileReader/GCodeProfileReader.py @@ -1,9 +1,10 @@ -# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import re #Regular expressions for parsing escape characters in the settings. import json +from UM.Settings.ContainerFormatError import ContainerFormatError from UM.Settings.InstanceContainer import InstanceContainer from UM.Logger import Logger from UM.i18n import i18nCatalog @@ -113,6 +114,9 @@ def readQualityProfileFromString(profile_string): profile = InstanceContainer("") try: profile.deserialize(profile_string) + except ContainerFormatError as e: + Logger.log("e", "Corrupt profile in this g-code file: %s", str(e)) + return None except Exception as e: # Not a valid g-code file. Logger.log("e", "Unable to serialise the profile: %s", str(e)) return None diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index 8e7756ac3b..2679cc23a4 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -362,12 +362,14 @@ class FlavorParser: else: Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type) - # When the layer change is reached, the polygon is computed so we have just one layer per layer per extruder + # When the layer change is reached, the polygon is computed so we have just one layer per extruder if self._is_layers_in_file and line[:len(self._layer_keyword)] == self._layer_keyword: try: layer_number = int(line[len(self._layer_keyword):]) self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() + # Start the new layer at the end position of the last layer + current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType]) # When using a raft, the raft layers are stored as layers < 0, it mimics the same behavior # as in ProcessSlicedLayersJob @@ -406,7 +408,11 @@ class FlavorParser: self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) current_path.clear() + # When changing tool, store the end point of the previous path, then process the code and finally + # add another point with the new position of the head. + current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType]) current_position = self.processTCode(T, line, current_position, current_path) + current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType]) if line.startswith("M"): M = self._getInt(line, "M") diff --git a/plugins/ModelChecker/ModelChecker.py b/plugins/ModelChecker/ModelChecker.py index f85a7a249a..297844a0a3 100644 --- a/plugins/ModelChecker/ModelChecker.py +++ b/plugins/ModelChecker/ModelChecker.py @@ -31,9 +31,10 @@ class ModelChecker(QObject, Extension): Application.getInstance().initializationFinished.connect(self._pluginsInitialized) Application.getInstance().getController().getScene().sceneChanged.connect(self._onChanged) + Application.getInstance().globalContainerStackChanged.connect(self._onChanged) ## Pass-through to allow UM.Signal to connect with a pyqtSignal. - def _onChanged(self, _): + def _onChanged(self, *args, **kwargs): self.onChanged.emit() ## Called when plug-ins are initialized. @@ -53,7 +54,6 @@ class ModelChecker(QObject, Extension): # has not done yet. global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack is None: - Application.getInstance().callLater(lambda: self.onChanged.emit()) return False material_shrinkage = self._getMaterialShrinkage() diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.py b/plugins/PostProcessingPlugin/PostProcessingPlugin.py index d000c67dde..1bbe4bb66c 100644 --- a/plugins/PostProcessingPlugin/PostProcessingPlugin.py +++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.py @@ -208,7 +208,7 @@ class PostProcessingPlugin(QObject, Extension): for script_str in scripts_list_strs.split("\n"): #Encoded config files should never contain three newlines in a row. At most 2, just before section headers. if not script_str: #There were no scripts in this one (or a corrupt file caused more than 3 consecutive newlines here). continue - script_str = script_str.replace("\\n", "\n").replace("\\\\", "\\") #Unescape escape sequences. + script_str = script_str.replace(r"\\\n", "\n").replace(r"\\\\", "\\\\") #Unescape escape sequences. script_parser = configparser.ConfigParser(interpolation = None) script_parser.optionxform = str #Don't transform the setting keys as they are case-sensitive. script_parser.read_string(script_str) @@ -241,7 +241,7 @@ class PostProcessingPlugin(QObject, Extension): parser.write(serialized) serialized.seek(0) script_str = serialized.read() - script_str = script_str.replace("\\", "\\\\").replace("\n", "\\n") #Escape newlines because configparser sees those as section delimiters. + script_str = script_str.replace("\\\\", r"\\\\").replace("\n", r"\\\n") #Escape newlines because configparser sees those as section delimiters. script_list_strs.append(script_str) script_list_strs = "\n".join(script_list_strs) #ConfigParser should never output three newlines in a row when serialised, so it's a safe delimiter. diff --git a/plugins/PostProcessingPlugin/Script.py b/plugins/PostProcessingPlugin/Script.py index 7f419cd422..d844705f1c 100644 --- a/plugins/PostProcessingPlugin/Script.py +++ b/plugins/PostProcessingPlugin/Script.py @@ -1,12 +1,12 @@ # Copyright (c) 2015 Jaime van Kessel -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # The PostProcessingPlugin is released under the terms of the AGPLv3 or higher. -from UM.Logger import Logger from UM.Signal import Signal, signalemitter from UM.i18n import i18nCatalog # Setting stuff import from UM.Application import Application +from UM.Settings.ContainerFormatError import ContainerFormatError from UM.Settings.ContainerStack import ContainerStack from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.DefinitionContainer import DefinitionContainer @@ -39,8 +39,12 @@ class Script: self._definition = definitions[0] else: self._definition = DefinitionContainer(setting_data["key"]) - self._definition.deserialize(json.dumps(setting_data)) - ContainerRegistry.getInstance().addContainer(self._definition) + try: + self._definition.deserialize(json.dumps(setting_data)) + ContainerRegistry.getInstance().addContainer(self._definition) + except ContainerFormatError: + self._definition = None + return self._stack.addContainer(self._definition) self._instance = InstanceContainer(container_id="ScriptInstanceContainer") self._instance.setDefinition(self._definition.getId()) diff --git a/plugins/SliceInfoPlugin/MoreInfoWindow.qml b/plugins/SliceInfoPlugin/MoreInfoWindow.qml new file mode 100644 index 0000000000..985ebe94a2 --- /dev/null +++ b/plugins/SliceInfoPlugin/MoreInfoWindow.qml @@ -0,0 +1,151 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.7 +import QtQuick.Window 2.2 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 + +import UM 1.3 as UM +import Cura 1.0 as Cura + + +UM.Dialog +{ + id: baseDialog + title: catalog.i18nc("@title:window", "More information on anonymous data collection") + visible: false + + minimumWidth: 500 * screenScaleFactor + minimumHeight: 400 * screenScaleFactor + width: minimumWidth + height: minimumHeight + + property bool allowSendData: true // for saving the user's choice + + onAccepted: manager.setSendSliceInfo(allowSendData) + + onVisibilityChanged: + { + if (visible) + { + baseDialog.allowSendData = UM.Preferences.getValue("info/send_slice_info"); + if (baseDialog.allowSendData) + { + allowSendButton.checked = true; + } + else + { + dontSendButton.checked = true; + } + } + } + + Item + { + id: textRow + anchors + { + top: parent.top + bottom: radioButtonsRow.top + bottomMargin: UM.Theme.getSize("default_margin").height + left: parent.left + right: parent.right + } + + Label + { + id: headerText + anchors + { + top: parent.top + left: parent.left + right: parent.right + } + + text: catalog.i18nc("@text:window", "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent.") + wrapMode: Text.WordWrap + } + + TextArea + { + id: exampleData + anchors + { + top: headerText.bottom + topMargin: UM.Theme.getSize("default_margin").height + bottom: parent.bottom + bottomMargin: UM.Theme.getSize("default_margin").height + left: parent.left + right: parent.right + } + + text: manager.getExampleData() + readOnly: true + textFormat: TextEdit.PlainText + } + } + + Column + { + id: radioButtonsRow + width: parent.width + anchors.bottom: buttonRow.top + anchors.bottomMargin: UM.Theme.getSize("default_margin").height + + ExclusiveGroup { id: group } + + RadioButton + { + id: dontSendButton + text: catalog.i18nc("@text:window", "I don't want to send these data") + exclusiveGroup: group + onClicked: + { + baseDialog.allowSendData = !checked; + } + } + RadioButton + { + id: allowSendButton + text: catalog.i18nc("@text:window", "Allow sending these data to Ultimaker and help us improve Cura") + exclusiveGroup: group + onClicked: + { + baseDialog.allowSendData = checked; + } + } + } + + Item + { + id: buttonRow + anchors.bottom: parent.bottom + width: parent.width + anchors.bottomMargin: UM.Theme.getSize("default_margin").height + + UM.I18nCatalog { id: catalog; name: "cura" } + + Button + { + anchors.right: parent.right + text: catalog.i18nc("@action:button", "OK") + onClicked: + { + baseDialog.accepted() + baseDialog.hide() + } + } + + Button + { + anchors.left: parent.left + text: catalog.i18nc("@action:button", "Cancel") + onClicked: + { + baseDialog.rejected() + baseDialog.hide() + } + } + } +} diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index d618f63b53..82e07da464 100755 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -1,8 +1,12 @@ -# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from cura.CuraApplication import CuraApplication -from cura.Settings.ExtruderManager import ExtruderManager +import json +import os +import platform +import time + +from PyQt5.QtCore import pyqtSlot, QObject from UM.Extension import Extension from UM.Application import Application @@ -11,18 +15,11 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Message import Message from UM.i18n import i18nCatalog from UM.Logger import Logger - -import time - +from UM.PluginRegistry import PluginRegistry from UM.Qt.Duration import DurationFormat from .SliceInfoJob import SliceInfoJob -import platform -import math -import urllib.request -import urllib.parse -import json catalog = i18nCatalog("cura") @@ -30,15 +27,19 @@ catalog = i18nCatalog("cura") ## This Extension runs in the background and sends several bits of information to the Ultimaker servers. # The data is only sent when the user in question gave permission to do so. All data is anonymous and # no model files are being sent (Just a SHA256 hash of the model). -class SliceInfo(Extension): +class SliceInfo(QObject, Extension): info_url = "https://stats.ultimaker.com/api/cura" - def __init__(self): - super().__init__() + def __init__(self, parent = None): + QObject.__init__(self, parent) + Extension.__init__(self) Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted) Preferences.getInstance().addPreference("info/send_slice_info", True) Preferences.getInstance().addPreference("info/asked_send_slice_info", False) + self._more_info_dialog = None + self._example_data_content = None + if not Preferences.getInstance().getValue("info/asked_send_slice_info"): self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymized usage statistics."), lifetime = 0, @@ -47,32 +48,64 @@ class SliceInfo(Extension): self.send_slice_info_message.addAction("Dismiss", name = catalog.i18nc("@action:button", "Allow"), icon = None, description = catalog.i18nc("@action:tooltip", "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing.")) - self.send_slice_info_message.addAction("Disable", name = catalog.i18nc("@action:button", "Disable"), icon = None, - description = catalog.i18nc("@action:tooltip", "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."), button_style = Message.ActionButtonStyle.LINK) + self.send_slice_info_message.addAction("MoreInfo", name = catalog.i18nc("@action:button", "More info"), icon = None, + description = catalog.i18nc("@action:tooltip", "See more information on what data Cura sends."), button_style = Message.ActionButtonStyle.LINK) self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered) self.send_slice_info_message.show() + Application.getInstance().initializationFinished.connect(self._onAppInitialized) + + def _onAppInitialized(self): + if self._more_info_dialog is None: + self._more_info_dialog = self._createDialog("MoreInfoWindow.qml") + ## Perform action based on user input. # Note that clicking "Disable" won't actually disable the data sending, but rather take the user to preferences where they can disable it. def messageActionTriggered(self, message_id, action_id): Preferences.getInstance().setValue("info/asked_send_slice_info", True) - if action_id == "Disable": - Preferences.getInstance().addPreference("info/send_slice_info", False) + if action_id == "MoreInfo": + self.showMoreInfoDialog() self.send_slice_info_message.hide() + def showMoreInfoDialog(self): + if self._more_info_dialog is None: + self._more_info_dialog = self._createDialog("MoreInfoWindow.qml") + self._more_info_dialog.open() + + def _createDialog(self, qml_name): + Logger.log("d", "Creating dialog [%s]", qml_name) + file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), qml_name) + dialog = Application.getInstance().createQmlComponent(file_path, {"manager": self}) + return dialog + + @pyqtSlot(result = str) + def getExampleData(self) -> str: + if self._example_data_content is None: + file_path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "example_data.json") + with open(file_path, "r", encoding = "utf-8") as f: + self._example_data_content = f.read() + return self._example_data_content + + @pyqtSlot(bool) + def setSendSliceInfo(self, enabled: bool): + Preferences.getInstance().setValue("info/send_slice_info", enabled) + def _onWriteStarted(self, output_device): try: if not Preferences.getInstance().getValue("info/send_slice_info"): Logger.log("d", "'info/send_slice_info' is turned off.") return # Do nothing, user does not want to send data - global_container_stack = Application.getInstance().getGlobalContainerStack() - print_information = Application.getInstance().getPrintInformation() + application = Application.getInstance() + machine_manager = application.getMachineManager() + print_information = application.getPrintInformation() + + global_stack = machine_manager.activeMachine data = dict() # The data that we're going to submit. data["time_stamp"] = time.time() data["schema_version"] = 0 - data["cura_version"] = Application.getInstance().getVersion() + data["cura_version"] = application.getVersion() active_mode = Preferences.getInstance().getValue("cura/active_mode") if active_mode == 0: @@ -80,7 +113,7 @@ class SliceInfo(Extension): else: data["active_mode"] = "custom" - definition_changes = global_container_stack.definitionChanges + definition_changes = global_stack.definitionChanges machine_settings_changed_by_user = False if definition_changes.getId() != "empty": # Now a definition_changes container will always be created for a stack, @@ -92,16 +125,17 @@ class SliceInfo(Extension): data["language"] = Preferences.getInstance().getValue("general/language") data["os"] = {"type": platform.system(), "version": platform.version()} - data["active_machine"] = {"definition_id": global_container_stack.definition.getId(), "manufacturer": global_container_stack.definition.getMetaData().get("manufacturer","")} + data["active_machine"] = {"definition_id": global_stack.definition.getId(), + "manufacturer": global_stack.definition.getMetaDataEntry("manufacturer", "")} # add extruder specific data to slice info data["extruders"] = [] - extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) + extruders = list(global_stack.extruders.values()) extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position")) for extruder in extruders: extruder_dict = dict() - extruder_dict["active"] = ExtruderManager.getInstance().getActiveExtruderStack() == extruder + extruder_dict["active"] = machine_manager.activeStack == extruder extruder_dict["material"] = {"GUID": extruder.material.getMetaData().get("GUID", ""), "type": extruder.material.getMetaData().get("material", ""), "brand": extruder.material.getMetaData().get("brand", "") @@ -123,11 +157,11 @@ class SliceInfo(Extension): extruder_dict["extruder_settings"] = extruder_settings data["extruders"].append(extruder_dict) - data["quality_profile"] = global_container_stack.quality.getMetaData().get("quality_type") + data["quality_profile"] = global_stack.quality.getMetaData().get("quality_type") data["models"] = [] # Listing all files placed on the build plate - for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()): + for node in DepthFirstIterator(application.getController().getScene().getRoot()): if node.callDecoration("isSliceable"): model = dict() model["hash"] = node.getMeshData().getHash() @@ -173,28 +207,28 @@ class SliceInfo(Extension): "total": int(print_information.currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))} print_settings = dict() - print_settings["layer_height"] = global_container_stack.getProperty("layer_height", "value") + print_settings["layer_height"] = global_stack.getProperty("layer_height", "value") # Support settings - print_settings["support_enabled"] = global_container_stack.getProperty("support_enable", "value") - print_settings["support_extruder_nr"] = int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr")) + print_settings["support_enabled"] = global_stack.getProperty("support_enable", "value") + print_settings["support_extruder_nr"] = int(global_stack.getExtruderPositionValueWithDefault("support_extruder_nr")) # Platform adhesion settings - print_settings["adhesion_type"] = global_container_stack.getProperty("adhesion_type", "value") + print_settings["adhesion_type"] = global_stack.getProperty("adhesion_type", "value") # Shell settings - print_settings["wall_line_count"] = global_container_stack.getProperty("wall_line_count", "value") - print_settings["retraction_enable"] = global_container_stack.getProperty("retraction_enable", "value") + print_settings["wall_line_count"] = global_stack.getProperty("wall_line_count", "value") + print_settings["retraction_enable"] = global_stack.getProperty("retraction_enable", "value") # Prime tower settings - print_settings["prime_tower_enable"] = global_container_stack.getProperty("prime_tower_enable", "value") + print_settings["prime_tower_enable"] = global_stack.getProperty("prime_tower_enable", "value") # Infill settings - print_settings["infill_sparse_density"] = global_container_stack.getProperty("infill_sparse_density", "value") - print_settings["infill_pattern"] = global_container_stack.getProperty("infill_pattern", "value") - print_settings["gradual_infill_steps"] = global_container_stack.getProperty("gradual_infill_steps", "value") + print_settings["infill_sparse_density"] = global_stack.getProperty("infill_sparse_density", "value") + print_settings["infill_pattern"] = global_stack.getProperty("infill_pattern", "value") + print_settings["gradual_infill_steps"] = global_stack.getProperty("gradual_infill_steps", "value") - print_settings["print_sequence"] = global_container_stack.getProperty("print_sequence", "value") + print_settings["print_sequence"] = global_stack.getProperty("print_sequence", "value") data["print_settings"] = print_settings diff --git a/plugins/SliceInfoPlugin/example_data.json b/plugins/SliceInfoPlugin/example_data.json new file mode 100644 index 0000000000..ec953e0842 --- /dev/null +++ b/plugins/SliceInfoPlugin/example_data.json @@ -0,0 +1,113 @@ +{ + "time_stamp": 1523973715.486928, + "schema_version": 0, + "cura_version": "3.3", + "active_mode": "custom", + "machine_settings_changed_by_user": true, + "language": "en_US", + "os": { + "type": "Linux", + "version": "#43~16.04.1-Ubuntu SMP Wed Mar 14 17:48:43 UTC 2018" + }, + "active_machine": { + "definition_id": "ultimaker3", + "manufacturer": "Ultimaker B.V." + }, + "extruders": [ + { + "active": true, + "material": { + "GUID": "506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9", + "type": "PLA", + "brand": "Generic" + }, + "material_used": 0.84, + "variant": "AA 0.4", + "nozzle_size": 0.4, + "extruder_settings": { + "wall_line_count": 3, + "retraction_enable": true, + "infill_sparse_density": 30, + "infill_pattern": "triangles", + "gradual_infill_steps": 0, + "default_material_print_temperature": 200, + "material_print_temperature": 200 + } + }, + { + "active": false, + "material": { + "GUID": "86a89ceb-4159-47f6-ab97-e9953803d70f", + "type": "PVA", + "brand": "Generic" + }, + "material_used": 0.5, + "variant": "BB 0.4", + "nozzle_size": 0.4, + "extruder_settings": { + "wall_line_count": 3, + "retraction_enable": true, + "infill_sparse_density": 20, + "infill_pattern": "triangles", + "gradual_infill_steps": 0, + "default_material_print_temperature": 215, + "material_print_temperature": 220 + } + } + ], + "quality_profile": "fast", + "models": [ + { + "hash": "b72789b9beb5366dff20b1cf501020c3d4d4df7dc2295ecd0fddd0a6436df070", + "bounding_box": { + "minimum": { + "x": -10.0, + "y": 0.0, + "z": -5.0 + }, + "maximum": { + "x": 9.999999046325684, + "y": 40.0, + "z": 5.0 + } + }, + "transformation": { + "data": "[[ 1. 0. 0. 0.] [ 0. 1. 0. 20.] [ 0. 0. 1. 0.] [ 0. 0. 0. 1.]]" + }, + "extruder": 0, + "model_settings": { + "support_enabled": true, + "support_extruder_nr": 1, + "infill_mesh": false, + "cutting_mesh": false, + "support_mesh": false, + "anti_overhang_mesh": false, + "wall_line_count": 3, + "retraction_enable": true, + "infill_sparse_density": 30, + "infill_pattern": "triangles", + "gradual_infill_steps": 0 + } + } + ], + "print_times": { + "travel": 187, + "support": 825, + "infill": 351, + "total": 7234 + }, + "print_settings": { + "layer_height": 0.15, + "support_enabled": true, + "support_extruder_nr": 1, + "adhesion_type": "brim", + "wall_line_count": 3, + "retraction_enable": true, + "prime_tower_enable": true, + "infill_sparse_density": 20, + "infill_pattern": "triangles", + "gradual_infill_steps": 0, + "print_sequence": "all_at_once" + }, + "output_to": "LocalFileOutputDevice" +} diff --git a/plugins/SupportEraser/SupportEraser.py b/plugins/SupportEraser/SupportEraser.py index 7884ca30c7..06d9fc3707 100644 --- a/plugins/SupportEraser/SupportEraser.py +++ b/plugins/SupportEraser/SupportEraser.py @@ -19,8 +19,10 @@ from cura.Scene.CuraSceneNode import CuraSceneNode from cura.PickingPass import PickingPass +from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation +from cura.Operations.SetParentOperation import SetParentOperation from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from cura.Scene.BuildPlateDecorator import BuildPlateDecorator @@ -56,7 +58,7 @@ class SupportEraser(Tool): modifiers = QApplication.keyboardModifiers() ctrl_is_active = modifiers & Qt.ControlModifier - if event.type == Event.MousePressEvent and self._controller.getToolsEnabled(): + if event.type == Event.MousePressEvent and MouseEvent.LeftButton in event.buttons and self._controller.getToolsEnabled(): if ctrl_is_active: self._controller.setActiveTool("TranslateTool") return @@ -117,7 +119,10 @@ class SupportEraser(Tool): new_instance.resetState() # Ensure that the state is not seen as a user state. settings.addInstance(new_instance) - op = AddSceneNodeOperation(node, parent) + op = GroupedOperation() + # First add node to the scene at the correct position/scale, before parenting, so the eraser mesh does not get scaled with the parent + op.addOperation(AddSceneNodeOperation(node, self._controller.getScene().getRoot())) + op.addOperation(SetParentOperation(node, parent)) op.push() node.setPosition(position, CuraSceneNode.TransformSpace.World) diff --git a/plugins/SupportEraser/__init__.py b/plugins/SupportEraser/__init__.py index 72700571fe..2ed7521808 100644 --- a/plugins/SupportEraser/__init__.py +++ b/plugins/SupportEraser/__init__.py @@ -4,7 +4,7 @@ from . import SupportEraser from UM.i18n import i18nCatalog -i18n_catalog = i18nCatalog("uranium") +i18n_catalog = i18nCatalog("cura") def getMetaData(): return { diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index ff942c54e1..282d507e09 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -50,6 +50,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._number_of_extruders = 2 + self._dummy_lambdas = set() + self._print_jobs = [] self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml") @@ -64,6 +66,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._authentication_state = AuthState.Authenticated self._error_message = None + self._write_job_progress_message = None self._progress_message = None self._active_printer = None # type: Optional[PrinterOutputModel] @@ -179,16 +182,33 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): job = WriteFileJob(writer, stream, nodes, preferred_format["mode"]) + self._write_job_progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1, + title = i18n_catalog.i18nc("@info:title", "Sending Data"), use_inactivity_timer = False) + self._write_job_progress_message.show() + + self._dummy_lambdas = (target_printer, preferred_format, stream) + job.finished.connect(self._sendPrintJobWaitOnWriteJobFinished) + + job.start() + + yield True #Return that we had success! + yield #To prevent having to catch the StopIteration exception. + + from cura.Utils.Threading import call_on_qt_thread + + def _sendPrintJobWaitOnWriteJobFinished(self, job): + self._write_job_progress_message.hide() + self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1, title = i18n_catalog.i18nc("@info:title", "Sending Data")) self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = None, description = "") self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered) self._progress_message.show() - job.start() - parts = [] + target_printer, preferred_format, stream = self._dummy_lambdas + # If a specific printer was selected, it should be printed with that machine. if target_printer: target_printer = self._printer_uuid_to_unique_name_mapping[target_printer] @@ -199,8 +219,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): file_name = Application.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"] - while not job.isFinished(): - sleep(0.1) output = stream.getvalue() #Either str or bytes depending on the output mode. if isinstance(stream, io.StringIO): output = output.encode("utf-8") @@ -209,9 +227,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress) - yield True #Return that we had success! - yield #To prevent having to catch the StopIteration exception. - @pyqtProperty(QObject, notify=activePrinterChanged) def activePrinter(self) -> Optional[PrinterOutputModel]: return self._active_printer diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml index 0aaeef8fbd..72c956b8d7 100644 --- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml @@ -249,13 +249,19 @@ Cura.MachineAction { if(base.selectedDevice) { - if(base.selectedDevice.printerType == "ultimaker3") + if (base.selectedDevice.printerType == "ultimaker3") { - return catalog.i18nc("@label", "Ultimaker 3") - } else if(base.selectedDevice.printerType == "ultimaker3_extended") + return "Ultimaker 3"; + } + else if (base.selectedDevice.printerType == "ultimaker3_extended") + { + return "Ultimaker 3 Extended"; + } + else if (base.selectedDevice.printerType == "ultimaker_s5") { - return catalog.i18nc("@label", "Ultimaker 3 Extended") - } else + return "Ultimaker S5"; + } + else { return catalog.i18nc("@label", "Unknown") // We have no idea what type it is. Should not happen 'in the field' } diff --git a/plugins/UM3NetworkPrinting/PrintWindow.qml b/plugins/UM3NetworkPrinting/PrintWindow.qml index 43afbcdfe0..5b011d98c4 100644 --- a/plugins/UM3NetworkPrinting/PrintWindow.qml +++ b/plugins/UM3NetworkPrinting/PrintWindow.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Ultimaker B.V. +// Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -48,7 +48,7 @@ UM.Dialog anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.rightMargin: UM.Theme.getSize("default_margin").width - height: 50 * screenScaleFactord + height: 50 * screenScaleFactor Label { id: manualPrinterSelectionLabel diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index c943454410..7070ad7c3f 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -104,6 +104,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice): if self._is_printing: return # Aleady printing + # cancel any ongoing preheat timer before starting a print + self._printers[0].getController().stopPreheatTimers() + Application.getInstance().getController().setActiveStage("MonitorStage") # find the G-code for the active build plate to print diff --git a/plugins/VersionUpgrade/VersionUpgrade27to30/VersionUpgrade27to30.py b/plugins/VersionUpgrade/VersionUpgrade27to30/VersionUpgrade27to30.py index 972d238921..5a141f1558 100644 --- a/plugins/VersionUpgrade/VersionUpgrade27to30/VersionUpgrade27to30.py +++ b/plugins/VersionUpgrade/VersionUpgrade27to30/VersionUpgrade27to30.py @@ -4,6 +4,8 @@ import configparser #To parse preference files. import io #To serialise the preference files afterwards. import os +import urllib.parse +import re from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this. @@ -118,6 +120,12 @@ class VersionUpgrade27to30(VersionUpgrade): if not parser.has_section("general"): parser.add_section("general") + # Clean up the filename + file_base_name = os.path.basename(filename) + file_base_name = urllib.parse.unquote_plus(file_base_name) + + um2_pattern = re.compile(r"^ultimaker[^a-zA-Z\\d\\s:]2_.*$") + # The ultimaker 2 family ultimaker2_prefix_list = ["ultimaker2_extended_", "ultimaker2_go_", @@ -127,9 +135,8 @@ class VersionUpgrade27to30(VersionUpgrade): "ultimaker2_plus_"] # set machine definition to "ultimaker2" for the custom quality profiles that can be for the ultimaker 2 family - file_base_name = os.path.basename(filename) - is_ultimaker2_family = False - if not any(file_base_name.startswith(ep) for ep in exclude_prefix_list): + is_ultimaker2_family = um2_pattern.match(file_base_name) is not None + if not is_ultimaker2_family and not any(file_base_name.startswith(ep) for ep in exclude_prefix_list): is_ultimaker2_family = any(file_base_name.startswith(ep) for ep in ultimaker2_prefix_list) # ultimaker2 family quality profiles used to set as "fdmprinter" profiles diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py index 3de451632f..18851b82c7 100644 --- a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py +++ b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py @@ -85,6 +85,34 @@ class VersionUpgrade32to33(VersionUpgrade): setting_version = int(parser.get("metadata", "setting_version", fallback = 0)) return format_version * 1000000 + setting_version + ## Upgrades a preferences file from version 3.2 to 3.3. + # + # \param serialised The serialised form of a preferences file. + # \param filename The name of the file to upgrade. + def upgradePreferences(self, serialised, filename): + parser = configparser.ConfigParser(interpolation = None) + parser.read_string(serialised) + + # Update version numbers + if "general" not in parser: + parser["general"] = {} + parser["general"]["version"] = "6" + if "metadata" not in parser: + parser["metadata"] = {} + parser["metadata"]["setting_version"] = "4" + + # The auto_slice preference changed its default value to "disabled" so if there is no value in previous versions, + # then it means the desired value is auto_slice "enabled" + if "auto_slice" not in parser["general"]: + parser["general"]["auto_slice"] = "True" + elif parser["general"]["auto_slice"] == "False": # If the value is False, then remove the entry + del parser["general"]["auto_slice"] + + # Re-serialise the file. + output = io.StringIO() + parser.write(output) + return [filename], [output.getvalue()] + ## Upgrades a container stack from version 3.2 to 3.3. # # \param serialised The serialised form of a container stack. diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py index ae4bf7b2f9..5073be772d 100644 --- a/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py @@ -9,6 +9,8 @@ def getMetaData(): return { "version_upgrade": { # From To Upgrade function + ("preferences", 5000004): ("preferences", 6000004, upgrade.upgradePreferences), + ("machine_stack", 3000004): ("machine_stack", 4000004, upgrade.upgradeStack), ("extruder_train", 3000004): ("extruder_train", 4000004, upgrade.upgradeStack), @@ -18,6 +20,10 @@ def getMetaData(): ("variant", 2000004): ("variant", 3000004, upgrade.upgradeVariants) }, "sources": { + "preferences": { + "get_version": upgrade.getCfgVersion, + "location": {"."} + }, "machine_stack": { "get_version": upgrade.getCfgVersion, "location": {"./machine_instances"} |