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

This commit is contained in:
Diego Prado Gesto 2019-06-11 09:54:14 +02:00
commit de5de8b9f1
21 changed files with 661 additions and 298 deletions

View File

@ -1,6 +1,6 @@
# Copyright (c) 2019 Ultimaker B.V. # Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # 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.Scene.CuraSceneNode import CuraSceneNode
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from UM.Application import Application #To modify the maximum zoom level. 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 PyQt5.QtCore import QTimer
from UM.View.RenderBatch import RenderBatch from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL from UM.View.GL.OpenGL import OpenGL
from cura.Settings.GlobalStack import GlobalStack
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
import numpy import numpy
import math import math
import copy 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. # Radius of disallowed area in mm around prime. I.e. how much distance to keep from prime position.
PRIME_CLEARANCE = 6.5 PRIME_CLEARANCE = 6.5
@ -36,17 +43,17 @@ PRIME_CLEARANCE = 6.5
class BuildVolume(SceneNode): class BuildVolume(SceneNode):
raftThicknessChanged = Signal() raftThicknessChanged = Signal()
def __init__(self, application, parent = None): def __init__(self, application: "CuraApplication", parent: Optional[SceneNode] = None) -> None:
super().__init__(parent) super().__init__(parent)
self._application = application self._application = application
self._machine_manager = self._application.getMachineManager() self._machine_manager = self._application.getMachineManager()
self._volume_outline_color = None self._volume_outline_color = None # type: Optional[Color]
self._x_axis_color = None self._x_axis_color = None # type: Optional[Color]
self._y_axis_color = None self._y_axis_color = None # type: Optional[Color]
self._z_axis_color = None self._z_axis_color = None # type: Optional[Color]
self._disallowed_area_color = None self._disallowed_area_color = None # type: Optional[Color]
self._error_area_color = None self._error_area_color = None # type: Optional[Color]
self._width = 0 # type: float self._width = 0 # type: float
self._height = 0 # type: float self._height = 0 # type: float
@ -55,26 +62,27 @@ class BuildVolume(SceneNode):
self._shader = None self._shader = None
self._origin_mesh = None self._origin_mesh = None # type: Optional[MeshData]
self._origin_line_length = 20 self._origin_line_length = 20
self._origin_line_width = 0.5 self._origin_line_width = 0.5
self._grid_mesh = None self._grid_mesh = None # type: Optional[MeshData]
self._grid_shader = None self._grid_shader = None
self._disallowed_areas = [] self._disallowed_areas = [] # type: List[Polygon]
self._disallowed_areas_no_brim = [] self._disallowed_areas_no_brim = [] # type: List[Polygon]
self._disallowed_area_mesh = None self._disallowed_area_mesh = None # type: Optional[MeshData]
self._disallowed_area_size = 0.
self._error_areas = [] self._error_areas = [] # type: List[Polygon]
self._error_mesh = None self._error_mesh = None # type: Optional[MeshData]
self.setCalculateBoundingBox(False) self.setCalculateBoundingBox(False)
self._volume_aabb = None self._volume_aabb = None # type: Optional[AxisAlignedBox]
self._raft_thickness = 0.0 self._raft_thickness = 0.0
self._extra_z_clearance = 0.0 self._extra_z_clearance = 0.0
self._adhesion_type = None self._adhesion_type = None # type: Any
self._platform = Platform(self) self._platform = Platform(self)
self._build_volume_message = Message(catalog.i18nc("@info:status", 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" " \"Print Sequence\" setting to prevent the gantry from colliding"
" with printed models."), title = catalog.i18nc("@info:title", "Build Volume")) " 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 = QTimer()
self._stack_change_timer.setInterval(100) self._stack_change_timer.setInterval(100)
@ -100,7 +108,7 @@ class BuildVolume(SceneNode):
self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged) self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged)
#Objects loaded at the moment. We are connected to the property changed events of these objects. #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 = QTimer()
self._scene_change_timer.setInterval(100) self._scene_change_timer.setInterval(100)
@ -124,8 +132,8 @@ class BuildVolume(SceneNode):
# Enable and disable extruder # Enable and disable extruder
self._machine_manager.extruderChanged.connect(self.updateNodeBoundaryCheck) self._machine_manager.extruderChanged.connect(self.updateNodeBoundaryCheck)
# list of settings which were updated # List of settings which were updated
self._changed_settings_since_last_rebuild = [] self._changed_settings_since_last_rebuild = [] # type: List[str]
def _onSceneChanged(self, source): def _onSceneChanged(self, source):
if self._global_container_stack: if self._global_container_stack:
@ -219,9 +227,12 @@ class BuildVolume(SceneNode):
## For every sliceable node, update node._outside_buildarea ## For every sliceable node, update node._outside_buildarea
# #
def updateNodeBoundaryCheck(self): def updateNodeBoundaryCheck(self):
if not self._global_container_stack:
return
root = self._application.getController().getScene().getRoot() root = self._application.getController().getScene().getRoot()
nodes = list(BreadthFirstIterator(root)) nodes = cast(List[SceneNode], list(cast(Iterable, BreadthFirstIterator(root))))
group_nodes = [] group_nodes = [] # type: List[SceneNode]
build_volume_bounding_box = self.getBoundingBox() build_volume_bounding_box = self.getBoundingBox()
if build_volume_bounding_box: if build_volume_bounding_box:
@ -240,6 +251,9 @@ class BuildVolume(SceneNode):
group_nodes.append(node) # Keep list of affected group_nodes group_nodes.append(node) # Keep list of affected group_nodes
if node.callDecoration("isSliceable") or node.callDecoration("isGroup"): if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
if not isinstance(node, CuraSceneNode):
continue
if node.collidesWithBbox(build_volume_bounding_box): if node.collidesWithBbox(build_volume_bounding_box):
node.setOutsideBuildArea(True) node.setOutsideBuildArea(True)
continue continue
@ -277,8 +291,8 @@ class BuildVolume(SceneNode):
child_node.setOutsideBuildArea(group_node.isOutsideBuildArea()) child_node.setOutsideBuildArea(group_node.isOutsideBuildArea())
## Update the outsideBuildArea of a single node, given bounds or current build volume ## Update the outsideBuildArea of a single node, given bounds or current build volume
def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None): def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None) -> None:
if not isinstance(node, CuraSceneNode): if not isinstance(node, CuraSceneNode) or self._global_container_stack is None:
return return
if bounds is None: if bounds is None:
@ -310,7 +324,7 @@ class BuildVolume(SceneNode):
node.setOutsideBuildArea(False) 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() mb = MeshBuilder()
if self._shape != "elliptic": if self._shape != "elliptic":
# Build plate grid mesh # Build plate grid mesh
@ -346,7 +360,7 @@ class BuildVolume(SceneNode):
mb.setVertexUVCoordinates(n, v[0], v[2] * aspect) mb.setVertexUVCoordinates(n, v[0], v[2] * aspect)
return mb.build().getTransformed(scale_matrix) 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": if self._shape != "elliptic":
# Outline 'cube' of the build volume # Outline 'cube' of the build volume
mb = MeshBuilder() 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) mb.addArc(max_w, Vector.Unit_Y, center = (0, max_h, 0), color = self._volume_outline_color)
return mb.build().getTransformed(scale_matrix) return mb.build().getTransformed(scale_matrix)
def _buildOriginMesh(self, origin): def _buildOriginMesh(self, origin: Vector) -> MeshData:
mb = MeshBuilder() mb = MeshBuilder()
mb.addCube( mb.addCube(
width=self._origin_line_length, width=self._origin_line_length,
@ -404,16 +418,10 @@ class BuildVolume(SceneNode):
) )
return mb.build() return mb.build()
## Recalculates the build volume & disallowed areas. def _updateColors(self):
def rebuild(self):
if not self._width or not self._height or not self._depth:
return
if not self._engine_ready:
return
if not self._volume_outline_color:
theme = self._application.getTheme() theme = self._application.getTheme()
if theme is None:
return
self._volume_outline_color = Color(*theme.getColor("volume_outline").getRgb()) self._volume_outline_color = Color(*theme.getColor("volume_outline").getRgb())
self._x_axis_color = Color(*theme.getColor("x_axis").getRgb()) self._x_axis_color = Color(*theme.getColor("x_axis").getRgb())
self._y_axis_color = Color(*theme.getColor("y_axis").getRgb()) self._y_axis_color = Color(*theme.getColor("y_axis").getRgb())
@ -421,6 +429,70 @@ class BuildVolume(SceneNode):
self._disallowed_area_color = Color(*theme.getColor("disallowed_area").getRgb()) self._disallowed_area_color = Color(*theme.getColor("disallowed_area").getRgb())
self._error_area_color = Color(*theme.getColor("error_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) -> 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:
self._updateColors()
min_w = -self._width / 2 min_w = -self._width / 2
max_w = self._width / 2 max_w = self._width / 2
min_h = 0.0 min_h = 0.0
@ -442,52 +514,10 @@ class BuildVolume(SceneNode):
self._origin_mesh = self._buildOriginMesh(origin) self._origin_mesh = self._buildOriginMesh(origin)
disallowed_area_height = 0.1 disallowed_area_height = 0.1
disallowed_area_size = 0 self._disallowed_area_size = 0.
if self._disallowed_areas: self._disallowed_area_mesh = self._buildDisallowedAreaMesh(min_w, max_w, min_h, max_h, min_d, max_d, disallowed_area_height)
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)) self._error_mesh = self._buildErrorMesh(min_w, max_w, min_h, max_h, min_d, max_d, disallowed_area_height)
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
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._volume_aabb = AxisAlignedBox( self._volume_aabb = AxisAlignedBox(
minimum = Vector(min_w, min_h - 1.0, min_d), 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! # 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. # The +1 and -1 is added as there is always a bit of extra room required to work properly.
scale_to_max_bounds = AxisAlignedBox( scale_to_max_bounds = AxisAlignedBox(
minimum = Vector(min_w + bed_adhesion_size + 1, min_h, min_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 - 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() self.updateNodeBoundaryCheck()
def getBoundingBox(self) -> AxisAlignedBox: def getBoundingBox(self):
return self._volume_aabb return self._volume_aabb
def getRaftThickness(self) -> float: def getRaftThickness(self) -> float:
return self._raft_thickness return self._raft_thickness
def _updateRaftThickness(self): def _updateRaftThickness(self) -> None:
if not self._global_container_stack:
return
old_raft_thickness = self._raft_thickness old_raft_thickness = self._raft_thickness
if self._global_container_stack.extruders: if self._global_container_stack.extruders:
# This might be called before the extruder stacks have initialised, in which case getting the adhesion_type fails # This might be called before the extruder stacks have initialised, in which case getting the adhesion_type fails
@ -533,28 +566,23 @@ class BuildVolume(SceneNode):
self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World) self.setPosition(Vector(0, -self._raft_thickness, 0), SceneNode.TransformSpace.World)
self.raftThicknessChanged.emit() 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 extra_z = 0.0
extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
use_extruders = False
for extruder in extruders: for extruder in extruders:
if extruder.getProperty("retraction_hop_enabled", "value"): if extruder.getProperty("retraction_hop_enabled", "value"):
retraction_hop = extruder.getProperty("retraction_hop", "value") retraction_hop = extruder.getProperty("retraction_hop", "value")
if extra_z is None or retraction_hop > extra_z: if extra_z is None or retraction_hop > extra_z:
extra_z = retraction_hop extra_z = retraction_hop
use_extruders = True return extra_z
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
def _onStackChanged(self): def _onStackChanged(self):
self._stack_change_timer.start() self._stack_change_timer.start()
## Update the build volume visualization ## Update the build volume visualization
def _onStackChangeTimerFinished(self): def _onStackChangeTimerFinished(self) -> None:
if self._global_container_stack: if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged) self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
extruders = ExtruderManager.getInstance().getActiveExtruderStacks() extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
@ -585,7 +613,7 @@ class BuildVolume(SceneNode):
self._updateDisallowedAreas() self._updateDisallowedAreas()
self._updateRaftThickness() self._updateRaftThickness()
self._updateExtraZClearance() self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
if self._engine_ready: if self._engine_ready:
self.rebuild() self.rebuild()
@ -594,20 +622,23 @@ class BuildVolume(SceneNode):
if camera: if camera:
diagonal = self.getDiagonalSize() diagonal = self.getDiagonalSize()
if diagonal > 1: 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._engine_ready = True
self.rebuild() self.rebuild()
def _onSettingChangeTimerFinished(self): def _onSettingChangeTimerFinished(self) -> None:
if not self._global_container_stack:
return
rebuild_me = False rebuild_me = False
update_disallowed_areas = False update_disallowed_areas = False
update_raft_thickness = False update_raft_thickness = False
update_extra_z_clearance = True update_extra_z_clearance = True
for setting_key in self._changed_settings_since_last_rebuild: for setting_key in self._changed_settings_since_last_rebuild:
if setting_key == "print_sequence": if setting_key == "print_sequence":
machine_height = self._global_container_stack.getProperty("machine_height", "value") 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: 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._height = self._global_container_stack.getProperty("machine_height", "value")
self._build_volume_message.hide() self._build_volume_message.hide()
update_disallowed_areas = True update_disallowed_areas = True
rebuild_me = True
# sometimes the machine size or shape settings are adjusted on the active machine, we should reflect this # sometimes the machine size or shape settings are adjusted on the active machine, we should reflect this
if setting_key in self._machine_settings: if setting_key in self._machine_settings:
self._height = self._global_container_stack.getProperty("machine_height", "value") self._updateMachineSizeProperties()
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")
update_extra_z_clearance = True update_extra_z_clearance = True
update_disallowed_areas = 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 update_disallowed_areas = True
rebuild_me = True
if setting_key in self._raft_settings: if setting_key in self._raft_settings:
update_raft_thickness = True update_raft_thickness = True
rebuild_me = True
if setting_key in self._extra_z_settings: if setting_key in self._extra_z_settings:
update_extra_z_clearance = True update_extra_z_clearance = True
rebuild_me = True
if setting_key in self._limit_to_extruder_settings: if setting_key in self._limit_to_extruder_settings:
update_disallowed_areas = True 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. # We only want to update all of them once.
if update_disallowed_areas: if update_disallowed_areas:
@ -656,7 +680,7 @@ class BuildVolume(SceneNode):
self._updateRaftThickness() self._updateRaftThickness()
if update_extra_z_clearance: if update_extra_z_clearance:
self._updateExtraZClearance() self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
if rebuild_me: if rebuild_me:
self.rebuild() self.rebuild()
@ -664,7 +688,7 @@ class BuildVolume(SceneNode):
# We just did a rebuild, reset the list. # We just did a rebuild, reset the list.
self._changed_settings_since_last_rebuild = [] 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": if property_name != "value":
return return
@ -675,6 +699,14 @@ class BuildVolume(SceneNode):
def hasErrors(self) -> bool: def hasErrors(self) -> bool:
return self._has_errors 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 ## Calls _updateDisallowedAreas and makes sure the changes appear in the
# scene. # scene.
# #
@ -686,10 +718,10 @@ class BuildVolume(SceneNode):
def _updateDisallowedAreasAndRebuild(self): def _updateDisallowedAreasAndRebuild(self):
self._updateDisallowedAreas() self._updateDisallowedAreas()
self._updateRaftThickness() self._updateRaftThickness()
self._updateExtraZClearance() self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
self.rebuild() self.rebuild()
def _updateDisallowedAreas(self): def _updateDisallowedAreas(self) -> None:
if not self._global_container_stack: if not self._global_container_stack:
return return
@ -843,9 +875,10 @@ 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 _computeDisallowedAreasPrimeBlob(self, border_size, used_extruders): def _computeDisallowedAreasPrimeBlob(self, border_size: float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]:
result = {} 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_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:
@ -1015,14 +1048,86 @@ class BuildVolume(SceneNode):
# stack. # stack.
# #
# \return A sequence of setting values, one for each extruder. # \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_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type") all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
for i in range(len(all_values)): for i, (setting_value, setting_type) in enumerate(zip(all_values, all_types)):
if not all_values[i] and (all_types[i] == "int" or all_types[i] == "float"): if not setting_value and (setting_type == "int" or setting_type == "float"):
all_values[i] = 0 all_values[i] = 0
return all_values return all_values
def _calculateBedAdhesionSize(self, used_extruders):
if self._global_container_stack is None:
return
container_stack = self._global_container_stack
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"):
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
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
# 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.
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
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
# We don't create an additional line for the extruder we're printing the skirt with.
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?")
max_length_available = 0.5 * min(
self._global_container_stack.getProperty("machine_width", "value"),
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
def _calculateMoveFromWallRadius(self, used_extruders):
move_from_wall_radius = 0 # Moves that start from outer wall.
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).
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. ## Calculate the disallowed radius around the edge.
# #
# This disallowed radius is to allow for space around the models that is # This disallowed radius is to allow for space around the models that is
@ -1039,65 +1144,10 @@ class BuildVolume(SceneNode):
if container_stack.getProperty("print_sequence", "value") == "one_at_a_time": 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. 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") bed_adhesion_size = self._calculateBedAdhesionSize(used_extruders)
skirt_brim_line_width = self._global_container_stack.getProperty("skirt_brim_line_width", "value") support_expansion = self._calculateSupportExpansion(self._global_container_stack)
initial_layer_line_width_factor = self._global_container_stack.getProperty("initial_layer_line_width_factor", "value") farthest_shield_distance = self._calculateFarthestShieldDistance(self._global_container_stack)
#Use brim width if brim is enabled OR the prime tower has a brim. move_from_wall_radius = self._calculateMoveFromWallRadius(used_extruders)
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
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
# 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.
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
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
# We don't create an additional line for the extruder we're printing the skirt with.
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?")
max_length_available = 0.5 * min(
self._global_container_stack.getProperty("machine_width", "value"),
self._global_container_stack.getProperty("machine_depth", "value")
)
bed_adhesion_size = min(bed_adhesion_size, max_length_available)
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
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"))
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]
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).
if avoid_other_parts_enabled:
move_from_wall_radius = max(move_from_wall_radius, avoid_distance)
# Now combine our different pieces of data to get the final border size. # 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. # 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"] _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. _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"] _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

View File

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

View File

@ -62,6 +62,14 @@ class DiscoveredPrinter(QObject):
self._machine_type = machine_type self._machine_type = machine_type
self.machineTypeChanged.emit() 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 # Human readable machine type string
@pyqtProperty(str, notify = machineTypeChanged) @pyqtProperty(str, notify = machineTypeChanged)
def readableMachineType(self) -> str: 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 # 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 # "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. # 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 readable_type = self._machine_type
else: else:
readable_type = machine_manager.getMachineTypeNameFromId(self._machine_type) readable_type = self._getMachineTypeNameFromId(self._machine_type)
if not readable_type: if not readable_type:
readable_type = catalog.i18nc("@label", "Unknown") readable_type = catalog.i18nc("@label", "Unknown")
return readable_type return readable_type
@pyqtProperty(bool, notify = machineTypeChanged) @pyqtProperty(bool, notify = machineTypeChanged)
def isUnknownMachineType(self) -> bool: def isUnknownMachineType(self) -> bool:
from cura.CuraApplication import CuraApplication if self._hasHumanReadableMachineTypeName(self._machine_type):
machine_manager = CuraApplication.getInstance().getMachineManager()
if machine_manager.hasHumanReadableMachineTypeName(self._machine_type):
readable_type = self._machine_type readable_type = self._machine_type
else: else:
readable_type = machine_manager.getMachineTypeNameFromId(self._machine_type) readable_type = self._getMachineTypeNameFromId(self._machine_type)
return not readable_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) @pyqtProperty(QObject, constant = True)
def device(self) -> "NetworkedPrinterOutputDevice": def device(self) -> "NetworkedPrinterOutputDevice":
return self._device return self._device

