Browse Source

Merge pull request #5 from Ultimaker/master

update
MaukCC 7 years ago
parent
commit
21cf47b352

+ 10 - 8
cura/BuildVolume.py

@@ -433,7 +433,8 @@ class BuildVolume(SceneNode):
                 self._global_container_stack.getProperty("raft_interface_thickness", "value") +
                 self._global_container_stack.getProperty("raft_interface_thickness", "value") +
                 self._global_container_stack.getProperty("raft_surface_layers", "value") *
                 self._global_container_stack.getProperty("raft_surface_layers", "value") *
                     self._global_container_stack.getProperty("raft_surface_thickness", "value") +
                     self._global_container_stack.getProperty("raft_surface_thickness", "value") +
-                self._global_container_stack.getProperty("raft_airgap", "value"))
+                self._global_container_stack.getProperty("raft_airgap", "value") -
+                self._global_container_stack.getProperty("layer_0_z_overlap", "value"))
 
 
         # Rounding errors do not matter, we check if raft_thickness has changed at all
         # Rounding errors do not matter, we check if raft_thickness has changed at all
         if old_raft_thickness != self._raft_thickness:
         if old_raft_thickness != self._raft_thickness:
@@ -562,7 +563,7 @@ class BuildVolume(SceneNode):
                 used_extruders = [self._global_container_stack]
                 used_extruders = [self._global_container_stack]
 
 
         result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added.
         result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added.
-        prime_areas = self._computeDisallowedAreasPrime(disallowed_border_size, used_extruders)
+        prime_areas = self._computeDisallowedAreasPrimeBlob(disallowed_border_size, used_extruders)
         prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
         prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
 
 
         #Check if prime positions intersect with disallowed areas.
         #Check if prime positions intersect with disallowed areas.
@@ -658,7 +659,7 @@ class BuildVolume(SceneNode):
 
 
         return result
         return result
 
 
-    ##  Computes the disallowed areas for the prime locations.
+    ##  Computes the disallowed areas for the prime blobs.
     #
     #
     #   These are special because they are not subject to things like brim or
     #   These are special because they are not subject to things like brim or
     #   travel avoidance. They do get a dilute with the border size though
     #   travel avoidance. They do get a dilute with the border size though
@@ -669,17 +670,18 @@ class BuildVolume(SceneNode):
     #   \param used_extruders The extruder stacks to generate disallowed areas
     #   \param used_extruders The extruder stacks to generate disallowed areas
     #   for.
     #   for.
     #   \return A dictionary with for each used extruder ID the prime areas.
     #   \return A dictionary with for each used extruder ID the prime areas.
-    def _computeDisallowedAreasPrime(self, border_size, used_extruders):
+    def _computeDisallowedAreasPrimeBlob(self, border_size, used_extruders):
         result = {}
         result = {}
 
 
         machine_width = self._global_container_stack.getProperty("machine_width", "value")
         machine_width = self._global_container_stack.getProperty("machine_width", "value")
         machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
         machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
         for extruder in used_extruders:
         for extruder in used_extruders:
+            prime_blob_enabled = extruder.getProperty("prime_blob_enable", "value")
             prime_x = extruder.getProperty("extruder_prime_pos_x", "value")
             prime_x = extruder.getProperty("extruder_prime_pos_x", "value")
             prime_y = - extruder.getProperty("extruder_prime_pos_y", "value")
             prime_y = - extruder.getProperty("extruder_prime_pos_y", "value")
 
 
-            #Ignore extruder prime position if it is not set
-            if prime_x == 0 and prime_y == 0:
+            #Ignore extruder prime position if it is not set or if blob is disabled
+            if (prime_x == 0 and prime_y == 0) or not prime_blob_enabled:
                 result[extruder.getId()] = []
                 result[extruder.getId()] = []
                 continue
                 continue
 
 
@@ -950,9 +952,9 @@ class BuildVolume(SceneNode):
         return max(min(value, max_value), min_value)
         return max(min(value, max_value), min_value)
 
 
     _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist"]
     _skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist"]
-    _raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"]
+    _raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
     _extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
     _extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
-    _prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z"]
+    _prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"]
     _tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
     _tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
     _ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
     _ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
     _distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts"]
     _distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts"]

+ 1 - 2
cura/ConvexHullDecorator.py

