Browse Source

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

Diego Prado Gesto 5 years ago
parent
commit
de5de8b9f1

+ 212 - 161
cura/BuildVolume.py

@@ -1,6 +1,6 @@
 # Copyright (c) 2019 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
-
+from UM.Mesh.MeshData import MeshData
 from cura.Scene.CuraSceneNode import CuraSceneNode
 from cura.Settings.ExtruderManager import ExtruderManager
 from UM.Application import Application #To modify the maximum zoom level.
@@ -20,13 +20,20 @@ from UM.Signal import Signal
 from PyQt5.QtCore import QTimer
 from UM.View.RenderBatch import RenderBatch
 from UM.View.GL.OpenGL import OpenGL
+from cura.Settings.GlobalStack import GlobalStack
+
 catalog = i18nCatalog("cura")
 
 import numpy
 import math
 import copy
 
-from typing import List, Optional
+from typing import List, Optional, TYPE_CHECKING, Any, Set, cast, Iterable, Dict
+
+if TYPE_CHECKING:
+    from cura.CuraApplication import CuraApplication
+    from cura.Settings.ExtruderStack import ExtruderStack
+    from UM.Settings.ContainerStack import ContainerStack
 
 # Radius of disallowed area in mm around prime. I.e. how much distance to keep from prime position.
 PRIME_CLEARANCE = 6.5
@@ -36,45 +43,46 @@ PRIME_CLEARANCE = 6.5
 class BuildVolume(SceneNode):
     raftThicknessChanged = Signal()
 
-    def __init__(self, application, parent = None):
+    def __init__(self, application: "CuraApplication", parent: Optional[SceneNode] = None) -> None:
         super().__init__(parent)
         self._application = application
         self._machine_manager = self._application.getMachineManager()
 
-        self._volume_outline_color = None
-        self._x_axis_color = None
-        self._y_axis_color = None
-        self._z_axis_color = None
-        self._disallowed_area_color = None
-        self._error_area_color = None
+        self._volume_outline_color = None  # type: Optional[Color]
+        self._x_axis_color = None  # type: Optional[Color]
+        self._y_axis_color = None  # type: Optional[Color]
+        self._z_axis_color = None  # type: Optional[Color]
+        self._disallowed_area_color = None  # type: Optional[Color]
+        self._error_area_color = None  # type: Optional[Color]
 
-        self._width = 0 #type: float
-        self._height = 0 #type: float
-        self._depth = 0 #type: float
-        self._shape = "" #type: str
+        self._width = 0  # type: float
+        self._height = 0  # type: float
+        self._depth = 0  # type: float
+        self._shape = ""  # type: str
 
         self._shader = None
 
-        self._origin_mesh = None
+        self._origin_mesh = None  # type: Optional[MeshData]
         self._origin_line_length = 20
         self._origin_line_width = 0.5
 
-        self._grid_mesh = None
+        self._grid_mesh = None   # type: Optional[MeshData]
         self._grid_shader = None
 
-        self._disallowed_areas = []
-        self._disallowed_areas_no_brim = []
-        self._disallowed_area_mesh = None
+        self._disallowed_areas = []  # type: List[Polygon]
+        self._disallowed_areas_no_brim = []  # type: List[Polygon]
+        self._disallowed_area_mesh = None  # type: Optional[MeshData]
+        self._disallowed_area_size = 0.
 
-        self._error_areas = []
-        self._error_mesh = None
+        self._error_areas = []  # type: List[Polygon]
+        self._error_mesh = None  # type: Optional[MeshData]
 
         self.setCalculateBoundingBox(False)
-        self._volume_aabb = None
+        self._volume_aabb = None  # type: Optional[AxisAlignedBox]
 
         self._raft_thickness = 0.0
         self._extra_z_clearance = 0.0
-        self._adhesion_type = None
+        self._adhesion_type = None  # type: Any
         self._platform = Platform(self)
 
         self._build_volume_message = Message(catalog.i18nc("@info:status",
@@ -82,7 +90,7 @@ class BuildVolume(SceneNode):
             " \"Print Sequence\" setting to prevent the gantry from colliding"
             " with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
 
-        self._global_container_stack = None
+        self._global_container_stack = None  # type: Optional[GlobalStack]
 
         self._stack_change_timer = QTimer()
         self._stack_change_timer.setInterval(100)
@@ -100,7 +108,7 @@ class BuildVolume(SceneNode):
         self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged)
 
         #Objects loaded at the moment. We are connected to the property changed events of these objects.
-        self._scene_objects = set()
+        self._scene_objects = set()  # type: Set[SceneNode]
 
         self._scene_change_timer = QTimer()
         self._scene_change_timer.setInterval(100)
@@ -124,8 +132,8 @@ class BuildVolume(SceneNode):
         # Enable and disable extruder
         self._machine_manager.extruderChanged.connect(self.updateNodeBoundaryCheck)
 
-        # list of settings which were updated
-        self._changed_settings_since_last_rebuild = []
+        # List of settings which were updated
+        self._changed_settings_since_last_rebuild = []  # type: List[str]
 
     def _onSceneChanged(self, source):
         if self._global_container_stack:
@@ -219,9 +227,12 @@ class BuildVolume(SceneNode):
     ##  For every sliceable node, update node._outside_buildarea
     #
     def updateNodeBoundaryCheck(self):
+        if not self._global_container_stack:
+            return
+
         root = self._application.getController().getScene().getRoot()
-        nodes = list(BreadthFirstIterator(root))
-        group_nodes = []
+        nodes = cast(List[SceneNode], list(cast(Iterable, BreadthFirstIterator(root))))
+        group_nodes = []  # type: List[SceneNode]
 
         build_volume_bounding_box = self.getBoundingBox()
         if build_volume_bounding_box:
@@ -240,6 +251,9 @@ class BuildVolume(SceneNode):
                 group_nodes.append(node)  # Keep list of affected group_nodes
 
             if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
+                if not isinstance(node, CuraSceneNode):
+                    continue
+
                 if node.collidesWithBbox(build_volume_bounding_box):
                     node.setOutsideBuildArea(True)
                     continue
@@ -277,8 +291,8 @@ class BuildVolume(SceneNode):
                 child_node.setOutsideBuildArea(group_node.isOutsideBuildArea())
 
     ##  Update the outsideBuildArea of a single node, given bounds or current build volume
-    def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None):
-        if not isinstance(node, CuraSceneNode):
+    def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None) -> None:
+        if not isinstance(node, CuraSceneNode) or self._global_container_stack is None:
             return
 
         if bounds is None:
@@ -310,7 +324,7 @@ class BuildVolume(SceneNode):
 
             node.setOutsideBuildArea(False)
 
-    def _buildGridMesh(self, min_w, max_w, min_h, max_h, min_d, max_d, z_fight_distance):
+    def _buildGridMesh(self, min_w: float, max_w: float, min_h: float, max_h: float, min_d: float, max_d:float, z_fight_distance: float) -> MeshData:
         mb = MeshBuilder()
         if self._shape != "elliptic":
             # Build plate grid mesh
@@ -346,7 +360,7 @@ class BuildVolume(SceneNode):
                 mb.setVertexUVCoordinates(n, v[0], v[2] * aspect)
             return mb.build().getTransformed(scale_matrix)
 
-    def _buildMesh(self, min_w, max_w, min_h, max_h, min_d, max_d, z_fight_distance):
+    def _buildMesh(self, min_w: float, max_w: float, min_h: float, max_h: float, min_d: float, max_d:float, z_fight_distance: float) -> MeshData:
         if self._shape != "elliptic":
             # Outline 'cube' of the build volume
             mb = MeshBuilder()
@@ -379,7 +393,7 @@ class BuildVolume(SceneNode):
             mb.addArc(max_w, Vector.Unit_Y, center = (0, max_h, 0),  color = self._volume_outline_color)
             return mb.build().getTransformed(scale_matrix)
 
-    def _buildOriginMesh(self, origin):
+    def _buildOriginMesh(self, origin: Vector) -> MeshData:
         mb = MeshBuilder()
         mb.addCube(
             width=self._origin_line_length,
@@ -404,22 +418,80 @@ class BuildVolume(SceneNode):
         )
         return mb.build()
 
+    def _updateColors(self):
+        theme = self._application.getTheme()
+        if theme is None:
+            return
+        self._volume_outline_color = Color(*theme.getColor("volume_outline").getRgb())
+        self._x_axis_color = Color(*theme.getColor("x_axis").getRgb())
+        self._y_axis_color = Color(*theme.getColor("y_axis").getRgb())
+        self._z_axis_color = Color(*theme.getColor("z_axis").getRgb())
+        self._disallowed_area_color = Color(*theme.getColor("disallowed_area").getRgb())
+        self._error_area_color = Color(*theme.getColor("error_area").getRgb())
+
+    def _buildErrorMesh(self, min_w: float, max_w: float, min_h: float, max_h: float, min_d: float, max_d: float, disallowed_area_height: float) -> Optional[MeshData]:
+        if not self._error_areas:
+            return None
+        mb = MeshBuilder()
+        for error_area in self._error_areas:
+            color = self._error_area_color
+            points = error_area.getPoints()
+            first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
+                           self._clamp(points[0][1], min_d, max_d))
+            previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
+                                    self._clamp(points[0][1], min_d, max_d))
+            for point in points:
+                new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height,
+                                   self._clamp(point[1], min_d, max_d))
+                mb.addFace(first, previous_point, new_point, color=color)
+                previous_point = new_point
+        return mb.build()
+
+    def _buildDisallowedAreaMesh(self, min_w: float, max_w: float, min_h: float, max_h: float, min_d: float, max_d: float, disallowed_area_height: float) -> Optional[MeshData]:
+        if not self._disallowed_areas:
+            return None
+
+        mb = MeshBuilder()
+        color = self._disallowed_area_color
+        for polygon in self._disallowed_areas:
+            points = polygon.getPoints()
+            if len(points) == 0:
+                continue
+
+            first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
+                           self._clamp(points[0][1], min_d, max_d))
+            previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
+                                    self._clamp(points[0][1], min_d, max_d))
+            for point in points:
+                new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height,
+                                   self._clamp(point[1], min_d, max_d))
+                mb.addFace(first, previous_point, new_point, color=color)
+                previous_point = new_point
+
+            # Find the largest disallowed area to exclude it from the maximum scale bounds.
+            # This is a very nasty hack. This pretty much only works for UM machines.
+            # This disallowed area_size needs a -lot- of rework at some point in the future: TODO
+            if numpy.min(points[:,
+                         1]) >= 0:  # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
+                size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
+            else:
+                size = 0
+            self._disallowed_area_size = max(size, self._disallowed_area_size)
+        return mb.build()
+
     ##  Recalculates the build volume & disallowed areas.
