Browse Source

Merge branch 'master' into feature_ufp_writer

Diego Prado Gesto 7 years ago
parent
commit
2024d6aa12

+ 3 - 3
cura/CrashHandler.py

@@ -189,7 +189,7 @@ class CrashHandler:
 
             json_metadata_file = os.path.join(directory, "plugin.json")
             try:
-                with open(json_metadata_file, "r") as f:
+                with open(json_metadata_file, "r", encoding = "utf-8") as f:
                     try:
                         metadata = json.loads(f.read())
                         module_version = metadata["version"]
@@ -217,9 +217,9 @@ class CrashHandler:
         text_area = QTextEdit()
         tmp_file_fd, tmp_file_path = tempfile.mkstemp(prefix = "cura-crash", text = True)
         os.close(tmp_file_fd)
-        with open(tmp_file_path, "w") as f:
+        with open(tmp_file_path, "w", encoding = "utf-8") as f:
             faulthandler.dump_traceback(f, all_threads=True)
-        with open(tmp_file_path, "r") as f:
+        with open(tmp_file_path, "r", encoding = "utf-8") as f:
             logdata = f.read()
 
         text_area.setText(logdata)

+ 6 - 4
cura/Settings/ContainerManager.py

@@ -3,7 +3,7 @@
 
 import copy
 import os.path
-import urllib
+import urllib.parse
 import uuid
 from typing import Any, Dict, List, Union
 
@@ -459,7 +459,7 @@ class ContainerManager(QObject):
     #   \return \type{Dict} dict with a 'status' key containing the string 'success' or 'error', and a 'message' key
     #       containing a message for the user
     @pyqtSlot(QUrl, result = "QVariantMap")
-    def importContainer(self, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
+    def importMaterialContainer(self, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
         if not file_url_or_string:
             return { "status": "error", "message": "Invalid path"}
 
@@ -486,12 +486,14 @@ class ContainerManager(QObject):
         container = container_type(container_id)
 
         try:
-            with open(file_url, "rt") as f:
+            with open(file_url, "rt", encoding = "utf-8") as f:
                 container.deserialize(f.read())
         except PermissionError:
             return { "status": "error", "message": "Permission denied when trying to read the file"}
+        except Exception as ex:
+            return {"status": "error", "message": str(ex)}
 
-        container.setName(container_id)
+        container.setDirty(True)
 
         self._container_registry.addContainer(container)
 

+ 45 - 8
cura/Settings/CuraContainerRegistry.py

@@ -208,7 +208,7 @@ class CuraContainerRegistry(ContainerRegistry):
             except Exception as e:
                 # Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
                 Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name,profile_reader.getPluginId(), str(e))
-                return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, str(e))}
+                return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "\n" + str(e))}
 
             if profile_or_list:
                 # Ensure it is always a list of profiles
@@ -246,6 +246,41 @@ class CuraContainerRegistry(ContainerRegistry):
                 if type(profile_or_list) is not list:
                     profile_or_list = [profile_or_list]
 
+                # Make sure that there are also extruder stacks' quality_changes, not just one for the global stack
+                if len(profile_or_list) == 1:
+                    global_profile = profile_or_list[0]
+                    extruder_profiles = []
+                    for idx, extruder in enumerate(global_container_stack.extruders.values()):
+                        profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1))
+                        profile = InstanceContainer(profile_id)
+                        profile.setName(global_profile.getName())
+                        profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
+                        profile.addMetaDataEntry("type", "quality_changes")
+                        profile.addMetaDataEntry("definition", global_profile.getMetaDataEntry("definition"))
+                        profile.addMetaDataEntry("quality_type", global_profile.getMetaDataEntry("quality_type"))
+                        profile.addMetaDataEntry("extruder", extruder.getId())
+                        profile.setDirty(True)
+                        if idx == 0:
+                            # move all per-extruder settings to the first extruder's quality_changes
+                            for qc_setting_key in global_profile.getAllKeys():
+                                settable_per_extruder = global_container_stack.getProperty(qc_setting_key,
+                                                                                           "settable_per_extruder")
+                                if settable_per_extruder:
+                                    setting_value = global_profile.getProperty(qc_setting_key, "value")
+
+                                    setting_definition = global_container_stack.getSettingDefinition(qc_setting_key)
+                                    new_instance = SettingInstance(setting_definition, profile)
+                                    new_instance.setProperty("value", setting_value)
+                                    new_instance.resetState()  # Ensure that the state is not seen as a user state.
+                                    profile.addInstance(new_instance)
+                                    profile.setDirty(True)
+
+                                    global_profile.removeInstance(qc_setting_key, postpone_emit=True)
+                        extruder_profiles.append(profile)
+
+                    for profile in extruder_profiles:
+                        profile_or_list.append(profile)
+
                 # Import all profiles
                 for profile_index, profile in enumerate(profile_or_list):
                     if profile_index == 0:
@@ -296,7 +331,7 @@ class CuraContainerRegistry(ContainerRegistry):
         profile.setDirty(True)  # Ensure the profiles are correctly saved
 
         new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
-        profile._id = new_id
+        profile.setMetaDataEntry("id", new_id)
         profile.setName(new_name)
 
         # Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile
@@ -454,8 +489,9 @@ class CuraContainerRegistry(ContainerRegistry):
     #      - override the current machine
     #      - create new for custom quality profile
     # new_global_quality_changes is the new global quality changes container in this scenario.
+    # create_new_ids indicates if new unique ids must be created
     #
-    def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id, new_global_quality_changes = None):
+    def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id, new_global_quality_changes = None, create_new_ids = True):
         new_extruder_id = extruder_id
 
         extruder_definitions = self.findDefinitionContainers(id = new_extruder_id)
@@ -464,7 +500,7 @@ class CuraContainerRegistry(ContainerRegistry):
             return
 
         extruder_definition = extruder_definitions[0]
-        unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id)
+        unique_name = self.uniqueName(machine.getName() + " " + new_extruder_id) if create_new_ids else machine.getName() + " " + new_extruder_id
 
         extruder_stack = ExtruderStack.ExtruderStack(unique_name)
         extruder_stack.setName(extruder_definition.getName())
@@ -474,7 +510,7 @@ class CuraContainerRegistry(ContainerRegistry):
         from cura.CuraApplication import CuraApplication
 
         # create a new definition_changes container for the extruder stack
-        definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings")
+        definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings") if create_new_ids else extruder_stack.getId() + "_settings"
         definition_changes_name = definition_changes_id
         definition_changes = InstanceContainer(definition_changes_id)
         definition_changes.setName(definition_changes_name)
@@ -501,14 +537,15 @@ class CuraContainerRegistry(ContainerRegistry):
         extruder_stack.setDefinitionChanges(definition_changes)
 
         # create empty user changes container otherwise
-        user_container_id = self.uniqueName(extruder_stack.getId() + "_user")
+        user_container_id = self.uniqueName(extruder_stack.getId() + "_user") if create_new_ids else extruder_stack.getId() + "_user"
         user_container_name = user_container_id
         user_container = InstanceContainer(user_container_id)
         user_container.setName(user_container_name)
         user_container.addMetaDataEntry("type", "user")
-        user_container.addMetaDataEntry("machine", extruder_stack.getId())
+        user_container.addMetaDataEntry("machine", machine.getId())
         user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
         user_container.setDefinition(machine.definition.getId())
+        user_container.setMetaDataEntry("extruder", extruder_stack.getId())
 
         if machine.userChanges:
             # for the newly created extruder stack, we need to move all "per-extruder" settings to the user changes
@@ -692,7 +729,7 @@ class CuraContainerRegistry(ContainerRegistry):
                     continue
 
                 instance_container = InstanceContainer(container_id)