@@ -328,8 +328,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
         return self.__isDescendant(root, node.getParent())
         return self.__isDescendant(root, node.getParent())
 
 
     _affected_settings = [
     _affected_settings = [
-        "adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers",
-        "raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence",
+        "adhesion_type", "raft_margin", "print_sequence",
         "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
         "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
 
 
     ##  Settings that change the convex hull.
     ##  Settings that change the convex hull.

+ 10 - 3
cura/CuraApplication.py

@@ -99,6 +99,11 @@ if not MYPY:
 
 
 
 
 class CuraApplication(QtApplication):
 class CuraApplication(QtApplication):
+    # SettingVersion represents the set of settings available in the machine/extruder definitions.
+    # You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
+    # changes of the settings.
+    SettingVersion = 1
+
     class ResourceTypes:
     class ResourceTypes:
         QmlFiles = Resources.UserType + 1
         QmlFiles = Resources.UserType + 1
         Firmware = Resources.UserType + 2
         Firmware = Resources.UserType + 2
@@ -169,11 +174,11 @@ class CuraApplication(QtApplication):
 
 
         UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
         UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
             {
             {
-                ("quality", InstanceContainer.Version):    (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
+                ("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion):    (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
                 ("machine_stack", ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"),
                 ("machine_stack", ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"),
                 ("extruder_train", ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"),
                 ("extruder_train", ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"),
                 ("preferences", Preferences.Version):               (Resources.Preferences, "application/x-uranium-preferences"),
                 ("preferences", Preferences.Version):               (Resources.Preferences, "application/x-uranium-preferences"),
-                ("user", InstanceContainer.Version):       (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer")
+                ("user", InstanceContainer.Version * 1000000 + self.SettingVersion):       (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer")
             }
             }
         )
         )
 
 
@@ -620,7 +625,9 @@ class CuraApplication(QtApplication):
         camera.lookAt(Vector(0, 0, 0))
         camera.lookAt(Vector(0, 0, 0))
         controller.getScene().setActiveCamera("3d")
         controller.getScene().setActiveCamera("3d")
 
 
-        self.getController().getTool("CameraTool").setOrigin(Vector(0, 100, 0))
+        camera_tool = self.getController().getTool("CameraTool")
+        camera_tool.setOrigin(Vector(0, 100, 0))
+        camera_tool.setZoomRange(0.1, 200000)
 
 
         self._camera_animation = CameraAnimation.CameraAnimation()
         self._camera_animation = CameraAnimation.CameraAnimation()
         self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))
         self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))

+ 2 - 2
cura/PrintInformation.py

@@ -31,8 +31,8 @@ catalog = i18nCatalog("cura")
 #   - This triggers a new slice with the current settings - this is the "current settings pass".
 #   - This triggers a new slice with the current settings - this is the "current settings pass".
 #   - When the slice is done, we update the current print time and material amount.
 #   - When the slice is done, we update the current print time and material amount.
 #   - If the source of the slice was not a Setting change, we start the second slice pass, the "low quality settings pass". Otherwise we stop here.
 #   - If the source of the slice was not a Setting change, we start the second slice pass, the "low quality settings pass". Otherwise we stop here.
-#   - When that is done, we update the minimum print time and start the final slice pass, the "high quality settings pass".
-#   - When the high quality pass is done, we update the maximum print time.
+#   - When that is done, we update the minimum print time and start the final slice pass, the "Extra Fine settings pass".
+#   - When the Extra Fine pass is done, we update the maximum print time.
 #
 #
 #   This class also mangles the current machine name and the filename of the first loaded mesh into a job name.
 #   This class also mangles the current machine name and the filename of the first loaded mesh into a job name.
 #   This job name is requested by the JobSpecs qml file.
 #   This job name is requested by the JobSpecs qml file.

+ 38 - 36
cura/QualityManager.py

@@ -3,7 +3,7 @@
 
 
 # This collects a lot of quality and quality changes related code which was split between ContainerManager
 # This collects a lot of quality and quality changes related code which was split between ContainerManager
 # and the MachineManager and really needs to usable from both.
 # and the MachineManager and really needs to usable from both.
-from typing import List
+from typing import List, Optional, Dict, TYPE_CHECKING
 
 
 from UM.Application import Application
 from UM.Application import Application
 from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.Settings.ContainerRegistry import ContainerRegistry
@@ -11,6 +11,10 @@ from UM.Settings.DefinitionContainer import DefinitionContainer
 from UM.Settings.InstanceContainer import InstanceContainer
 from UM.Settings.InstanceContainer import InstanceContainer
 from cura.Settings.ExtruderManager import ExtruderManager
 from cura.Settings.ExtruderManager import ExtruderManager
 
 
+if TYPE_CHECKING:
+    from cura.Settings.GlobalStack import GlobalStack
+    from cura.Settings.ExtruderStack import ExtruderStack
+    from UM.Settings.DefinitionContainer import DefinitionContainerInterface
 
 
 class QualityManager:
 class QualityManager:
 
 
@@ -27,12 +31,12 @@ class QualityManager:
     ##  Find a quality by name for a specific machine definition and materials.
     ##  Find a quality by name for a specific machine definition and materials.
     #
     #
     #   \param quality_name
     #   \param quality_name
-    #   \param machine_definition (Optional) \type{ContainerInstance} If nothing is
+    #   \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is
     #                               specified then the currently selected machine definition is used.
     #                               specified then the currently selected machine definition is used.
-    #   \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
+    #   \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then
     #                               the current set of selected materials is used.
     #                               the current set of selected materials is used.
-    #   \return the matching quality container \type{ContainerInstance}
-    def findQualityByName(self, quality_name, machine_definition=None, material_containers=None):
+    #   \return the matching quality container \type{InstanceContainer}
+    def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None) -> Optional[InstanceContainer]:
         criteria = {"type": "quality", "name": quality_name}
         criteria = {"type": "quality", "name": quality_name}
         result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
         result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
 
 
@@ -46,12 +50,10 @@ class QualityManager:
     ##  Find a quality changes container by name.
     ##  Find a quality changes container by name.
     #
     #
     #   \param quality_changes_name \type{str} the name of the quality changes container.
     #   \param quality_changes_name \type{str} the name of the quality changes container.
-    #   \param machine_definition (Optional) \type{ContainerInstance} If nothing is
-    #                               specified then the currently selected machine definition is used.
-    #   \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
-    #                               the current set of selected materials is used.
-    #   \return the matching quality changes containers \type{List[ContainerInstance]}
-    def findQualityChangesByName(self, quality_changes_name, machine_definition=None):
+    #   \param machine_definition (Optional) \type{DefinitionContainer} If nothing is
+    #                               specified then the currently selected machine definition is used..
+    #   \return the matching quality changes containers \type{List[InstanceContainer]}
+    def findQualityChangesByName(self, quality_changes_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None):
         criteria = {"type": "quality_changes", "name": quality_changes_name}
         criteria = {"type": "quality_changes", "name": quality_changes_name}
         result = self._getFilteredContainersForStack(machine_definition, [], **criteria)
         result = self._getFilteredContainersForStack(machine_definition, [], **criteria)
 
 
@@ -62,7 +64,7 @@ class QualityManager:
     #   \param machine_definition \type{DefinitionContainer}
     #   \param machine_definition \type{DefinitionContainer}
     #   \param material_containers \type{List[InstanceContainer]}
     #   \param material_containers \type{List[InstanceContainer]}
     #   \return \type{List[str]}
     #   \return \type{List[str]}
-    def findAllQualityTypesForMachineAndMaterials(self, machine_definition, material_containers):
+    def findAllQualityTypesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[str]:
         # Determine the common set of quality types which can be
         # Determine the common set of quality types which can be
         # applied to all of the materials for this machine.
         # applied to all of the materials for this machine.
         quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
         quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
@@ -76,9 +78,9 @@ class QualityManager:
     ##  Fetches a dict of quality types names to quality profiles for a combination of machine and material.
     ##  Fetches a dict of quality types names to quality profiles for a combination of machine and material.
     #
     #
     #   \param machine_definition \type{DefinitionContainer} the machine definition.
     #   \param machine_definition \type{DefinitionContainer} the machine definition.
-    #   \param material \type{ContainerInstance} the material.
-    #   \return \type{Dict[str, ContainerInstance]} the dict of suitable quality type names mapping to qualities.
-    def __fetchQualityTypeDictForMaterial(self, machine_definition, material):
+    #   \param material \type{InstanceContainer} the material.
+    #   \return \type{Dict[str, InstanceContainer]} the dict of suitable quality type names mapping to qualities.
+    def __fetchQualityTypeDictForMaterial(self, machine_definition: "DefinitionContainerInterface", material: InstanceContainer) -> Dict[str, InstanceContainer]:
         qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material)
         qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material)
         quality_type_dict = {}
         quality_type_dict = {}
         for quality in qualities:
         for quality in qualities:
@@ -88,12 +90,12 @@ class QualityManager:
     ##  Find a quality container by quality type.
     ##  Find a quality container by quality type.
     #
     #
     #   \param quality_type \type{str} the name of the quality type to search for.
     #   \param quality_type \type{str} the name of the quality type to search for.
-    #   \param machine_definition (Optional) \type{ContainerInstance} If nothing is
+    #   \param machine_definition (Optional) \type{InstanceContainer} If nothing is
     #                               specified then the currently selected machine definition is used.
     #                               specified then the currently selected machine definition is used.
-    #   \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
+    #   \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then
     #                               the current set of selected materials is used.
     #                               the current set of selected materials is used.
-    #   \return the matching quality container \type{ContainerInstance}
-    def findQualityByQualityType(self, quality_type, machine_definition=None, material_containers=None, **kwargs):
+    #   \return the matching quality container \type{InstanceContainer}
+    def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None, **kwargs) -> InstanceContainer:
         criteria = kwargs
         criteria = kwargs
         criteria["type"] = "quality"
         criteria["type"] = "quality"
         if quality_type:
         if quality_type:
