Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'cura/BuildVolume.py')
-rwxr-xr-xcura/BuildVolume.py227
1 files changed, 138 insertions, 89 deletions
diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py
index d7ab18b09e..6bda2d94e3 100755
--- a/cura/BuildVolume.py
+++ b/cura/BuildVolume.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2019 Ultimaker B.V.
+# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import numpy
@@ -44,8 +44,9 @@ catalog = i18nCatalog("cura")
PRIME_CLEARANCE = 6.5
-## Build volume is a special kind of node that is responsible for rendering the printable area & disallowed areas.
class BuildVolume(SceneNode):
+ """Build volume is a special kind of node that is responsible for rendering the printable area & disallowed areas."""
+
raftThicknessChanged = Signal()
def __init__(self, application: "CuraApplication", parent: Optional[SceneNode] = None) -> None:
@@ -91,6 +92,8 @@ class BuildVolume(SceneNode):
self._adhesion_type = None # type: Any
self._platform = Platform(self)
+ self._edge_disallowed_size = None
+
self._build_volume_message = Message(catalog.i18nc("@info:status",
"The build volume height has been reduced due to the value of the"
" \"Print Sequence\" setting to prevent the gantry from colliding"
@@ -105,19 +108,17 @@ class BuildVolume(SceneNode):
self._application.globalContainerStackChanged.connect(self._onStackChanged)
- self._onStackChanged()
-
self._engine_ready = False
self._application.engineCreatedSignal.connect(self._onEngineCreated)
self._has_errors = False
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() # type: Set[SceneNode]
self._scene_change_timer = QTimer()
- self._scene_change_timer.setInterval(100)
+ self._scene_change_timer.setInterval(200)
self._scene_change_timer.setSingleShot(True)
self._scene_change_timer.timeout.connect(self._onSceneChangeTimerFinished)
@@ -163,10 +164,12 @@ class BuildVolume(SceneNode):
self._scene_objects = new_scene_objects
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered.
- ## Updates the listeners that listen for changes in per-mesh stacks.
- #
- # \param node The node for which the decorators changed.
def _updateNodeListeners(self, node: SceneNode):
+ """Updates the listeners that listen for changes in per-mesh stacks.
+
+ :param node: The node for which the decorators changed.
+ """
+
per_mesh_stack = node.callDecoration("getStack")
if per_mesh_stack:
per_mesh_stack.propertyChanged.connect(self._onSettingPropertyChanged)
@@ -177,20 +180,33 @@ class BuildVolume(SceneNode):
def setWidth(self, width: float) -> None:
self._width = width
+ def getWidth(self) -> float:
+ return self._width
+
def setHeight(self, height: float) -> None:
self._height = height
+ def getHeight(self) -> float:
+ return self._height
+
def setDepth(self, depth: float) -> None:
self._depth = depth
+ def getDepth(self) -> float:
+ return self._depth
+
def setShape(self, shape: str) -> None:
if shape:
self._shape = shape
- ## Get the length of the 3D diagonal through the build volume.
- #
- # This gives a sense of the scale of the build volume in general.
def getDiagonalSize(self) -> float:
+ """Get the length of the 3D diagonal through the build volume.
+
+ This gives a sense of the scale of the build volume in general.
+
+ :return: length of the 3D diagonal through the build volume
+ """
+
return math.sqrt(self._width * self._width + self._height * self._height + self._depth * self._depth)
def getDisallowedAreas(self) -> List[Polygon]:
@@ -226,9 +242,9 @@ class BuildVolume(SceneNode):
return True
- ## For every sliceable node, update node._outside_buildarea
- #
def updateNodeBoundaryCheck(self):
+ """For every sliceable node, update node._outside_buildarea"""
+
if not self._global_container_stack:
return
@@ -265,7 +281,7 @@ class BuildVolume(SceneNode):
continue
# If the entire node is below the build plate, still mark it as outside.
node_bounding_box = node.getBoundingBox()
- if node_bounding_box and node_bounding_box.top < 0:
+ if node_bounding_box and node_bounding_box.top < 0 and not node.getParent().callDecoration("isGroup"):
node.setOutsideBuildArea(True)
continue
# Mark the node as outside build volume if the set extruder is disabled
@@ -274,7 +290,9 @@ class BuildVolume(SceneNode):
if not self._global_container_stack.extruderList[int(extruder_position)].isEnabled:
node.setOutsideBuildArea(True)
continue
- except IndexError:
+ except IndexError: # Happens when the extruder list is too short. We're not done building the printer in memory yet.
+ continue
+ except TypeError: # Happens when extruder_position is None. This object has no extruder decoration.
continue
node.setOutsideBuildArea(False)
@@ -293,8 +311,13 @@ class BuildVolume(SceneNode):
for child_node in children:
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) -> None:
+ """Update the outsideBuildArea of a single node, given bounds or current build volume
+
+ :param node: single node
+ :param bounds: bounds or current build volume
+ """
+
if not isinstance(node, CuraSceneNode) or self._global_container_stack is None:
return
@@ -321,7 +344,12 @@ class BuildVolume(SceneNode):
# Mark the node as outside build volume if the set extruder is disabled
extruder_position = node.callDecoration("getActiveExtruderPosition")
- if not self._global_container_stack.extruderList[int(extruder_position)].isEnabled:
+ try:
+ if not self._global_container_stack.extruderList[int(extruder_position)].isEnabled:
+ node.setOutsideBuildArea(True)
+ return
+ except IndexError:
+ # If the extruder doesn't exist, also mark it as unprintable.
node.setOutsideBuildArea(True)
return
@@ -482,8 +510,9 @@ class BuildVolume(SceneNode):
self._disallowed_area_size = max(size, self._disallowed_area_size)
return mb.build()
- ## Recalculates the build volume & disallowed areas.
def rebuild(self) -> None:
+ """Recalculates the build volume & disallowed areas."""
+
if not self._width or not self._height or not self._depth:
return
@@ -572,7 +601,7 @@ class BuildVolume(SceneNode):
def _calculateExtraZClearance(self, extruders: List["ContainerStack"]) -> float:
if not self._global_container_stack:
return 0
-
+
extra_z = 0.0
for extruder in extruders:
if extruder.getProperty("retraction_hop_enabled", "value"):
@@ -584,8 +613,9 @@ class BuildVolume(SceneNode):
def _onStackChanged(self):
self._stack_change_timer.start()
- ## Update the build volume visualization
def _onStackChangeTimerFinished(self) -> None:
+ """Update the build volume visualization"""
+
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
@@ -710,15 +740,15 @@ class BuildVolume(SceneNode):
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.
- #
- # This is required for a signal to trigger the update in one go. The
- # ``_updateDisallowedAreas`` method itself shouldn't call ``rebuild``,
- # since there may be other changes before it needs to be rebuilt, which
- # would hit performance.
-
def _updateDisallowedAreasAndRebuild(self):
+ """Calls :py:meth:`cura.BuildVolume._updateDisallowedAreas` and makes sure the changes appear in the scene.
+
+ This is required for a signal to trigger the update in one go. The
+ :py:meth:`cura.BuildVolume._updateDisallowedAreas` method itself shouldn't call
+ :py:meth:`cura.BuildVolume.rebuild`, since there may be other changes before it needs to be rebuilt,
+ which would hit performance.
+ """
+
self._updateDisallowedAreas()
self._updateRaftThickness()
self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
@@ -731,6 +761,7 @@ class BuildVolume(SceneNode):
self._error_areas = []
used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
+ self._edge_disallowed_size = None # Force a recalculation
disallowed_border_size = self.getEdgeDisallowedSize()
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) # Normal machine disallowed areas can always be added.
@@ -764,7 +795,10 @@ class BuildVolume(SceneNode):
if prime_tower_collision: # Already found a collision.
break
if self._global_container_stack.getProperty("prime_tower_brim_enable", "value") and self._global_container_stack.getProperty("adhesion_type", "value") != "raft":
- prime_tower_areas[extruder_id][area_index] = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(disallowed_border_size))
+ brim_size = self._calculateBedAdhesionSize(used_extruders, "brim")
+ # Use 2x the brim size, since we need 1x brim size distance due to the object brim and another
+ # times the brim due to the brim of the prime tower
+ prime_tower_areas[extruder_id][area_index] = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(2 * brim_size, num_segments = 24))
if not prime_tower_collision:
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
result_areas_no_brim[extruder_id].extend(prime_tower_areas[extruder_id])
@@ -780,15 +814,14 @@ class BuildVolume(SceneNode):
for extruder_id in result_areas_no_brim:
self._disallowed_areas_no_brim.extend(result_areas_no_brim[extruder_id])
- ## Computes the disallowed areas for objects that are printed with print
- # features.
- #
- # This means that the brim, travel avoidance and such will be applied to
- # these features.
- #
- # \return A dictionary with for each used extruder ID the disallowed areas
- # where that extruder may not print.
def _computeDisallowedAreasPrinted(self, used_extruders):
+ """Computes the disallowed areas for objects that are printed with print features.
+
+ This means that the brim, travel avoidance and such will be applied to these features.
+
+ :return: A dictionary with for each used extruder ID the disallowed areas where that extruder may not print.
+ """
+
result = {}
adhesion_extruder = None #type: ExtruderStack
for extruder in used_extruders:
@@ -817,7 +850,7 @@ class BuildVolume(SceneNode):
prime_tower_y += brim_size
radius = prime_tower_size / 2
- prime_tower_area = Polygon.approximatedCircle(radius)
+ prime_tower_area = Polygon.approximatedCircle(radius, num_segments = 24)
prime_tower_area = prime_tower_area.translate(prime_tower_x - radius, prime_tower_y - radius)
prime_tower_area = prime_tower_area.getMinkowskiHull(Polygon.approximatedCircle(0))
@@ -826,18 +859,18 @@ class BuildVolume(SceneNode):
return result
- ## Computes the disallowed areas for the prime blobs.
- #
- # These are special because they are not subject to things like brim or
- # travel avoidance. They do get a dilute with the border size though
- # because they may not intersect with brims and such of other objects.
- #
- # \param border_size The size with which to offset the disallowed areas
- # due to skirt, brim, travel avoid distance, etc.
- # \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: float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]:
+ """Computes the disallowed areas for the prime blobs.
+
+ These are special because they are not subject to things like brim or travel avoidance. They do get a dilute
+ with the border size though because they may not intersect with brims and such of other objects.
+
+ :param border_size: The size with which to offset the disallowed areas due to skirt, brim, travel avoid distance
+ , etc.
+ :param used_extruders: The extruder stacks to generate disallowed areas for.
+ :return: A dictionary with for each used extruder ID the prime areas.
+ """
+
result = {} # type: Dict[str, List[Polygon]]
if not self._global_container_stack:
return result
@@ -865,19 +898,18 @@ class BuildVolume(SceneNode):
return result
- ## Computes the disallowed areas that are statically placed in the machine.
- #
- # It computes different disallowed areas depending on the offset of the
- # extruder. The resulting dictionary will therefore have an entry for each
- # extruder that is used.
- #
- # \param border_size The size with which to offset the disallowed areas
- # due to skirt, brim, travel avoid distance, etc.
- # \param used_extruders The extruder stacks to generate disallowed areas
- # for.
- # \return A dictionary with for each used extruder ID the disallowed areas
- # where that extruder may not print.
def _computeDisallowedAreasStatic(self, border_size:float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]:
+ """Computes the disallowed areas that are statically placed in the machine.
+
+ It computes different disallowed areas depending on the offset of the extruder. The resulting dictionary will
+ therefore have an entry for each extruder that is used.
+
+ :param border_size: The size with which to offset the disallowed areas due to skirt, brim, travel avoid distance
+ , etc.
+ :param used_extruders: The extruder stacks to generate disallowed areas for.
+ :return: A dictionary with for each used extruder ID the disallowed areas where that extruder may not print.
+ """
+
# Convert disallowed areas to polygons and dilate them.
machine_disallowed_polygons = []
if self._global_container_stack is None:
@@ -1008,13 +1040,14 @@ class BuildVolume(SceneNode):
return result
- ## Private convenience function to get a setting from every extruder.
- #
- # For single extrusion machines, this gets the setting from the global
- # stack.
- #
- # \return A sequence of setting values, one for each extruder.
def _getSettingFromAllExtruders(self, setting_key: str) -> List[Any]:
+ """Private convenience function to get a setting from every extruder.
+
+ For single extrusion machines, this gets the setting from the global stack.
+
+ :return: A sequence of setting values, one for each extruder.
+ """
+
all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
for i, (setting_value, setting_type) in enumerate(zip(all_values, all_types)):
@@ -1022,16 +1055,23 @@ class BuildVolume(SceneNode):
all_values[i] = 0
return all_values
- def _calculateBedAdhesionSize(self, used_extruders):
+ def _calculateBedAdhesionSize(self, used_extruders, adhesion_override = None):
+ """Get the bed adhesion size for the global container stack and used extruders
+
+ :param adhesion_override: override adhesion type.
+ Use None to use the global stack default, "none" for no adhesion, "brim" for brim etc.
+ """
if self._global_container_stack is None:
return None
container_stack = self._global_container_stack
- adhesion_type = container_stack.getProperty("adhesion_type", "value")
+ adhesion_type = adhesion_override
+ if adhesion_type is None:
+ 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"):
+ if adhesion_type == "brim":
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
@@ -1040,7 +1080,7 @@ class BuildVolume(SceneNode):
# We don't create an additional line for the extruder we're printing the brim with.
bed_adhesion_size -= skirt_brim_line_width * initial_layer_line_width_factor / 100.0
- elif adhesion_type == "skirt": # No brim? Also not on prime tower? Then use whatever the adhesion type is saying: Skirt, raft or none.
+ elif adhesion_type == "skirt":
skirt_distance = self._global_container_stack.getProperty("skirt_gap", "value")
skirt_line_count = self._global_container_stack.getProperty("skirt_line_count", "value")
@@ -1084,25 +1124,34 @@ class BuildVolume(SceneNode):
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)
+
+ for stack in used_extruders:
+ if stack.getProperty("travel_avoid_other_parts", "value"):
+ move_from_wall_radius = max(move_from_wall_radius, stack.getProperty("travel_avoid_distance", "value"))
+
+ infill_wipe_distance = stack.getProperty("infill_wipe_dist", "value")
+ num_walls = stack.getProperty("wall_line_count", "value")
+ if num_walls >= 1: # Infill wipes start from the infill, so subtract the total wall thickness from this.
+ infill_wipe_distance -= stack.getProperty("wall_line_width_0", "value")
+ if num_walls >= 2:
+ infill_wipe_distance -= stack.getProperty("wall_line_width_x", "value") * (num_walls - 1)
+ move_from_wall_radius = max(move_from_wall_radius, infill_wipe_distance)
+
return move_from_wall_radius
- ## Calculate the disallowed radius around the edge.
- #
- # This disallowed radius is to allow for space around the models that is
- # not part of the collision radius, such as bed adhesion (skirt/brim/raft)
- # and travel avoid distance.
def getEdgeDisallowedSize(self):
+ """Calculate the disallowed radius around the edge.
+
+ This disallowed radius is to allow for space around the models that is not part of the collision radius,
+ such as bed adhesion (skirt/brim/raft) and travel avoid distance.
+ """
+
if not self._global_container_stack or not self._global_container_stack.extruderList:
return 0
+ if self._edge_disallowed_size is not None:
+ return self._edge_disallowed_size
+
container_stack = self._global_container_stack
used_extruders = ExtruderManager.getInstance().getUsedExtruderStacks()
@@ -1118,8 +1167,8 @@ class BuildVolume(SceneNode):
# 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 farthest shield distance, since the shields go around support.
- border_size = max(move_from_wall_radius, support_expansion + farthest_shield_distance, support_expansion + bed_adhesion_size)
- return border_size
+ self._edge_disallowed_size = max(move_from_wall_radius, support_expansion + farthest_shield_distance, support_expansion + bed_adhesion_size)
+ return self._edge_disallowed_size
def _clamp(self, value, min_value, max_value):
return max(min(value, max_value), min_value)
@@ -1128,10 +1177,10 @@ class BuildVolume(SceneNode):
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist", "initial_layer_line_width_factor"]
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
- _prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"]
+ _prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "prime_blob_enable"]
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y", "prime_tower_brim_enable"]
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
- _distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts", "travel_avoid_supports"]
+ _distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts", "travel_avoid_supports", "wall_line_count", "wall_line_width_0", "wall_line_width_x"]
_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