-    def rebuild(self):
+    def rebuild(self) -> None:
         if not self._width or not self._height or not self._depth:
             return
 
         if not self._engine_ready:
             return
 
+        if not self._global_container_stack:
+            return
+
         if not self._volume_outline_color:
-            theme = self._application.getTheme()
-            self._volume_outline_color = Color(*theme.getColor("volume_outline").getRgb())
-            self._x_axis_color = Color(*theme.getColor("x_axis").getRgb())
-            self._y_axis_color = Color(*theme.getColor("y_axis").getRgb())
-            self._z_axis_color = Color(*theme.getColor("z_axis").getRgb())
-            self._disallowed_area_color = Color(*theme.getColor("disallowed_area").getRgb())
-            self._error_area_color = Color(*theme.getColor("error_area").getRgb())
+            self._updateColors()
 
         min_w = -self._width / 2
         max_w = self._width / 2
@@ -442,52 +514,10 @@ class BuildVolume(SceneNode):
         self._origin_mesh = self._buildOriginMesh(origin)
 
         disallowed_area_height = 0.1
-        disallowed_area_size = 0
-        if self._disallowed_areas:
-            mb = MeshBuilder()
-            color = self._disallowed_area_color
-            for polygon in self._disallowed_areas:
-                points = polygon.getPoints()
-                if len(points) == 0:
-                    continue
-
-                first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d))
-                previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height, self._clamp(points[0][1], min_d, max_d))
-                for point in points:
-                    new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height, self._clamp(point[1], min_d, max_d))
-                    mb.addFace(first, previous_point, new_point, color = color)
-                    previous_point = new_point
-
-                # Find the largest disallowed area to exclude it from the maximum scale bounds.
-                # This is a very nasty hack. This pretty much only works for UM machines.
-                # This disallowed area_size needs a -lot- of rework at some point in the future: TODO
-                if numpy.min(points[:, 1]) >= 0: # This filters out all areas that have points to the left of the centre. This is done to filter the skirt area.
-                    size = abs(numpy.max(points[:, 1]) - numpy.min(points[:, 1]))
-                else:
-                    size = 0
-                disallowed_area_size = max(size, disallowed_area_size)
-
-            self._disallowed_area_mesh = mb.build()
-        else:
-            self._disallowed_area_mesh = None
+        self._disallowed_area_size = 0.
+        self._disallowed_area_mesh = self._buildDisallowedAreaMesh(min_w, max_w, min_h, max_h, min_d, max_d, disallowed_area_height)
 
-        if self._error_areas:
-            mb = MeshBuilder()
-            for error_area in self._error_areas:
-                color = self._error_area_color
-                points = error_area.getPoints()
-                first = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
-                               self._clamp(points[0][1], min_d, max_d))
-                previous_point = Vector(self._clamp(points[0][0], min_w, max_w), disallowed_area_height,
-                                        self._clamp(points[0][1], min_d, max_d))
-                for point in points:
-                    new_point = Vector(self._clamp(point[0], min_w, max_w), disallowed_area_height,
-                                       self._clamp(point[1], min_d, max_d))
-                    mb.addFace(first, previous_point, new_point, color=color)
-                    previous_point = new_point
-            self._error_mesh = mb.build()
-        else:
-            self._error_mesh = None
+        self._error_mesh = self._buildErrorMesh(min_w, max_w, min_h, max_h, min_d, max_d, disallowed_area_height)
 
         self._volume_aabb = AxisAlignedBox(
             minimum = Vector(min_w, min_h - 1.0, min_d),
@@ -499,21 +529,24 @@ class BuildVolume(SceneNode):
         # This is probably wrong in all other cases. TODO!
         # The +1 and -1 is added as there is always a bit of extra room required to work properly.
         scale_to_max_bounds = AxisAlignedBox(
-            minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + disallowed_area_size - bed_adhesion_size + 1),
-            maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - disallowed_area_size + bed_adhesion_size - 1)
+            minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_d + self._disallowed_area_size - bed_adhesion_size + 1),
+            maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - self._disallowed_area_size + bed_adhesion_size - 1)
         )
 
-        self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds
+        self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds  # type: ignore
 
         self.updateNodeBoundaryCheck()
 
-    def getBoundingBox(self) -> AxisAlignedBox:
+    def getBoundingBox(self):
         return self._volume_aabb
 
     def getRaftThickness(self) -> float:
         return self._raft_thickness
 
-    def _updateRaftThickness(self):
+    def _updateRaftThickness(self) -> None:
+        if not self._global_container_stack:
+            return
+
         old_raft_thickness = self._raft_thickness
         if self._global_container_stack.extruders:
             # This might be called before the extruder stacks have initialised, in which case getting the adhesion_type fails
@@ -524,7 +557,7 @@ class BuildVolume(SceneNode):
                 self._global_container_stack.getProperty("raft_base_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_thickness", "value") +
+                self._global_container_stack.getProperty("raft_surface_thickness", "value") +
                 self._global_container_stack.getProperty("raft_airgap", "value") -
                 self._global_container_stack.getProperty("layer_0_z_overlap", "value"))
 
@@ -533,28 +566,23 @@ class BuildVolume(SceneNode):
             self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World)
             self.raftThicknessChanged.emit()
 