@@ -110,9 +112,9 @@ class QualityManager:
     ##  Find all suitable qualities for a combination of machine and material.
     ##  Find all suitable qualities for a combination of machine and material.
     #
     #
     #   \param machine_definition \type{DefinitionContainer} the machine definition.
     #   \param machine_definition \type{DefinitionContainer} the machine definition.
-    #   \param material_container \type{ContainerInstance} the material.
-    #   \return \type{List[ContainerInstance]} the list of suitable qualities.
-    def findAllQualitiesForMachineMaterial(self, machine_definition, material_container):
+    #   \param material_container \type{InstanceContainer} the material.
+    #   \return \type{List[InstanceContainer]} the list of suitable qualities.
+    def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]:
         criteria = {"type": "quality" }
         criteria = {"type": "quality" }
         result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria)
         result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria)
         if not result:
         if not result:
@@ -125,7 +127,7 @@ class QualityManager:
     #
     #
     #   \param machine_definition \type{DefinitionContainer} the machine definition.
     #   \param machine_definition \type{DefinitionContainer} the machine definition.
     #   \return \type{List[InstanceContainer]} the list of quality changes
     #   \return \type{List[InstanceContainer]} the list of quality changes
-    def findAllQualityChangesForMachine(self, machine_definition: DefinitionContainer) -> List[InstanceContainer]:
+    def findAllQualityChangesForMachine(self, machine_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
         if machine_definition.getMetaDataEntry("has_machine_quality"):
         if machine_definition.getMetaDataEntry("has_machine_quality"):
             definition_id = machine_definition.getId()
             definition_id = machine_definition.getId()
         else:
         else:
@@ -141,19 +143,19 @@ class QualityManager:
     #   Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal
     #   Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal
     #   then only one of then is returned (at random).
     #   then only one of then is returned (at random).
     #
     #
-    #   \param global_container_stack \type{ContainerStack} the global machine definition
-    #   \param extruder_stacks \type{List[ContainerStack]} the list of extruder stacks
+    #   \param global_container_stack \type{GlobalStack} the global machine definition
+    #   \param extruder_stacks \type{List[ExtruderStack]} the list of extruder stacks
     #   \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles
     #   \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles
     #       return come from the first extruder in the given list of extruders.
     #       return come from the first extruder in the given list of extruders.
-    def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack, extruder_stacks):
+    def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]:
         global_machine_definition = global_container_stack.getBottom()
         global_machine_definition = global_container_stack.getBottom()
 
 
         if extruder_stacks:
         if extruder_stacks:
             # Multi-extruder machine detected.
             # Multi-extruder machine detected.