-                with open(file_path, "r") as f:
+                with open(file_path, "r", encoding = "utf-8") as f:
                     serialized = f.read()
                 instance_container.deserialize(serialized, file_path)
                 self.addContainer(instance_container)

+ 27 - 9
cura/Settings/ExtruderStack.py

@@ -60,18 +60,33 @@ class ExtruderStack(CuraContainerStack):
         keys_to_copy = ["material_diameter", "machine_nozzle_size"]  # these will be copied over to all extruders
 
         for key in keys_to_copy:
-            # Since material_diameter is not on the extruder definition, we need to add it here
-            # WARNING: this might be very dangerous and should be refactored ASAP!
-            definition = stack.getSettingDefinition(key)
-            if definition:
-                self.definition.addDefinition(definition)
-
             # Only copy the value when this extruder doesn't have the value.
             if self.definitionChanges.hasProperty(key, "value"):
                 continue
 
-            setting_value = stack.definitionChanges.getProperty(key, "value")
-            if setting_value is None:
+            # WARNING: this might be very dangerous and should be refactored ASAP!
+            #
+            # We cannot add a setting definition of "material_diameter" into the extruder's definition at runtime
+            # because all other machines which uses "fdmextruder" as the extruder definition will be affected.
+            #
+            # The problem is that single extrusion machines have their default material diameter defined in the global
+            # definitions. Now we automatically create an extruder stack for those machines using "fdmextruder"
+            # definition, which doesn't have the specific "material_diameter" and "machine_nozzle_size" defined for
+            # each machine. This results in wrong values which can be found in the MachineSettings dialog.
+            #
+            # To solve this, we put "material_diameter" back into the "fdmextruder" definition because modifying it in
+            # the extruder definition will affect all machines which uses the "fdmextruder" definition. Moreover, now
+            # we also check the value defined in the machine definition. If present, the value defined in the global
+            # stack's definition changes container will be copied. Otherwise, we will check if the default values in the
+            # machine definition and the extruder definition are the same, and if not, the default value in the machine
+            # definition will be copied to the extruder stack's definition changes.
+            #
+            setting_value_in_global_def_changes = stack.definitionChanges.getProperty(key, "value")
+            setting_value_in_global_def = stack.definition.getProperty(key, "value")
+            setting_value = setting_value_in_global_def
+            if setting_value_in_global_def_changes is not None:
+                setting_value = setting_value_in_global_def_changes
+            if setting_value == self.definition.getProperty(key, "value"):
                 continue
 
             setting_definition = stack.getSettingDefinition(key)
@@ -83,8 +98,11 @@ class ExtruderStack(CuraContainerStack):
 
             # Make sure the material diameter is up to date for the extruder stack.
             if key == "material_diameter":
+                from cura.CuraApplication import CuraApplication
+                machine_manager = CuraApplication.getInstance().getMachineManager()
                 position = self.getMetaDataEntry("position", "0")
-                Application.getInstance().getExtruderManager().updateMaterialForDiameter(position)
+                func = lambda p = position: CuraApplication.getInstance().getExtruderManager().updateMaterialForDiameter(p)
+                machine_manager.machine_extruder_material_update_dict[stack.getId()].append(func)
 
             # NOTE: We cannot remove the setting from the global stack's definition changes container because for
             # material diameter, it needs to be applied to all extruders, but here we don't know how many extruders

+ 8 - 0
cura/Settings/MachineManager.py

@@ -1,6 +1,7 @@
 # Copyright (c) 2017 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
+import collections
 import time
 #Type hinting.
 from typing import Union, List, Dict
@@ -51,6 +52,8 @@ class MachineManager(QObject):
         self._active_container_stack = None     # type: CuraContainerStack
         self._global_container_stack = None     # type: GlobalStack
 
+        self.machine_extruder_material_update_dict = collections.defaultdict(list)
+
         # Used to store the new containers until after confirming the dialog
         self._new_variant_container = None
         self._new_buildplate_container = None