-    def _updateExtraZClearance(self) -> None:
+    def _calculateExtraZClearance(self, extruders: List["ContainerStack"]) -> float:
+        if not self._global_container_stack:
+            return 0
+        
         extra_z = 0.0
-        extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
-        use_extruders = False
         for extruder in extruders:
             if extruder.getProperty("retraction_hop_enabled", "value"):
                 retraction_hop = extruder.getProperty("retraction_hop", "value")
                 if extra_z is None or retraction_hop > extra_z:
                     extra_z = retraction_hop
-            use_extruders = True
-        if not use_extruders:
-            # If no extruders, take global value.
-            if self._global_container_stack.getProperty("retraction_hop_enabled", "value"):
-                extra_z = self._global_container_stack.getProperty("retraction_hop", "value")
-        if extra_z != self._extra_z_clearance:
-            self._extra_z_clearance = extra_z
+        return extra_z
 
     def _onStackChanged(self):
         self._stack_change_timer.start()
 
     ##  Update the build volume visualization
-    def _onStackChangeTimerFinished(self):
+    def _onStackChangeTimerFinished(self) -> None:
         if self._global_container_stack:
             self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
             extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
@@ -585,7 +613,7 @@ class BuildVolume(SceneNode):
 
             self._updateDisallowedAreas()
             self._updateRaftThickness()
-            self._updateExtraZClearance()
+            self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
 
             if self._engine_ready:
                 self.rebuild()
@@ -594,20 +622,23 @@ class BuildVolume(SceneNode):
             if camera:
                 diagonal = self.getDiagonalSize()
                 if diagonal > 1:
-                    camera.setZoomRange(min = 0.1, max = diagonal * 5) #You can zoom out up to 5 times the diagonal. This gives some space around the volume.
+                    # You can zoom out up to 5 times the diagonal. This gives some space around the volume.
+                    camera.setZoomRange(min = 0.1, max = diagonal * 5)  # type: ignore
 
-    def _onEngineCreated(self):
+    def _onEngineCreated(self) -> None:
         self._engine_ready = True
         self.rebuild()
 
-    def _onSettingChangeTimerFinished(self):
+    def _onSettingChangeTimerFinished(self) -> None:
+        if not self._global_container_stack:
+            return
+
         rebuild_me = False
         update_disallowed_areas = False
         update_raft_thickness = False
         update_extra_z_clearance = True
 
         for setting_key in self._changed_settings_since_last_rebuild:
-
             if setting_key == "print_sequence":
                 machine_height = self._global_container_stack.getProperty("machine_height", "value")
                 if self._application.getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time" and len(self._scene_objects) > 1:
@@ -620,33 +651,26 @@ class BuildVolume(SceneNode):
                     self._height = self._global_container_stack.getProperty("machine_height", "value")
                     self._build_volume_message.hide()
                 update_disallowed_areas = True
-                rebuild_me = True
 
             # sometimes the machine size or shape settings are adjusted on the active machine, we should reflect this
             if setting_key in self._machine_settings:
-                self._height = self._global_container_stack.getProperty("machine_height", "value")
-                self._width = self._global_container_stack.getProperty("machine_width", "value")
-                self._depth = self._global_container_stack.getProperty("machine_depth", "value")
-                self._shape = self._global_container_stack.getProperty("machine_shape", "value")
+                self._updateMachineSizeProperties()
                 update_extra_z_clearance = True
                 update_disallowed_areas = True
-                rebuild_me = True
 
-            if setting_key in self._skirt_settings + self._prime_settings + self._tower_settings + self._ooze_shield_settings + self._distance_settings + self._extruder_settings:
+            if setting_key in self._disallowed_area_settings:
                 update_disallowed_areas = True
-                rebuild_me = True
 
             if setting_key in self._raft_settings:
                 update_raft_thickness = True
-                rebuild_me = True
 
             if setting_key in self._extra_z_settings:
                 update_extra_z_clearance = True
-                rebuild_me = True
 
             if setting_key in self._limit_to_extruder_settings:
                 update_disallowed_areas = True
-                rebuild_me = True
+
+            rebuild_me = update_extra_z_clearance or update_disallowed_areas or update_raft_thickness
 
         # We only want to update all of them once.
         if update_disallowed_areas:
@@ -656,7 +680,7 @@ class BuildVolume(SceneNode):
             self._updateRaftThickness()
 
         if update_extra_z_clearance:
-            self._updateExtraZClearance()
+            self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
 
         if rebuild_me:
             self.rebuild()
@@ -664,7 +688,7 @@ class BuildVolume(SceneNode):
         # We just did a rebuild, reset the list.
         self._changed_settings_since_last_rebuild = []
 
-    def _onSettingPropertyChanged(self, setting_key: str, property_name: str):
+    def _onSettingPropertyChanged(self, setting_key: str, property_name: str) -> None:
         if property_name != "value":
             return
 
@@ -675,6 +699,14 @@ class BuildVolume(SceneNode):
     def hasErrors(self) -> bool:
         return self._has_errors
 
+    def _updateMachineSizeProperties(self) -> None:
+        if not self._global_container_stack:
+            return
+        self._height = self._global_container_stack.getProperty("machine_height", "value")
+        self._width = self._global_container_stack.getProperty("machine_width", "value")
+        self._depth = self._global_container_stack.getProperty("machine_depth", "value")
+        self._shape = self._global_container_stack.getProperty("machine_shape", "value")
+
     ##  Calls _updateDisallowedAreas and makes sure the changes appear in the
     #   scene.
     #