-            materials = [stack.findContainer(type="material") for stack in extruder_stacks]
+            materials = [stack.material for stack in extruder_stacks]
         else:
         else:
             # Machine with one extruder.
             # Machine with one extruder.
-            materials = [global_container_stack.findContainer(type="material")]
+            materials = [global_container_stack.material]
 
 
         quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
         quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
 
 
@@ -170,7 +172,7 @@ class QualityManager:
     #   This tries to find a generic or basic version of the given material.
     #   This tries to find a generic or basic version of the given material.
     #   \param material_container \type{InstanceContainer} the material
     #   \param material_container \type{InstanceContainer} the material
     #   \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found.
     #   \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found.
-    def _getBasicMaterials(self, material_container):
+    def _getBasicMaterials(self, material_container: InstanceContainer):
         base_material = material_container.getMetaDataEntry("material")
         base_material = material_container.getMetaDataEntry("material")
         material_container_definition = material_container.getDefinition()
         material_container_definition = material_container.getDefinition()
         if material_container_definition and material_container_definition.getMetaDataEntry("has_machine_quality"):
         if material_container_definition and material_container_definition.getMetaDataEntry("has_machine_quality"):
@@ -192,7 +194,7 @@ class QualityManager:
     def _getFilteredContainers(self, **kwargs):
     def _getFilteredContainers(self, **kwargs):
         return self._getFilteredContainersForStack(None, None, **kwargs)
         return self._getFilteredContainersForStack(None, None, **kwargs)
 
 
-    def _getFilteredContainersForStack(self, machine_definition=None, material_containers=None, **kwargs):
+    def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_containers: List[InstanceContainer] = None, **kwargs):
         # Fill in any default values.
         # Fill in any default values.
         if machine_definition is None:
         if machine_definition is None:
             machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
             machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
@@ -202,7 +204,8 @@ class QualityManager:
 
 
         if material_containers is None:
         if material_containers is None:
             active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
             active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
-            material_containers = [stack.findContainer(type="material") for stack in active_stacks]
+            if active_stacks:
+                material_containers = [stack.material for stack in active_stacks]
 
 
         criteria = kwargs
         criteria = kwargs
         filter_by_material = False
         filter_by_material = False