@@ -334,6 +337,11 @@ class MachineManager(QObject):
                 extruder_stack.propertyChanged.connect(self._onPropertyChanged)
                 extruder_stack.containersChanged.connect(self._onInstanceContainersChanged)
 
+            if self._global_container_stack.getId() in self.machine_extruder_material_update_dict:
+                for func in self.machine_extruder_material_update_dict[self._global_container_stack.getId()]:
+                    Application.getInstance().callLater(func)
+                del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
+
         self._error_check_timer.start()
 
     ##  Update self._stacks_valid according to _checkStacksForErrors and emit if change.

+ 2 - 2
cura_app.py

@@ -30,8 +30,8 @@ if not known_args["debug"]:
     if hasattr(sys, "frozen"):
         dirpath = get_cura_dir_path()
         os.makedirs(dirpath, exist_ok = True)
-        sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w")
-        sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w")
+        sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8")
+        sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w", encoding = "utf-8")
 
 import platform
 import faulthandler

+ 12 - 3
plugins/3MFReader/ThreeMFWorkspaceReader.py

@@ -721,7 +721,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
                 stack.setName(global_stack_name_new)
 
                 container_stacks_added.append(stack)
-                self._container_registry.addContainer(stack)
+                # self._container_registry.addContainer(stack)
                 containers_added.append(stack)
             else:
                 Logger.log("e", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
@@ -793,13 +793,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
                 # If we choose to override a machine but to create a new custom quality profile, the custom quality
                 # profile is not immediately applied to the global_stack, so this fix for single extrusion machines
                 # will use the current custom quality profile on the existing machine. The extra optional argument
-                # in that function is used in thia case to specify a new global stack quality_changes container so
+                # in that function is used in this case to specify a new global stack quality_changes container so
                 # the fix can correctly create and copy over the custom quality settings to the newly created extruder.
                 new_global_quality_changes = None
                 if self._resolve_strategies["quality_changes"] == "new" and len(quality_changes_instance_containers) > 0:
                     new_global_quality_changes = quality_changes_instance_containers[0]
+
+                # Depending if the strategy is to create a new or override, the ids must be or not be unique
                 stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder",
-                                                                                           new_global_quality_changes)
+                                                                                           new_global_quality_changes,
+                                                                                           create_new_ids = self._resolve_strategies["machine"] == "new")
                 if new_global_quality_changes is not None:
                     quality_changes_instance_containers.append(stack.qualityChanges)
                     quality_and_definition_changes_instance_containers.append(stack.qualityChanges)
@@ -822,6 +825,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
                 self._container_registry.removeContainer(container.getId())
             return
 
+        ## In case there is a new machine and once the extruders are created, the global stack is added to the registry,
+        # otherwise the accContainers function in CuraContainerRegistry will create an extruder stack and then creating
+        # useless files
+        if self._resolve_strategies["machine"] == "new":
+            self._container_registry.addContainer(global_stack)
+
         # Check quality profiles to make sure that if one stack has the "not supported" quality profile,
         # all others should have the same.
         #

+ 46 - 0
plugins/ChangeLogPlugin/ChangeLog.txt