View File

@ -202,9 +202,6 @@ class QualityManager(QObject):
def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]: def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition) 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: # To find the quality container for the GlobalStack, check in the following fall-back manner:
# (1) the machine-specific node # (1) the machine-specific node
# (2) the generic node # (2) the generic node

View File

@ -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. # Cura is released under the terms of the LGPLv3 or higher.
from copy import deepcopy 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.ExtruderStack import ExtruderStack #For typing.
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator #For per-object settings. from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator #For per-object settings.
## Scene nodes that are models are only seen when selecting the corresponding build plate ## 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. # Note that many other nodes can just be UM SceneNode objects.
class CuraSceneNode(SceneNode): class CuraSceneNode(SceneNode):
@ -85,16 +86,6 @@ class CuraSceneNode(SceneNode):
1.0 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 ## Return if any area collides with the convex hull of this scene node
def collidesWithArea(self, areas: List[Polygon]) -> bool: def collidesWithArea(self, areas: List[Polygon]) -> bool:
convex_hull = self.callDecoration("getConvexHull") convex_hull = self.callDecoration("getConvexHull")

View File

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

View File

@ -264,18 +264,18 @@ class GlobalStack(CuraContainerStack):
def getHeadAndFansCoordinates(self): def getHeadAndFansCoordinates(self):
return self.getProperty("machine_head_with_fans_polygon", "value") 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)) 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)) 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)) 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 ## Get default firmware file name if one is specified in the firmware
@pyqtSlot(result = str) @pyqtSlot(result = str)
def getDefaultFirmwareName(self) -> str: def getDefaultFirmwareName(self) -> str:

View File

@ -538,6 +538,7 @@ class MachineManager(QObject):
return bool(self._printer_output_devices) return bool(self._printer_output_devices)
@pyqtProperty(bool, notify = printerConnectedStatusChanged) @pyqtProperty(bool, notify = printerConnectedStatusChanged)
@deprecated("use Cura.MachineManager.activeMachine.configuredConnectionTypes instead", "4.2")
def activeMachineHasRemoteConnection(self) -> bool: def activeMachineHasRemoteConnection(self) -> bool:
if self._global_container_stack: if self._global_container_stack:
has_remote_connection = False has_remote_connection = False
@ -816,21 +817,24 @@ class MachineManager(QObject):
self.removeMachine(hidden_containers[0].getId()) self.removeMachine(hidden_containers[0].getId())
@pyqtProperty(bool, notify = globalContainerChanged) @pyqtProperty(bool, notify = globalContainerChanged)
@deprecated("use Cura.MachineManager.activeMachine.hasMaterials instead", "4.2")
def hasMaterials(self) -> bool: def hasMaterials(self) -> bool:
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.getHasMaterials() return self._global_container_stack.hasMaterials
return False return False
@pyqtProperty(bool, notify = globalContainerChanged) @pyqtProperty(bool, notify = globalContainerChanged)
@deprecated("use Cura.MachineManager.activeMachine.hasVariants instead", "4.2")
def hasVariants(self) -> bool: def hasVariants(self) -> bool:
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.getHasVariants() return self._global_container_stack.hasVariants
return False return False
@pyqtProperty(bool, notify = globalContainerChanged) @pyqtProperty(bool, notify = globalContainerChanged)
@deprecated("use Cura.MachineManager.activeMachine.hasVariantBuildplates instead", "4.2")
def hasVariantBuildplates(self) -> bool: def hasVariantBuildplates(self) -> bool:
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.getHasVariantsBuildPlates() return self._global_container_stack.hasVariantBuildplates
return False return False
## The selected buildplate is compatible if it is compatible with all the materials in all the extruders ## 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() self.forceUpdateAllSettings()
@pyqtSlot(int, result = QObject) @pyqtSlot(int, result = QObject)
@deprecated("use Cura.MachineManager.activeMachine.extruders instead", "4.2")
def getExtruder(self, position: int) -> Optional[ExtruderStack]: def getExtruder(self, position: int) -> Optional[ExtruderStack]:
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.extruders.get(str(position)) return self._global_container_stack.extruders.get(str(position))
@ -1097,6 +1102,7 @@ class MachineManager(QObject):
container.removeInstance(setting_name) container.removeInstance(setting_name)
@pyqtProperty("QVariantList", notify = globalContainerChanged) @pyqtProperty("QVariantList", notify = globalContainerChanged)
@deprecated("use Cura.MachineManager.activeMachine.extruders instead", "4.2")
def currentExtruderPositions(self) -> List[str]: def currentExtruderPositions(self) -> List[str]:
if self._global_container_stack is None: if self._global_container_stack is None:
return [] return []
@ -1642,21 +1648,6 @@ class MachineManager(QObject):
return abbr_machine 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. # Gets all machines that belong to the given group_id.
def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]: def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]:
return self._container_registry.findContainerStacks(type = "machine", group_id = group_id) return self._container_registry.findContainerStacks(type = "machine", group_id = group_id)