@@ -216,12 +219,11 @@ class QualityManager:
             filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials")
             filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials")
         else:
         else:
             criteria["definition"] = "fdmprinter"
             criteria["definition"] = "fdmprinter"
-
+        material_ids = set()
         # Stick the material IDs in a set
         # Stick the material IDs in a set
         if material_containers is None or len(material_containers) == 0:
         if material_containers is None or len(material_containers) == 0:
             filter_by_material = False
             filter_by_material = False
         else:
         else:
-            material_ids = set()
             for material_instance in material_containers:
             for material_instance in material_containers:
                 if material_instance is not None:
                 if material_instance is not None:
                     # Add the parent material too.
                     # Add the parent material too.
@@ -245,7 +247,7 @@ class QualityManager:
     #               an extruder definition.
     #               an extruder definition.
     #    \return  \type{DefinitionContainer} the parent machine definition. If the given machine
     #    \return  \type{DefinitionContainer} the parent machine definition. If the given machine
     #               definition doesn't have a parent then it is simply returned.
     #               definition doesn't have a parent then it is simply returned.
-    def getParentMachineDefinition(self, machine_definition: DefinitionContainer) -> DefinitionContainer:
+    def getParentMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
         container_registry = ContainerRegistry.getInstance()
         container_registry = ContainerRegistry.getInstance()
 
 
         machine_entry = machine_definition.getMetaDataEntry("machine")
         machine_entry = machine_definition.getMetaDataEntry("machine")
@@ -274,8 +276,8 @@ class QualityManager:
     #
     #
     #    \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
     #    \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
     #               an extruder definition.
     #               an extruder definition.
-    #    \return \type{DefinitionContainer}
-    def getWholeMachineDefinition(self, machine_definition):
+    #    \return \type{DefinitionContainerInterface}
+    def getWholeMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
         machine_entry = machine_definition.getMetaDataEntry("machine")
         machine_entry = machine_definition.getMetaDataEntry("machine")
         if machine_entry is None:
         if machine_entry is None:
             # This already is a 'global' machine definition.
             # This already is a 'global' machine definition.

+ 117 - 1
cura/Settings/ContainerManager.py

@@ -1,13 +1,15 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2017 Ultimaker B.V.
 # Cura is released under the terms of the AGPLv3 or higher.
 # Cura is released under the terms of the AGPLv3 or higher.
 
 
 import os.path
 import os.path
 import urllib
 import urllib
+import uuid
 from typing import Dict, Union
 from typing import Dict, Union
 
 
 from PyQt5.QtCore import QObject, QUrl, QVariant
 from PyQt5.QtCore import QObject, QUrl, QVariant
 from UM.FlameProfiler import pyqtSlot
 from UM.FlameProfiler import pyqtSlot
 from PyQt5.QtWidgets import QMessageBox
 from PyQt5.QtWidgets import QMessageBox
+from UM.Util import parseBool
 
 
 from UM.PluginRegistry import PluginRegistry
 from UM.PluginRegistry import PluginRegistry
 from UM.SaveFile import SaveFile
 from UM.SaveFile import SaveFile
@@ -671,6 +673,9 @@ class ContainerManager(QObject):
 
 
         return new_change_instances
         return new_change_instances
 
 
+    ##  Create a duplicate of a material, which has the same GUID and base_file metadata
+    #
+    #   \return \type{str} the id of the newly created container.
     @pyqtSlot(str, result = str)
     @pyqtSlot(str, result = str)
     def duplicateMaterial(self, material_id: str) -> str:
     def duplicateMaterial(self, material_id: str) -> str:
         containers = self._container_registry.findInstanceContainers(id=material_id)
         containers = self._container_registry.findInstanceContainers(id=material_id)
@@ -693,6 +698,115 @@ class ContainerManager(QObject):
             duplicated_container.deserialize(f.read())
             duplicated_container.deserialize(f.read())
         duplicated_container.setDirty(True)
         duplicated_container.setDirty(True)
         self._container_registry.addContainer(duplicated_container)
         self._container_registry.addContainer(duplicated_container)
