diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 18b762bb2c..e931b7c056 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -116,7 +116,8 @@ class BuildVolume(SceneNode): self._application.engineCreatedSignal.connect(self._onEngineCreated) self._has_errors = False - self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged) + scene = self._application.getController().getScene() + scene.sceneChanged.connect(self._onSceneChanged) # Objects loaded at the moment. We are connected to the property changed events of these objects. self._scene_objects = set() # type: Set[SceneNode] @@ -655,7 +656,7 @@ class BuildVolume(SceneNode): extra_z = retraction_hop return extra_z - def _onStackChanged(self): + def _onStackChanged(self, *args) -> None: self._stack_change_timer.start() def _onStackChangeTimerFinished(self) -> None: diff --git a/cura/Scene/SliceableObjectDecorator.py b/cura/Scene/SliceableObjectDecorator.py index 7b4cbab3e5..3e7d9901ad 100644 --- a/cura/Scene/SliceableObjectDecorator.py +++ b/cura/Scene/SliceableObjectDecorator.py @@ -1,9 +1,12 @@ +# Copyright (c) 2025 UltiMaker +# Cura is released under the terms of the LGPLv3 or higher. import copy import json +import numpy -from typing import Optional, Dict +from typing import Optional, Dict, List -from PyQt6.QtCore import QBuffer +from PyQt6.QtCore import QBuffer, QTimer from PyQt6.QtGui import QImage, QImageWriter from UM.Scene.SceneNodeDecorator import SceneNodeDecorator @@ -18,8 +21,15 @@ class SliceableObjectDecorator(SceneNodeDecorator): self._paint_texture = None self._texture_data_mapping: Dict[str, tuple[int, int]] = {} + self._painted_extruders: Optional[List[int]] = None + self.paintTextureChanged = Signal() + self._texture_change_timer = QTimer() + self._texture_change_timer.setInterval(500) # Long interval to avoid triggering during painting + self._texture_change_timer.setSingleShot(True) + self._texture_change_timer.timeout.connect(self._onTextureChangeTimerFinished) + def isSliceable(self) -> bool: return True @@ -29,6 +39,32 @@ class SliceableObjectDecorator(SceneNodeDecorator): def getPaintTextureChangedSignal(self) -> Signal: return self.paintTextureChanged + def setPaintedExtrudersCountDirty(self) -> None: + self._texture_change_timer.start() + + def _onTextureChangeTimerFinished(self) -> None: + self._painted_extruders = None + + if (self._paint_texture is None or self._paint_texture.getImage() is None or + "extruder" not in self._texture_data_mapping): + return + + image = self._paint_texture.getImage() + image_bits = image.constBits() + image_bits.setsize(image.sizeInBytes()) + image_array = numpy.frombuffer(image_bits, dtype=numpy.uint32) + + bit_range_start, bit_range_end = self._texture_data_mapping["extruder"] + full_int32 = 0xffffffff + bit_mask = (((full_int32 << (32 - 1 - (bit_range_end - bit_range_start))) & full_int32) >> ( + 32 - 1 - bit_range_end)) + + texel_counts = numpy.bincount((image_array & bit_mask) >> bit_range_start) + self._painted_extruders = [extruder_nr for extruder_nr, count in enumerate(texel_counts) if count > 0] + + from cura.CuraApplication import CuraApplication + CuraApplication.getInstance().globalContainerStackChanged.emit() + def setPaintTexture(self, texture: Texture) -> None: self._paint_texture = texture self.paintTextureChanged.emit() @@ -63,6 +99,9 @@ class SliceableObjectDecorator(SceneNodeDecorator): return texture_buffer.data() + def getPaintedExtruders(self) -> Optional[List[int]]: + return self._painted_extruders + def __deepcopy__(self, memo) -> "SliceableObjectDecorator": copied_decorator = SliceableObjectDecorator() copied_decorator.setPaintTexture(copy.deepcopy(self.getPaintTexture())) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index cd39947bf8..8e950350b3 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 Ultimaker B.V. +# Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. from PyQt6.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt. @@ -254,6 +254,11 @@ class ExtruderManager(QObject): if not support_roof_enabled: support_roof_enabled |= stack_to_use.getProperty("support_roof_enable", "value") + painted_extruders = node.callDecoration("getPaintedExtruders") + if painted_extruders is not None: + for extruder_nr in painted_extruders: + used_extruder_stack_ids.add(self.extruderIds[str(extruder_nr)]) + # Check limit to extruders limit_to_extruder_feature_list = ["wall_0_extruder_nr", "wall_x_extruder_nr", diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 72a2a203e8..b376aaaf78 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -416,7 +416,7 @@ class StartSliceJob(Job): # Only check if the printing extruder is enabled for printing meshes is_non_printing_mesh = node.callDecoration("evaluateIsNonPrintingMesh") if not is_non_printing_mesh: - for used_extruder in StartSliceJob._getUsedExtruders(node): + for used_extruder in StartSliceJob._getMainExtruders(node): if not extruders_enabled[used_extruder]: skip_group = True has_model_with_disabled_extruders = True @@ -763,28 +763,11 @@ class StartSliceJob(Job): self._addRelations(relations_set, relation.target.relations) @staticmethod - def _getUsedExtruders(node: SceneNode) -> List[int]: - used_extruders = [] - - # Look at extruders used in painted texture - node_texture = node.callDecoration("getPaintTexture") - texture_data_mapping = node.callDecoration("getTextureDataMapping") - if node_texture is not None and texture_data_mapping is not None and "extruder" in texture_data_mapping: - texture_image = node_texture.getImage() - image_ptr = texture_image.constBits() - image_ptr.setsize(texture_image.sizeInBytes()) - image_size = texture_image.height(), texture_image.width() - image_array = numpy.frombuffer(image_ptr, dtype=numpy.uint32).reshape(image_size) - - bit_range_start, bit_range_end = texture_data_mapping["extruder"] - full_int32 = 0xffffffff - bit_mask = (((full_int32 << (32 - 1 - (bit_range_end - bit_range_start))) & full_int32) >> ( - 32 - 1 - bit_range_end)) - - used_extruders = (numpy.unique(image_array & bit_mask) >> bit_range_start).tolist() + def _getMainExtruders(node: SceneNode) -> List[int]: + used_extruders = node.callDecoration("getPaintedExtruders") # There is no relevant painting data, just take the extruder associated to the model if not used_extruders: used_extruders = [int(node.callDecoration("getActiveExtruderPosition"))] - return used_extruders \ No newline at end of file + return used_extruders diff --git a/plugins/PaintTool/MultiMaterialExtruderConverter.py b/plugins/PaintTool/MultiMaterialExtruderConverter.py index be1d0e05db..8dbf56893e 100644 --- a/plugins/PaintTool/MultiMaterialExtruderConverter.py +++ b/plugins/PaintTool/MultiMaterialExtruderConverter.py @@ -109,4 +109,6 @@ class MultiMaterialExtruderConverter: texture.updateImagePart(image.rect()) + node.callDecoration("setPaintedExtrudersCountDirty") + self.mainExtruderChanged.emit(node) diff --git a/plugins/PaintTool/PaintClearCommand.py b/plugins/PaintTool/PaintClearCommand.py index 1a7d95c98a..1be8b9bcbf 100644 --- a/plugins/PaintTool/PaintClearCommand.py +++ b/plugins/PaintTool/PaintClearCommand.py @@ -5,6 +5,7 @@ from typing import Optional from PyQt6.QtGui import QUndoCommand, QImage, QPainter, QBrush +from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from UM.View.GL.Texture import Texture from .PaintCommand import PaintCommand @@ -13,8 +14,12 @@ from .PaintCommand import PaintCommand class PaintClearCommand(PaintCommand): """Provides the command that clears all the painting for the current mode""" - def __init__(self, texture: Texture, bit_range: tuple[int, int], set_value: int) -> None: - super().__init__(texture, bit_range) + def __init__(self, + texture: Texture, + bit_range: tuple[int, int], + set_value: int, + sliceable_object_decorator: Optional[SliceableObjectDecorator] = None) -> None: + super().__init__(texture, bit_range, sliceable_object_decorator=sliceable_object_decorator) self._set_value = set_value def id(self) -> int: @@ -22,13 +27,12 @@ class PaintClearCommand(PaintCommand): def redo(self) -> None: painter = self._makeClearedTexture() - if self._set_value > 0: painter.setCompositionMode(QPainter.CompositionMode.RasterOp_SourceOrDestination) painter.fillRect(self._texture.getImage().rect(), QBrush(self._set_value)) - painter.end() + self._setPaintedExtrudersCountDirty() self._texture.updateImagePart(self._bounding_rect) def mergeWith(self, command: QUndoCommand) -> bool: @@ -38,6 +42,6 @@ class PaintClearCommand(PaintCommand): # There is actually nothing more to do here, both clear commands already have the same original texture return True - def _clearTextureBits(self, painter: QPainter): + def _clearTextureBits(self, painter: QPainter, extended = False): painter.setCompositionMode(QPainter.CompositionMode.RasterOp_NotSourceAndDestination) painter.fillRect(self._texture.getImage().rect(), QBrush(self._getBitRangeMask())) \ No newline at end of file diff --git a/plugins/PaintTool/PaintCommand.py b/plugins/PaintTool/PaintCommand.py index 9dfae1d092..5367d0e0ca 100644 --- a/plugins/PaintTool/PaintCommand.py +++ b/plugins/PaintTool/PaintCommand.py @@ -1,12 +1,14 @@ # Copyright (c) 2025 UltiMaker # Cura is released under the terms of the LGPLv3 or higher. -from typing import Tuple, Optional +from typing import Tuple, Optional, Dict -from PyQt6.QtCore import QRect +import numpy from PyQt6.QtGui import QUndoCommand, QImage, QPainter, QBrush from UM.View.GL.Texture import Texture +from cura.CuraApplication import CuraApplication +from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator class PaintCommand(QUndoCommand): @@ -14,7 +16,11 @@ class PaintCommand(QUndoCommand): FULL_INT32 = 0xffffffff - def __init__(self, texture: Texture, bit_range: tuple[int, int], make_original_image = True) -> None: + def __init__(self, + texture: Texture, + bit_range: tuple[int, int], + make_original_image = True, + sliceable_object_decorator: Optional[SliceableObjectDecorator] = None) -> None: super().__init__() self._texture: Texture = texture @@ -22,6 +28,8 @@ class PaintCommand(QUndoCommand): self._original_texture_image = None self._bounding_rect = texture.getImage().rect() + self._sliceable_object_decorator: Optional[SliceableObjectDecorator] = sliceable_object_decorator + if make_original_image: self._original_texture_image = self._texture.getImage().copy() painter = QPainter(self._original_texture_image) @@ -35,21 +43,26 @@ class PaintCommand(QUndoCommand): if self._original_texture_image is None: return - painter = self._makeClearedTexture() + painter = self._makeClearedTexture(extended=True) painter.setCompositionMode(QPainter.CompositionMode.RasterOp_SourceOrDestination) painter.drawImage(0, 0, self._original_texture_image) painter.end() + self._setPaintedExtrudersCountDirty() self._texture.updateImagePart(self._bounding_rect) - def _makeClearedTexture(self) -> QPainter: + def _setPaintedExtrudersCountDirty(self) -> None: + if self._sliceable_object_decorator is not None: + self._sliceable_object_decorator.setPaintedExtrudersCountDirty() + + def _makeClearedTexture(self, extended = False) -> QPainter: painter = QPainter(self._texture.getImage()) painter.setRenderHint(QPainter.RenderHint.Antialiasing, False) - self._clearTextureBits(painter) + self._clearTextureBits(painter, extended) return painter - def _clearTextureBits(self, painter: QPainter): + def _clearTextureBits(self, painter: QPainter, extended = False): raise NotImplementedError() @staticmethod diff --git a/plugins/PaintTool/PaintStrokeCommand.py b/plugins/PaintTool/PaintStrokeCommand.py index 8d4a5c2dbd..bcb38a19bc 100644 --- a/plugins/PaintTool/PaintStrokeCommand.py +++ b/plugins/PaintTool/PaintStrokeCommand.py @@ -7,6 +7,7 @@ import math from PyQt6.QtCore import QRect, QRectF, QPoint from PyQt6.QtGui import QUndoCommand, QImage, QPainter, QPainterPath, QPen, QBrush +from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from UM.View.GL.Texture import Texture from UM.Math.Polygon import Polygon @@ -16,14 +17,16 @@ class PaintStrokeCommand(PaintCommand): """Provides the command that does the actual painting on objects with undo/redo mechanisms""" PEN_OVERLAP_WIDTH = 2.5 + PEN_OVERLAP_WIDTH_EXTENDED = PEN_OVERLAP_WIDTH + 0.5 def __init__(self, texture: Texture, stroke_polygons: List[Polygon], set_value: int, bit_range: tuple[int, int], - mergeable: bool) -> None: - super().__init__(texture, bit_range, make_original_image = not mergeable) + mergeable: bool, + sliceable_object_decorator: Optional[SliceableObjectDecorator] = None) -> None: + super().__init__(texture, bit_range, make_original_image = not mergeable, sliceable_object_decorator=sliceable_object_decorator) self._stroke_polygons: List[Polygon] = stroke_polygons self._calculateBoundingRect() self._set_value: int = set_value @@ -40,6 +43,7 @@ class PaintStrokeCommand(PaintCommand): painter.drawPath(self._makePainterPath()) painter.end() + self._setPaintedExtrudersCountDirty() self._texture.updateImagePart(self._bounding_rect) def mergeWith(self, command: QUndoCommand) -> bool: @@ -55,9 +59,9 @@ class PaintStrokeCommand(PaintCommand): return True - def _clearTextureBits(self, painter: QPainter): + def _clearTextureBits(self, painter: QPainter, extended = False): painter.setBrush(QBrush(self._getBitRangeMask())) - painter.setPen(QPen(painter.brush(), self.PEN_OVERLAP_WIDTH)) + painter.setPen(QPen(painter.brush(), self.PEN_OVERLAP_WIDTH_EXTENDED if extended else self.PEN_OVERLAP_WIDTH)) painter.setCompositionMode(QPainter.CompositionMode.RasterOp_NotSourceAndDestination) painter.drawPath(self._makePainterPath()) diff --git a/plugins/PaintTool/PaintTool.py b/plugins/PaintTool/PaintTool.py index ff2f196431..72796837e0 100644 --- a/plugins/PaintTool/PaintTool.py +++ b/plugins/PaintTool/PaintTool.py @@ -412,7 +412,7 @@ class PaintTool(Tool): Logger.logException("e", "Error when adding paint stroke") self._last_world_coords = world_coords - self._updateScene(painted_object, update_node = self._mouse_held) + self._updateScene(painted_object, update_node = event_caught) return event_caught return False diff --git a/plugins/PaintTool/PaintView.py b/plugins/PaintTool/PaintView.py index 6750c5b33e..d7bfb6f1c9 100644 --- a/plugins/PaintTool/PaintView.py +++ b/plugins/PaintTool/PaintView.py @@ -23,6 +23,7 @@ from UM.View.GL.OpenGL import OpenGL from UM.i18n import i18nCatalog from UM.Math.Color import Color from UM.Math.Polygon import Polygon +from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from .PaintStrokeCommand import PaintStrokeCommand from .PaintClearCommand import PaintClearCommand @@ -242,7 +243,14 @@ class PaintView(CuraView): stroke_path, set_value, self._current_bits_ranges, - merge_with_previous)) + merge_with_previous, + self._getSliceableObjectDecorator())) + + def _getSliceableObjectDecorator(self) -> Optional[SliceableObjectDecorator]: + if self._painted_object is None or self._current_paint_type != "extruder": + return None + + return self._painted_object.getDecorator(SliceableObjectDecorator) def _makeClearCommand(self) -> Optional[PaintClearCommand]: if self._painted_object is None or self._paint_texture is None or self._current_bits_ranges is None: @@ -254,7 +262,10 @@ class PaintView(CuraView): if extruder_stack is not None: set_value = extruder_stack.getValue("extruder_nr") - return PaintClearCommand(self._paint_texture, self._current_bits_ranges, set_value) + return PaintClearCommand(self._paint_texture, + self._current_bits_ranges, + set_value, + self._getSliceableObjectDecorator()) def clearPaint(self): self._prepareDataMapping() diff --git a/plugins/PostProcessingPlugin/scripts/PurgeLinesAndUnload.py b/plugins/PostProcessingPlugin/scripts/PurgeLinesAndUnload.py index 44c2b50f9e..6345f951dd 100644 --- a/plugins/PostProcessingPlugin/scripts/PurgeLinesAndUnload.py +++ b/plugins/PostProcessingPlugin/scripts/PurgeLinesAndUnload.py @@ -486,9 +486,9 @@ class PurgeLinesAndUnload(Script): data[0] += "; [Purge Lines and Unload] 'Add Purge Lines' did not run because the assumed primary nozzle (T0) has tool offsets.\n" Message(title = "[Purge Lines and Unload]", text = "'Add Purge Lines' did not run because the assumed primary nozzle (T0) has tool offsets").show() return data - + self.purge_line_hgt = round(float(self.global_stack.getProperty("layer_height_0", "value")),2) def calculate_purge_volume(line_width, purge_length, volume_per_mm): - return round((line_width * 0.3 * purge_length) * 1.25 / volume_per_mm, 5) + return round((line_width * self.purge_line_hgt * purge_length) * 1.25 / volume_per_mm, 5) def adjust_for_prime_blob_gcode(retract_speed, retract_distance): """Generates G-code lines for prime blob adjustment.""" @@ -511,7 +511,7 @@ class PurgeLinesAndUnload(Script): purge_str = purge_str.replace("Lines", "Lines at MinX") # Travel to the purge start purge_str += f"G0 F{self.speed_travel} X{self.machine_left + self.border_distance} Y{self.machine_front + 10} ; Move to start\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" if self.prime_blob_enable: purge_str += adjust_for_prime_blob_gcode(self.retract_speed, self.retract_dist) # Purge two lines @@ -522,7 +522,7 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round(purge_volume * 2 - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z8 ; Move Up\nG4 S1 ; Wait for 1 second\n" # Wipe - purge_str += f"G0 F{self.print_speed} X{self.machine_left + 3 + self.border_distance} Y{self.machine_front + 20} Z0.3 ; Slide over and down\n" + purge_str += f"G0 F{self.print_speed} X{self.machine_left + 3 + self.border_distance} Y{self.machine_front + 20} Z{self.purge_line_hgt} ; Slide over and down\n" purge_str += f"G0 X{self.machine_left + 3 + self.border_distance} Y{self.machine_front + 35} ; Wipe\n" self.end_purge_location = Position.LEFT_FRONT elif purge_location == Location.RIGHT: @@ -532,7 +532,7 @@ class PurgeLinesAndUnload(Script): purge_str = purge_str.replace("Lines", "Lines at MaxX") # Travel to the purge start purge_str += f"G0 F{self.speed_travel} X{self.machine_right - self.border_distance} ; Move\nG0 Y{self.machine_back - 10} ; Move\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" if self.prime_blob_enable: purge_str += adjust_for_prime_blob_gcode(self.retract_speed, self.retract_dist) # Purge two lines @@ -543,7 +543,7 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round(purge_volume * 2 - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z8 ; Move Up\nG4 S1 ; Wait for 1 second\n" # Wipe - purge_str += f"G0 F{self.print_speed} X{self.machine_right - 3 - self.border_distance} Y{self.machine_back - 20} Z0.3 ; Slide over and down\n" + purge_str += f"G0 F{self.print_speed} X{self.machine_right - 3 - self.border_distance} Y{self.machine_back - 20} Z{self.purge_line_hgt} ; Slide over and down\n" purge_str += f"G0 X{self.machine_right - 3 - self.border_distance} Y{self.machine_back - 35} ; Wipe\n" self.end_purge_location = Position.RIGHT_REAR elif purge_location == Location.FRONT: @@ -554,7 +554,7 @@ class PurgeLinesAndUnload(Script): purge_str = purge_str.replace("Lines", "Lines at MinY") # Travel to the purge start purge_str += f"G0 F{self.speed_travel} X{self.machine_left + 10} Y{self.machine_front + self.border_distance} ; Move to start\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" if self.prime_blob_enable: purge_str += adjust_for_prime_blob_gcode(self.retract_speed, self.retract_dist) # Purge two lines @@ -565,7 +565,7 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round(purge_volume * 2 - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z8 ; Move Up\nG4 S1 ; Wait for 1 second\n" # Wipe - purge_str += f"G0 F{self.print_speed} X{self.machine_left + 20} Y{self.machine_front + 3 + self.border_distance} Z0.3 ; Slide over and down\n" + purge_str += f"G0 F{self.print_speed} X{self.machine_left + 20} Y{self.machine_front + 3 + self.border_distance} Z{self.purge_line_hgt} ; Slide over and down\n" purge_str += f"G0 X{self.machine_left + 35} Y{self.machine_front + 3 + self.border_distance} ; Wipe\n" self.end_purge_location = Position.LEFT_FRONT elif purge_location == Location.REAR: @@ -577,7 +577,7 @@ class PurgeLinesAndUnload(Script): # Travel to the purge start purge_str += f"G0 F{self.speed_travel} Y{self.machine_back - self.border_distance} ; Ortho Move to back\n" purge_str += f"G0 X{self.machine_right - 10} ; Ortho move to start\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" if self.prime_blob_enable: purge_str += adjust_for_prime_blob_gcode(self.retract_speed, self.retract_dist) # Purge two lines @@ -588,7 +588,7 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round(purge_volume * 2 - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z8 ; Move Up\nG4 S1 ; Wait 1 second\n" # Wipe - purge_str += f"G0 F{self.print_speed} X{self.machine_right - 20} Y{self.machine_back - 3 - self.border_distance} Z0.3 ; Slide over and down\n" + purge_str += f"G0 F{self.print_speed} X{self.machine_right - 20} Y{self.machine_back - 3 - self.border_distance} Z{self.purge_line_hgt} ; Slide over and down\n" purge_str += f"G0 X{self.machine_right - 35} Y{self.machine_back - 3 - self.border_distance} ; Wipe\n" self.end_purge_location = Position.RIGHT_REAR # Some cartesian printers (BIBO, Weedo, MethodX, etc.) are Origin at Center @@ -600,7 +600,7 @@ class PurgeLinesAndUnload(Script): purge_volume = calculate_purge_volume(self.init_line_width, purge_len, self.mm3_per_mm) # Travel to the purge start purge_str += f"G0 F{self.speed_travel} X{self.machine_left + self.border_distance} Y{self.machine_front + 10} ; Move to start\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" if self.prime_blob_enable: purge_str += adjust_for_prime_blob_gcode(self.retract_speed, self.retract_dist) # Purge two lines @@ -611,7 +611,7 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round(purge_volume * 2 - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z8 ; Move Up\nG4 S1 ; Wait for 1 second\n" # Wipe - purge_str += f"G0 F{self.print_speed} X{self.machine_left + 3 + self.border_distance} Y{self.machine_front + 20} Z0.3 ; Slide over and down\n" + purge_str += f"G0 F{self.print_speed} X{self.machine_left + 3 + self.border_distance} Y{self.machine_front + 20} Z{self.purge_line_hgt} ; Slide over and down\n" purge_str += f"G0 X{self.machine_left + 3 + self.border_distance} Y{self.machine_front + 35} ; Wipe\n" self.end_purge_location = Position.LEFT_FRONT elif purge_location == Location.RIGHT: @@ -621,7 +621,7 @@ class PurgeLinesAndUnload(Script): purge_volume = calculate_purge_volume(self.init_line_width, purge_len, self.mm3_per_mm) # Travel to the purge start purge_str += f"G0 F{self.speed_travel} X{self.machine_right - self.border_distance} Z2 ; Move\nG0 Y{self.machine_back - 10} Z2 ; Move to start\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" if self.prime_blob_enable: purge_str += adjust_for_prime_blob_gcode(self.retract_speed, self.retract_dist) # Purge two lines @@ -632,7 +632,7 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round(purge_volume * 2 - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z8 ; Move Up\nG4 S1 ; Wait for 1 second\n" # Wipe - purge_str += f"G0 F{self.print_speed} X{self.machine_right - 3 - self.border_distance} Y{self.machine_back - 20} Z0.3 ; Slide over and down\n" + purge_str += f"G0 F{self.print_speed} X{self.machine_right - 3 - self.border_distance} Y{self.machine_back - 20} Z{self.purge_line_hgt} ; Slide over and down\n" purge_str += f"G0 X{self.machine_right - 3 - self.border_distance} Y{self.machine_back - 35} ; Wipe\n" self.end_purge_location = Position.RIGHT_REAR elif purge_location == Location.FRONT: @@ -642,7 +642,7 @@ class PurgeLinesAndUnload(Script): purge_volume = calculate_purge_volume(self.init_line_width, purge_len, self.mm3_per_mm) # Travel to the purge start purge_str += f"G0 F{self.speed_travel} X{self.machine_left + 10} Z2 ; Move\nG0 Y{self.machine_front + self.border_distance} Z2 ; Move to start\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" if self.prime_blob_enable: purge_str += adjust_for_prime_blob_gcode(self.retract_speed, self.retract_dist) # Purge two lines @@ -653,7 +653,7 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round(purge_volume * 2 - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z8 ; Move Up\nG4 S1 ; Wait for 1 second\n" # Wipe - purge_str += f"G0 F{self.print_speed} X{self.machine_left + 20} Y{self.machine_front + 3 + self.border_distance} Z0.3 ; Slide over and down\n" + purge_str += f"G0 F{self.print_speed} X{self.machine_left + 20} Y{self.machine_front + 3 + self.border_distance} Z{self.purge_line_hgt} ; Slide over and down\n" purge_str += f"G0 X{self.machine_left + 35} Y{self.machine_front + 3 + self.border_distance} ; Wipe\n" self.end_purge_location = Position.LEFT_FRONT elif purge_location == Location.REAR: @@ -664,7 +664,7 @@ class PurgeLinesAndUnload(Script): # Travel to the purge start purge_str += f"G0 F{self.speed_travel} Y{self.machine_back - self.border_distance} Z2; Ortho Move to back\n" purge_str += f"G0 X{self.machine_right - 10} Z2 ; Ortho Move to start\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" if self.prime_blob_enable: purge_str += adjust_for_prime_blob_gcode(self.retract_speed, self.retract_dist) # Purge two lines @@ -675,7 +675,7 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round(purge_volume * 2 - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z8 ; Move Up\nG4 S1 ; Wait for 1 second\n" # Wipe - purge_str += f"G0 F{self.print_speed} X{self.machine_right - 20} Y{self.machine_back - 3 - self.border_distance} Z0.3 ; Slide over and down\n" + purge_str += f"G0 F{self.print_speed} X{self.machine_right - 20} Y{self.machine_back - 3 - self.border_distance} Z{self.purge_line_hgt} ; Slide over and down\n" purge_str += f"G0 X{self.machine_right - 35} Y{self.machine_back - 3 - self.border_distance} ; Wipe\n" self.end_purge_location = Position.RIGHT_REAR # Elliptic printers with Origin at Center @@ -689,7 +689,7 @@ class PurgeLinesAndUnload(Script): if purge_location == Location.LEFT: # Travel to the purge start purge_str += f"G0 F{self.speed_travel} X-{round(radius_1 * .707, 2)} Y-{round(radius_1 * .707, 2)} ; Travel\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" # Purge two arcs purge_str += f"G2 F{self.print_speed} X-{round(radius_1 * .707, 2)} Y{round(radius_1 * .707, 2)} I{round(radius_1 * .707, 2)} J{round(radius_1 * .707, 2)} E{purge_volume} ; First Arc\n" purge_str += f"G0 X-{round((radius_1 - 3) * .707, 2)} Y{round((radius_1 - 3) * .707, 2)} ; Move Over\n" @@ -699,13 +699,13 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round((purge_volume * 2 + 1) - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z5 ; Move Up\nG4 S1 ; Wait 1 Second\n" # Wipe - purge_str += f"G0 F{self.print_speed} X-{round((radius_1 - 3) * .707 - 15, 2)} Z0.3 ; Slide Over\n" + purge_str += f"G0 F{self.print_speed} X-{round((radius_1 - 3) * .707 - 15, 2)} Z{self.purge_line_hgt} ; Slide Over\n" purge_str += f"G0 F{self.print_speed} X-{round((radius_1 - 3) * .707, 2)} ; Wipe\n" self.end_purge_location = Position.LEFT_FRONT elif purge_location == Location.RIGHT: # Travel to the purge start purge_str += f"G0 F{self.speed_travel} X{round(radius_1 * .707, 2)} Y-{round(radius_1 * .707, 2)} ; Travel\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" # Purge two arcs purge_str += f"G3 F{self.print_speed} X{round(radius_1 * .707, 2)} Y{round(radius_1 * .707, 2)} I-{round(radius_1 * .707, 2)} J{round(radius_1 * .707, 2)} E{purge_volume} ; First Arc\n" purge_str += f"G0 X{round((radius_1 - 3) * .707, 2)} Y{round((radius_1 - 3) * .707, 2)} ; Move Over\n" @@ -715,13 +715,13 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round((purge_volume * 2 + 1) - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z5 ; Move Up\nG4 S1 ; Wait 1 Second\n" # Wipe - purge_str += f"G0 F{self.print_speed} X{round((radius_1 - 3) * .707 - 15, 2)} Z0.3 ; Slide Over\n" + purge_str += f"G0 F{self.print_speed} X{round((radius_1 - 3) * .707 - 15, 2)} Z{self.purge_line_hgt} ; Slide Over\n" purge_str += f"G0 F{self.print_speed} X{round((radius_1 - 3) * .707, 2)} ; Wipe\n" self.end_purge_location = Position.RIGHT_REAR elif purge_location == Location.FRONT: # Travel to the purge start purge_str += f"G0 F{self.speed_travel} X-{round(radius_1 * .707, 2)} Y-{round(radius_1 * .707, 2)} ; Travel\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" # Purge two arcs purge_str += f"G3 F{self.print_speed} X{round(radius_1 * .707, 2)} Y-{round(radius_1 * .707, 2)} I{round(radius_1 * .707, 2)} J{round(radius_1 * .707, 2)} E{purge_volume} ; First Arc\n" purge_str += f"G0 X{round((radius_1 - 3) * .707, 2)} Y-{round((radius_1 - 3) * .707, 2)} ; Move Over\n" @@ -731,13 +731,13 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round((purge_volume * 2 + 1) - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z5 ; Move Up\nG4 S1 ; Wait 1 Second\n" # Wipe - purge_str += f"G0 F{self.print_speed} Y-{round((radius_1 - 3) * .707 - 15, 2)} Z0.3 ; Slide Over\n" + purge_str += f"G0 F{self.print_speed} Y-{round((radius_1 - 3) * .707 - 15, 2)} Z{self.purge_line_hgt} ; Slide Over\n" purge_str += f"G0 F{self.print_speed} Y-{round((radius_1 - 3) * .707, 2)} ; Wipe\n" self.end_purge_location = Position.LEFT_FRONT elif purge_location == Location.REAR: # Travel to the purge start purge_str += f"G0 F{self.speed_travel} X{round(radius_1 * .707, 2)} Y{round(radius_1 * .707, 2)} ; Travel\n" - purge_str += f"G0 F600 Z0.3 ; Move down\n" + purge_str += f"G0 F600 Z{self.purge_line_hgt} ; Move down\n" # Purge two arcs purge_str += f"G3 F{self.print_speed} X-{round(radius_1 * .707, 2)} Y{round(radius_1 * .707, 2)} I-{round(radius_1 * .707, 2)} J-{round(radius_1 * .707, 2)} E{purge_volume} ; First Arc\n" purge_str += f"G0 X-{round((radius_1 - 3) * .707, 2)} Y{round((radius_1 - 3) * .707, 2)} ; Move Over\n" @@ -747,7 +747,7 @@ class PurgeLinesAndUnload(Script): purge_str += f"G1 F{int(self.retract_speed)} E{round((purge_volume * 2 + 1) - self.retract_dist, 5)} ; Retract\n" if self.retraction_enable else "" purge_str += "G0 F600 Z5\nG4 S1 ; Wait 1 Second\n" # Wipe - purge_str += f"G0 F{self.print_speed} Y{round((radius_1 - 3) * .707 - 15, 2)} Z0.3 ; Slide Over\n" + purge_str += f"G0 F{self.print_speed} Y{round((radius_1 - 3) * .707 - 15, 2)} Z{self.purge_line_hgt} ; Slide Over\n" purge_str += f"G0 F{self.print_speed} Y{round((radius_1 - 3) * .707, 2)} ; Wipe\n" self.end_purge_location = Position.RIGHT_REAR