View File

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

View File

@ -32,7 +32,8 @@ if not known_args["debug"]:
elif Platform.isOSX(): elif Platform.isOSX():
return os.path.expanduser("~/Library/Logs/" + CuraAppName) 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() dirpath = get_cura_dir_path()
os.makedirs(dirpath, exist_ok = True) os.makedirs(dirpath, exist_ok = True)
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8") sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8")

View File

@ -20,14 +20,14 @@ Item
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
property int labelWidth: 120 * screenScaleFactor
property int controlWidth: (UM.Theme.getSize("setting_control").width * 3 / 4) | 0
property var labelFont: UM.Theme.getFont("default")
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0 property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
property int columnSpacing: 3 * screenScaleFactor property int columnSpacing: 3 * screenScaleFactor
property int propertyStoreIndex: manager ? manager.storeContainerIndex : 1 // definition_changes property int propertyStoreIndex: manager ? manager.storeContainerIndex : 1 // definition_changes
property int labelWidth: (columnWidth * 2 / 3 - UM.Theme.getSize("default_margin").width * 2) | 0
property int controlWidth: (columnWidth / 3) | 0
property var labelFont: UM.Theme.getFont("default")
property string machineStackId: Cura.MachineManager.activeMachineId property string machineStackId: Cura.MachineManager.activeMachineId
property var forceUpdateFunction: manager.forceUpdate property var forceUpdateFunction: manager.forceUpdate
@ -59,6 +59,8 @@ Item
font: UM.Theme.getFont("medium_bold") font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
renderType: Text.NativeRendering renderType: Text.NativeRendering
width: parent.width
elide: Text.ElideRight
} }
Cura.NumericTextFieldWithUnit // "X (Width)" Cura.NumericTextFieldWithUnit // "X (Width)"
@ -175,6 +177,8 @@ Item
font: UM.Theme.getFont("medium_bold") font: UM.Theme.getFont("medium_bold")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
renderType: Text.NativeRendering renderType: Text.NativeRendering
width: parent.width
elide: Text.ElideRight
} }
Cura.PrintHeadMinMaxTextField // "X min" Cura.PrintHeadMinMaxTextField // "X min"