+        return self._getMaterialContainerIdForActiveMachine(new_id)
+
+    ##  Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue
+    #
+    #   \return \type{str} the id of the newly created container.
+    @pyqtSlot(result = str)
+    def createMaterial(self) -> str:
+        # Ensure all settings are saved.
+        Application.getInstance().saveSettings()
+
+        global_stack = Application.getInstance().getGlobalContainerStack()
+        if not global_stack:
+            return ""
+
+        approximate_diameter = round(global_stack.getProperty("material_diameter", "value"))
+        containers = self._container_registry.findInstanceContainers(id = "generic_pla*", approximate_diameter = approximate_diameter)
+        if not containers:
+            Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.")
+            return ""
+
+        base_file = containers[0].getMetaDataEntry("base_file")
+        containers = self._container_registry.findInstanceContainers(id = base_file)
+        if not containers:
+            Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.")
+            return ""
+
+        # Create a new ID & container to hold the data.
+        new_id = self._container_registry.uniqueName("custom_material")
+        container_type = type(containers[0])  # Always XMLMaterialProfile, since we specifically clone the base_file
+        duplicated_container = container_type(new_id)
+
+        # Instead of duplicating we load the data from the basefile again.
+        # This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile
+        # are also correctly created.
+        with open(containers[0].getPath(), encoding="utf-8") as f:
+            duplicated_container.deserialize(f.read())
+
+        duplicated_container.setMetaDataEntry("GUID", str(uuid.uuid4()))
+        duplicated_container.setMetaDataEntry("brand", catalog.i18nc("@label", "Custom"))
+        duplicated_container.setMetaDataEntry("material", catalog.i18nc("@label", "Custom"))
+        duplicated_container.setName(catalog.i18nc("@label", "Custom Material"))
+
+        self._container_registry.addContainer(duplicated_container)
+        return self._getMaterialContainerIdForActiveMachine(new_id)
+
+    ##  Find the id of a material container based on the new material
+    #   Utilty function that is shared between duplicateMaterial and createMaterial
+    #
+    #   \param base_file \type{str} the id of the created container.
+    def _getMaterialContainerIdForActiveMachine(self, base_file):
+        global_stack = Application.getInstance().getGlobalContainerStack()
+        if not global_stack:
+            return base_file
+
+        has_machine_materials = parseBool(global_stack.getMetaDataEntry("has_machine_materials", default = False))
+        has_variant_materials = parseBool(global_stack.getMetaDataEntry("has_variant_materials", default = False))
+        has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False))
+        if has_machine_materials or has_variant_materials:
+            if has_variants:
+                materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
+            else:
+                materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
+
+            if materials:
+                return materials[0].getId()
+
+            Logger.log("w", "Unable to find a suitable container based on %s for the current machine .", base_file)
+            return "" # do not activate a new material if a container can not be found
+
+        return base_file
+
+    ##  Get a list of materials that have the same GUID as the reference material
+    #
+    #   \param material_id \type{str} the id of the material for which to get the linked materials.
+    #   \return \type{list} a list of names of materials with the same GUID
+    @pyqtSlot(str, result = "QStringList")
+    def getLinkedMaterials(self, material_id: str):
+        containers = self._container_registry.findInstanceContainers(id=material_id)
+        if not containers:
+            Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
+            return []
+
+        material_container = containers[0]
+        material_base_file = material_container.getMetaDataEntry("base_file", "")
+        material_guid = material_container.getMetaDataEntry("GUID", "")
+        if not material_guid:
+            Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id)
+            return []
+
+        containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_guid)
+        linked_material_names = []
+        for container in containers:
+            if container.getId() in [material_id, material_base_file] or container.getMetaDataEntry("base_file") != container.getId():
+                continue
+
+            linked_material_names.append(container.getName())
+        return linked_material_names
+
+    ##  Unlink a material from all other materials by creating a new GUID
+    #   \param material_id \type{str} the id of the material to create a new GUID for.
+    @pyqtSlot(str)
+    def unlinkMaterial(self, material_id: str):
+        containers = self._container_registry.findInstanceContainers(id=material_id)
+        if not containers:
+            Logger.log("d", "Unable to make the material with id %s unique, because it doesn't exist.", material_id)
+            return ""
+
+        containers[0].setMetaDataEntry("GUID", str(uuid.uuid4()))
+
 
 
     ##  Get the singleton instance for this class.
     ##  Get the singleton instance for this class.
     @classmethod
     @classmethod
@@ -815,6 +929,8 @@ class ContainerManager(QObject):
             quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0])
             quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0])
         else:
         else:
             quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition))
             quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition))
+        from cura.CuraApplication import CuraApplication
+        quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
         return quality_changes
         return quality_changes
 
 
 
 

+ 3 - 1
cura/Settings/CuraContainerRegistry.py

@@ -22,6 +22,8 @@ from . import GlobalStack
 from .ContainerManager import ContainerManager
 from .ContainerManager import ContainerManager
 from .ExtruderManager import ExtruderManager
 from .ExtruderManager import ExtruderManager
 
 
+from cura.CuraApplication import CuraApplication
+
 from UM.i18n import i18nCatalog
 from UM.i18n import i18nCatalog
 catalog = i18nCatalog("cura")
 catalog = i18nCatalog("cura")
 
 
@@ -43,7 +45,7 @@ class CuraContainerRegistry(ContainerRegistry):
 
 
         if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()):
         if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()):
             #Check against setting version of the definition.
             #Check against setting version of the definition.