@@ -686,10 +718,10 @@ class BuildVolume(SceneNode):
     def _updateDisallowedAreasAndRebuild(self):
         self._updateDisallowedAreas()
         self._updateRaftThickness()
-        self._updateExtraZClearance()
+        self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
         self.rebuild()
 
-    def _updateDisallowedAreas(self):
+    def _updateDisallowedAreas(self) -> None:
         if not self._global_container_stack:
             return
 
@@ -843,9 +875,10 @@ class BuildVolume(SceneNode):
     #   \param used_extruders The extruder stacks to generate disallowed areas
     #   for.
     #   \return A dictionary with for each used extruder ID the prime areas.
-    def _computeDisallowedAreasPrimeBlob(self, border_size, used_extruders):
-        result = {}
-
+    def _computeDisallowedAreasPrimeBlob(self, border_size: float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]:
+        result = {}  # type: Dict[str, List[Polygon]]
+        if not self._global_container_stack:
+            return result
         machine_width = self._global_container_stack.getProperty("machine_width", "value")
         machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
         for extruder in used_extruders:
@@ -853,13 +886,13 @@ class BuildVolume(SceneNode):
             prime_x = extruder.getProperty("extruder_prime_pos_x", "value")
             prime_y = -extruder.getProperty("extruder_prime_pos_y", "value")
 
-            #Ignore extruder prime position if it is not set or if blob is disabled
+            # 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()] = []
                 continue
 
             if not self._global_container_stack.getProperty("machine_center_is_zero", "value"):
-                prime_x = prime_x - machine_width / 2 #Offset by half machine_width and _depth to put the origin in the front-left.
+                prime_x = prime_x - machine_width / 2  # Offset by half machine_width and _depth to put the origin in the front-left.
                 prime_y = prime_y + machine_depth / 2
 
             prime_polygon = Polygon.approximatedCircle(PRIME_CLEARANCE)
@@ -1015,36 +1048,24 @@ class BuildVolume(SceneNode):
     #   stack.
     #
     #   \return A sequence of setting values, one for each extruder.
-    def _getSettingFromAllExtruders(self, setting_key):
+    def _getSettingFromAllExtruders(self, setting_key: str) -> List[Any]:
         all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
         all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
-        for i in range(len(all_values)):
-            if not all_values[i] and (all_types[i] == "int" or all_types[i] == "float"):
+        for i, (setting_value, setting_type) in enumerate(zip(all_values, all_types)):
+            if not setting_value and (setting_type == "int" or setting_type == "float"):
                 all_values[i] = 0
         return all_values
 
-    ##  Calculate the disallowed radius around the edge.
-    #
-    #   This disallowed radius is to allow for space around the models that is
-    #   not part of the collision radius, such as bed adhesion (skirt/brim/raft)
-    #   and travel avoid distance.
-    def getEdgeDisallowedSize(self):
-        if not self._global_container_stack or not self._global_container_stack.extruders:
-            return 0
+    def _calculateBedAdhesionSize(self, used_extruders):
+        if self._global_container_stack is None:
+            return
 
         container_stack = self._global_container_stack
-        used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
-
-        # If we are printing one at a time, we need to add the bed adhesion size to the disallowed areas of the objects
-        if container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
-            return 0.1  # Return a very small value, so we do draw disallowed area's near the edges.
-
         adhesion_type = container_stack.getProperty("adhesion_type", "value")
         skirt_brim_line_width = self._global_container_stack.getProperty("skirt_brim_line_width", "value")
         initial_layer_line_width_factor = self._global_container_stack.getProperty("initial_layer_line_width_factor", "value")
-        #Use brim width if brim is enabled OR the prime tower has a brim.
-        if adhesion_type == "brim" or (self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and
-                                       adhesion_type != "raft"):
+        # Use brim width if brim is enabled OR the prime tower has a brim.
+        if adhesion_type == "brim" or (self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and adhesion_type != "raft"):
             brim_line_count = self._global_container_stack.getProperty("brim_line_count", "value")
             bed_adhesion_size = skirt_brim_line_width * brim_line_count * initial_layer_line_width_factor / 100.0
 
@@ -1053,11 +1074,12 @@ class BuildVolume(SceneNode):
 
             # We don't create an additional line for the extruder we're printing the brim with.
             bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
-        elif adhesion_type == "skirt": #No brim? Also not on prime tower? Then use whatever the adhesion type is saying: Skirt, raft or none.
+        elif adhesion_type == "skirt":  # No brim? Also not on prime tower? Then use whatever the adhesion type is saying: Skirt, raft or none.
             skirt_distance = self._global_container_stack.getProperty("skirt_gap", "value")
             skirt_line_count = self._global_container_stack.getProperty("skirt_line_count", "value")
 
-            bed_adhesion_size = skirt_distance + (skirt_brim_line_width * skirt_line_count) * initial_layer_line_width_factor / 100.0
+            bed_adhesion_size = skirt_distance + (
+                        skirt_brim_line_width * skirt_line_count) * initial_layer_line_width_factor / 100.0
 
             for extruder_stack in used_extruders:
                 bed_adhesion_size += extruder_stack.getProperty("skirt_brim_line_width", "value") * extruder_stack.getProperty("initial_layer_line_width_factor", "value") / 100.0
@@ -1066,10 +1088,8 @@ class BuildVolume(SceneNode):
             bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
         elif adhesion_type == "raft":
             bed_adhesion_size = self._global_container_stack.getProperty("raft_margin", "value")
