mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-04-28 23:04:34 +08:00
Merge branch 'master' of github.com:Ultimaker/Cura
This commit is contained in:
commit
de5de8b9f1
@ -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
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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,14 +1048,86 @@ 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
|
||||
|
||||
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.
|
||||
#
|
||||
# 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":
|
||||
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"):
|
||||
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)
|
||||
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
|
||||
|
@ -839,7 +839,6 @@ class CuraApplication(QtApplication):
|
||||
if diagonal < 1: #No printer added yet. Set a default camera distance for normal-sized printers.
|
||||
diagonal = 375
|
||||
camera.setPosition(Vector(-80, 250, 700) * diagonal / 375)
|
||||
camera.setPerspective(True)
|
||||
camera.lookAt(Vector(0, 0, 0))
|
||||
controller.getScene().setActiveCamera("3d")
|
||||
|
||||
|
@ -62,6 +62,14 @@ class DiscoveredPrinter(QObject):
|
||||
self._machine_type = machine_type
|
||||
self.machineTypeChanged.emit()
|
||||
|
||||
# Checks if the given machine type name in the available machine list.
|
||||
# The machine type is a code name such as "ultimaker_3", while the machine type name is the human-readable name of
|
||||
# the machine type, which is "Ultimaker 3" for "ultimaker_3".
|
||||
def _hasHumanReadableMachineTypeName(self, machine_type_name: str) -> bool:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
results = CuraApplication.getInstance().getContainerRegistry().findDefinitionContainersMetadata(name = machine_type_name)
|
||||
return len(results) > 0
|
||||
|
||||
# Human readable machine type string
|
||||
@pyqtProperty(str, notify = machineTypeChanged)
|
||||
def readableMachineType(self) -> str:
|
||||
@ -70,24 +78,30 @@ class DiscoveredPrinter(QObject):
|
||||
# In ClusterUM3OutputDevice, when it updates a printer information, it updates the machine type using the field
|
||||
# "machine_variant", and for some reason, it's not the machine type ID/codename/... but a human-readable string
|
||||
# like "Ultimaker 3". The code below handles this case.
|
||||
if machine_manager.hasHumanReadableMachineTypeName(self._machine_type):
|
||||
if self._hasHumanReadableMachineTypeName(self._machine_type):
|
||||
readable_type = self._machine_type
|
||||
else:
|
||||
readable_type = machine_manager.getMachineTypeNameFromId(self._machine_type)
|
||||
readable_type = self._getMachineTypeNameFromId(self._machine_type)
|
||||
if not readable_type:
|
||||
readable_type = catalog.i18nc("@label", "Unknown")
|
||||
return readable_type
|
||||
|
||||
@pyqtProperty(bool, notify = machineTypeChanged)
|
||||
def isUnknownMachineType(self) -> bool:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||
if machine_manager.hasHumanReadableMachineTypeName(self._machine_type):
|
||||
if self._hasHumanReadableMachineTypeName(self._machine_type):
|
||||
readable_type = self._machine_type
|
||||
else:
|
||||
readable_type = machine_manager.getMachineTypeNameFromId(self._machine_type)
|
||||
readable_type = self._getMachineTypeNameFromId(self._machine_type)
|
||||
return not readable_type
|
||||
|
||||
def _getMachineTypeNameFromId(self, machine_type_id: str) -> str:
|
||||
machine_type_name = ""
|
||||
from cura.CuraApplication import CuraApplication
|
||||
results = CuraApplication.getInstance().getContainerRegistry().findDefinitionContainersMetadata(id = machine_type_id)
|
||||
if results:
|
||||
machine_type_name = results[0]["name"]
|
||||
return machine_type_name
|
||||
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def device(self) -> "NetworkedPrinterOutputDevice":
|
||||
return self._device
|
||||
|
@ -202,9 +202,6 @@ class QualityManager(QObject):
|
||||
def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
|
||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||
|
||||
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
|
||||
has_machine_specific_qualities = machine.getHasMachineQuality()
|
||||
|
||||
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||
# (1) the machine-specific node
|
||||
# (2) the generic node
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from copy import deepcopy
|
||||
@ -14,6 +14,7 @@ import cura.CuraApplication #To get the build plate.
|
||||
from cura.Settings.ExtruderStack import ExtruderStack #For typing.
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator #For per-object settings.
|
||||
|
||||
|
||||
## Scene nodes that are models are only seen when selecting the corresponding build plate
|
||||
# Note that many other nodes can just be UM SceneNode objects.
|
||||
class CuraSceneNode(SceneNode):
|
||||
@ -85,16 +86,6 @@ class CuraSceneNode(SceneNode):
|
||||
1.0
|
||||
]
|
||||
|
||||
## Return if the provided bbox collides with the bbox of this scene node
|
||||
def collidesWithBbox(self, check_bbox: AxisAlignedBox) -> bool:
|
||||
bbox = self.getBoundingBox()
|
||||
if bbox is not None:
|
||||
# Mark the node as outside the build volume if the bounding box test fails.
|
||||
if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
## Return if any area collides with the convex hull of this scene node
|
||||
def collidesWithArea(self, areas: List[Polygon]) -> bool:
|
||||
convex_hull = self.callDecoration("getConvexHull")
|
||||
|
@ -180,7 +180,7 @@ class ExtruderManager(QObject):
|
||||
# \param setting_key \type{str} The setting to get the property of.
|
||||
# \param property \type{str} The property to get.
|
||||
# \return \type{List} the list of results
|
||||
def getAllExtruderSettings(self, setting_key: str, prop: str) -> List:
|
||||
def getAllExtruderSettings(self, setting_key: str, prop: str) -> List[Any]:
|
||||
result = []
|
||||
|
||||
for extruder_stack in self.getActiveExtruderStacks():
|
||||
|
@ -264,18 +264,18 @@ class GlobalStack(CuraContainerStack):
|
||||
def getHeadAndFansCoordinates(self):
|
||||
return self.getProperty("machine_head_with_fans_polygon", "value")
|
||||
|
||||
def getHasMaterials(self) -> bool:
|
||||
@pyqtProperty(int, constant=True)
|
||||
def hasMaterials(self):
|
||||
return parseBool(self.getMetaDataEntry("has_materials", False))
|
||||
|
||||
def getHasVariants(self) -> bool:
|
||||
@pyqtProperty(int, constant=True)
|
||||
def hasVariants(self):
|
||||
return parseBool(self.getMetaDataEntry("has_variants", False))
|
||||
|
||||
def getHasVariantsBuildPlates(self) -> bool:
|
||||
@pyqtProperty(int, constant=True)
|
||||
def hasVariantBuildplates(self) -> bool:
|
||||
return parseBool(self.getMetaDataEntry("has_variant_buildplates", False))
|
||||
|
||||
def getHasMachineQuality(self) -> bool:
|
||||
return parseBool(self.getMetaDataEntry("has_machine_quality", False))
|
||||
|
||||
## Get default firmware file name if one is specified in the firmware
|
||||
@pyqtSlot(result = str)
|
||||
def getDefaultFirmwareName(self) -> str:
|
||||
|
@ -538,6 +538,7 @@ class MachineManager(QObject):
|
||||
return bool(self._printer_output_devices)
|
||||
|
||||
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
|
||||
@deprecated("use Cura.MachineManager.activeMachine.configuredConnectionTypes instead", "4.2")
|
||||
def activeMachineHasRemoteConnection(self) -> bool:
|
||||
if self._global_container_stack:
|
||||
has_remote_connection = False
|
||||
@ -816,21 +817,24 @@ class MachineManager(QObject):
|
||||
self.removeMachine(hidden_containers[0].getId())
|
||||
|
||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||
@deprecated("use Cura.MachineManager.activeMachine.hasMaterials instead", "4.2")
|
||||
def hasMaterials(self) -> bool:
|
||||
if self._global_container_stack:
|
||||
return self._global_container_stack.getHasMaterials()
|
||||
return self._global_container_stack.hasMaterials
|
||||
return False
|
||||
|
||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||
@deprecated("use Cura.MachineManager.activeMachine.hasVariants instead", "4.2")
|
||||
def hasVariants(self) -> bool:
|
||||
if self._global_container_stack:
|
||||
return self._global_container_stack.getHasVariants()
|
||||
return self._global_container_stack.hasVariants
|
||||
return False
|
||||
|
||||
@pyqtProperty(bool, notify = globalContainerChanged)
|
||||
@deprecated("use Cura.MachineManager.activeMachine.hasVariantBuildplates instead", "4.2")
|
||||
def hasVariantBuildplates(self) -> bool:
|
||||
if self._global_container_stack:
|
||||
return self._global_container_stack.getHasVariantsBuildPlates()
|
||||
return self._global_container_stack.hasVariantBuildplates
|
||||
return False
|
||||
|
||||
## The selected buildplate is compatible if it is compatible with all the materials in all the extruders
|
||||
@ -984,6 +988,7 @@ class MachineManager(QObject):
|
||||
self.forceUpdateAllSettings()
|
||||
|
||||
@pyqtSlot(int, result = QObject)
|
||||
@deprecated("use Cura.MachineManager.activeMachine.extruders instead", "4.2")
|
||||
def getExtruder(self, position: int) -> Optional[ExtruderStack]:
|
||||
if self._global_container_stack:
|
||||
return self._global_container_stack.extruders.get(str(position))
|
||||
@ -1097,6 +1102,7 @@ class MachineManager(QObject):
|
||||
container.removeInstance(setting_name)
|
||||
|
||||
@pyqtProperty("QVariantList", notify = globalContainerChanged)
|
||||
@deprecated("use Cura.MachineManager.activeMachine.extruders instead", "4.2")
|
||||
def currentExtruderPositions(self) -> List[str]:
|
||||
if self._global_container_stack is None:
|
||||
return []
|
||||
@ -1642,21 +1648,6 @@ class MachineManager(QObject):
|
||||
|
||||
return abbr_machine
|
||||
|
||||
# Checks if the given machine type name in the available machine list.
|
||||
# The machine type is a code name such as "ultimaker_3", while the machine type name is the human-readable name of
|
||||
# the machine type, which is "Ultimaker 3" for "ultimaker_3".
|
||||
def hasHumanReadableMachineTypeName(self, machine_type_name: str) -> bool:
|
||||
results = self._container_registry.findDefinitionContainersMetadata(name = machine_type_name)
|
||||
return len(results) > 0
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def getMachineTypeNameFromId(self, machine_type_id: str) -> str:
|
||||
machine_type_name = ""
|
||||
results = self._container_registry.findDefinitionContainersMetadata(id = machine_type_id)
|
||||
if results:
|
||||
machine_type_name = results[0]["name"]
|
||||
return machine_type_name
|
||||
|
||||
# Gets all machines that belong to the given group_id.
|
||||
def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]:
|
||||
return self._container_registry.findContainerStacks(type = "machine", group_id = group_id)
|
||||
|
@ -66,7 +66,7 @@ class Snapshot:
|
||||
looking_from_offset = Vector(-1, 1, 2)
|
||||
if size > 0:
|
||||
# determine the watch distance depending on the size
|
||||
looking_from_offset = looking_from_offset * size * 1.3
|
||||
looking_from_offset = looking_from_offset * size * 1.75
|
||||
camera.setPosition(look_at + looking_from_offset)
|
||||
camera.lookAt(look_at)
|
||||
|
||||
|
@ -32,7 +32,8 @@ if not known_args["debug"]:
|
||||
elif Platform.isOSX():
|
||||
return os.path.expanduser("~/Library/Logs/" + CuraAppName)
|
||||
|
||||
if hasattr(sys, "frozen"):
|
||||
# Do not redirect stdout and stderr to files if we are running CLI.
|
||||
if hasattr(sys, "frozen") and "cli" not in os.path.basename(sys.argv[0]).lower():
|
||||
dirpath = get_cura_dir_path()
|
||||
os.makedirs(dirpath, exist_ok = True)
|
||||
sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8")
|
||||
|
@ -20,14 +20,14 @@ Item
|
||||
anchors.right: parent.right
|
||||
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 columnSpacing: 3 * screenScaleFactor
|
||||
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 var forceUpdateFunction: manager.forceUpdate
|
||||
@ -59,6 +59,8 @@ Item
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Cura.NumericTextFieldWithUnit // "X (Width)"
|
||||
@ -175,6 +177,8 @@ Item
|
||||
font: UM.Theme.getFont("medium_bold")
|
||||
color: UM.Theme.getColor("text")
|
||||
renderType: Text.NativeRendering
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Cura.PrintHeadMinMaxTextField // "X min"
|
||||
|
@ -89,6 +89,7 @@ Item
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Your rating") + ":"
|
||||
visible: details.type == "plugin"
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
renderType: Text.NativeRendering
|
||||
|
@ -2784,6 +2784,19 @@
|
||||
"settable_per_extruder": true,
|
||||
"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":
|
||||
{
|
||||
"label": "Maximum Z Speed",
|
||||
|
@ -101,7 +101,7 @@ Item
|
||||
Action
|
||||
{
|
||||
id: redoAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:edit","&Redo");
|
||||
text: catalog.i18nc("@action:inmenu menubar:edit", "&Redo");
|
||||
iconName: "edit-redo";
|
||||
shortcut: StandardKey.Redo;
|
||||
onTriggered: UM.OperationStack.redo();
|
||||
@ -110,65 +110,65 @@ Item
|
||||
|
||||
Action
|
||||
{
|
||||
id: quitAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:file","&Quit");
|
||||
iconName: "application-exit";
|
||||
shortcut: StandardKey.Quit;
|
||||
id: quitAction
|
||||
text: catalog.i18nc("@action:inmenu menubar:file","&Quit")
|
||||
iconName: "application-exit"
|
||||
shortcut: StandardKey.Quit
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: view3DCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","3D View");
|
||||
onTriggered: UM.Controller.rotateView("3d", 0);
|
||||
id: view3DCameraAction
|
||||
text: catalog.i18nc("@action:inmenu menubar:view", "3D View")
|
||||
onTriggered: UM.Controller.setCameraRotation("3d", 0)
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: viewFrontCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","Front View");
|
||||
onTriggered: UM.Controller.rotateView("home", 0);
|
||||
id: viewFrontCameraAction
|
||||
text: catalog.i18nc("@action:inmenu menubar:view", "Front View")
|
||||
onTriggered: UM.Controller.setCameraRotation("home", 0)
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: viewTopCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","Top View");
|
||||
onTriggered: UM.Controller.rotateView("y", 90);
|
||||
id: viewTopCameraAction
|
||||
text: catalog.i18nc("@action:inmenu menubar:view", "Top View")
|
||||
onTriggered: UM.Controller.setCameraRotation("y", 90)
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: viewLeftSideCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","Left Side View");
|
||||
onTriggered: UM.Controller.rotateView("x", 90);
|
||||
id: viewLeftSideCameraAction
|
||||
text: catalog.i18nc("@action:inmenu menubar:view", "Left Side View")
|
||||
onTriggered: UM.Controller.setCameraRotation("x", 90)
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: viewRightSideCameraAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:view","Right Side View");
|
||||
onTriggered: UM.Controller.rotateView("x", -90);
|
||||
id: viewRightSideCameraAction
|
||||
text: catalog.i18nc("@action:inmenu menubar:view", "Right Side View")
|
||||
onTriggered: UM.Controller.setCameraRotation("x", -90)
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: preferencesAction;
|
||||
text: catalog.i18nc("@action:inmenu","Configure Cura...");
|
||||
iconName: "configure";
|
||||
id: preferencesAction
|
||||
text: catalog.i18nc("@action:inmenu", "Configure Cura...")
|
||||
iconName: "configure"
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: addMachineAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:printer","&Add Printer...");
|
||||
id: addMachineAction
|
||||
text: catalog.i18nc("@action:inmenu menubar:printer", "&Add Printer...")
|
||||
}
|
||||
|
||||
Action
|
||||
{
|
||||
id: settingsAction;
|
||||
text: catalog.i18nc("@action:inmenu menubar:printer","Manage Pr&inters...");
|
||||
iconName: "configure";
|
||||
id: settingsAction
|
||||
text: catalog.i18nc("@action:inmenu menubar:printer", "Manage Pr&inters...")
|
||||
iconName: "configure"
|
||||
}
|
||||
|
||||
Action
|
||||
|
@ -95,6 +95,10 @@ UM.PreferencesPage
|
||||
UM.Preferences.resetPreference("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")
|
||||
setDefaultDiscardOrKeepProfile(UM.Preferences.getValue("cura/choice_on_profile_override"))
|
||||
|
||||
@ -330,7 +334,8 @@ UM.PreferencesPage
|
||||
}
|
||||
}
|
||||
|
||||
UM.TooltipArea {
|
||||
UM.TooltipArea
|
||||
{
|
||||
width: childrenRect.width;
|
||||
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")
|
||||
@ -344,7 +349,8 @@ UM.PreferencesPage
|
||||
}
|
||||
}
|
||||
|
||||
UM.TooltipArea {
|
||||
UM.TooltipArea
|
||||
{
|
||||
width: childrenRect.width;
|
||||
height: childrenRect.height;
|
||||
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
|
||||
{
|
||||
//: Spacer
|
||||
|
@ -6,7 +6,7 @@ import QtQuick.Controls 1.1
|
||||
import QtQuick.Controls.Styles 1.1
|
||||
|
||||
import UM 1.4 as UM
|
||||
|
||||
import Cura 1.1 as Cura
|
||||
// A row of buttons that control the view direction
|
||||
Row
|
||||
{
|
||||
@ -19,30 +19,30 @@ Row
|
||||
ViewOrientationButton
|
||||
{
|
||||
iconSource: UM.Theme.getIcon("view_3d")
|
||||
onClicked: UM.Controller.rotateView("3d", 0)
|
||||
onClicked: Cura.Actions.view3DCamera.trigger()
|
||||
}
|
||||
|
||||
ViewOrientationButton
|
||||
{
|
||||
iconSource: UM.Theme.getIcon("view_front")
|
||||
onClicked: UM.Controller.rotateView("home", 0)
|
||||
onClicked: Cura.Actions.viewFrontCamera.trigger()
|
||||
}
|
||||
|
||||
ViewOrientationButton
|
||||
{
|
||||
iconSource: UM.Theme.getIcon("view_top")
|
||||
onClicked: UM.Controller.rotateView("y", 90)
|
||||
onClicked: Cura.Actions.viewTopCamera.trigger()
|
||||
}
|
||||
|
||||
ViewOrientationButton
|
||||
{
|
||||
iconSource: UM.Theme.getIcon("view_left")
|
||||
onClicked: UM.Controller.rotateView("x", 90)
|
||||
onClicked: Cura.Actions.viewLeftSideCamera.trigger()
|
||||
}
|
||||
|
||||
ViewOrientationButton
|
||||
{
|
||||
iconSource: UM.Theme.getIcon("view_right")
|
||||
onClicked: UM.Controller.rotateView("x", -90)
|
||||
onClicked: Cura.Actions.viewRightSideCamera.trigger()
|
||||
}
|
||||
}
|
||||
|
@ -193,19 +193,20 @@
|
||||
"xray": [26, 26, 62, 255],
|
||||
"xray_error": [255, 0, 0, 255],
|
||||
|
||||
"layerview_ghost": [32, 32, 32, 96],
|
||||
"layerview_none": [255, 255, 255, 255],
|
||||
"layerview_inset_0": [255, 0, 0, 255],
|
||||
"layerview_inset_x": [0, 255, 0, 255],
|
||||
"layerview_skin": [255, 255, 0, 255],
|
||||
"layerview_support": [0, 255, 255, 255],
|
||||
"layerview_skirt": [0, 255, 255, 255],
|
||||
"layerview_infill": [255, 192, 0, 255],
|
||||
"layerview_support_infill": [0, 255, 255, 255],
|
||||
"layerview_move_combing": [0, 0, 255, 255],
|
||||
"layerview_move_retraction": [128, 128, 255, 255],
|
||||
"layerview_support_interface": [64, 192, 255, 255],
|
||||
"layerview_nozzle": [181, 166, 66, 120],
|
||||
"layerview_ghost": [31, 31, 31, 95],
|
||||
"layerview_none": [255, 255, 255, 255],
|
||||
"layerview_inset_0": [255, 0, 0, 255],
|
||||
"layerview_inset_x": [0, 255, 0, 255],
|
||||
"layerview_skin": [255, 255, 0, 255],
|
||||
"layerview_support": [0, 255, 255, 255],
|
||||
"layerview_skirt": [0, 255, 255, 255],
|
||||
"layerview_infill": [255, 127, 0, 255],
|
||||
"layerview_support_infill": [0, 255, 255, 255],
|
||||
"layerview_move_combing": [0, 0, 255, 255],
|
||||
"layerview_move_retraction": [128, 127, 255, 255],
|
||||
"layerview_support_interface": [63, 127, 255, 255],
|
||||
"layerview_prime_tower": [0, 255, 255, 255],
|
||||
"layerview_nozzle": [181, 166, 66, 50],
|
||||
|
||||
"material_compatibility_warning": [255, 255, 255, 255],
|
||||
|
||||
|
@ -372,18 +372,18 @@
|
||||
"xray": [26, 26, 62, 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_inset_0": [255, 0, 0, 255],
|
||||
"layerview_inset_x": [0, 255, 0, 255],
|
||||
"layerview_skin": [255, 255, 0, 255],
|
||||
"layerview_support": [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_move_combing": [0, 0, 255, 255],
|
||||
"layerview_move_retraction": [128, 128, 255, 255],
|
||||
"layerview_support_interface": [64, 192, 255, 255],
|
||||
"layerview_move_retraction": [128, 127, 255, 255],
|
||||
"layerview_support_interface": [63, 127, 255, 255],
|
||||
"layerview_prime_tower": [0, 255, 255, 255],
|
||||
"layerview_nozzle": [181, 166, 66, 50],
|
||||
|
||||
|
@ -2,10 +2,16 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
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
|
||||
def build_volume():
|
||||
def build_volume() -> BuildVolume:
|
||||
mocked_application = MagicMock()
|
||||
mocked_platform = MagicMock(name="platform")
|
||||
with patch("cura.BuildVolume.Platform", mocked_platform):
|
||||
@ -25,3 +31,238 @@ def test_buildVolumeSetSizes(build_volume):
|
||||
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
|
||||
|
||||
|
@ -62,3 +62,12 @@ def test_hasUserSettings(machine_manager, application):
|
||||
|
||||
assert machine_manager.numUserSettings == 12
|
||||
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
|
||||
|
@ -101,7 +101,7 @@ def test_initialize():
|
||||
initialize_preferences = MagicMock()
|
||||
authorization_service = AuthorizationService(OAUTH_SETTINGS, original_preference)
|
||||
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()
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user