-            required_setting_version = int(container.getDefinition().getMetaDataEntry("setting_version"))
+            required_setting_version = CuraApplication.SettingVersion
             actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0))
             actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0))
             if required_setting_version != actual_setting_version:
             if required_setting_version != actual_setting_version:
                 Logger.log("w", "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}.".format(container_id = container.getId(), actual_setting_version = actual_setting_version, required_setting_version = required_setting_version))
                 Logger.log("w", "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}.".format(container_id = container.getId(), actual_setting_version = actual_setting_version, required_setting_version = required_setting_version))

+ 4 - 1
cura/Settings/CuraStackBuilder.py

@@ -9,7 +9,6 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
 
 
 from .GlobalStack import GlobalStack
 from .GlobalStack import GlobalStack
 from .ExtruderStack import ExtruderStack
 from .ExtruderStack import ExtruderStack
-from .CuraContainerStack import CuraContainerStack
 from typing import Optional
 from typing import Optional
 
 
 
 
@@ -76,6 +75,8 @@ class CuraStackBuilder:
         user_container = InstanceContainer(new_stack_id + "_user")
         user_container = InstanceContainer(new_stack_id + "_user")
         user_container.addMetaDataEntry("type", "user")
         user_container.addMetaDataEntry("type", "user")
         user_container.addMetaDataEntry("extruder", new_stack_id)
         user_container.addMetaDataEntry("extruder", new_stack_id)
+        from cura.CuraApplication import CuraApplication
+        user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
         user_container.setDefinition(machine_definition)
         user_container.setDefinition(machine_definition)
 
 
         stack.setUserChanges(user_container)
         stack.setUserChanges(user_container)
@@ -124,6 +125,8 @@ class CuraStackBuilder:
         user_container = InstanceContainer(new_stack_id + "_user")
         user_container = InstanceContainer(new_stack_id + "_user")
         user_container.addMetaDataEntry("type", "user")
         user_container.addMetaDataEntry("type", "user")
         user_container.addMetaDataEntry("machine", new_stack_id)
         user_container.addMetaDataEntry("machine", new_stack_id)
+        from cura.CuraApplication import CuraApplication
+        user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
         user_container.setDefinition(definition)
         user_container.setDefinition(definition)
 
 
         stack.setUserChanges(user_container)
         stack.setUserChanges(user_container)

+ 17 - 7
cura/Settings/ExtruderManager.py

@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Ultimaker B.V.
+# Copyright (c) 2017 Ultimaker B.V.
 # Cura is released under the terms of the AGPLv3 or higher.
 # Cura is released under the terms of the AGPLv3 or higher.
 
 
 from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant #For communicating data and events to Qt.
 from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant #For communicating data and events to Qt.
@@ -20,6 +20,7 @@ from typing import Optional, List, TYPE_CHECKING, Union
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from cura.Settings.ExtruderStack import ExtruderStack
     from cura.Settings.ExtruderStack import ExtruderStack
+    from cura.Settings.GlobalStack import GlobalStack
 
 
 
 
 ##  Manages all existing extruder stacks.
 ##  Manages all existing extruder stacks.
@@ -76,8 +77,9 @@ class ExtruderManager(QObject):
     @pyqtProperty("QVariantMap", notify=extrudersChanged)
     @pyqtProperty("QVariantMap", notify=extrudersChanged)
     def extruderIds(self):
     def extruderIds(self):
         map = {}
         map = {}
-        for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]:
-            map[position] = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position].getId()
+        global_stack_id = Application.getInstance().getGlobalContainerStack().getId()
+        for position in self._extruder_trains[global_stack_id]:
+            map[position] = self._extruder_trains[global_stack_id][position].getId()
         return map
         return map
 
 
     @pyqtSlot(str, result = str)
     @pyqtSlot(str, result = str)
@@ -85,7 +87,7 @@ class ExtruderManager(QObject):
         for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]:
         for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]:
             extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position]
             extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position]
             if extruder.getId() == id:
             if extruder.getId() == id:
-                return extruder.findContainer(type = "quality_changes").getId()
+                return extruder.qualityChanges.getId()
 
 
     ##  The instance of the singleton pattern.
     ##  The instance of the singleton pattern.
     #
     #
@@ -235,6 +237,13 @@ class ExtruderManager(QObject):
         if machine_id not in self._extruder_trains:
         if machine_id not in self._extruder_trains:
             self._extruder_trains[machine_id] = {}
             self._extruder_trains[machine_id] = {}
             changed = True
             changed = True
+
+        # do not register if an extruder has already been registered at the position on this machine
+        if any(item.getId() == extruder_train.getId() for item in self._extruder_trains[machine_id].values()):
+            Logger.log("w", "Extruder [%s] has already been registered on machine [%s], not doing anything",
+                       extruder_train.getId(), machine_id)
+            return
+
         if extruder_train:
         if extruder_train:
             self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
             self._extruder_trains[machine_id][extruder_train.getMetaDataEntry("position")] = extruder_train
             changed = True
             changed = True