-
         elif adhesion_type == "none":
             bed_adhesion_size = 0
-
         else:
             raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?")
 
@@ -1078,26 +1098,56 @@ class BuildVolume(SceneNode):
             self._global_container_stack.getProperty("machine_depth", "value")
         )
         bed_adhesion_size = min(bed_adhesion_size, max_length_available)
+        return bed_adhesion_size
+
+    def _calculateFarthestShieldDistance(self, container_stack):
+        farthest_shield_distance = 0
+        if container_stack.getProperty("draft_shield_enabled", "value"):
+            farthest_shield_distance = max(farthest_shield_distance, container_stack.getProperty("draft_shield_dist", "value"))
+        if container_stack.getProperty("ooze_shield_enabled", "value"):
+            farthest_shield_distance = max(farthest_shield_distance,container_stack.getProperty("ooze_shield_dist", "value"))
+        return farthest_shield_distance
 
+    def _calculateSupportExpansion(self, container_stack):
         support_expansion = 0
         support_enabled = self._global_container_stack.getProperty("support_enable", "value")
         support_offset = self._global_container_stack.getProperty("support_offset", "value")
         if support_enabled and support_offset:
             support_expansion += support_offset
+        return support_expansion
 
-        farthest_shield_distance = 0
-        if container_stack.getProperty("draft_shield_enabled", "value"):
-            farthest_shield_distance = max(farthest_shield_distance, container_stack.getProperty("draft_shield_dist", "value"))
-        if container_stack.getProperty("ooze_shield_enabled", "value"):
-            farthest_shield_distance = max(farthest_shield_distance, container_stack.getProperty("ooze_shield_dist", "value"))
-
+    def _calculateMoveFromWallRadius(self, used_extruders):
         move_from_wall_radius = 0  # Moves that start from outer wall.
-        move_from_wall_radius = max(move_from_wall_radius, max(self._getSettingFromAllExtruders("infill_wipe_dist")))
-        avoid_enabled_per_extruder = [stack.getProperty("travel_avoid_other_parts","value") for stack in used_extruders]
+        all_values = [move_from_wall_radius]
+        all_values.extend(self._getSettingFromAllExtruders("infill_wipe_dist"))
+        move_from_wall_radius = max(all_values)
+        avoid_enabled_per_extruder = [stack.getProperty("travel_avoid_other_parts", "value") for stack in used_extruders]
         travel_avoid_distance_per_extruder = [stack.getProperty("travel_avoid_distance", "value") for stack in used_extruders]
-        for avoid_other_parts_enabled, avoid_distance in zip(avoid_enabled_per_extruder, travel_avoid_distance_per_extruder): #For each extruder (or just global).
+        for avoid_other_parts_enabled, avoid_distance in zip(avoid_enabled_per_extruder, travel_avoid_distance_per_extruder):  # For each extruder (or just global).
             if avoid_other_parts_enabled:
                 move_from_wall_radius = max(move_from_wall_radius, avoid_distance)
+        return move_from_wall_radius
+
+    ##  Calculate the disallowed radius around the edge.
+    #
+    #   This disallowed radius is to allow for space around the models that is
+    #   not part of the collision radius, such as bed adhesion (skirt/brim/raft)
+    #   and travel avoid distance.
+    def getEdgeDisallowedSize(self):
+        if not self._global_container_stack or not self._global_container_stack.extruders:
+            return 0
+
+        container_stack = self._global_container_stack
+        used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
+
+        # If we are printing one at a time, we need to add the bed adhesion size to the disallowed areas of the objects
+        if container_stack.getProperty("print_sequence", "value") == "one_at_a_time":
+            return 0.1  # Return a very small value, so we do draw disallowed area's near the edges.
+
+        bed_adhesion_size = self._calculateBedAdhesionSize(used_extruders)
+        support_expansion = self._calculateSupportExpansion(self._global_container_stack)
+        farthest_shield_distance = self._calculateFarthestShieldDistance(self._global_container_stack)
+        move_from_wall_radius = self._calculateMoveFromWallRadius(used_extruders)
 
         # Now combine our different pieces of data to get the final border size.
         # Support expansion is added to the bed adhesion, since the bed adhesion goes around support.
@@ -1118,3 +1168,4 @@ class BuildVolume(SceneNode):
     _distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts", "travel_avoid_supports"]
     _extruder_settings = ["support_enable", "support_bottom_enable", "support_roof_enable", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "brim_line_count", "adhesion_extruder_nr", "adhesion_type"] #Settings that can affect which extruders are used.
     _limit_to_extruder_settings = ["wall_extruder_nr", "wall_0_extruder_nr", "wall_x_extruder_nr", "top_bottom_extruder_nr", "infill_extruder_nr", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "adhesion_extruder_nr"]
+    _disallowed_area_settings = _skirt_settings + _prime_settings + _tower_settings + _ooze_shield_settings + _distance_settings + _extruder_settings

+ 0 - 1
cura/CuraApplication.py

@@ -839,7 +839,6 @@ class CuraApplication(QtApplication):
         if diagonal < 1: #No printer added yet. Set a default camera distance for normal-sized printers.
             diagonal = 375
         camera.setPosition(Vector(-80, 250, 700) * diagonal / 375)
-        camera.setPerspective(True)
         camera.lookAt(Vector(0, 0, 0))
         controller.getScene().setActiveCamera("3d")
 

