|
@@ -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
|