@@ -362,6 +371,8 @@ class ExtruderManager(QObject):
             user_profile = InstanceContainer(extruder_stack_id + "_current_settings")  # Add an empty user profile.
             user_profile = InstanceContainer(extruder_stack_id + "_current_settings")  # Add an empty user profile.
             user_profile.addMetaDataEntry("type", "user")
             user_profile.addMetaDataEntry("type", "user")
             user_profile.addMetaDataEntry("extruder", extruder_stack_id)
             user_profile.addMetaDataEntry("extruder", extruder_stack_id)
+            from cura.CuraApplication import CuraApplication
+            user_profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
             user_profile.setDefinition(machine_definition)
             user_profile.setDefinition(machine_definition)
             container_registry.addContainer(user_profile)
             container_registry.addContainer(user_profile)
         container_stack.addContainer(user_profile)
         container_stack.addContainer(user_profile)
@@ -457,10 +468,9 @@ class ExtruderManager(QObject):
     #   \param machine_id The machine to remove the extruders for.
     #   \param machine_id The machine to remove the extruders for.
     def removeMachineExtruders(self, machine_id: str):
     def removeMachineExtruders(self, machine_id: str):
         for extruder in self.getMachineExtruders(machine_id):
         for extruder in self.getMachineExtruders(machine_id):
-            containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", extruder = extruder.getId())
-            for container in containers:
-                ContainerRegistry.getInstance().removeContainer(container.getId())
+            ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId())
             ContainerRegistry.getInstance().removeContainer(extruder.getId())
             ContainerRegistry.getInstance().removeContainer(extruder.getId())
+        del self._extruder_trains[machine_id]
 
 
     ##  Returns extruders for a specific machine.
     ##  Returns extruders for a specific machine.
     #
     #

+ 15 - 6
cura/Settings/ExtruderStack.py

@@ -1,22 +1,21 @@
 # Copyright (c) 2017 Ultimaker B.V.
 # Copyright (c) 2017 Ultimaker B.V.
 # Cura is released under the terms of the AGPLv3 or higher.
 # Cura is released under the terms of the AGPLv3 or higher.
 
 
-from typing import Any
-
-from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
+from typing import Any, TYPE_CHECKING, Optional
 
 
 from UM.Decorators import override
 from UM.Decorators import override
 from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
 from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
-from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
+from UM.Settings.ContainerStack import ContainerStack
 from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.Settings.ContainerRegistry import ContainerRegistry
-from UM.Settings.InstanceContainer import InstanceContainer
-from UM.Settings.DefinitionContainer import DefinitionContainer
 from UM.Settings.Interfaces import ContainerInterface
 from UM.Settings.Interfaces import ContainerInterface
 
 
 from . import Exceptions
 from . import Exceptions
 from .CuraContainerStack import CuraContainerStack
 from .CuraContainerStack import CuraContainerStack
 from .ExtruderManager import ExtruderManager
 from .ExtruderManager import ExtruderManager
 
 
+if TYPE_CHECKING:
+    from cura.Settings.GlobalStack import GlobalStack
+
 ##  Represents an Extruder and its related containers.
 ##  Represents an Extruder and its related containers.
 #
 #
 #
 #
@@ -38,6 +37,10 @@ class ExtruderStack(CuraContainerStack):
         # For backward compatibility: Register the extruder with the Extruder Manager
         # For backward compatibility: Register the extruder with the Extruder Manager
         ExtruderManager.getInstance().registerExtruder(self, stack.id)
         ExtruderManager.getInstance().registerExtruder(self, stack.id)
 
 
+    @override(ContainerStack)
+    def getNextStack(self) -> Optional["GlobalStack"]:
+        return super().getNextStack()
+
     @classmethod
     @classmethod
     def getLoadingPriority(cls) -> int:
     def getLoadingPriority(cls) -> int:
         return 3
         return 3
@@ -59,6 +62,12 @@ class ExtruderStack(CuraContainerStack):
         if not super().getProperty(key, "settable_per_extruder"):
         if not super().getProperty(key, "settable_per_extruder"):
             return self.getNextStack().getProperty(key, property_name)
             return self.getNextStack().getProperty(key, property_name)
 
 
+        limit_to_extruder = super().getProperty(key, "limit_to_extruder")
+        if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder):
+            result = self.getNextStack().extruders[str(limit_to_extruder)].getProperty(key, property_name)
+            if result is not None:
+                return result
+
         return super().getProperty(key, property_name)
         return super().getProperty(key, property_name)
 
 
     @override(CuraContainerStack)
     @override(CuraContainerStack)

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