+ 20 - 6
cura/Machines/Models/DiscoveredPrintersModel.py

@@ -62,6 +62,14 @@ class DiscoveredPrinter(QObject):
             self._machine_type = machine_type
             self.machineTypeChanged.emit()
 
+    # Checks if the given machine type name in the available machine list.
+    # The machine type is a code name such as "ultimaker_3", while the machine type name is the human-readable name of
+    # the machine type, which is "Ultimaker 3" for "ultimaker_3".
+    def _hasHumanReadableMachineTypeName(self, machine_type_name: str) -> bool:
+        from cura.CuraApplication import CuraApplication
+        results = CuraApplication.getInstance().getContainerRegistry().findDefinitionContainersMetadata(name = machine_type_name)
+        return len(results) > 0
+
     # Human readable machine type string
     @pyqtProperty(str, notify = machineTypeChanged)
     def readableMachineType(self) -> str:
@@ -70,24 +78,30 @@ class DiscoveredPrinter(QObject):
         # In ClusterUM3OutputDevice, when it updates a printer information, it updates the machine type using the field
         # "machine_variant", and for some reason, it's not the machine type ID/codename/... but a human-readable string
         # like "Ultimaker 3". The code below handles this case.
-        if machine_manager.hasHumanReadableMachineTypeName(self._machine_type):
+        if self._hasHumanReadableMachineTypeName(self._machine_type):
             readable_type = self._machine_type
         else:
-            readable_type = machine_manager.getMachineTypeNameFromId(self._machine_type)
+            readable_type = self._getMachineTypeNameFromId(self._machine_type)
             if not readable_type:
                 readable_type = catalog.i18nc("@label", "Unknown")
         return readable_type
 
     @pyqtProperty(bool, notify = machineTypeChanged)
     def isUnknownMachineType(self) -> bool:
-        from cura.CuraApplication import CuraApplication
-        machine_manager = CuraApplication.getInstance().getMachineManager()
-        if machine_manager.hasHumanReadableMachineTypeName(self._machine_type):
+        if self._hasHumanReadableMachineTypeName(self._machine_type):
             readable_type = self._machine_type
         else:
-            readable_type = machine_manager.getMachineTypeNameFromId(self._machine_type)
+            readable_type = self._getMachineTypeNameFromId(self._machine_type)
         return not readable_type
 
+    def _getMachineTypeNameFromId(self, machine_type_id: str) -> str:
+        machine_type_name = ""
+        from cura.CuraApplication import CuraApplication
+        results = CuraApplication.getInstance().getContainerRegistry().findDefinitionContainersMetadata(id = machine_type_id)
+        if results:
+            machine_type_name = results[0]["name"]
+        return machine_type_name
+
     @pyqtProperty(QObject, constant = True)
     def device(self) -> "NetworkedPrinterOutputDevice":
         return self._device

+ 0 - 3
cura/Machines/QualityManager.py

@@ -202,9 +202,6 @@ class QualityManager(QObject):
     def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
         machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
 
-        # This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
-        has_machine_specific_qualities = machine.getHasMachineQuality()
-
         # To find the quality container for the GlobalStack, check in the following fall-back manner:
         #   (1) the machine-specific node
         #   (2) the generic node

+ 2 - 11
cura/Scene/CuraSceneNode.py

@@ -1,4 +1,4 @@
-# Copyright (c) 2018 Ultimaker B.V.
+# Copyright (c) 2019 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
 from copy import deepcopy
@@ -14,6 +14,7 @@ import cura.CuraApplication #To get the build plate.
 from cura.Settings.ExtruderStack import ExtruderStack #For typing.
 from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator #For per-object settings.
 
+
 ##  Scene nodes that are models are only seen when selecting the corresponding build plate
 #   Note that many other nodes can just be UM SceneNode objects.
 class CuraSceneNode(SceneNode):
@@ -85,16 +86,6 @@ class CuraSceneNode(SceneNode):
             1.0
         ]
 
-    ##  Return if the provided bbox collides with the bbox of this scene node
-    def collidesWithBbox(self, check_bbox: AxisAlignedBox) -> bool:
-        bbox = self.getBoundingBox()
-        if bbox is not None:
-            # Mark the node as outside the build volume if the bounding box test fails.
-            if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
-                return True
-
-        return False
-
     ##  Return if any area collides with the convex hull of this scene node
     def collidesWithArea(self, areas: List[Polygon]) -> bool:
         convex_hull = self.callDecoration("getConvexHull")

+ 1 - 1
cura/Settings/ExtruderManager.py

@@ -180,7 +180,7 @@ class ExtruderManager(QObject):
     #   \param setting_key  \type{str} The setting to get the property of.
     #   \param property  \type{str} The property to get.
     #   \return \type{List} the list of results
-    def getAllExtruderSettings(self, setting_key: str, prop: str) -> List:
+    def getAllExtruderSettings(self, setting_key: str, prop: str) -> List[Any]:
         result = []
 
         for extruder_stack in self.getActiveExtruderStacks():

+ 6 - 6
cura/Settings/GlobalStack.py

@@ -264,18 +264,18 @@ class GlobalStack(CuraContainerStack):
     def getHeadAndFansCoordinates(self):
         return self.getProperty("machine_head_with_fans_polygon", "value")
 
-    def getHasMaterials(self) -> bool:
+    @pyqtProperty(int, constant=True)
+    def hasMaterials(self):
         return parseBool(self.getMetaDataEntry("has_materials", False))
 