View File

@ -89,6 +89,7 @@ Item
Label Label
{ {
text: catalog.i18nc("@label", "Your rating") + ":" text: catalog.i18nc("@label", "Your rating") + ":"
visible: details.type == "plugin"
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text_medium") color: UM.Theme.getColor("text_medium")
renderType: Text.NativeRendering renderType: Text.NativeRendering

View File

@ -2784,6 +2784,19 @@
"settable_per_extruder": true, "settable_per_extruder": true,
"limit_to_extruder": "adhesion_extruder_nr" "limit_to_extruder": "adhesion_extruder_nr"
}, },
"speed_z_hop":
{
"label": "Z Hop Speed",
"description": "The speed at which the vertical Z movement is made for Z Hops. This is typically lower than the print speed since the build plate or machine's gantry is harder to move.",
"unit": "mm/s",
"type": "float",
"default_value": 10,
"minimum_value": "0",
"maximum_value": "machine_max_feedrate_z",
"enabled": "retraction_enable and retraction_hop_enabled",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"max_feedrate_z_override": "max_feedrate_z_override":
{ {
"label": "Maximum Z Speed", "label": "Maximum Z Speed",

View File

@ -110,65 +110,65 @@ Item
Action Action
{ {
id: quitAction; id: quitAction
text: catalog.i18nc("@action:inmenu menubar:file","&Quit"); text: catalog.i18nc("@action:inmenu menubar:file","&Quit")
iconName: "application-exit"; iconName: "application-exit"
shortcut: StandardKey.Quit; shortcut: StandardKey.Quit
} }
Action Action
{ {
id: view3DCameraAction; id: view3DCameraAction
text: catalog.i18nc("@action:inmenu menubar:view","3D View"); text: catalog.i18nc("@action:inmenu menubar:view", "3D View")
onTriggered: UM.Controller.rotateView("3d", 0); onTriggered: UM.Controller.setCameraRotation("3d", 0)
} }
Action Action
{ {
id: viewFrontCameraAction; id: viewFrontCameraAction
text: catalog.i18nc("@action:inmenu menubar:view","Front View"); text: catalog.i18nc("@action:inmenu menubar:view", "Front View")
onTriggered: UM.Controller.rotateView("home", 0); onTriggered: UM.Controller.setCameraRotation("home", 0)
} }
Action Action
{ {
id: viewTopCameraAction; id: viewTopCameraAction
text: catalog.i18nc("@action:inmenu menubar:view","Top View"); text: catalog.i18nc("@action:inmenu menubar:view", "Top View")
onTriggered: UM.Controller.rotateView("y", 90); onTriggered: UM.Controller.setCameraRotation("y", 90)
} }
Action Action
{ {
id: viewLeftSideCameraAction; id: viewLeftSideCameraAction
text: catalog.i18nc("@action:inmenu menubar:view","Left Side View"); text: catalog.i18nc("@action:inmenu menubar:view", "Left Side View")
onTriggered: UM.Controller.rotateView("x", 90); onTriggered: UM.Controller.setCameraRotation("x", 90)
} }
Action Action
{ {
id: viewRightSideCameraAction; id: viewRightSideCameraAction
text: catalog.i18nc("@action:inmenu menubar:view","Right Side View"); text: catalog.i18nc("@action:inmenu menubar:view", "Right Side View")
onTriggered: UM.Controller.rotateView("x", -90); onTriggered: UM.Controller.setCameraRotation("x", -90)
} }
Action Action
{ {
id: preferencesAction; id: preferencesAction
text: catalog.i18nc("@action:inmenu","Configure Cura..."); text: catalog.i18nc("@action:inmenu", "Configure Cura...")
iconName: "configure"; iconName: "configure"
} }
Action Action
{ {
id: addMachineAction; id: addMachineAction
text: catalog.i18nc("@action:inmenu menubar:printer","&Add Printer..."); text: catalog.i18nc("@action:inmenu menubar:printer", "&Add Printer...")
} }
Action Action
{ {
id: settingsAction; id: settingsAction
text: catalog.i18nc("@action:inmenu menubar:printer","Manage Pr&inters..."); text: catalog.i18nc("@action:inmenu menubar:printer", "Manage Pr&inters...")
iconName: "configure"; iconName: "configure"
} }
Action Action

View File

@ -95,6 +95,10 @@ UM.PreferencesPage
UM.Preferences.resetPreference("view/top_layer_count"); UM.Preferences.resetPreference("view/top_layer_count");
topLayerCountCheckbox.checked = boolCheck(UM.Preferences.getValue("view/top_layer_count")) topLayerCountCheckbox.checked = boolCheck(UM.Preferences.getValue("view/top_layer_count"))
UM.Preferences.resetPreference("general/camera_perspective_mode")
var defaultCameraMode = UM.Preferences.getValue("general/camera_perspective_mode")
setDefaultCameraMode(defaultCameraMode)
UM.Preferences.resetPreference("cura/choice_on_profile_override") UM.Preferences.resetPreference("cura/choice_on_profile_override")
setDefaultDiscardOrKeepProfile(UM.Preferences.getValue("cura/choice_on_profile_override")) setDefaultDiscardOrKeepProfile(UM.Preferences.getValue("cura/choice_on_profile_override"))
@ -330,7 +334,8 @@ UM.PreferencesPage
} }
} }
UM.TooltipArea { UM.TooltipArea
{
width: childrenRect.width; width: childrenRect.width;
height: childrenRect.height; height: childrenRect.height;
text: catalog.i18nc("@info:tooltip", "Moves the camera so the model is in the center of the view when a model is selected") text: catalog.i18nc("@info:tooltip", "Moves the camera so the model is in the center of the view when a model is selected")
@ -344,7 +349,8 @@ UM.PreferencesPage
} }
} }
UM.TooltipArea { UM.TooltipArea
{
width: childrenRect.width; width: childrenRect.width;
height: childrenRect.height; height: childrenRect.height;
text: catalog.i18nc("@info:tooltip", "Should the default zoom behavior of cura be inverted?") text: catalog.i18nc("@info:tooltip", "Should the default zoom behavior of cura be inverted?")
@ -436,6 +442,50 @@ UM.PreferencesPage
} }
} }
UM.TooltipArea
{
width: childrenRect.width
height: childrenRect.height
text: catalog.i18nc("@info:tooltip", "What type of camera rendering should be used?")
Column
{
spacing: 4 * screenScaleFactor
Label
{
text: catalog.i18nc("@window:text", "Camera rendering: ")
}
ComboBox
{
id: cameraComboBox
model: ListModel
{
id: comboBoxList
Component.onCompleted: {
append({ text: catalog.i18n("Perspective"), code: "perspective" })
append({ text: catalog.i18n("Orthogonal"), code: "orthogonal" })
}
}
currentIndex:
{
var code = UM.Preferences.getValue("general/camera_perspective_mode");
for(var i = 0; i < comboBoxList.count; ++i)
{
if(model.get(i).code == code)
{
return i
}
}
return 0
}
onActivated: UM.Preferences.setValue("general/camera_perspective_mode", model.get(index).code)
}
}
}
Item Item
{ {
//: Spacer //: Spacer

View File

@ -6,7 +6,7 @@ import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1 import QtQuick.Controls.Styles 1.1
import UM 1.4 as UM import UM 1.4 as UM
import Cura 1.1 as Cura
// A row of buttons that control the view direction // A row of buttons that control the view direction
Row Row
{ {
@ -19,30 +19,30 @@ Row
ViewOrientationButton ViewOrientationButton
{ {
iconSource: UM.Theme.getIcon("view_3d") iconSource: UM.Theme.getIcon("view_3d")
onClicked: UM.Controller.rotateView("3d", 0) onClicked: Cura.Actions.view3DCamera.trigger()
} }
ViewOrientationButton ViewOrientationButton
{ {
iconSource: UM.Theme.getIcon("view_front") iconSource: UM.Theme.getIcon("view_front")
onClicked: UM.Controller.rotateView("home", 0) onClicked: Cura.Actions.viewFrontCamera.trigger()
} }
ViewOrientationButton ViewOrientationButton
{ {
iconSource: UM.Theme.getIcon("view_top") iconSource: UM.Theme.getIcon("view_top")
onClicked: UM.Controller.rotateView("y", 90) onClicked: Cura.Actions.viewTopCamera.trigger()
} }
ViewOrientationButton ViewOrientationButton
{ {
iconSource: UM.Theme.getIcon("view_left") iconSource: UM.Theme.getIcon("view_left")
onClicked: UM.Controller.rotateView("x", 90) onClicked: Cura.Actions.viewLeftSideCamera.trigger()
} }
ViewOrientationButton ViewOrientationButton
{ {
iconSource: UM.Theme.getIcon("view_right") iconSource: UM.Theme.getIcon("view_right")
onClicked: UM.Controller.rotateView("x", -90) onClicked: Cura.Actions.viewRightSideCamera.trigger()
} }
} }

View File

@ -193,19 +193,20 @@
"xray": [26, 26, 62, 255], "xray": [26, 26, 62, 255],
"xray_error": [255, 0, 0, 255], "xray_error": [255, 0, 0, 255],
"layerview_ghost": [32, 32, 32, 96], "layerview_ghost": [31, 31, 31, 95],
"layerview_none": [255, 255, 255, 255], "layerview_none": [255, 255, 255, 255],
"layerview_inset_0": [255, 0, 0, 255], "layerview_inset_0": [255, 0, 0, 255],
"layerview_inset_x": [0, 255, 0, 255], "layerview_inset_x": [0, 255, 0, 255],
"layerview_skin": [255, 255, 0, 255], "layerview_skin": [255, 255, 0, 255],
"layerview_support": [0, 255, 255, 255], "layerview_support": [0, 255, 255, 255],
"layerview_skirt": [0, 255, 255, 255], "layerview_skirt": [0, 255, 255, 255],
"layerview_infill": [255, 192, 0, 255], "layerview_infill": [255, 127, 0, 255],
"layerview_support_infill": [0, 255, 255, 255], "layerview_support_infill": [0, 255, 255, 255],
"layerview_move_combing": [0, 0, 255, 255], "layerview_move_combing": [0, 0, 255, 255],
"layerview_move_retraction": [128, 128, 255, 255], "layerview_move_retraction": [128, 127, 255, 255],
"layerview_support_interface": [64, 192, 255, 255], "layerview_support_interface": [63, 127, 255, 255],
"layerview_nozzle": [181, 166, 66, 120], "layerview_prime_tower": [0, 255, 255, 255],
"layerview_nozzle": [181, 166, 66, 50],
"material_compatibility_warning": [255, 255, 255, 255], "material_compatibility_warning": [255, 255, 255, 255],

View File

@ -372,18 +372,18 @@
"xray": [26, 26, 62, 255], "xray": [26, 26, 62, 255],
"xray_error": [255, 0, 0, 255], "xray_error": [255, 0, 0, 255],
"layerview_ghost": [32, 32, 32, 96], "layerview_ghost": [31, 31, 31, 95],
"layerview_none": [255, 255, 255, 255], "layerview_none": [255, 255, 255, 255],
"layerview_inset_0": [255, 0, 0, 255], "layerview_inset_0": [255, 0, 0, 255],
"layerview_inset_x": [0, 255, 0, 255], "layerview_inset_x": [0, 255, 0, 255],
"layerview_skin": [255, 255, 0, 255], "layerview_skin": [255, 255, 0, 255],
"layerview_support": [0, 255, 255, 255], "layerview_support": [0, 255, 255, 255],
"layerview_skirt": [0, 255, 255, 255], "layerview_skirt": [0, 255, 255, 255],
"layerview_infill": [255, 192, 0, 255], "layerview_infill": [255, 127, 0, 255],
"layerview_support_infill": [0, 255, 255, 255], "layerview_support_infill": [0, 255, 255, 255],
"layerview_move_combing": [0, 0, 255, 255], "layerview_move_combing": [0, 0, 255, 255],
"layerview_move_retraction": [128, 128, 255, 255], "layerview_move_retraction": [128, 127, 255, 255],
"layerview_support_interface": [64, 192, 255, 255], "layerview_support_interface": [63, 127, 255, 255],
"layerview_prime_tower": [0, 255, 255, 255], "layerview_prime_tower": [0, 255, 255, 255],
"layerview_nozzle": [181, 166, 66, 50], "layerview_nozzle": [181, 166, 66, 50],

View File

@ -2,10 +2,16 @@ from unittest.mock import MagicMock, patch
import pytest import pytest
from cura.BuildVolume import BuildVolume from UM.Math.Polygon import Polygon
from UM.Settings.SettingInstance import InstanceState
from cura.BuildVolume import BuildVolume, PRIME_CLEARANCE
import numpy
@pytest.fixture @pytest.fixture
def build_volume(): def build_volume() -> BuildVolume:
mocked_application = MagicMock() mocked_application = MagicMock()
mocked_platform = MagicMock(name="platform") mocked_platform = MagicMock(name="platform")
with patch("cura.BuildVolume.Platform", mocked_platform): with patch("cura.BuildVolume.Platform", mocked_platform):
@ -25,3 +31,238 @@ def test_buildVolumeSetSizes(build_volume):
assert build_volume.getDiagonalSize() == 200 assert build_volume.getDiagonalSize() == 200
def test_buildMesh(build_volume):
mesh = build_volume._buildMesh(0, 100, 0, 100, 0, 100, 1)
result_vertices = numpy.array([[0., 0., 0.], [100., 0., 0.], [0., 0., 0.], [0., 100., 0.], [0., 100., 0.], [100., 100., 0.], [100., 0., 0.], [100., 100., 0.], [0., 0., 100.], [100., 0., 100.], [0., 0., 100.], [0., 100., 100.], [0., 100., 100.], [100., 100., 100.], [100., 0., 100.], [100., 100., 100.], [0., 0., 0.], [0., 0., 100.], [100., 0., 0.], [100., 0., 100.], [0., 100., 0.], [0., 100., 100.], [100., 100., 0.], [100., 100., 100.]], dtype=numpy.float32)
assert numpy.array_equal(result_vertices, mesh.getVertices())
def test_buildGridMesh(build_volume):
mesh = build_volume._buildGridMesh(0, 100, 0, 100, 0, 100, 1)
result_vertices = numpy.array([[0., -1., 0.], [100., -1., 100.], [100., -1., 0.], [0., -1., 0.], [0., -1., 100.], [100., -1., 100.]])
assert numpy.array_equal(result_vertices, mesh.getVertices())
class TestUpdateRaftThickness:
setting_property_dict = {"raft_base_thickness": {"value": 1},
"raft_interface_thickness": {"value": 1},
"raft_surface_layers": {"value": 1},
"raft_surface_thickness": {"value": 1},
"raft_airgap": {"value": 1},
"layer_0_z_overlap": {"value": 1},
"adhesion_type": {"value": "raft"}}
def getPropertySideEffect(*args, **kwargs):
properties = TestUpdateRaftThickness.setting_property_dict.get(args[1])
if properties:
return properties.get(args[2])
def createMockedStack(self):
mocked_global_stack = MagicMock(name="mocked_global_stack")
mocked_global_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
extruder_stack = MagicMock()
mocked_global_stack.extruders = {"0": extruder_stack}
return mocked_global_stack
def test_simple(self, build_volume: BuildVolume):
build_volume.raftThicknessChanged = MagicMock()
mocked_global_stack = self.createMockedStack()
build_volume._global_container_stack = mocked_global_stack
assert build_volume.getRaftThickness() == 0
build_volume._updateRaftThickness()
assert build_volume.getRaftThickness() == 3
assert build_volume.raftThicknessChanged.emit.call_count == 1
def test_adhesionIsNotRaft(self, build_volume: BuildVolume):
patched_dictionary = self.setting_property_dict.copy()
patched_dictionary["adhesion_type"] = {"value": "not_raft"}
mocked_global_stack = self.createMockedStack()
build_volume._global_container_stack = mocked_global_stack
assert build_volume.getRaftThickness() == 0
with patch.dict(self.setting_property_dict, patched_dictionary):
build_volume._updateRaftThickness()
assert build_volume.getRaftThickness() == 0
def test_noGlobalStack(self, build_volume: BuildVolume):
build_volume.raftThicknessChanged = MagicMock()
assert build_volume.getRaftThickness() == 0
build_volume._updateRaftThickness()
assert build_volume.getRaftThickness() == 0
assert build_volume.raftThicknessChanged.emit.call_count == 0
class TestComputeDisallowedAreasPrimeBlob:
setting_property_dict = {"machine_width": {"value": 50},
"machine_depth": {"value": 100},
"prime_blob_enable": {"value": True},
"extruder_prime_pos_x": {"value": 25},
"extruder_prime_pos_y": {"value": 50},
"machine_center_is_zero": {"value": True},
}
def getPropertySideEffect(*args, **kwargs):
properties = TestComputeDisallowedAreasPrimeBlob.setting_property_dict.get(args[1])
if properties:
return properties.get(args[2])
def test_noGlobalContainer(self, build_volume: BuildVolume):
# No global container and no extruders, so we expect no blob areas
assert build_volume._computeDisallowedAreasPrimeBlob(12, []) == {}
def test_noExtruders(self, build_volume: BuildVolume):
mocked_stack = MagicMock()
mocked_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
build_volume._global_container_stack = mocked_stack
# No extruders, so still expect that we get no area
assert build_volume._computeDisallowedAreasPrimeBlob(12, []) == {}
def test_singleExtruder(self, build_volume: BuildVolume):
mocked_global_stack = MagicMock(name = "mocked_global_stack")
mocked_global_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
mocked_extruder_stack = MagicMock(name = "mocked_extruder_stack")
mocked_extruder_stack.getId = MagicMock(return_value = "0")
mocked_extruder_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
build_volume._global_container_stack = mocked_global_stack
# Create a polygon that should be the result
resulting_polygon = Polygon.approximatedCircle(PRIME_CLEARANCE)
# Since we want a blob of size 12;
resulting_polygon = resulting_polygon.getMinkowskiHull(Polygon.approximatedCircle(12))
# In the The translation result is 25, -50 (due to the settings used)
resulting_polygon = resulting_polygon.translate(25, -50)
assert build_volume._computeDisallowedAreasPrimeBlob(12, [mocked_extruder_stack]) == {"0": [resulting_polygon]}
class TestCalculateExtraZClearance:
setting_property_dict = {"retraction_hop": {"value": 12},
"retraction_hop_enabled": {"value": True}}
def getPropertySideEffect(*args, **kwargs):
properties = TestCalculateExtraZClearance.setting_property_dict.get(args[1])
if properties:
return properties.get(args[2])
def test_noContainerStack(self, build_volume: BuildVolume):
assert build_volume._calculateExtraZClearance([]) is 0
def test_withRetractionHop(self, build_volume: BuildVolume):
mocked_global_stack = MagicMock(name="mocked_global_stack")
mocked_extruder = MagicMock()
mocked_extruder.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
build_volume._global_container_stack = mocked_global_stack
# It should be 12 because we have the hop enabled and the hop distance is set to 12
assert build_volume._calculateExtraZClearance([mocked_extruder]) == 12
def test_withoutRetractionHop(self, build_volume: BuildVolume):
mocked_global_stack = MagicMock(name="mocked_global_stack")
mocked_extruder = MagicMock()
mocked_extruder.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
build_volume._global_container_stack = mocked_global_stack
patched_dictionary = self.setting_property_dict.copy()
patched_dictionary["retraction_hop_enabled"] = {"value": False}
with patch.dict(self.setting_property_dict, patched_dictionary):
# It should be 12 because we have the hop enabled and the hop distance is set to 12
assert build_volume._calculateExtraZClearance([mocked_extruder]) == 0
class TestRebuild:
def test_zeroWidthHeightDepth(self, build_volume: BuildVolume):
build_volume.rebuild()
assert build_volume.getMeshData() is None
def test_engineIsNotRead(self, build_volume: BuildVolume):
build_volume.setWidth(10)
build_volume.setHeight(10)
build_volume.setDepth(10)
build_volume.rebuild()
assert build_volume.getMeshData() is None
def test_noGlobalStack(self, build_volume: BuildVolume):
build_volume.setWidth(10)
build_volume.setHeight(10)
build_volume.setDepth(10)
# Fake the the "engine is created callback"
build_volume._onEngineCreated()
build_volume.rebuild()
assert build_volume.getMeshData() is None
class TestUpdateMachineSizeProperties:
setting_property_dict = {"machine_width": {"value": 50},
"machine_depth": {"value": 100},
"machine_height": {"value": 200},
"machine_shape": {"value": "DERP!"}}
def getPropertySideEffect(*args, **kwargs):
properties = TestUpdateMachineSizeProperties.setting_property_dict.get(args[1])
if properties:
return properties.get(args[2])
def test_noGlobalStack(self, build_volume: BuildVolume):
build_volume._updateMachineSizeProperties()
assert build_volume._width == 0
assert build_volume._height == 0
assert build_volume._depth == 0
assert build_volume._shape == ""
def test_happy(self, build_volume: BuildVolume):
mocked_global_stack = MagicMock(name="mocked_global_stack")
mocked_global_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
build_volume._global_container_stack = mocked_global_stack
build_volume._updateMachineSizeProperties()
assert build_volume._width == 50
assert build_volume._height == 200
assert build_volume._depth == 100
assert build_volume._shape == "DERP!"
class TestGetEdgeDisallowedSize:
setting_property_dict = {}
bed_adhesion_size = 1
@pytest.fixture()
def build_volume(self, build_volume):
build_volume._calculateBedAdhesionSize = MagicMock(return_value = 1)
return build_volume
def getPropertySideEffect(*args, **kwargs):
properties = TestGetEdgeDisallowedSize.setting_property_dict.get(args[1])
if properties:
return properties.get(args[2])
def createMockedStack(self):
mocked_global_stack = MagicMock(name="mocked_global_stack")
mocked_global_stack.getProperty = MagicMock(side_effect=self.getPropertySideEffect)
return mocked_global_stack
def test_noGlobalContainer(self, build_volume: BuildVolume):
assert build_volume.getEdgeDisallowedSize() == 0
def test_unknownAdhesion(self, build_volume: BuildVolume):
build_volume._global_container_stack = self.createMockedStack()
with patch("cura.Settings.ExtruderManager.ExtruderManager.getInstance"):
#with pytest.raises(Exception):
# Since we don't have any adhesion set, this should break.
build_volume.getEdgeDisallowedSize()
def test_oneAtATime(self, build_volume: BuildVolume):
build_volume._global_container_stack = self.createMockedStack()
with patch("cura.Settings.ExtruderManager.ExtruderManager.getInstance"):
with patch.dict(self.setting_property_dict, {"print_sequence": {"value": "one_at_a_time"}}):
assert build_volume.getEdgeDisallowedSize() == 0.1

View File

@ -62,3 +62,12 @@ def test_hasUserSettings(machine_manager, application):
assert machine_manager.numUserSettings == 12 assert machine_manager.numUserSettings == 12
assert machine_manager.hasUserSettings assert machine_manager.hasUserSettings
def test_totalNumberOfSettings(machine_manager):
registry = MagicMock()
mocked_definition = MagicMock()
mocked_definition.getAllKeys = MagicMock(return_value = ["omg", "zomg", "foo"])
registry.findDefinitionContainers = MagicMock(return_value = [mocked_definition])
with patch("cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance", MagicMock(return_value=registry)):
assert machine_manager.totalNumberOfSettings == 3

View File

@ -101,7 +101,7 @@ def test_initialize():
initialize_preferences = MagicMock() initialize_preferences = MagicMock()
authorization_service = AuthorizationService(OAUTH_SETTINGS, original_preference) authorization_service = AuthorizationService(OAUTH_SETTINGS, original_preference)
authorization_service.initialize(initialize_preferences) authorization_service.initialize(initialize_preferences)
assert initialize_preferences.addPreference.called initialize_preferences.addPreference.assert_called_once_with("test/auth_data", "{}")
original_preference.addPreference.assert_not_called() original_preference.addPreference.assert_not_called()