@@ -1,3 +1,49 @@
+[3.2.0]
+*Tree support
+Experimental tree-like support structure that uses ‘branches’ to support prints. Branches ‘grow’ and multiply towards the model, with fewer contact points than alternative support methods. This results in better surface finishes for organic-shaped prints.
+
+*Adaptive layers
+Prints with a variable layer thickness which adapts to the angle of the model’s surfaces. The result is high-quality surface finishes with a marginally increased print time. This setting can be found under the experimental category.
+
+*Faster startup
+Printer definitions are now loaded when adding a printer, instead of loading all available printers on startup.
+
+*Backface culling in layer view
+Doubled frame rate by only rendering visible surfaces of the model in the layer view, instead of rendering the entire model. Good for lower spec GPUs as it is less resource-intensive.
+
+*Multi build plate
+Experimental feature that creates separate build plates with shared settings in a single session, eliminating the need to clear the build plate multiple times. Multiple build plates can be sliced and sent to a printer or printer group in Cura Connect. This feature must be enabled manually in the preferences ‘general’ tab.
+
+*Improved mesh type selection
+New button in the left toolbar to edit per model settings, giving the user more control over where to place support. Objects can be used as meshes, with a drop down list where ‘Print as support’, ‘Don't overlap support with other models’, ‘Modify settings for overlap with other models’, or ‘Modify settings for infill of other models’ can be specified. Contributed by fieldOfView.
+
+*View optimization
+Quick camera controls introduced in version 3.1 have been revised to create more accurate isometric, front, left, and right views.
+
+*Updated sidebar to QtQuick 2.0
+Application framework updated to increase speed, achieve a better width and style fit, and gives users dropdown menus that are styled to fit the enabled Ultimaker Cura theme, instead of the operating system’s theme.
+
+*Hide sidebar
+The sidebar can now be hidden/shown by selecting View > Expand/Collapse Sidebar, or with the hotkey CMD + E (Mac) or CTRL + E (PC and Linux).
+
+*Disable ‘Send slice information’
+A shortcut to disable ‘Send slice information’ has been added to the first launch to make it easier for privacy-conscious users to keep slice information private.
+
+*Signed binaries (Windows)
+For security-conscious users, the Windows installer and Windows binaries have been digitally signed to prevent “Unknown application” warnings and virus scanner false-positives.
+
+*Start/end gcode script per extruder
+Variables from both extruders in the start and end gcode snippets can now be accessed and edited, creating uniformity between profiles in different slicing environments. Contributed by fieldOfView.
+
+*OctoPrint plugin added to plugin browser
+This plugin enables printers managed with OctoPrint to print via Ultimaker Cura interface (version 3.2 or later).
+
+*Bugfixes
+- Fixed a bug where the mirror tool and center model options when used together would reset the model transformations
+- Updated config file path to fix crashes caused by user config files that are located on remote drives
+- Updated Arduino drivers to fix triggering errors during OTA updates in shared environments. This also fixes an issue when upgrading the firmware of the Ultimaker Original.
+- Fixed an issue where arranging small models would fail, due to conflict with small model files combined with the “Ensure models are kept apart” option
+
 [3.1.0]
 *Profile added for 0.25 mm print core
 This new print core gives extra fine line widths which gives prints extra definition and surface quality.

+ 3 - 3
plugins/CuraProfileReader/CuraProfileReader.py

@@ -39,7 +39,7 @@ class CuraProfileReader(ProfileReader):
 
         except zipfile.BadZipFile:
             # It must be an older profile from Cura 2.1.
-            with open(file_name, encoding="utf-8") as fhandle:
+            with open(file_name, encoding = "utf-8") as fhandle:
                 serialized = fhandle.read()
             return [self._loadProfile(serialized, profile_id) for serialized, profile_id in self._upgradeProfile(serialized, file_name)]
 
@@ -52,10 +52,10 @@ class CuraProfileReader(ProfileReader):
         parser = configparser.ConfigParser(interpolation=None)
         parser.read_string(serialized)
 
-        if not "general" in parser:
+        if "general" not in parser:
             Logger.log("w", "Missing required section 'general'.")
             return []
-        if not "version" in parser["general"]:
+        if "version" not in parser["general"]:
             Logger.log("w", "Missing required 'version' property")
             return []
 

+ 1 - 1
plugins/GCodeReader/GCodeReader.py

@@ -26,7 +26,7 @@ class GCodeReader(MeshReader):
 
     # PreRead is used to get the correct flavor. If not, Marlin is set by default
     def preRead(self, file_name, *args, **kwargs):
-        with open(file_name, "r") as file:
+        with open(file_name, "r", encoding = "utf-8") as file:
             for line in file:
                 if line[:len(self._flavor_keyword)] == self._flavor_keyword:
                     try:

Some files were not shown because too many files changed in this diff