-    def getHasVariants(self) -> bool:
+    @pyqtProperty(int, constant=True)
+    def hasVariants(self):
         return parseBool(self.getMetaDataEntry("has_variants", False))
 
-    def getHasVariantsBuildPlates(self) -> bool:
+    @pyqtProperty(int, constant=True)
+    def hasVariantBuildplates(self) -> bool:
         return parseBool(self.getMetaDataEntry("has_variant_buildplates", False))
 
-    def getHasMachineQuality(self) -> bool:
-        return parseBool(self.getMetaDataEntry("has_machine_quality", False))
-
     ##  Get default firmware file name if one is specified in the firmware
     @pyqtSlot(result = str)
     def getDefaultFirmwareName(self) -> str:

+ 9 - 18
cura/Settings/MachineManager.py

@@ -538,6 +538,7 @@ class MachineManager(QObject):
         return bool(self._printer_output_devices)
 
     @pyqtProperty(bool, notify = printerConnectedStatusChanged)
+    @deprecated("use Cura.MachineManager.activeMachine.configuredConnectionTypes instead", "4.2")
     def activeMachineHasRemoteConnection(self) -> bool:
         if self._global_container_stack:
             has_remote_connection = False
@@ -816,21 +817,24 @@ class MachineManager(QObject):
                 self.removeMachine(hidden_containers[0].getId())
 
     @pyqtProperty(bool, notify = globalContainerChanged)
+    @deprecated("use Cura.MachineManager.activeMachine.hasMaterials instead", "4.2")
     def hasMaterials(self) -> bool:
         if self._global_container_stack:
-            return self._global_container_stack.getHasMaterials()
+            return self._global_container_stack.hasMaterials
         return False
 
     @pyqtProperty(bool, notify = globalContainerChanged)
+    @deprecated("use Cura.MachineManager.activeMachine.hasVariants instead", "4.2")
     def hasVariants(self) -> bool:
         if self._global_container_stack:
-            return self._global_container_stack.getHasVariants()
+            return self._global_container_stack.hasVariants
         return False
 
     @pyqtProperty(bool, notify = globalContainerChanged)
+    @deprecated("use Cura.MachineManager.activeMachine.hasVariantBuildplates instead", "4.2")
     def hasVariantBuildplates(self) -> bool:
         if self._global_container_stack:
-            return self._global_container_stack.getHasVariantsBuildPlates()
+            return self._global_container_stack.hasVariantBuildplates
         return False
 
     ##  The selected buildplate is compatible if it is compatible with all the materials in all the extruders
@@ -984,6 +988,7 @@ class MachineManager(QObject):
         self.forceUpdateAllSettings()
 
     @pyqtSlot(int, result = QObject)
+    @deprecated("use Cura.MachineManager.activeMachine.extruders instead", "4.2")
     def getExtruder(self, position: int) -> Optional[ExtruderStack]:
         if self._global_container_stack:
             return self._global_container_stack.extruders.get(str(position))
@@ -1097,6 +1102,7 @@ class MachineManager(QObject):
             container.removeInstance(setting_name)
 
     @pyqtProperty("QVariantList", notify = globalContainerChanged)
+    @deprecated("use Cura.MachineManager.activeMachine.extruders instead", "4.2")
     def currentExtruderPositions(self) -> List[str]:
         if self._global_container_stack is None:
             return []
@@ -1642,21 +1648,6 @@ class MachineManager(QObject):
 
         return abbr_machine
 
-    # Checks if the given machine type name in the available machine list.
-    # The machine type is a code name such as "ultimaker_3", while the machine type name is the human-readable name of
-    # the machine type, which is "Ultimaker 3" for "ultimaker_3".
-    def hasHumanReadableMachineTypeName(self, machine_type_name: str) -> bool:
-        results = self._container_registry.findDefinitionContainersMetadata(name = machine_type_name)
-        return len(results) > 0
-
-    @pyqtSlot(str, result = str)
-    def getMachineTypeNameFromId(self, machine_type_id: str) -> str:
-        machine_type_name = ""
-        results = self._container_registry.findDefinitionContainersMetadata(id = machine_type_id)
-        if results:
-            machine_type_name = results[0]["name"]
-        return machine_type_name
-
     # Gets all machines that belong to the given group_id.
     def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]:
         return self._container_registry.findContainerStacks(type = "machine", group_id = group_id)

+ 1 - 1
cura/Snapshot.py

@@ -66,7 +66,7 @@ class Snapshot:
         looking_from_offset = Vector(-1, 1, 2)
         if size > 0:
             # determine the watch distance depending on the size
-            looking_from_offset = looking_from_offset * size * 1.3
+            looking_from_offset = looking_from_offset * size * 1.75
         camera.setPosition(look_at + looking_from_offset)
         camera.lookAt(look_at)
 

+ 2 - 1
cura_app.py

@@ -32,7 +32,8 @@ if not known_args["debug"]:
         elif Platform.isOSX():
             return os.path.expanduser("~/Library/Logs/" + CuraAppName)
 
-    if hasattr(sys, "frozen"):
+    # Do not redirect stdout and stderr to files if we are running CLI.
+    if hasattr(sys, "frozen") and "cli" not in os.path.basename(sys.argv[0]).lower():
         dirpath = get_cura_dir_path()
         os.makedirs(dirpath, exist_ok = True)
         sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8")

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