Merge branch 'main' into fix_end_gcode_typo
Some checks failed
printer-linter-format / Printer linter auto format (push) Has been cancelled
|
|
@ -45,10 +45,3 @@ jobs:
|
|||
with:
|
||||
name: printer-linter-result
|
||||
path: printer-linter-result/
|
||||
|
||||
- name: Run clang-tidy-pr-comments action
|
||||
uses: platisd/clang-tidy-pr-comments@v1.8.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clang_tidy_fixes: result.yml
|
||||
request_changes: true
|
||||
|
|
|
|||
3
.github/workflows/printer-linter-pr-post.yml
vendored
|
|
@ -103,9 +103,10 @@ jobs:
|
|||
body-path: 'printer-linter-result/comment.md'
|
||||
|
||||
- name: Run clang-tidy-pr-comments action
|
||||
uses: platisd/clang-tidy-pr-comments@v1
|
||||
uses: platisd/clang-tidy-pr-comments@v1.8.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
clang_tidy_fixes: printer-linter-result/fixes.yml
|
||||
pull_request_id: ${{ env.PR_ID }}
|
||||
request_changes: true
|
||||
auto_resolve_conversations: true
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ class FormatMaps:
|
|||
"abs-cf10": {"name": "ABS-CF", "guid": "495a0ce5-9daf-4a16-b7b2-06856d82394d"},
|
||||
"abs-wss1": {"name": "ABS-R", "guid": "88c8919c-6a09-471a-b7b6-e801263d862d"},
|
||||
"asa": {"name": "ASA", "guid": "f79bc612-21eb-482e-ad6c-87d75bdde066"},
|
||||
"nylon": {"name": "Nylon", "guid": "9475b03d-fd19-48a2-b7b5-be1fb46abb02"},
|
||||
"nylon12-cf": {"name": "Nylon 12 CF", "guid": "3c6f2877-71cc-4760-84e6-4b89ab243e3b"},
|
||||
"nylon-cf": {"name": "Nylon CF", "guid": "17abb865-ca73-4ccd-aeda-38e294c9c60b"},
|
||||
"pet": {"name": "PETG", "guid": "2d004bbd-d1bb-47f8-beac-b066702d5273"},
|
||||
|
|
|
|||
|
|
@ -288,9 +288,11 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
def postFormWithParts(self, target: str, parts: List[QHttpPart],
|
||||
on_finished: Optional[Callable[[QNetworkReply], None]],
|
||||
on_progress: Optional[Callable[[int, int], None]] = None) -> QNetworkReply:
|
||||
on_progress: Optional[Callable[[int, int], None]] = None,
|
||||
request: Optional[QNetworkRequest] = None) -> QNetworkReply:
|
||||
self._validateManager()
|
||||
request = self._createEmptyRequest(target, content_type=None)
|
||||
if request is None:
|
||||
request = self._createEmptyRequest(target, content_type=None)
|
||||
multi_post_part = QHttpMultiPart(QHttpMultiPart.ContentType.FormDataType)
|
||||
for part in parts:
|
||||
multi_post_part.append(part)
|
||||
|
|
|
|||
|
|
@ -158,12 +158,10 @@ RmDir /r /REBOOTOK "$INSTDIR"
|
|||
|
||||
!ifdef REG_START_MENU
|
||||
Delete "$SMPROGRAMS\${APP_NAME}.lnk"
|
||||
Delete "$SMPROGRAMS\Uninstall ${APP_NAME}.lnk"
|
||||
!endif
|
||||
|
||||
!ifndef REG_START_MENU
|
||||
Delete "$SMPROGRAMS\${APP_NAME}.lnk"
|
||||
Delete "$SMPROGRAMS\Uninstall ${APP_NAME}.lnk"
|
||||
!endif
|
||||
|
||||
!insertmacro APP_UNASSOCIATE "stl" "Cura.model"
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
# Copyright (c) 2025 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import math
|
||||
|
||||
from enum import IntEnum
|
||||
import numpy
|
||||
from PyQt6.QtCore import Qt, QObject, pyqtEnum
|
||||
from PyQt6.QtGui import QImage, QPainter, QColor, QPen
|
||||
from PyQt6 import QtWidgets
|
||||
from typing import cast, Dict, List, Optional, Tuple
|
||||
|
||||
from numpy import ndarray
|
||||
from PyQt6.QtCore import Qt, QObject, pyqtEnum, QPointF
|
||||
from PyQt6.QtGui import QImage, QPainter, QPen, QBrush, QPolygonF
|
||||
from typing import cast, Optional, Tuple, List
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Event import Event, MouseEvent, KeyEvent
|
||||
from UM.Event import Event, MouseEvent
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.AxisAlignedBox2D import AxisAlignedBox2D
|
||||
from UM.Math.Polygon import Polygon
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Scene.Camera import Camera
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Tool import Tool
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PickingPass import PickingPass
|
||||
|
|
@ -58,28 +59,47 @@ class PaintTool(Tool):
|
|||
self._mesh_transformed_cache = None
|
||||
self._cache_dirty: bool = True
|
||||
|
||||
self._brush_size: int = 200
|
||||
self._brush_size: int = 10
|
||||
self._brush_color: str = "preferred"
|
||||
self._brush_extruder: int = 0
|
||||
self._brush_shape: PaintTool.Brush.Shape = PaintTool.Brush.Shape.CIRCLE
|
||||
self._brush_pen: QPen = self._createBrushPen()
|
||||
|
||||
self._mouse_held: bool = False
|
||||
|
||||
self._last_text_coords: Optional[numpy.ndarray] = None
|
||||
self._last_mouse_coords: Optional[Tuple[int, int]] = None
|
||||
self._last_face_id: Optional[int] = None
|
||||
self._last_world_coords: Optional[numpy.ndarray] = None
|
||||
|
||||
self._state: PaintTool.Paint.State = PaintTool.Paint.State.MULTIPLE_SELECTION
|
||||
self._prepare_texture_job: Optional[PrepareTextureJob] = None
|
||||
|
||||
self.setExposedProperties("PaintType", "BrushSize", "BrushColor", "BrushShape", "State", "CanUndo", "CanRedo")
|
||||
self.setExposedProperties("PaintType", "BrushSize", "BrushColor", "BrushShape", "BrushExtruder", "State", "CanUndo", "CanRedo")
|
||||
|
||||
self._controller.activeViewChanged.connect(self._updateIgnoreUnselectedObjects)
|
||||
self._controller.activeToolChanged.connect(self._updateState)
|
||||
|
||||
self._camera: Optional[Camera] = None
|
||||
self._cam_pos: numpy.ndarray = numpy.array([0.0, 0.0, 0.0])
|
||||
self._cam_norm: numpy.ndarray = numpy.array([0.0, 0.0, 1.0])
|
||||
self._cam_axis_q: numpy.ndarray = numpy.array([1.0, 0.0, 0.0])
|
||||
self._cam_axis_r: numpy.ndarray = numpy.array([0.0, -1.0, 0.0])
|
||||
|
||||
def _updateCamera(self, *args) -> None:
|
||||
if self._camera is None:
|
||||
self._camera = Application.getInstance().getController().getScene().getActiveCamera()
|
||||
self._camera.transformationChanged.connect(self._updateCamera)
|
||||
self._cam_pos = self._camera.getPosition().getData()
|
||||
cam_ray = self._camera.getRay(0, 0)
|
||||
self._cam_norm = cam_ray.direction.getData()
|
||||
self._cam_norm /= -numpy.linalg.norm(self._cam_norm)
|
||||
axis_up = numpy.array([0.0, -1.0, 0.0]) if abs(self._cam_norm[1]) < abs(self._cam_norm[2]) else numpy.array([0.0, 0.0, 1.0])
|
||||
self._cam_axis_q = numpy.cross(self._cam_norm, axis_up)
|
||||
self._cam_axis_q /= numpy.linalg.norm(self._cam_axis_q)
|
||||
self._cam_axis_r = numpy.cross(self._cam_axis_q, self._cam_norm)
|
||||
self._cam_axis_r /= numpy.linalg.norm(self._cam_axis_r)
|
||||
|
||||
def _createBrushPen(self) -> QPen:
|
||||
pen = QPen()
|
||||
pen.setWidth(self._brush_size)
|
||||
pen.setWidth(2)
|
||||
pen.setColor(Qt.GlobalColor.white)
|
||||
|
||||
match self._brush_shape:
|
||||
|
|
@ -87,29 +107,12 @@ class PaintTool(Tool):
|
|||
pen.setCapStyle(Qt.PenCapStyle.SquareCap)
|
||||
case PaintTool.Brush.Shape.CIRCLE:
|
||||
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
|
||||
case _:
|
||||
Logger.error(f"Unknown brush shape '{self._brush_shape}', painting may not work.")
|
||||
return pen
|
||||
|
||||
def _createStrokeImage(self, x0: float, y0: float, x1: float, y1: float) -> Tuple[QImage, Tuple[int, int]]:
|
||||
xdiff = int(x1 - x0)
|
||||
ydiff = int(y1 - y0)
|
||||
|
||||
half_brush_size = self._brush_size // 2
|
||||
start_x = int(min(x0, x1) - half_brush_size)
|
||||
start_y = int(min(y0, y1) - half_brush_size)
|
||||
|
||||
stroke_image = QImage(abs(xdiff) + self._brush_size, abs(ydiff) + self._brush_size, QImage.Format.Format_RGB32)
|
||||
stroke_image.fill(0)
|
||||
|
||||
painter = QPainter(stroke_image)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
|
||||
painter.setPen(self._brush_pen)
|
||||
if xdiff == 0 and ydiff == 0:
|
||||
painter.drawPoint(int(x0 - start_x), int(y0 - start_y))
|
||||
else:
|
||||
painter.drawLine(int(x0 - start_x), int(y0 - start_y), int(x1 - start_x), int(y1 - start_y))
|
||||
painter.end()
|
||||
|
||||
return stroke_image, (start_x, start_y)
|
||||
def _createStrokeImage(self, polys: List[Polygon]) -> Tuple[QImage, Tuple[int, int]]:
|
||||
return PaintTool._rasterizePolygons(polys, self._brush_pen, QBrush(self._brush_pen.color()))
|
||||
|
||||
def getPaintType(self) -> str:
|
||||
return self._view.getPaintType()
|
||||
|
|
@ -140,6 +143,14 @@ class PaintTool(Tool):
|
|||
self._brush_color = brush_color
|
||||
self.propertyChanged.emit()
|
||||
|
||||
def getBrushExtruder(self) -> int:
|
||||
return self._brush_extruder
|
||||
|
||||
def setBrushExtruder(self, brush_extruder: int) -> None:
|
||||
if brush_extruder != self._brush_extruder:
|
||||
self._brush_extruder = brush_extruder
|
||||
self.propertyChanged.emit()
|
||||
|
||||
def getBrushShape(self) -> int:
|
||||
return self._brush_shape
|
||||
|
||||
|
|
@ -152,15 +163,15 @@ class PaintTool(Tool):
|
|||
def getCanUndo(self) -> bool:
|
||||
return self._view.canUndo()
|
||||
|
||||
def getCanRedo(self) -> bool:
|
||||
return self._view.canRedo()
|
||||
|
||||
def getState(self) -> int:
|
||||
return self._state
|
||||
|
||||
def _onCanUndoChanged(self):
|
||||
self.propertyChanged.emit()
|
||||
|
||||
def getCanRedo(self) -> bool:
|
||||
return self._view.canRedo()
|
||||
|
||||
def _onCanRedoChanged(self):
|
||||
self.propertyChanged.emit()
|
||||
|
||||
|
|
@ -176,7 +187,7 @@ class PaintTool(Tool):
|
|||
width, height = self._view.getUvTexDimensions()
|
||||
clear_image = QImage(width, height, QImage.Format.Format_RGB32)
|
||||
clear_image.fill(Qt.GlobalColor.white)
|
||||
self._view.addStroke(clear_image, 0, 0, "none", False)
|
||||
self._view.addStroke(clear_image, 0, 0, "none" if self.getPaintType() != "extruder" else "0", False)
|
||||
|
||||
self._updateScene()
|
||||
|
||||
|
|
@ -217,61 +228,127 @@ class PaintTool(Tool):
|
|||
def _nodeTransformChanged(self, *args) -> None:
|
||||
self._cache_dirty = True
|
||||
|
||||
def _getTexCoordsFromClick(self, node: SceneNode, x: float, y: float) -> Tuple[int, Optional[numpy.ndarray]]:
|
||||
face_id = self._faces_selection_pass.getFaceIdAtPosition(x, y)
|
||||
if face_id < 0 or face_id >= node.getMeshData().getFaceCount():
|
||||
return face_id, None
|
||||
@staticmethod
|
||||
def _getBarycentricCoordinates(points: numpy.array, triangle: numpy.array) -> Optional[numpy.array]:
|
||||
v0 = triangle[1] - triangle[0]
|
||||
v1 = triangle[2] - triangle[0]
|
||||
v2 = points - triangle[0]
|
||||
|
||||
pt = self._picking_pass.getPickedPosition(x, y).getData()
|
||||
d00 = numpy.sum(v0 * v0, axis=0)
|
||||
d01 = numpy.sum(v0 * v1, axis=0)
|
||||
d11 = numpy.sum(v1 * v1, axis=0)
|
||||
d20 = numpy.sum(v2 * v0, axis=1)
|
||||
d21 = numpy.sum(v2 * v1, axis=1)
|
||||
|
||||
va, vb, vc = self._mesh_transformed_cache.getFaceNodes(face_id)
|
||||
denominator = d00 * d11 - d01 ** 2
|
||||
|
||||
face_uv_coordinates = node.getMeshData().getFaceUvCoords(face_id)
|
||||
if face_uv_coordinates is None:
|
||||
return face_id, None
|
||||
ta, tb, tc = face_uv_coordinates
|
||||
if denominator < 1e-6: # Degenerate triangle
|
||||
return None
|
||||
|
||||
# 'Weight' of each vertex that would produce point pt, so we can generate the texture coordinates from the uv ones of the vertices.
|
||||
# See (also) https://mathworld.wolfram.com/BarycentricCoordinates.html
|
||||
wa = PaintTool._get_intersect_ratio_via_pt(va, pt, vb, vc)
|
||||
wb = PaintTool._get_intersect_ratio_via_pt(vb, pt, vc, va)
|
||||
wc = PaintTool._get_intersect_ratio_via_pt(vc, pt, va, vb)
|
||||
wt = wa + wb + wc
|
||||
if wt == 0:
|
||||
return face_id, None
|
||||
wa /= wt
|
||||
wb /= wt
|
||||
wc /= wt
|
||||
texcoords = wa * ta + wb * tb + wc * tc
|
||||
return face_id, texcoords
|
||||
v = (d11 * d20 - d01 * d21) / denominator
|
||||
w = (d00 * d21 - d01 * d20) / denominator
|
||||
u = 1 - v - w
|
||||
|
||||
def _iteratateSplitSubstroke(self, node, substrokes,
|
||||
info_a: Tuple[Tuple[float, float], Tuple[int, Optional[numpy.ndarray]]],
|
||||
info_b: Tuple[Tuple[float, float], Tuple[int, Optional[numpy.ndarray]]]) -> None:
|
||||
click_a, (face_a, texcoords_a) = info_a
|
||||
click_b, (face_b, texcoords_b) = info_b
|
||||
return numpy.column_stack((u, v, w))
|
||||
|
||||
if (abs(click_a[0] - click_b[0]) < 0.0001 and abs(click_a[1] - click_b[1]) < 0.0001) or (face_a < 0 and face_b < 0):
|
||||
return
|
||||
if face_b < 0 or face_a == face_b:
|
||||
substrokes.append((self._last_text_coords, texcoords_a))
|
||||
return
|
||||
if face_a < 0:
|
||||
substrokes.append((self._last_text_coords, texcoords_b))
|
||||
return
|
||||
def _getStrokePolygon(self, stroke_a: numpy.ndarray, stroke_b: numpy.ndarray) -> Polygon:
|
||||
shape = None
|
||||
side = self._brush_size
|
||||
match self._brush_shape:
|
||||
case PaintTool.Brush.Shape.SQUARE:
|
||||
shape = Polygon([(side, side), (-side, side), (-side, -side), (side, -side)])
|
||||
case PaintTool.Brush.Shape.CIRCLE:
|
||||
shape = Polygon.approximatedCircle(side, 32)
|
||||
case _:
|
||||
Logger.error(f"Unknown brush shape '{self._brush_shape}'.")
|
||||
if shape is None:
|
||||
return Polygon()
|
||||
return shape.translate(stroke_a[0], stroke_a[1]).unionConvexHulls(shape.translate(stroke_b[0], stroke_b[1]))
|
||||
|
||||
mouse_mid = (click_a[0] + click_b[0]) / 2.0, (click_a[1] + click_b[1]) / 2.0
|
||||
face_mid, texcoords_mid = self._getTexCoordsFromClick(node, mouse_mid[0], mouse_mid[1])
|
||||
mid_struct = (mouse_mid, (face_mid, texcoords_mid))
|
||||
if face_mid == face_a:
|
||||
substrokes.append((texcoords_a, texcoords_mid))
|
||||
self._iteratateSplitSubstroke(node, substrokes, mid_struct, info_b)
|
||||
elif face_mid == face_b:
|
||||
substrokes.append((texcoords_mid, texcoords_b))
|
||||
self._iteratateSplitSubstroke(node, substrokes, info_a, mid_struct)
|
||||
else:
|
||||
self._iteratateSplitSubstroke(node, substrokes, mid_struct, info_b)
|
||||
self._iteratateSplitSubstroke(node, substrokes, info_a, mid_struct)
|
||||
@staticmethod
|
||||
def _rasterizePolygons(polygons: List[Polygon], pen: QPen, brush: QBrush) -> Tuple[QImage, Tuple[int, int]]:
|
||||
if not polygons:
|
||||
return QImage(), (0, 0)
|
||||
|
||||
bounding_box = polygons[0].getBoundingBox()
|
||||
for polygon in polygons[1:]:
|
||||
bounding_box += polygon.getBoundingBox()
|
||||
|
||||
bounding_box = AxisAlignedBox2D(numpy.array([math.floor(bounding_box.left), math.floor(bounding_box.top)]),
|
||||
numpy.array([math.ceil(bounding_box.right), math.ceil(bounding_box.bottom)]))
|
||||
|
||||
# Use RGB32 which is more optimized for drawing to
|
||||
image = QImage(int(bounding_box.width), int(bounding_box.height), QImage.Format.Format_RGB32)
|
||||
image.fill(0)
|
||||
|
||||
painter = QPainter(image)
|
||||
painter.translate(-bounding_box.left, -bounding_box.bottom)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(brush)
|
||||
|
||||
for polygon in polygons:
|
||||
painter.drawPolygon(QPolygonF([QPointF(point[0], point[1]) for point in polygon]))
|
||||
|
||||
painter.end()
|
||||
|
||||
return image, (int(bounding_box.left), int(bounding_box.bottom))
|
||||
|
||||
# NOTE: Currently, it's unclear how well this would work for non-convex brush-shapes.
|
||||
def _getUvAreasForStroke(self, world_coords_a: numpy.ndarray, world_coords_b: numpy.ndarray) -> List[Polygon]:
|
||||
""" Fetches all texture-coordinate areas within the provided stroke on the mesh.
|
||||
|
||||
Calculates intersections of the stroke with the surface of the geometry and maps them to UV-space polygons.
|
||||
|
||||
:param face_id_a: ID of the face where the stroke starts.
|
||||
:param face_id_b: ID of the face where the stroke ends.
|
||||
:param world_coords_a: 3D ('world') coordinates corresponding to the starting stroke point.
|
||||
:param world_coords_b: 3D ('world') coordinates corresponding to the ending stroke point.
|
||||
:return: A list of UV-mapped polygons representing areas intersected by the stroke on the node's mesh surface.
|
||||
"""
|
||||
|
||||
def get_projected_on_plane(pt: numpy.ndarray) -> numpy.ndarray:
|
||||
return numpy.array([*self._camera.projectToViewport(Vector(*pt))], dtype=numpy.float32)
|
||||
|
||||
def get_projected_on_viewport_image(pt: numpy) -> numpy.ndarray:
|
||||
return numpy.array([pt[0] + self._camera.getViewportWidth() / 2.0,
|
||||
self._camera.getViewportHeight() - (pt[1] + self._camera.getViewportHeight() / 2.0)],
|
||||
dtype=numpy.float32)
|
||||
|
||||
stroke_poly = self._getStrokePolygon(get_projected_on_plane(world_coords_a), get_projected_on_plane(world_coords_b))
|
||||
stroke_poly_viewport = Polygon([get_projected_on_viewport_image(point) for point in stroke_poly])
|
||||
|
||||
faces_image, (faces_x, faces_y) = PaintTool._rasterizePolygons([stroke_poly_viewport],
|
||||
QPen(Qt.PenStyle.NoPen),
|
||||
QBrush(Qt.GlobalColor.white))
|
||||
faces = self._faces_selection_pass.getFacesIdsUnderMask(faces_image, faces_x, faces_y)
|
||||
|
||||
texture_dimensions = numpy.array(list(self._view.getUvTexDimensions()))
|
||||
|
||||
res = []
|
||||
for face in faces:
|
||||
_, fnorm = self._mesh_transformed_cache.getFacePlane(face)
|
||||
if numpy.dot(fnorm, self._cam_norm) < 0: # <- facing away from the viewer
|
||||
continue
|
||||
|
||||
va, vb, vc = self._mesh_transformed_cache.getFaceNodes(face)
|
||||
stroke_tri = Polygon([
|
||||
get_projected_on_plane(va),
|
||||
get_projected_on_plane(vb),
|
||||
get_projected_on_plane(vc)])
|
||||
face_uv_coordinates = self._node_cache.getMeshData().getFaceUvCoords(face)
|
||||
if face_uv_coordinates is None:
|
||||
continue
|
||||
ta, tb, tc = face_uv_coordinates
|
||||
original_uv_poly = numpy.array([ta, tb, tc])
|
||||
uv_area = stroke_poly.intersection(stroke_tri)
|
||||
|
||||
if uv_area.isValid():
|
||||
uv_area_barycentric = PaintTool._getBarycentricCoordinates(uv_area.getPoints(), stroke_tri.getPoints())
|
||||
if uv_area_barycentric is not None:
|
||||
res.append(Polygon((uv_area_barycentric @ original_uv_poly) * texture_dimensions))
|
||||
|
||||
return res
|
||||
|
||||
def event(self, event: Event) -> bool:
|
||||
"""Handle mouse and keyboard events.
|
||||
|
|
@ -282,7 +359,6 @@ class PaintTool(Tool):
|
|||
"""
|
||||
super().event(event)
|
||||
|
||||
controller = Application.getInstance().getController()
|
||||
node = Selection.getSelectedObject(0)
|
||||
if node is None:
|
||||
return False
|
||||
|
|
@ -301,18 +377,19 @@ class PaintTool(Tool):
|
|||
if MouseEvent.LeftButton not in cast(MouseEvent, event).buttons:
|
||||
return False
|
||||
self._mouse_held = False
|
||||
self._last_text_coords = None
|
||||
self._last_mouse_coords = None
|
||||
self._last_face_id = None
|
||||
self._last_world_coords = None
|
||||
return True
|
||||
|
||||
is_moved = event.type == Event.MouseMoveEvent
|
||||
is_pressed = event.type == Event.MousePressEvent
|
||||
if (is_moved or is_pressed) and self._controller.getToolsEnabled():
|
||||
if is_moved and not self._mouse_held:
|
||||
return False
|
||||
|
||||
mouse_evt = cast(MouseEvent, event)
|
||||
|
||||
if not self._picking_pass:
|
||||
self._picking_pass = CuraApplication.getInstance().getRenderer().getRenderPass("picking_selected")
|
||||
if not self._picking_pass:
|
||||
return False
|
||||
|
||||
if is_pressed:
|
||||
if MouseEvent.LeftButton not in mouse_evt.buttons:
|
||||
return False
|
||||
|
|
@ -324,13 +401,9 @@ class PaintTool(Tool):
|
|||
if not self._faces_selection_pass:
|
||||
return False
|
||||
|
||||
if not self._picking_pass:
|
||||
self._picking_pass = CuraApplication.getInstance().getRenderer().getRenderPass("picking_selected")
|
||||
if not self._picking_pass:
|
||||
return False
|
||||
|
||||
camera = self._controller.getScene().getActiveCamera()
|
||||
if not camera:
|
||||
if self._camera is None:
|
||||
self._updateCamera()
|
||||
if self._camera is None:
|
||||
return False
|
||||
|
||||
if node != self._node_cache:
|
||||
|
|
@ -345,35 +418,37 @@ class PaintTool(Tool):
|
|||
if not self._mesh_transformed_cache:
|
||||
return False
|
||||
|
||||
face_id, texcoords = self._getTexCoordsFromClick(node, mouse_evt.x, mouse_evt.y)
|
||||
if texcoords is None:
|
||||
face_id = self._faces_selection_pass.getFaceIdAtPosition(mouse_evt.x, mouse_evt.y)
|
||||
if face_id < 0 or face_id >= self._mesh_transformed_cache.getFaceCount():
|
||||
if self._view.clearCursorStroke():
|
||||
self._updateScene(node)
|
||||
return True
|
||||
return False
|
||||
if self._last_text_coords is None:
|
||||
self._last_text_coords = texcoords
|
||||
self._last_mouse_coords = (mouse_evt.x, mouse_evt.y)
|
||||
self._last_face_id = face_id
|
||||
|
||||
substrokes = []
|
||||
if face_id == self._last_face_id:
|
||||
substrokes.append((self._last_text_coords, texcoords))
|
||||
else:
|
||||
self._iteratateSplitSubstroke(node, substrokes,
|
||||
(self._last_mouse_coords, (self._last_face_id, self._last_text_coords)),
|
||||
((mouse_evt.x, mouse_evt.y), (face_id, texcoords)))
|
||||
world_coords_vec = self._picking_pass.getPickedPosition(mouse_evt.x, mouse_evt.y)
|
||||
world_coords = world_coords_vec.getData()
|
||||
if self._last_world_coords is None:
|
||||
self._last_world_coords = world_coords
|
||||
|
||||
w, h = self._view.getUvTexDimensions()
|
||||
for start_coords, end_coords in substrokes:
|
||||
sub_image, (start_x, start_y) = self._createStrokeImage(
|
||||
start_coords[0] * w,
|
||||
start_coords[1] * h,
|
||||
end_coords[0] * w,
|
||||
end_coords[1] * h
|
||||
)
|
||||
self._view.addStroke(sub_image, start_x, start_y, self._brush_color, is_moved)
|
||||
try:
|
||||
brush_color = self._brush_color if self.getPaintType() != "extruder" else str(self._brush_extruder)
|
||||
uv_areas_cursor = self._getUvAreasForStroke(world_coords, world_coords)
|
||||
if len(uv_areas_cursor) > 0:
|
||||
cursor_stroke_img, (start_x, start_y) = self._createStrokeImage(uv_areas_cursor)
|
||||
self._view.setCursorStroke(cursor_stroke_img, start_x, start_y, brush_color)
|
||||
else:
|
||||
self._view.clearCursorStroke()
|
||||
|
||||
self._last_text_coords = texcoords
|
||||
self._last_mouse_coords = (mouse_evt.x, mouse_evt.y)
|
||||
self._last_face_id = face_id
|
||||
if self._mouse_held:
|
||||
uv_areas = self._getUvAreasForStroke(self._last_world_coords, world_coords)
|
||||
if len(uv_areas) == 0:
|
||||
return False
|
||||
stroke_img, (start_x, start_y) = self._createStrokeImage(uv_areas)
|
||||
self._view.addStroke(stroke_img, start_x, start_y, brush_color, is_moved)
|
||||
except:
|
||||
Logger.logException("e", "Error when adding paint stroke")
|
||||
|
||||
self._last_world_coords = world_coords
|
||||
self._updateScene(node)
|
||||
return True
|
||||
|
||||
|
|
@ -421,4 +496,4 @@ class PaintTool(Tool):
|
|||
def _updateIgnoreUnselectedObjects(self):
|
||||
ignore_unselected_objects = self._controller.getActiveView().name == "PaintTool"
|
||||
CuraApplication.getInstance().getRenderer().getRenderPass("selection").setIgnoreUnselectedObjects(ignore_unselected_objects)
|
||||
CuraApplication.getInstance().getRenderer().getRenderPass("selection_faces").setIgnoreUnselectedObjects(ignore_unselected_objects)
|
||||
CuraApplication.getInstance().getRenderer().getRenderPass("selection_faces").setIgnoreUnselectedObjects(ignore_unselected_objects)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import Cura 1.0 as Cura
|
|||
Item
|
||||
{
|
||||
id: base
|
||||
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
UM.I18nCatalog { id: catalog; name: "cura"}
|
||||
|
|
@ -57,6 +58,14 @@ Item
|
|||
mode: "support"
|
||||
visible: false
|
||||
}
|
||||
|
||||
PaintModeButton
|
||||
{
|
||||
text: catalog.i18nc("@action:button", "Material")
|
||||
icon: "Extruder"
|
||||
tooltipText: catalog.i18nc("@tooltip", "Paint on model to select the material to be used")
|
||||
mode: "extruder"
|
||||
}
|
||||
}
|
||||
|
||||
//Line between the sections.
|
||||
|
|
@ -70,6 +79,7 @@ Item
|
|||
RowLayout
|
||||
{
|
||||
id: rowBrushColor
|
||||
visible: !rowExtruder.visible
|
||||
|
||||
UM.Label
|
||||
{
|
||||
|
|
@ -116,6 +126,30 @@ Item
|
|||
}
|
||||
}
|
||||
|
||||
RowLayout
|
||||
{
|
||||
id: rowExtruder
|
||||
visible: UM.Controller.properties.getValue("PaintType") === "extruder"
|
||||
|
||||
UM.Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Mark as")
|
||||
}
|
||||
|
||||
Repeater
|
||||
{
|
||||
id: repeaterExtruders
|
||||
model: CuraApplication.getExtrudersModel()
|
||||
delegate: Cura.ExtruderButton
|
||||
{
|
||||
extruder: model
|
||||
|
||||
checked: UM.Controller.properties.getValue("BrushExtruder") === model.index
|
||||
onClicked: UM.Controller.setProperty("BrushExtruder", model.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout
|
||||
{
|
||||
id: rowBrushShape
|
||||
|
|
@ -163,8 +197,8 @@ Item
|
|||
width: parent.width
|
||||
indicatorVisible: false
|
||||
|
||||
from: 10
|
||||
to: 1000
|
||||
from: 1
|
||||
to: 100
|
||||
value: UM.Controller.properties.getValue("BrushSize")
|
||||
|
||||
onPressedChanged: function(pressed)
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from PyQt6.QtCore import QRect, pyqtSignal
|
||||
from typing import Optional, Dict
|
||||
|
||||
from PyQt6.QtGui import QImage, QUndoStack
|
||||
from PyQt6.QtCore import QRect, pyqtSignal
|
||||
from PyQt6.QtGui import QImage, QUndoStack, QPainter, QColor
|
||||
from typing import Optional, List, Tuple, Dict
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.BuildVolume import BuildVolume
|
||||
from cura.CuraView import CuraView
|
||||
from cura.Machines.Models.ExtrudersModel import ExtrudersModel
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||
from UM.View.GL.Texture import Texture
|
||||
|
|
@ -36,6 +37,8 @@ class PaintView(CuraView):
|
|||
super().__init__(use_empty_menu_placeholder = True)
|
||||
self._paint_shader: Optional[ShaderProgram] = None
|
||||
self._current_paint_texture: Optional[Texture] = None
|
||||
self._previous_paint_texture_stroke: Optional[QRect] = None
|
||||
self._cursor_texture: Optional[Texture] = None
|
||||
self._current_bits_ranges: tuple[int, int] = (0, 0)
|
||||
self._current_paint_type = ""
|
||||
self._paint_modes: Dict[str, Dict[str, "PaintView.PaintType"]] = {}
|
||||
|
|
@ -49,6 +52,8 @@ class PaintView(CuraView):
|
|||
application.engineCreatedSignal.connect(self._makePaintModes)
|
||||
self._scene = application.getController().getScene()
|
||||
|
||||
self._extruders_model: Optional[ExtrudersModel] = None
|
||||
|
||||
canUndoChanged = pyqtSignal(bool)
|
||||
canRedoChanged = pyqtSignal(bool)
|
||||
|
||||
|
|
@ -59,22 +64,98 @@ class PaintView(CuraView):
|
|||
return self._paint_undo_stack.canRedo()
|
||||
|
||||
def _makePaintModes(self):
|
||||
theme = CuraApplication.getInstance().getTheme()
|
||||
application = CuraApplication.getInstance()
|
||||
|
||||
self._extruders_model = application.getExtrudersModel()
|
||||
self._extruders_model.modelChanged.connect(self._onExtrudersChanged)
|
||||
|
||||
theme = application.getTheme()
|
||||
usual_types = {"none": self.PaintType(Color(*theme.getColor("paint_normal_area").getRgb()), 0),
|
||||
"preferred": self.PaintType(Color(*theme.getColor("paint_preferred_area").getRgb()), 1),
|
||||
"avoid": self.PaintType(Color(*theme.getColor("paint_avoid_area").getRgb()), 2)}
|
||||
self._paint_modes = {
|
||||
"seam": usual_types,
|
||||
"support": usual_types,
|
||||
"extruder": self._makeExtrudersColors(),
|
||||
}
|
||||
|
||||
self._current_paint_type = "seam"
|
||||
|
||||
def _makeExtrudersColors(self) -> Dict[str, "PaintView.PaintType"]:
|
||||
extruders_colors: Dict[str, "PaintView.PaintType"] = {}
|
||||
|
||||
for extruder_item in self._extruders_model.items:
|
||||
if "color" in extruder_item:
|
||||
material_color = extruder_item["color"]
|
||||
else:
|
||||
material_color = self._extruders_model.defaultColors[0]
|
||||
|
||||
index = extruder_item["index"]
|
||||
extruders_colors[str(index)] = self.PaintType(Color(*QColor(material_color).getRgb()), index)
|
||||
|
||||
return extruders_colors
|
||||
|
||||
def _onExtrudersChanged(self) -> None:
|
||||
if self._paint_modes is None:
|
||||
return
|
||||
|
||||
self._paint_modes["extruder"] = self._makeExtrudersColors()
|
||||
|
||||
controller = CuraApplication.getInstance().getController()
|
||||
if controller.getActiveView() != self:
|
||||
return
|
||||
|
||||
selected_objects = Selection.getAllSelectedObjects()
|
||||
if len(selected_objects) != 1:
|
||||
return
|
||||
|
||||
controller.getScene().sceneChanged.emit(selected_objects[0])
|
||||
|
||||
def _checkSetup(self):
|
||||
if not self._paint_shader:
|
||||
shader_filename = os.path.join(PluginRegistry.getInstance().getPluginPath("PaintTool"), "paint.shader")
|
||||
self._paint_shader = OpenGL.getInstance().createShaderProgram(shader_filename)
|
||||
|
||||
def setCursorStroke(self, stroke_mask: QImage, start_x: int, start_y: int, brush_color: str):
|
||||
if self._cursor_texture is None or self._cursor_texture.getImage() is None:
|
||||
return
|
||||
|
||||
self.clearCursorStroke()
|
||||
|
||||
stroke_image = stroke_mask.copy()
|
||||
alpha_mask = stroke_image.convertedTo(QImage.Format.Format_Mono)
|
||||
stroke_image.setAlphaChannel(alpha_mask)
|
||||
|
||||
painter = QPainter(stroke_image)
|
||||
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceAtop)
|
||||
display_color = self._paint_modes[self._current_paint_type][brush_color].display_color
|
||||
paint_color = QColor(*[int(color_part * 255) for color_part in [display_color.r, display_color.g, display_color.b]])
|
||||
paint_color.setAlpha(255)
|
||||
painter.fillRect(0, 0, stroke_mask.width(), stroke_mask.height(), paint_color)
|
||||
|
||||
painter.end()
|
||||
|
||||
self._cursor_texture.setSubImage(stroke_image, start_x, start_y)
|
||||
|
||||
self._previous_paint_texture_stroke = QRect(start_x, start_y, stroke_mask.width(), stroke_mask.height())
|
||||
|
||||
def clearCursorStroke(self) -> bool:
|
||||
if (self._previous_paint_texture_stroke is None or
|
||||
self._cursor_texture is None or self._cursor_texture.getImage() is None):
|
||||
return False
|
||||
|
||||
clear_image = QImage(self._previous_paint_texture_stroke.width(),
|
||||
self._previous_paint_texture_stroke.height(),
|
||||
QImage.Format.Format_ARGB32)
|
||||
clear_image.fill(0)
|
||||
self._cursor_texture.setSubImage(clear_image,
|
||||
self._previous_paint_texture_stroke.x(),
|
||||
self._previous_paint_texture_stroke.y())
|
||||
self._previous_paint_texture_stroke = None
|
||||
|
||||
return True
|
||||
|
||||
def addStroke(self, stroke_mask: QImage, start_x: int, start_y: int, brush_color: str, merge_with_previous: bool) -> None:
|
||||
if self._current_paint_texture is None or self._current_paint_texture.getImage() is None:
|
||||
return
|
||||
|
|
@ -111,7 +192,7 @@ class PaintView(CuraView):
|
|||
def redoStroke(self) -> None:
|
||||
self._paint_undo_stack.redo()
|
||||
|
||||
def getUvTexDimensions(self):
|
||||
def getUvTexDimensions(self) -> Tuple[int, int]:
|
||||
if self._current_paint_texture is not None:
|
||||
return self._current_paint_texture.getWidth(), self._current_paint_texture.getHeight()
|
||||
return 0, 0
|
||||
|
|
@ -121,6 +202,7 @@ class PaintView(CuraView):
|
|||
|
||||
def setPaintType(self, paint_type: str) -> None:
|
||||
self._current_paint_type = paint_type
|
||||
self._prepareDataMapping()
|
||||
|
||||
def _prepareDataMapping(self):
|
||||
node = Selection.getAllSelectedObjects()[0]
|
||||
|
|
@ -162,8 +244,17 @@ class PaintView(CuraView):
|
|||
|
||||
for node in Selection.getAllSelectedObjects():
|
||||
paint_batch.addItem(node.getWorldTransformation(copy=False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix())
|
||||
self._current_paint_texture = node.callDecoration("getPaintTexture")
|
||||
self._paint_shader.setTexture(0, self._current_paint_texture)
|
||||
paint_texture = node.callDecoration("getPaintTexture")
|
||||
if paint_texture != self._current_paint_texture:
|
||||
self._current_paint_texture = paint_texture
|
||||
self._paint_shader.setTexture(0, self._current_paint_texture)
|
||||
|
||||
self._cursor_texture = OpenGL.getInstance().createTexture(paint_texture.getWidth(), paint_texture.getHeight())
|
||||
image = QImage(paint_texture.getWidth(), paint_texture.getHeight(), QImage.Format.Format_ARGB32)
|
||||
image.fill(0)
|
||||
self._cursor_texture.setImage(image)
|
||||
self._paint_shader.setTexture(1, self._cursor_texture)
|
||||
self._previous_paint_texture_stroke = None
|
||||
|
||||
self._paint_shader.setUniformValue("u_bitsRangesStart", self._current_bits_ranges[0])
|
||||
self._paint_shader.setUniformValue("u_bitsRangesEnd", self._current_bits_ranges[1])
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ fragment =
|
|||
uniform highp vec3 u_lightPosition;
|
||||
uniform highp vec3 u_viewPosition;
|
||||
uniform sampler2D u_texture;
|
||||
uniform sampler2D u_texture_cursor;
|
||||
uniform mediump int u_bitsRangesStart;
|
||||
uniform mediump int u_bitsRangesEnd;
|
||||
uniform mediump vec3 u_renderColors[16];
|
||||
|
|
@ -42,24 +43,33 @@ fragment =
|
|||
{
|
||||
mediump vec4 final_color = vec4(0.0);
|
||||
|
||||
/* Ambient Component */
|
||||
final_color += u_ambientColor;
|
||||
|
||||
highp vec3 normal = normalize(v_normal);
|
||||
highp vec3 light_dir = normalize(u_lightPosition - v_vertex);
|
||||
|
||||
/* Diffuse Component */
|
||||
ivec4 texture = ivec4(texture(u_texture, v_uvs) * 255.0);
|
||||
uint color_index = (texture.r << 16) | (texture.g << 8) | texture.b;
|
||||
color_index = (color_index << (32 - 1 - u_bitsRangesEnd)) >> 32 - 1 - (u_bitsRangesEnd - u_bitsRangesStart);
|
||||
vec4 cursor_color = texture(u_texture_cursor, v_uvs);
|
||||
if (cursor_color.a > 0.5)
|
||||
{
|
||||
/* Cursor */
|
||||
final_color.rgb = cursor_color.rgb;
|
||||
highp float n_dot_l = mix(0.7, 1.0, dot(normal, light_dir));
|
||||
final_color = (n_dot_l * final_color);
|
||||
}
|
||||
else
|
||||
{
|
||||
final_color += u_ambientColor;
|
||||
|
||||
vec4 diffuse_color = vec4(u_renderColors[color_index] / 255.0, 1.0);
|
||||
highp float n_dot_l = mix(0.3, 0.7, dot(normal, light_dir));
|
||||
final_color += (n_dot_l * diffuse_color);
|
||||
ivec4 texture_color = ivec4(texture(u_texture, v_uvs) * 255.0);
|
||||
uint color_index = (texture_color.r << 16) | (texture_color.g << 8) | texture_color.b;
|
||||
color_index = (color_index << (32 - 1 - u_bitsRangesEnd)) >> 32 - 1 - (u_bitsRangesEnd - u_bitsRangesStart);
|
||||
|
||||
vec4 diffuse_color = vec4(u_renderColors[color_index] / 255.0, 1.0);
|
||||
highp float n_dot_l = mix(0.3, 0.7, dot(normal, light_dir));
|
||||
final_color += (n_dot_l * diffuse_color);
|
||||
}
|
||||
|
||||
/* Output */
|
||||
final_color.a = 1.0;
|
||||
|
||||
frag_color = final_color;
|
||||
gl_FragColor = final_color;
|
||||
}
|
||||
|
||||
vertex41core =
|
||||
|
|
@ -95,6 +105,7 @@ fragment41core =
|
|||
uniform highp vec3 u_lightPosition;
|
||||
uniform highp vec3 u_viewPosition;
|
||||
uniform sampler2D u_texture;
|
||||
uniform sampler2D u_texture_cursor;
|
||||
uniform mediump int u_bitsRangesStart;
|
||||
uniform mediump int u_bitsRangesEnd;
|
||||
uniform mediump vec3 u_renderColors[16];
|
||||
|
|
@ -108,29 +119,39 @@ fragment41core =
|
|||
{
|
||||
mediump vec4 final_color = vec4(0.0);
|
||||
|
||||
/* Ambient Component */
|
||||
final_color += u_ambientColor;
|
||||
|
||||
highp vec3 normal = normalize(v_normal);
|
||||
highp vec3 light_dir = normalize(u_lightPosition - v_vertex);
|
||||
|
||||
/* Diffuse Component */
|
||||
ivec4 texture = ivec4(texture(u_texture, v_uvs) * 255.0);
|
||||
uint color_index = (texture.r << 16) | (texture.g << 8) | texture.b;
|
||||
color_index = (color_index << (32 - 1 - u_bitsRangesEnd)) >> 32 - 1 - (u_bitsRangesEnd - u_bitsRangesStart);
|
||||
vec4 cursor_color = texture(u_texture_cursor, v_uvs);
|
||||
if (cursor_color.a > 0.5)
|
||||
{
|
||||
/* Cursor */
|
||||
final_color.rgb = cursor_color.rgb;
|
||||
highp float n_dot_l = mix(0.7, 1.0, dot(normal, light_dir));
|
||||
final_color = (n_dot_l * final_color);
|
||||
}
|
||||
else
|
||||
{
|
||||
final_color += u_ambientColor;
|
||||
|
||||
vec4 diffuse_color = vec4(u_renderColors[color_index] / 255.0, 1.0);
|
||||
highp float n_dot_l = mix(0.3, 0.7, dot(normal, light_dir));
|
||||
final_color += (n_dot_l * diffuse_color);
|
||||
ivec4 texture_color = ivec4(texture(u_texture, v_uvs) * 255.0);
|
||||
uint color_index = (texture_color.r << 16) | (texture_color.g << 8) | texture_color.b;
|
||||
color_index = (color_index << (32 - 1 - u_bitsRangesEnd)) >> 32 - 1 - (u_bitsRangesEnd - u_bitsRangesStart);
|
||||
|
||||
vec4 diffuse_color = vec4(u_renderColors[color_index] / 255.0, 1.0);
|
||||
highp float n_dot_l = mix(0.3, 0.7, dot(normal, light_dir));
|
||||
final_color += (n_dot_l * diffuse_color);
|
||||
}
|
||||
|
||||
/* Output */
|
||||
final_color.a = 1.0;
|
||||
|
||||
frag_color = final_color;
|
||||
}
|
||||
|
||||
[defaults]
|
||||
u_ambientColor = [0.3, 0.3, 0.3, 1.0]
|
||||
u_texture = 0
|
||||
u_texture_cursor = 1
|
||||
|
||||
[bindings]
|
||||
u_modelMatrix = model_matrix
|
||||
|
|
|
|||
571
plugins/PostProcessingPlugin/scripts/AnnealingOrDrying.py
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
"""
|
||||
Copyright (c) 2025 GregValiant (Greg Foresi)
|
||||
|
||||
When Annealing:
|
||||
The user may elect to hold the build plate at a temperature for a period of time. When the hold expires, the 'Timed Cooldown' will begin.
|
||||
If there is no 'Hold Time' then the 'Annealing' cooldown will begin when the print ends. In 'Annealing' the bed temperature drops in 3° increments across the time span.
|
||||
G4 commands are used for the cooldown steps.
|
||||
If there is a 'Heated Chamber' then the chamber will start to cool when the bed temperature reaches the chamber temperature.
|
||||
|
||||
When drying filament:
|
||||
The bed must be empty because the printer will auto-home before raising the Z to 'machine_height minus 20mm' and then park the head in the XY.
|
||||
The bed will heat up to the set point.
|
||||
G4 commands are used to keep the machine from turning the bed off until the Drying Time has expired.
|
||||
If you happen to have an enclosure with a fan, the fan can be set up to run during the drying or annealing.
|
||||
|
||||
NOTE: This script uses the G4 Dwell command as a timer. It cannot be canceled from the LCD. If you wish to 'escape' from G4 you might have to cancel the print from the LCD or cycle the printer on and off to reset.
|
||||
"""
|
||||
|
||||
from UM.Application import Application
|
||||
from ..Script import Script
|
||||
from UM.Message import Message
|
||||
|
||||
class AnnealingOrDrying(Script):
|
||||
|
||||
def initialize(self) -> None:
|
||||
super().initialize()
|
||||
# Get the Bed Temperature from Cura
|
||||
self.global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
bed_temp_during_print = str(self.global_stack.getProperty("material_bed_temperature", "value"))
|
||||
self._instance.setProperty("startout_temp", "value", bed_temp_during_print)
|
||||
# Get the Build Volume temperature if there is one
|
||||
heated_build_volume = bool(self.global_stack.getProperty("machine_heated_build_volume", "value"))
|
||||
chamber_fan_nr = self.global_stack.getProperty("build_volume_fan_nr", "value")
|
||||
extruder_count = self.global_stack.getProperty("machine_extruder_count", "value")
|
||||
if heated_build_volume:
|
||||
chamber_temp = self.global_stack.getProperty("build_volume_temperature", "value")
|
||||
self._instance.setProperty("has_build_volume_heater", "value", heated_build_volume)
|
||||
self._instance.setProperty("build_volume_temp", "value", chamber_temp)
|
||||
try:
|
||||
if chamber_fan_nr > 0:
|
||||
self._instance.setProperty("enable_chamber_fan_setting", "value", True)
|
||||
except:
|
||||
pass
|
||||
|
||||
def getSettingDataString(self):
|
||||
return """{
|
||||
"name": "Annealing CoolDown or Filament Drying",
|
||||
"key": "AnnealingOrDrying",
|
||||
"metadata": {},
|
||||
"version": 2,
|
||||
"settings":
|
||||
{
|
||||
"enable_script":
|
||||
{
|
||||
"label": "Enable the Script",
|
||||
"description": "If it isn't enabled it doesn't run.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"enabled": true
|
||||
},
|
||||
"cycle_type":
|
||||
{
|
||||
"label": "Anneal Print or Dry Filament",
|
||||
"description": "Whether to Anneal the Print (by keeping the bed hot for a period of time), or to use the bed as a Filament Dryer. If drying; you will still need to slice a model, but it will not print. The gcode will consist only of a short script to heat the bed, wait for a while, then turn the bed off. The 'Z' will move to the max height and XY park position so the filament can be covered. The 'Hold Time', 'Bed Start Temp' and (if applicable) the 'Chamber Temp' come from these settings rather than from the Cura settings. When annealing; the Timed Cooldown will commence when the print ends.",
|
||||
"type": "enum",
|
||||
"options":
|
||||
{
|
||||
"anneal_cycle": "Anneal Print",
|
||||
"dry_cycle": "Dry Filament"
|
||||
},
|
||||
"default_value": "anneal_cycle",
|
||||
"enabled": true,
|
||||
"enabled": "enable_script"
|
||||
},
|
||||
"heating_zone_selection":
|
||||
{
|
||||
"label": "Hold the Temp for the:",
|
||||
"description": "Select the 'Bed' for just the bed, or 'Bed and Chamber' if you want to include your 'Heated Build Volume'.",
|
||||
"type": "enum",
|
||||
"options":
|
||||
{
|
||||
"bed_only": "Bed",
|
||||
"bed_chamber": "Bed and Chamber"
|
||||
},
|
||||
"default_value": "bed_only",
|
||||
"enabled": "enable_script"
|
||||
},
|
||||
"wait_time":
|
||||
{
|
||||
"label": "Hold Time at Temp(s)",
|
||||
"description": "Hold the bed temp at the 'Bed Start Out Temperature' for this amount of time (in decimal hours). When this time expires then the Annealing cool down will start. This is also the 'Drying Time' used when 'Drying Filament'.",
|
||||
"type": "float",
|
||||
"default_value": 0.0,
|
||||
"unit": "Decimal Hrs ",
|
||||
"enabled": "enable_script and cycle_type == 'anneal_cycle'"
|
||||
},
|
||||
"dry_time":
|
||||
{
|
||||
"label": "Drying Time",
|
||||
"description": "Hold the bed temp at the 'Bed Start Out Temperature' for this amount of time (in decimal hours). When this time expires the bed will shut off.",
|
||||
"type": "float",
|
||||
"default_value": 4.0,
|
||||
"unit": "Decimal Hrs ",
|
||||
"enabled": "enable_script and cycle_type == 'dry_cycle'"
|
||||
},
|
||||
"pause_cmd":
|
||||
{
|
||||
"label": "Pause Cmd for Auto-Home",
|
||||
"description": "Not required when you are paying attention and the bed is empty; ELSE; Enter the pause command to use prior to the Auto-Home command. The pause insures that the user IS paying attention and clears the build plate for Auto-Home. If you leave the box empty then there won't be a pause.",
|
||||
"type": "str",
|
||||
"default_value": "",
|
||||
"enabled": "enable_script and cycle_type == 'dry_cycle'"
|
||||
},
|
||||
"startout_temp":
|
||||
{
|
||||
"label": "Bed Start Out Temperature:",
|
||||
"description": "Enter the temperature to start at. This is typically the bed temperature during the print but can be changed here. This is also the temperature used when drying filament.",
|
||||
"type": "int",
|
||||
"value": 30,
|
||||
"unit": "Degrees ",
|
||||
"minimum_value": 30,
|
||||
"maximum_value": 110,
|
||||
"maximum_value_warning": 100,
|
||||
"enabled": "enable_script"
|
||||
},
|
||||
"lowest_temp":
|
||||
{
|
||||
"label": "Shut-Off Temp:",
|
||||
"description": "Enter the lowest temperature to control the cool down. This is the shut-off temperature for the build plate and (when applicable) the Heated Chamber. The minimum value is 30",
|
||||
"type": "int",
|
||||
"default_value": 30,
|
||||
"unit": "Degrees ",
|
||||
"minimum_value": 30,
|
||||
"enabled": "enable_script and cycle_type == 'anneal_cycle'"
|
||||
},
|
||||
"build_volume_temp":
|
||||
{
|
||||
"label": "Build Volume Temperature:",
|
||||
"description": "Enter the temperature for the Build Volume (Heated Chamber). This is typically the temperature during the print but can be changed here.",
|
||||
"type": "int",
|
||||
"value": 24,
|
||||
"unit": "Degrees ",
|
||||
"minimum_value": 0,
|
||||
"maximum_value": 90,
|
||||
"maximum_value_warning": 75,
|
||||
"enabled": "enable_script and has_build_volume_heater and heating_zone_selection == 'bed_chamber'"
|
||||
},
|
||||
"enable_chamber_fan_setting":
|
||||
{
|
||||
"label": "Hidden Setting",
|
||||
"description": "Enables chamber fan and speed.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": false
|
||||
},
|
||||
"chamber_fan_speed":
|
||||
{
|
||||
"label": "Chamber Fan Speed",
|
||||
"description": "Set to % fan speed. Set to 0 to turn it off.",
|
||||
"type": "int",
|
||||
"default_value": 0,
|
||||
"minimum_value": 0,
|
||||
"maximum_value": 100,
|
||||
"unit": "% ",
|
||||
"enabled": "enable_script and enable_chamber_fan_setting"
|
||||
},
|
||||
"time_span":
|
||||
{
|
||||
"label": "Cool Down Time Span:",
|
||||
"description": "The total amount of time (in decimal hours) to control the cool down. The build plate temperature will be dropped in 3° increments across this time span. 'Cool Down Time' starts at the end of the 'Hold Time' if you entered one.",
|
||||
"type": "float",
|
||||
"default_value": 1.0,
|
||||
"unit": "Decimal Hrs ",
|
||||
"minimum_value_warning": 0.25,
|
||||
"enabled": "enable_script and cycle_type == 'anneal_cycle'"
|
||||
},
|
||||
"park_head":
|
||||
{
|
||||
"label": "Park at MaxX and MaxY",
|
||||
"description": "When unchecked, the park position is X0 Y0. Enable this setting to move the nozzle to the Max X and Max Y to allow access to the print.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "enable_script and cycle_type == 'anneal_cycle'"
|
||||
},
|
||||
"park_max_z":
|
||||
{
|
||||
"label": "Move to MaxZ",
|
||||
"description": "Enable this setting to move the nozzle to 'Machine_Height - 20' to allow the print to be covered.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "enable_script and cycle_type == 'anneal_cycle'"
|
||||
},
|
||||
"beep_when_done":
|
||||
{
|
||||
"label": "Beep when done",
|
||||
"description": "Add an annoying noise when the Cool Down completes.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"enabled": "enable_script"
|
||||
},
|
||||
"beep_duration":
|
||||
{
|
||||
"label": "Beep Duration",
|
||||
"description": "The length of the buzzer sound. Units are in milliseconds so 1000ms = 1 second.",
|
||||
"type": "int",
|
||||
"unit": "milliseconds ",
|
||||
"default_value": 1000,
|
||||
"enabled": "beep_when_done and enable_script"
|
||||
},
|
||||
"add_messages":
|
||||
{
|
||||
"label": "Include M117 and M118 messages",
|
||||
"description": "Add messages to the LCD and any print server.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "enable_script"
|
||||
},
|
||||
"has_build_volume_heater":
|
||||
{
|
||||
"label": "Hidden setting",
|
||||
"description": "Hidden. This setting enables the build volume settings.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
def execute(self, data):
|
||||
# Exit if there is no heated bed.
|
||||
if not bool(self.global_stack.getProperty("machine_heated_bed", "value")):
|
||||
Message(title = "[Anneal or Dry Filament]", text = "The script did not run because Heated Bed is disabled in Machine Settings.").show()
|
||||
return data
|
||||
# Enter a message in the gcode if the script is not enabled.
|
||||
if not bool(self.getSettingValueByKey("enable_script")):
|
||||
data[0] += "; [Anneal or Dry Filament] was not enabled\n"
|
||||
return data
|
||||
lowest_temp = int(self.getSettingValueByKey("lowest_temp"))
|
||||
|
||||
# If the shutoff temp is under 30° then exit as a safety precaution so the bed doesn't stay on.
|
||||
if lowest_temp < 30:
|
||||
data[0] += "; Anneal or Dry Filament did not run. Shutoff Temp < 30\n"
|
||||
Message(title = "[Anneal or Dry Filament]", text = "The script did not run because the Shutoff Temp is less than 30°.").show()
|
||||
return data
|
||||
extruders = self.global_stack.extruderList
|
||||
bed_temperature = int(self.getSettingValueByKey("startout_temp"))
|
||||
heated_chamber = bool(self.global_stack.getProperty("machine_heated_build_volume", "value"))
|
||||
heating_zone = self.getSettingValueByKey("heating_zone_selection")
|
||||
|
||||
# Get the heated chamber temperature or set to 0 if no chamber
|
||||
if heated_chamber:
|
||||
chamber_temp = str(self.getSettingValueByKey("build_volume_temp"))
|
||||
else:
|
||||
heating_zone = "bed_only"
|
||||
chamber_temp = "0"
|
||||
|
||||
# Beep line
|
||||
if bool(self.getSettingValueByKey("beep_when_done")):
|
||||
beep_duration = self.getSettingValueByKey("beep_duration")
|
||||
self.beep_string = f"M300 S440 P{beep_duration} ; Beep\n"
|
||||
else:
|
||||
self.beep_string = ""
|
||||
|
||||
# For compatibility with earlier Cura versions
|
||||
if self.global_stack.getProperty("build_volume_fan_nr", "value") is not None:
|
||||
has_bv_fan = bool(self.global_stack.getProperty("build_volume_fan_nr", "value"))
|
||||
bv_fan_nr = int(self.global_stack.getProperty("build_volume_fan_nr", "value"))
|
||||
if bv_fan_nr > 0:
|
||||
speed_bv_fan = int(self.getSettingValueByKey("chamber_fan_speed"))
|
||||
else:
|
||||
speed_bv_fan = 0
|
||||
|
||||
if bool(extruders[0].getProperty("machine_scale_fan_speed_zero_to_one", "value")) and has_bv_fan:
|
||||
speed_bv_fan = round(speed_bv_fan * 0.01)
|
||||
else:
|
||||
speed_bv_fan = round(speed_bv_fan * 2.55)
|
||||
|
||||
if has_bv_fan and speed_bv_fan > 0:
|
||||
self.bv_fan_on_str = f"M106 S{speed_bv_fan} P{bv_fan_nr} ; Build Chamber Fan On\n"
|
||||
self.bv_fan_off_str = f"M106 S0 P{bv_fan_nr} ; Build Chamber Fan Off\n"
|
||||
else:
|
||||
self.bv_fan_on_str = ""
|
||||
self.bv_fan_off_str = ""
|
||||
else:
|
||||
has_bv_fan = False
|
||||
bv_fan_nr = 0
|
||||
speed_bv_fan = 0
|
||||
self.bv_fan_on_str = ""
|
||||
self.bv_fan_off_str = ""
|
||||
|
||||
# Park Head
|
||||
max_y = str(self.global_stack.getProperty("machine_depth", "value"))
|
||||
max_x = str(self.global_stack.getProperty("machine_width", "value"))
|
||||
|
||||
# Max_z is limited to 'machine_height - 20' just so the print head doesn't smack into anything.
|
||||
max_z = str(int(self.global_stack.getProperty("machine_height", "value")) - 20)
|
||||
speed_travel = str(round(extruders[0].getProperty("speed_travel", "value")*60))
|
||||
park_xy = bool(self.getSettingValueByKey("park_head"))
|
||||
park_z = bool(self.getSettingValueByKey("park_max_z"))
|
||||
cycle_type = self.getSettingValueByKey("cycle_type")
|
||||
add_messages = bool(self.getSettingValueByKey("add_messages"))
|
||||
|
||||
if cycle_type == "anneal_cycle":
|
||||
data = self._anneal_print(add_messages, data, bed_temperature, chamber_temp, heated_chamber, heating_zone, lowest_temp, max_x, max_y, max_z, park_xy, park_z, speed_travel)
|
||||
elif cycle_type == "dry_cycle":
|
||||
data = self._dry_filament_only(data, bed_temperature, chamber_temp, heated_chamber, heating_zone, max_y, max_z, speed_travel)
|
||||
|
||||
return data
|
||||
|
||||
def _anneal_print(
|
||||
self,
|
||||
add_messages: bool,
|
||||
anneal_data: str,
|
||||
bed_temperature: int,
|
||||
chamber_temp: str,
|
||||
heated_chamber: bool,
|
||||
heating_zone: str,
|
||||
lowest_temp: int,
|
||||
max_x: str,
|
||||
max_y: str,
|
||||
max_z: str,
|
||||
park_xy: bool,
|
||||
park_z: bool,
|
||||
speed_travel: str) -> str:
|
||||
"""
|
||||
The procedure disables the M140 (and M141) lines at the end of the print, and adds additional bed (and chamber) temperature commands to the end of the G-Code file.
|
||||
The bed is allowed to cool down over a period of time.
|
||||
|
||||
:param add_messages: Whether to include M117 and M118 messages for LCD and print server
|
||||
:param anneal_data: The G-code data to be modified with annealing commands
|
||||
:param bed_temperature: Starting bed temperature in degrees Celsius
|
||||
:param chamber_temp: Chamber/build volume temperature in degrees Celsius as string
|
||||
:param heated_chamber: Whether the printer has a heated build volume/chamber
|
||||
:param heating_zone: Zone selection - "bed_only" or "bed_chamber"
|
||||
:param lowest_temp: Final shutdown temperature in degrees Celsius
|
||||
:param max_x: Maximum X axis position for parking as string
|
||||
:param max_y: Maximum Y axis position for parking as string
|
||||
:param max_z: Maximum Z axis position (machine height - 20mm) as string
|
||||
:param park_xy: Whether to park the print head at max X and Y positions
|
||||
:param park_z: Whether to raise Z to maximum safe height
|
||||
:param speed_travel: Travel speed for positioning moves in mm/min as string
|
||||
:return: Modified G-code data with annealing cooldown sequence
|
||||
"""
|
||||
# Put the head parking string together
|
||||
bed_temp_during_print = int(self.global_stack.getProperty("material_bed_temperature", "value"))
|
||||
time_minutes = 1
|
||||
time_span = int(float(self.getSettingValueByKey("time_span")) * 3600)
|
||||
park_string = ""
|
||||
if park_xy:
|
||||
park_string += f"G0 F{speed_travel} X{max_x} Y{max_y} ; Park XY\n"
|
||||
if park_z:
|
||||
park_string += f"G0 Z{max_z} ; Raise Z to 'ZMax - 20'\n"
|
||||
if not park_xy and not park_z:
|
||||
park_string += f"G91 ; Relative movement\nG0 F{speed_travel} Z5 ; Raise Z\nG90 ; Absolute movement\nG0 X0 Y0 ; Park\n"
|
||||
park_string += "M84 X Y E ; Disable steppers except Z\n"
|
||||
|
||||
# Calculate the temperature differential
|
||||
hysteresis = bed_temperature - lowest_temp
|
||||
|
||||
# Exit if the bed temp is below the shutoff temp
|
||||
if hysteresis <= 0:
|
||||
anneal_data[0] += "; Anneal or Dry Filament did not run. Bed Temp < Shutoff Temp\n"
|
||||
Message(title = "Anneal or Dry Filament", text = "Did not run because the Bed Temp < Shutoff Temp.").show()
|
||||
return anneal_data
|
||||
|
||||
# Drop the bed temperature in 3° increments.
|
||||
num_steps = int(hysteresis / 3)
|
||||
step_index = 2
|
||||
deg_per_step = int(hysteresis / num_steps)
|
||||
time_per_step = int(time_span / num_steps)
|
||||
step_down = bed_temperature
|
||||
wait_time = int(float(self.getSettingValueByKey("wait_time")) * 3600)
|
||||
|
||||
# Put the first lines of the anneal string together
|
||||
anneal_string = ";\n;TYPE:CUSTOM ---------------- Anneal Print\n"
|
||||
if bed_temperature == bed_temp_during_print:
|
||||
anneal_string += self.beep_string
|
||||
if add_messages:
|
||||
anneal_string += "M117 Cool Down for " + str(round((wait_time + time_span)/3600,2)) + "hr\n"
|
||||
anneal_string += "M118 Cool Down for " + str(round((wait_time + time_span)/3600,2)) + "hr\n"
|
||||
anneal_string += self.bv_fan_on_str
|
||||
if wait_time > 0:
|
||||
# Add the parking string BEFORE the M190
|
||||
anneal_string += park_string
|
||||
if heating_zone == "bed_only":
|
||||
anneal_string += f"M190 S{bed_temperature} ; Set the bed temp\n{self.beep_string}"
|
||||
if heating_zone == "bed_chamber":
|
||||
anneal_string += f"M190 S{bed_temperature} ; Set the bed temp\nM141 S{chamber_temp} ; Set the chamber temp\n{self.beep_string}"
|
||||
anneal_string += f"G4 S{wait_time} ; Hold for {round(wait_time / 3600,2)} hrs\n"
|
||||
else:
|
||||
# Add the parking string AFTER the M140
|
||||
anneal_string += f"M140 S{step_down} ; Set bed temp\n"
|
||||
anneal_string += park_string
|
||||
anneal_string += f"G4 S{time_per_step} ; wait time in seconds\n"
|
||||
|
||||
step_down -= deg_per_step
|
||||
time_remaining = round(time_span/3600,2)
|
||||
|
||||
# Step the bed/chamber temps down and add each step to the anneal string. The chamber remains at it's temperature until the bed gets down to that temperature.
|
||||
for num in range(bed_temperature, lowest_temp, -3):
|
||||
anneal_string += f"M140 S{step_down} ; Step down bed\n"
|
||||
if heating_zone == "bed_chamber" and int(step_down) < int(chamber_temp):
|
||||
anneal_string += f"M141 S{step_down} ; Step down chamber\n"
|
||||
anneal_string += f"G4 S{time_per_step} ; Wait\n"
|
||||
if time_remaining >= 1.00:
|
||||
if add_messages:
|
||||
anneal_string += f"M117 CoolDown - {round(time_remaining,1)}hr\n"
|
||||
anneal_string += f"M118 CoolDown - {round(time_remaining,1)}hr\n"
|
||||
elif time_minutes > 0:
|
||||
time_minutes = round(time_remaining * 60,1)
|
||||
if add_messages:
|
||||
anneal_string += f"M117 CoolDown - {time_minutes}min\n"
|
||||
anneal_string += f"M118 CoolDown - {time_minutes}min\n"
|
||||
time_remaining = round((time_span-(step_index*time_per_step))/3600,2)
|
||||
step_down -= deg_per_step
|
||||
step_index += 1
|
||||
if step_down <= lowest_temp:
|
||||
break
|
||||
|
||||
# Close out the anneal string
|
||||
anneal_string += "M140 S0 ; Shut off the bed heater" + "\n"
|
||||
if heating_zone == "bed_chamber":
|
||||
anneal_string += "M141 S0 ; Shut off the chamber heater\n"
|
||||
anneal_string += self.bv_fan_off_str
|
||||
anneal_string += self.beep_string
|
||||
if add_messages:
|
||||
anneal_string += "M117 CoolDown Complete\n"
|
||||
anneal_string += "M118 CoolDown Complete\n"
|
||||
anneal_string += ";TYPE:CUSTOM ---------------- End of Anneal\n;"
|
||||
|
||||
# Format the inserted lines.
|
||||
anneal_lines = anneal_string.split("\n")
|
||||
for index, line in enumerate(anneal_lines):
|
||||
if not line.startswith(";") and ";" in line:
|
||||
front_txt = anneal_lines[index].split(";")[0]
|
||||
back_txt = anneal_lines[index].split(";")[1]
|
||||
anneal_lines[index] = front_txt + str(" " * (30 - len(front_txt))) +";" + back_txt
|
||||
anneal_string = "\n".join(anneal_lines) + "\n"
|
||||
|
||||
end_gcode = anneal_data[-1]
|
||||
end_lines = end_gcode.split("\n")
|
||||
|
||||
# Comment out the existing M140 S0 lines in the ending gcode.
|
||||
for num in range(len(end_lines)-1,-1,-1):
|
||||
if end_lines[num].startswith("M140 S0"):
|
||||
end_lines[num] = ";M140 S0 ; Shutoff Overide - Anneal or Dry Filament"
|
||||
anneal_data[-1] = "\n".join(end_lines)
|
||||
|
||||
# If there is a Heated Chamber and it's included then comment out the M141 S0 line
|
||||
if heating_zone == "bed_chamber" and heated_chamber:
|
||||
for num in range(0,len(end_lines)-1):
|
||||
if end_lines[num].startswith("M141 S0"):
|
||||
end_lines[num] = ";M141 S0 ; Shutoff Overide - Anneal or Dry Filament"
|
||||
anneal_data[-1] = "\n".join(end_lines)
|
||||
|
||||
# If park head is enabled then dont let the steppers disable until the head is parked
|
||||
disable_string = ""
|
||||
for num in range(0,len(end_lines)-1):
|
||||
if end_lines[num][:3] in ("M84", "M18"):
|
||||
disable_string = end_lines[num] + "\n"
|
||||
stepper_timeout = int(wait_time + time_span)
|
||||
if stepper_timeout > 14400: stepper_timeout = 14400
|
||||
end_lines[num] = ";" + end_lines[num] + " ; Overide - Anneal or Dry Filament"
|
||||
end_lines.insert(num, "M84 S" + str(stepper_timeout) + " ; Increase stepper timeout - Anneal or Dry Filament")
|
||||
anneal_data[-1] = "\n".join(end_lines)
|
||||
break
|
||||
|
||||
# The Anneal string is the new end of the gcode so move the 'End of Gcode' comment line in case there are other scripts running
|
||||
anneal_data[-1] = anneal_data[-1].replace(";End of Gcode", anneal_string + disable_string + ";End of Gcode")
|
||||
return anneal_data
|
||||
|
||||
def _dry_filament_only(
|
||||
self,
|
||||
bed_temperature: int,
|
||||
chamber_temp: int,
|
||||
drydata: str,
|
||||
heated_chamber: bool,
|
||||
heating_zone: str,
|
||||
max_y: str,
|
||||
max_z: str,
|
||||
speed_travel: str) -> str:
|
||||
"""
|
||||
This procedure turns the bed on, homes the printer, parks the head. After the time period the bed is turned off.
|
||||
There is no actual print in the generated gcode, just a couple of moves to get the nozzle out of the way, and the bed heat (and possibly chamber heat) control.
|
||||
It allows a user to use the bed to warm up and hopefully dry a filament roll.
|
||||
|
||||
:param bed_temperature: Bed temperature for drying in degrees Celsius
|
||||
:param chamber_temp: Chamber/build volume temperature for drying in degrees Celsius
|
||||
:param drydata: The G-code data to be replaced with filament drying commands
|
||||
:param heated_chamber: Whether the printer has a heated build volume/chamber
|
||||
:param heating_zone: Zone selection - "bed_only" or "bed_chamber"
|
||||
:param max_y: Maximum Y axis position for parking as string
|
||||
:param max_z: Maximum Z axis position (machine height - 20mm) as string
|
||||
:param speed_travel: Travel speed for positioning moves in mm/min as string
|
||||
:return: Modified G-code data containing only filament drying sequence
|
||||
"""
|
||||
for num in range(2, len(drydata)):
|
||||
drydata[num] = ""
|
||||
drydata[0] = drydata[0].split("\n")[0] + "\n"
|
||||
add_messages = bool(self.getSettingValueByKey("add_messages"))
|
||||
pause_cmd = self.getSettingValueByKey("pause_cmd")
|
||||
if pause_cmd != "":
|
||||
pause_cmd = self.beep_string + pause_cmd
|
||||
dry_time = self.getSettingValueByKey("dry_time") * 3600
|
||||
lines = drydata[1].split("\n")
|
||||
drying_string = lines[0] + f"\n;............TYPE:CUSTOM: Dry Filament\n{self.beep_string}"
|
||||
if add_messages:
|
||||
drying_string += f"M117 Cool Down for {round(dry_time/3600,2)} hr ; Message\n"
|
||||
drying_string += f"M118 Cool Down for {round(dry_time/3600,2)} hr ; Message\n"
|
||||
|
||||
# M113 sends messages to a print server as a 'Keep Alive' and can generate a lot of traffic over the USB
|
||||
drying_string += "M113 S0 ; No echo\n"
|
||||
drying_string += f"M84 S{round(dry_time)} ; Set stepper timeout\n"
|
||||
drying_string += f"M140 S{bed_temperature} ; Heat bed\n"
|
||||
drying_string += self.bv_fan_on_str
|
||||
if heated_chamber and heating_zone == "bed_chamber":
|
||||
drying_string += f"M141 S{chamber_temp} ; Chamber temp\n"
|
||||
if pause_cmd == "M0":
|
||||
pause_cmd = "M0 Clear bed and click...; Pause"
|
||||
if pause_cmd != "":
|
||||
drying_string += pause_cmd + " ; Pause\n"
|
||||
drying_string += "G28 ; Auto-Home\n"
|
||||
drying_string += f"G0 F{speed_travel} Z{max_z} ; Raise Z to 'ZMax - 20'\n"
|
||||
drying_string += f"G0 F{speed_travel} X0 Y{max_y} ; Park print head\n"
|
||||
if dry_time <= 3600:
|
||||
if add_messages:
|
||||
drying_string += f"M117 {dry_time/3600} hr remaining ; Message\n"
|
||||
drying_string += f"M118 {dry_time/3600} hr remaining ; Message\n"
|
||||
drying_string += f"G4 S{dry_time} ; Dry time\n"
|
||||
elif dry_time > 3600:
|
||||
temp_time = dry_time
|
||||
while temp_time > 3600:
|
||||
if add_messages:
|
||||
drying_string += f"M117 {temp_time/3600} hr remaining ; Message\n"
|
||||
drying_string += f"M118 {temp_time/3600} hr remaining ; Message\n"
|
||||
drying_string += f"G4 S3600 ; Dry time split\n"
|
||||
if temp_time > 3600:
|
||||
temp_time -= 3600
|
||||
if temp_time > 0:
|
||||
if add_messages:
|
||||
drying_string += f"M117 {temp_time/3600} hr remaining ; Message\n"
|
||||
drying_string += f"M118 {temp_time/3600} hr remaining ; Message\n"
|
||||
drying_string += f"G4 S{temp_time} ; Dry time\n"
|
||||
if heated_chamber and heating_zone == "bed_chamber":
|
||||
drying_string += f"M141 S0 ; Shut off chamber\n"
|
||||
drying_string += "M140 S0 ; Shut off bed\n"
|
||||
drying_string += self.bv_fan_off_str
|
||||
if self.getSettingValueByKey("beep_when_done"):
|
||||
beep_duration = self.getSettingValueByKey("beep_duration")
|
||||
drying_string += self.beep_string
|
||||
if add_messages:
|
||||
drying_string += "M117 End of drying cycle ; Message\n"
|
||||
drying_string += "M118 End of drying cycle ; Message\n"
|
||||
drying_string += "M84 X Y E ; Disable steppers except Z\n"
|
||||
drying_string += ";End of Gcode"
|
||||
|
||||
# Format the lines
|
||||
lines = drying_string.split("\n")
|
||||
for index, line in enumerate(lines):
|
||||
if not line.startswith(";") and ";" in line:
|
||||
front_txt = lines[index].split(";")[0]
|
||||
back_txt = lines[index].split(";")[1]
|
||||
lines[index] = front_txt + str(" " * (30 - len(front_txt))) +";" + back_txt
|
||||
drydata[1] = "\n".join(lines) + "\n"
|
||||
dry_txt = "; Drying time ...................... " + str(self.getSettingValueByKey("dry_time")) + " hrs\n"
|
||||
dry_txt += "; Drying temperature ........ " + str(bed_temperature) + "°\n"
|
||||
if heated_chamber and heating_zone == "bed_chamber":
|
||||
dry_txt += "; Chamber temperature ... " + str(chamber_temp) + "°\n"
|
||||
Message(title = "[Dry Filament]", text = dry_txt).show()
|
||||
drydata[0] = "; <<< This is a filament drying file only. There is no actual print. >>>\n;\n" + dry_txt + ";\n"
|
||||
return drydata
|
||||
|
|
@ -1,31 +1,36 @@
|
|||
# Display Filename and Layer on the LCD by Amanda de Castilho on August 28, 2018
|
||||
# Modified: Joshua Pope-Lewis on November 16, 2018
|
||||
# Display Progress on LCD by Mathias Lyngklip Kjeldgaard, Alexander Gee, Kimmo Toivanen, Inigo Martinez on July 31, 2019
|
||||
# Show Progress was adapted from Display Progress by Louis Wooters on January 6, 2020. His changes are included here.
|
||||
#---------------------------------------------------------------
|
||||
# DisplayNameOrProgressOnLCD.py
|
||||
# Cura Post-Process plugin
|
||||
# Combines 'Display Filename and Layer on the LCD' with 'Display Progress'
|
||||
# Combined and with additions by: GregValiant (Greg Foresi)
|
||||
# Date: September 8, 2023
|
||||
# NOTE: This combined post processor will make 'Display Filename and Layer on the LCD' and 'Display Progress' obsolete
|
||||
# Description: Display Filename and Layer options:
|
||||
# Status messages sent to the printer...
|
||||
# - Scrolling (SCROLL_LONG_FILENAMES) if enabled in Marlin and you aren't printing a small item select this option.
|
||||
# - Name: By default it will use the name generated by Cura (EG: TT_Test_Cube) - You may enter a custom name here
|
||||
# - Start Num: Choose which number you prefer for the initial layer, 0 or 1
|
||||
# - Max Layer: Enabling this will show how many layers are in the entire print (EG: Layer 1 of 265!)
|
||||
# - Add prefix 'Printing': Enabling this will add the prefix 'Printing'
|
||||
# - Example Line on LCD: Printing Layer 0 of 395 3DBenchy
|
||||
# Display Progress options:
|
||||
# - Display Total Layer Count
|
||||
# - Disply Time Remaining for the print
|
||||
# - Time Fudge Factor % - Divide the Actual Print Time by the Cura Estimate. Enter as a percentage and the displayed time will be adjusted. This allows you to bring the displayed time closer to reality (Ex: Entering 87.5 would indicate an adjustment to 87.5% of the Cura estimate).
|
||||
# - Example line on LCD: 1/479 | ET 2h13m
|
||||
# - Time to Pauses changes the M117/M118 lines to countdown to the next pause as 1/479 | TP 2h36m
|
||||
# - 'Add M118 Line' is available with either option. M118 will bounce the message back to a remote print server through the USB connection.
|
||||
# - 'Add M73 Line' is used by 'Display Progress' only. There are options to incluse M73 P(percent) and M73 R(time remaining)
|
||||
# - Enable 'Finish-Time' Message - when enabled, takes the Print Time and calculates when the print will end. It takes into account the Time Fudge Factor. The user may enter a print start time. This is also available for Display Filename.
|
||||
"""
|
||||
Display Filename and Layer on the LCD by Amanda de Castilho on August 28, 2018
|
||||
Modified: Joshua Pope-Lewis on November 16, 2018
|
||||
Display Progress on LCD by Mathias Lyngklip Kjeldgaard, Alexander Gee, Kimmo Toivanen, Inigo Martinez on July 31, 2019
|
||||
Show Progress was adapted from Display Progress by Louis Wooters on January 6, 2020. His changes are included here.
|
||||
---------------------------------------------------------------
|
||||
DisplayNameOrProgressOnLCD.py
|
||||
Cura Post-Process plugin
|
||||
Combines 'Display Filename and Layer on the LCD' with 'Display Progress'
|
||||
Combined and with additions by: GregValiant (Greg Foresi)
|
||||
Date: September 8, 2023
|
||||
Date: March 31, 2024 - Bug fix for problem with adding M118 lines if 'Remaining Time' was not checked.
|
||||
NOTE: This combined post processor will make 'Display Filename and Layer on the LCD' and 'Display Progress' obsolete
|
||||
Description: Display Filename and Layer options:
|
||||
Status messages sent to the printer...
|
||||
- Scrolling (SCROLL_LONG_FILENAMES) if enabled in Marlin and you aren't printing a small item select this option.
|
||||
- Name: By default it will use the name generated by Cura (EG: TT_Test_Cube) - You may enter a custom name here
|
||||
- Start Num: Choose which number you prefer for the initial layer, 0 or 1
|
||||
- Max Layer: Enabling this will show how many layers are in the entire print (EG: Layer 1 of 265!)
|
||||
- Add prefix 'Printing': Enabling this will add the prefix 'Printing'
|
||||
- Example Line on LCD: Printing Layer 0 of 395 3DBenchy
|
||||
Display Progress options:
|
||||
- Display Total Layer Count
|
||||
- Disply Time Remaining for the print
|
||||
- Time Fudge Factor % - Divide the Actual Print Time by the Cura Estimate. Enter as a percentage and the displayed time will be adjusted.
|
||||
This allows you to bring the displayed time closer to reality (Ex: Entering 87.5 would indicate an adjustment to 87.5% of the Cura estimate).
|
||||
- Example line on LCD: 1/479 | ET 2h13m
|
||||
- Time to Pauses changes the M117/M118 lines to countdown to the next pause as 1/479 | TP 2h36m
|
||||
- 'Add M118 Line' is available with either option. M118 will bounce the message back to a remote print server through the USB connection.
|
||||
- 'Add M73 Line' is used by 'Display Progress' only. There are options to incluse M73 P(percent) and M73 R(time remaining)
|
||||
- Enable 'Finish-Time' Message - when enabled, takes the Print Time and calculates when the print will end. It uses the Time Fudge Factor. The user may enter a print start time.
|
||||
Date: June 30, 2025 Cost of electricity added to the other print statistics in '_add_stats'.
|
||||
"""
|
||||
|
||||
from ..Script import Script
|
||||
from UM.Application import Application
|
||||
|
|
@ -37,6 +42,19 @@ from UM.Message import Message
|
|||
|
||||
class DisplayInfoOnLCD(Script):
|
||||
|
||||
def initialize(self) -> None:
|
||||
super().initialize()
|
||||
try:
|
||||
if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "all_at_once":
|
||||
enable_countdown = True
|
||||
self._instance.setProperty("enable_countdown", "value", enable_countdown)
|
||||
except AttributeError:
|
||||
# Handle cases where the global container stack or its properties are not accessible
|
||||
pass
|
||||
except KeyError:
|
||||
# Handle cases where the "print_sequence" property is missing
|
||||
pass
|
||||
|
||||
def getSettingDataString(self):
|
||||
return """{
|
||||
"name": "Display Info on LCD",
|
||||
|
|
@ -77,7 +95,7 @@ class DisplayInfoOnLCD(Script):
|
|||
"label": "Initial layer number:",
|
||||
"description": "Choose which number you prefer for the initial layer, 0 or 1",
|
||||
"type": "int",
|
||||
"default_value": 0,
|
||||
"default_value": 1,
|
||||
"minimum_value": 0,
|
||||
"maximum_value": 1,
|
||||
"enabled": "display_option == 'filename_layer'"
|
||||
|
|
@ -114,17 +132,40 @@ class DisplayInfoOnLCD(Script):
|
|||
"default_value": true,
|
||||
"enabled": "display_option == 'display_progress'"
|
||||
},
|
||||
"add_m117_line":
|
||||
{
|
||||
"label": "Add M117 Line",
|
||||
"description": "M117 sends a message to the LCD screen. Some screen firmware will not accept or display messages.",
|
||||
"type": "bool",
|
||||
"default_value": true
|
||||
},
|
||||
"add_m118_line":
|
||||
{
|
||||
"label": "Add M118 Line",
|
||||
"description": "Adds M118 in addition to the M117. It will bounce the message back through the USB port to a computer print server (if a printer server like Octoprint or Pronterface is in use).",
|
||||
"type": "bool",
|
||||
"default_value": false
|
||||
"default_value": true
|
||||
},
|
||||
"add_m118_a1":
|
||||
{
|
||||
"label": " Add A1 to M118 Line",
|
||||
"description": "Adds A1 parameter. A1 adds a double foreslash '//' to the response. Octoprint may require this.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "add_m118_line"
|
||||
},
|
||||
"add_m118_p0":
|
||||
{
|
||||
"label": " Add P0 to M118 Line",
|
||||
"description": "Adds P0 parameter. P0 has the printer send the response out through all it's ports. Octoprint may require this.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "add_m118_line"
|
||||
},
|
||||
"add_m73_line":
|
||||
{
|
||||
"label": "Add M73 Line(s)",
|
||||
"description": "Adds M73 in addition to the M117. For some firmware this will set the printers time and or percentage.",
|
||||
"description": "Adds M73 in addition to the M117. For some firmware this will set the printers time and or percentage. M75 is added to the beginning of the file and M77 is added to the end of the file. M73 will be added if one or both of the following options is chosen.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "display_option == 'display_progress'"
|
||||
|
|
@ -132,7 +173,7 @@ class DisplayInfoOnLCD(Script):
|
|||
"add_m73_percent":
|
||||
{
|
||||
"label": " Add M73 Percentage",
|
||||
"description": "Adds M73 with the P parameter. For some firmware this will set the printers 'percentage' of layers completed and it will count upward.",
|
||||
"description": "Adds M73 with the P parameter to the start of each layer. For some firmware this will set the printers 'percentage' of layers completed and it will count upward.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "add_m73_line and display_option == 'display_progress'"
|
||||
|
|
@ -140,10 +181,10 @@ class DisplayInfoOnLCD(Script):
|
|||
"add_m73_time":
|
||||
{
|
||||
"label": " Add M73 Time",
|
||||
"description": "Adds M73 with the R parameter. For some firmware this will set the printers 'print time' and it will count downward.",
|
||||
"description": "Adds M73 with the R parameter to the start of each layer. For some firmware this will set the printers 'print time' and it will count downward.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "add_m73_line and display_option == 'display_progress'"
|
||||
"enabled": "add_m73_line and display_option == 'display_progress' and display_remaining_time"
|
||||
},
|
||||
"speed_factor":
|
||||
{
|
||||
|
|
@ -154,13 +195,29 @@ class DisplayInfoOnLCD(Script):
|
|||
"default_value": 100,
|
||||
"enabled": "enable_end_message or display_option == 'display_progress'"
|
||||
},
|
||||
"enable_countdown":
|
||||
{
|
||||
"label": "Enable Countdown to Pauses",
|
||||
"description": "If print sequence is 'one_at_a_time' this is false. This setting is always hidden.",
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
"enabled": false
|
||||
},
|
||||
"countdown_to_pause":
|
||||
{
|
||||
"label": "Countdown to Pauses",
|
||||
"description": "Instead of the remaining print time the LCD will show the estimated time to pause (TP).",
|
||||
"description": "This must run AFTER any script that adds a pause. Instead of the remaining print time the LCD will show the estimated time to the next layer that has a pause (TP). Countdown to Pause is not available when in One-at-a-Time' mode.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "display_option == 'display_progress'"
|
||||
"enabled": "display_option == 'display_progress' and enable_countdown and display_remaining_time"
|
||||
},
|
||||
"pause_cmd":
|
||||
{
|
||||
"label": " What pause command(s) are used?",
|
||||
"description": "This might be M0, or M25 or M600 if Filament Change is used. If you have mixed commands then delimit them with a comma ',' (Ex: M0,M600). Spaces are not allowed.",
|
||||
"type": "str",
|
||||
"default_value": "M0",
|
||||
"enabled": "display_option == 'display_progress' and countdown_to_pause and enable_countdown and display_remaining_time"
|
||||
},
|
||||
"enable_end_message":
|
||||
{
|
||||
|
|
@ -173,11 +230,29 @@ class DisplayInfoOnLCD(Script):
|
|||
"print_start_time":
|
||||
{
|
||||
"label": "Print Start Time (Ex 16:45)",
|
||||
"description": "Use 'Military' time. 16:45 would be 4:45PM. 09:30 would be 9:30AM. If you leave this blank it will be assumed that the print will start Now. If you enter a guesstimate of your printer start time and that time is before 'Now' the guesstimate will consider that the print will start tomorrow at the entered time. ",
|
||||
"description": "Use 'Military' time. 16:45 would be 4:45PM. 09:30 would be 9:30AM. If you leave this blank it will be assumed that the print will start Now. If you enter a guesstimate of your printer start time and that time is before 'Now' then the guesstimate will consider that the print will start tomorrow at the entered time. ",
|
||||
"type": "str",
|
||||
"default_value": "",
|
||||
"unit": "hrs ",
|
||||
"enabled": "enable_end_message"
|
||||
},
|
||||
"electricity_cost":
|
||||
{
|
||||
"label": "Electricity Cost per kWh",
|
||||
"description": "Cost of electricity per kilowatt-hour. This should be on your electric utility bill.",
|
||||
"type": "float",
|
||||
"default_value": 0.151,
|
||||
"minimum_value": 0,
|
||||
"unit": "€/kWh "
|
||||
},
|
||||
"printer_power_usage":
|
||||
{
|
||||
"label": "Printer Power Usage",
|
||||
"description": "Average power usage of the 3D printer in Watts. The actual wattage has many variables. 50% of the power supply rating would be a ballpark figure.",
|
||||
"type": "float",
|
||||
"default_value": 175,
|
||||
"minimum_value": 0,
|
||||
"unit": "Watts "
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -185,239 +260,303 @@ class DisplayInfoOnLCD(Script):
|
|||
|
||||
def execute(self, data):
|
||||
display_option = self.getSettingValueByKey("display_option")
|
||||
add_m118_line = self.getSettingValueByKey("add_m118_line")
|
||||
add_m73_line = self.getSettingValueByKey("add_m73_line")
|
||||
add_m73_time = self.getSettingValueByKey("add_m73_time")
|
||||
add_m73_percent = self.getSettingValueByKey("add_m73_percent")
|
||||
|
||||
# This is Display Filename and Layer on LCD---------------------------------------------------------
|
||||
self.add_m117_line = self.getSettingValueByKey("add_m117_line")
|
||||
self.add_m118_line = self.getSettingValueByKey("add_m118_line")
|
||||
self.add_m118_a1 = self.getSettingValueByKey("add_m118_a1")
|
||||
self.add_m118_p0 = self.getSettingValueByKey("add_m118_p0")
|
||||
self.m118_text = "M118 "
|
||||
self.add_m73_line = self.getSettingValueByKey("add_m73_line")
|
||||
self.add_m73_time = self.getSettingValueByKey("add_m73_time")
|
||||
self.add_m73_percent = self.getSettingValueByKey("add_m73_percent")
|
||||
self.m73_str = ""
|
||||
para_1 = data[0].split("\n")
|
||||
for line in para_1:
|
||||
if line.startswith(";TIME:") or line.startswith(";PRINT.TIME:"):
|
||||
self.time_total = int(line.split(":")[1])
|
||||
break
|
||||
if display_option == "filename_layer":
|
||||
max_layer = 0
|
||||
lcd_text = "M117 "
|
||||
if self.getSettingValueByKey("file_name") != "":
|
||||
file_name = self.getSettingValueByKey("file_name")
|
||||
else:
|
||||
file_name = Application.getInstance().getPrintInformation().jobName
|
||||
if self.getSettingValueByKey("addPrefixPrinting"):
|
||||
lcd_text += "Printing "
|
||||
if not self.getSettingValueByKey("scroll"):
|
||||
lcd_text += "Layer "
|
||||
else:
|
||||
lcd_text += file_name + " - Layer "
|
||||
i = self.getSettingValueByKey("startNum")
|
||||
for layer in data:
|
||||
display_text = lcd_text + str(i)
|
||||
layer_index = data.index(layer)
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith(";LAYER_COUNT:"):
|
||||
max_layer = line
|
||||
max_layer = max_layer.split(":")[1]
|
||||
if self.getSettingValueByKey("startNum") == 0:
|
||||
max_layer = str(int(max_layer) - 1)
|
||||
if line.startswith(";LAYER:"):
|
||||
if self.getSettingValueByKey("maxlayer"):
|
||||
display_text = display_text + " of " + max_layer
|
||||
if not self.getSettingValueByKey("scroll"):
|
||||
display_text = display_text + " " + file_name
|
||||
else:
|
||||
if not self.getSettingValueByKey("scroll"):
|
||||
display_text = display_text + " " + file_name + "!"
|
||||
else:
|
||||
display_text = display_text + "!"
|
||||
line_index = lines.index(line)
|
||||
lines.insert(line_index + 1, display_text)
|
||||
if add_m118_line:
|
||||
lines.insert(line_index + 2, str(display_text.replace("M117", "M118", 1)))
|
||||
i += 1
|
||||
final_lines = "\n".join(lines)
|
||||
data[layer_index] = final_lines
|
||||
if bool(self.getSettingValueByKey("enable_end_message")):
|
||||
message_str = self.message_to_user(self.getSettingValueByKey("speed_factor") / 100)
|
||||
Message(title = "Display Info on LCD - Estimated Finish Time", text = message_str[0] + "\n\n" + message_str[1] + "\n" + message_str[2] + "\n" + message_str[3]).show()
|
||||
return data
|
||||
data = self._display_filename_layer(data)
|
||||
else:
|
||||
data = self._display_progress(data)
|
||||
return data
|
||||
|
||||
# Display Progress (from 'Show Progress' and 'Display Progress on LCD')---------------------------------------
|
||||
elif display_option == "display_progress":
|
||||
# get settings
|
||||
display_total_layers = self.getSettingValueByKey("display_total_layers")
|
||||
display_remaining_time = self.getSettingValueByKey("display_remaining_time")
|
||||
speed_factor = self.getSettingValueByKey("speed_factor") / 100
|
||||
m73_time = False
|
||||
m73_percent = False
|
||||
if add_m73_line and add_m73_time:
|
||||
m73_time = True
|
||||
if add_m73_line and add_m73_percent:
|
||||
m73_percent = True
|
||||
# initialize global variables
|
||||
first_layer_index = 0
|
||||
time_total = 0
|
||||
number_of_layers = 0
|
||||
time_elapsed = 0
|
||||
# if at least one of the settings is disabled, there is enough room on the display to display "layer"
|
||||
first_section = data[0]
|
||||
lines = first_section.split("\n")
|
||||
# This is from the original 'Display Filename and Layer on LCD'
|
||||
def _display_filename_layer(self, data: str) -> str:
|
||||
data[0] = self._add_stats(data)
|
||||
max_layer = 0
|
||||
format_option = self.getSettingValueByKey("format_option")
|
||||
lcd_text = "M117 "
|
||||
octo_text = "M118 "
|
||||
if self.getSettingValueByKey("file_name") != "":
|
||||
file_name = self.getSettingValueByKey("file_name")
|
||||
else:
|
||||
file_name = Application.getInstance().getPrintInformation().jobName
|
||||
if self.getSettingValueByKey("addPrefixPrinting"):
|
||||
lcd_text += "Printing "
|
||||
octo_text += "Printing "
|
||||
if not format_option:
|
||||
lcd_text += "Lay "
|
||||
octo_text += "Layer "
|
||||
else:
|
||||
lcd_text += file_name + " - Layer "
|
||||
octo_text += file_name + " - Layer "
|
||||
i = self.getSettingValueByKey("startNum")
|
||||
for layer in data:
|
||||
display_text = lcd_text + str(i)
|
||||
self.m118_text = octo_text + str(i)
|
||||
layer_index = data.index(layer)
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith(";TIME:"):
|
||||
tindex = lines.index(line)
|
||||
cura_time = int(line.split(":")[1])
|
||||
print_time = cura_time * speed_factor
|
||||
hhh = print_time/3600
|
||||
hr = round(hhh // 1)
|
||||
mmm = round((hhh % 1) * 60)
|
||||
orig_hhh = cura_time/3600
|
||||
orig_hr = round(orig_hhh // 1)
|
||||
orig_mmm = math.floor((orig_hhh % 1) * 60)
|
||||
orig_sec = round((((orig_hhh % 1) * 60) % 1) * 60)
|
||||
if add_m118_line: lines.insert(tindex + 3,"M118 Adjusted Print Time " + str(hr) + "hr " + str(mmm) + "min")
|
||||
lines.insert(tindex + 3,"M117 ET " + str(hr) + "hr " + str(mmm) + "min")
|
||||
# add M73 line at beginning
|
||||
mins = int(60 * hr + mmm)
|
||||
if m73_time:
|
||||
lines.insert(tindex + 3, "M73 R{}".format(mins))
|
||||
if m73_percent:
|
||||
lines.insert(tindex + 3, "M73 P0")
|
||||
# If Countdonw to pause is enabled then count the pauses
|
||||
pause_str = ""
|
||||
if bool(self.getSettingValueByKey("countdown_to_pause")):
|
||||
pause_count = 0
|
||||
for num in range(2,len(data) - 1, 1):
|
||||
if "PauseAtHeight.py" in data[num]:
|
||||
pause_count += 1
|
||||
pause_str = f" with {pause_count} pause(s)"
|
||||
# This line goes in to convert seconds to hours and minutes
|
||||
lines.insert(tindex + 3, f";Cura Time Estimate: {cura_time}sec = {orig_hr}hr {orig_mmm}min {orig_sec}sec {pause_str}")
|
||||
data[0] = "\n".join(lines)
|
||||
data[len(data)-1] += "M117 Orig Cura Est " + str(orig_hr) + "hr " + str(orig_mmm) + "min\n"
|
||||
if add_m118_line: data[len(data)-1] += "M118 Est w/FudgeFactor " + str(speed_factor * 100) + "% was " + str(hr) + "hr " + str(mmm) + "min\n"
|
||||
if not display_total_layers or not display_remaining_time:
|
||||
base_display_text = "layer "
|
||||
else:
|
||||
base_display_text = ""
|
||||
layer = data[len(data)-1]
|
||||
data[len(data)-1] = layer.replace(";End of Gcode" + "\n", "")
|
||||
data[len(data)-1] += ";End of Gcode" + "\n"
|
||||
# Search for the number of layers and the total time from the start code
|
||||
for index in range(len(data)):
|
||||
data_section = data[index]
|
||||
# We have everything we need, save the index of the first layer and exit the loop
|
||||
if ";LAYER:" in data_section:
|
||||
first_layer_index = index
|
||||
break
|
||||
else:
|
||||
for line in data_section.split("\n"):
|
||||
if line.startswith(";LAYER_COUNT:"):
|
||||
number_of_layers = int(line.split(":")[1])
|
||||
elif line.startswith(";TIME:"):
|
||||
time_total = int(line.split(":")[1])
|
||||
# for all layers...
|
||||
current_layer = 0
|
||||
for layer_counter in range(len(data)-2):
|
||||
current_layer += 1
|
||||
layer_index = first_layer_index + layer_counter
|
||||
display_text = base_display_text
|
||||
display_text += str(current_layer)
|
||||
# create a list where each element is a single line of code within the layer
|
||||
lines = data[layer_index].split("\n")
|
||||
if not ";LAYER:" in data[layer_index]:
|
||||
current_layer -= 1
|
||||
continue
|
||||
# add the total number of layers if this option is checked
|
||||
if display_total_layers:
|
||||
display_text += "/" + str(number_of_layers)
|
||||
# if display_remaining_time is checked, it is calculated in this loop
|
||||
if display_remaining_time:
|
||||
time_remaining_display = " | ET " # initialize the time display
|
||||
m = (time_total - time_elapsed) // 60 # estimated time in minutes
|
||||
m *= speed_factor # correct for printing time
|
||||
m = int(m)
|
||||
h, m = divmod(m, 60) # convert to hours and minutes
|
||||
# add the time remaining to the display_text
|
||||
if h > 0: # if it's more than 1 hour left, display format = xhxxm
|
||||
time_remaining_display += str(h) + "h"
|
||||
if m < 10: # add trailing zero if necessary
|
||||
time_remaining_display += "0"
|
||||
time_remaining_display += str(m) + "m"
|
||||
if line.startswith(";LAYER_COUNT:"):
|
||||
max_layer = line
|
||||
max_layer = max_layer.split(":")[1]
|
||||
if self.getSettingValueByKey("startNum") == 0:
|
||||
max_layer = str(int(max_layer) - 1)
|
||||
if line.startswith(";LAYER:"):
|
||||
if self.getSettingValueByKey("maxlayer"):
|
||||
display_text += "/" + max_layer
|
||||
self.m118_text += "/" + max_layer
|
||||
if not format_option:
|
||||
display_text += "|" + file_name
|
||||
self.m118_text += " | " + file_name
|
||||
else:
|
||||
time_remaining_display += str(m) + "m"
|
||||
display_text += time_remaining_display
|
||||
# find time_elapsed at the end of the layer (used to calculate the remaining time of the next layer)
|
||||
if not current_layer == number_of_layers:
|
||||
for line_index in range(len(lines) - 1, -1, -1):
|
||||
line = lines[line_index]
|
||||
if line.startswith(";TIME_ELAPSED:"):
|
||||
# update time_elapsed for the NEXT layer and exit the loop
|
||||
time_elapsed = int(float(line.split(":")[1]))
|
||||
break
|
||||
# insert the text AFTER the first line of the layer (in case other scripts use ";LAYER:")
|
||||
for l_index, line in enumerate(lines):
|
||||
if line.startswith(";LAYER:"):
|
||||
if not format_option:
|
||||
display_text += "|" + file_name + "!"
|
||||
self.m118_text += " | " + file_name + "!"
|
||||
else:
|
||||
display_text += "!"
|
||||
self.m118_text += "!"
|
||||
line_index = lines.index(line)
|
||||
if self.add_m117_line:
|
||||
lines.insert(line_index + 1, display_text)
|
||||
if self.add_m118_line:
|
||||
if self.add_m118_a1:
|
||||
self.m118_text = self.m118_text.replace("M118 ","M118 A1 ")
|
||||
if self.add_m118_p0:
|
||||
self.m118_text = self.m118_text.replace("M118 ","M118 P0 ")
|
||||
lines.insert(line_index + 2, self.m118_text)
|
||||
i += 1
|
||||
final_lines = "\n".join(lines)
|
||||
data[layer_index] = final_lines
|
||||
if bool(self.getSettingValueByKey("enable_end_message")):
|
||||
message_str = self._message_to_user(self.getSettingValueByKey("speed_factor") / 100)
|
||||
Message(title = "Display Info on LCD - Estimated Finish Time", text = message_str[0] + "\n\n" + message_str[1] + "\n" + message_str[2] + "\n" + message_str[3]).show()
|
||||
return data
|
||||
|
||||
# This is from 'Show Progress on LCD'
|
||||
def _display_progress(self, data: str) -> str:
|
||||
# Add some common print settings to the start of the gcode
|
||||
data[0] = self._add_stats(data)
|
||||
# Get settings
|
||||
print_sequence = Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value")
|
||||
display_total_layers = self.getSettingValueByKey("display_total_layers")
|
||||
display_remaining_time = self.getSettingValueByKey("display_remaining_time")
|
||||
speed_factor = self.getSettingValueByKey("speed_factor") / 100
|
||||
m73_time = False
|
||||
m73_percent = False
|
||||
if self.add_m73_line and self.add_m73_time:
|
||||
m73_time = True
|
||||
if self.add_m73_line and self.add_m73_percent:
|
||||
m73_percent = True
|
||||
if self.add_m73_line:
|
||||
data[1] = "M75\n" + data[1]
|
||||
data[len(data)-1] += "M77\n"
|
||||
# Initialize some variables
|
||||
first_layer_index = 0
|
||||
number_of_layers = 0
|
||||
time_elapsed = 0
|
||||
|
||||
# If at least one of the settings is disabled, there is enough room on the display to display "layer"
|
||||
first_section = data[0]
|
||||
lines = first_section.split("\n")
|
||||
pause_cmd = []
|
||||
for line in lines:
|
||||
if line.startswith(";TIME:"):
|
||||
tindex = lines.index(line)
|
||||
cura_time = int(line.split(":")[1])
|
||||
print_time = cura_time * speed_factor
|
||||
hhh = print_time/3600
|
||||
hr = round(hhh // 1)
|
||||
mmm = round((hhh % 1) * 60)
|
||||
orig_hhh = cura_time/3600
|
||||
orig_hr = round(orig_hhh // 1)
|
||||
orig_mmm = math.floor((orig_hhh % 1) * 60)
|
||||
if self.add_m118_line:
|
||||
lines.insert(len(lines) - 2, f"M118 Adjusted Print Time is {hr} hr {mmm} min")
|
||||
if self.add_m117_line:
|
||||
lines.insert(len(lines) - 2, f"M117 ET {hr} hr {mmm} min")
|
||||
# Add M73 line at beginning
|
||||
mins = int(60 * hr + mmm)
|
||||
if self.add_m73_line and (self.add_m73_time or self.add_m73_percent):
|
||||
if m73_time:
|
||||
self.m73_str += " R{}".format(mins)
|
||||
if m73_percent:
|
||||
self.m73_str += " P0"
|
||||
lines.insert(tindex + 4, "M73" + self.m73_str)
|
||||
# If Countdown to pause is enabled then count the pauses
|
||||
pause_str = ""
|
||||
if bool(self.getSettingValueByKey("countdown_to_pause")):
|
||||
pause_count = 0
|
||||
pause_setting = self.getSettingValueByKey("pause_cmd").upper()
|
||||
if pause_setting != "":
|
||||
pause_cmd = []
|
||||
if "," in pause_setting:
|
||||
pause_cmd = pause_setting.split(",")
|
||||
else:
|
||||
pause_cmd.append(pause_setting)
|
||||
for q in range(0, len(pause_cmd)):
|
||||
pause_cmd[q] = "\n" + pause_cmd[q]
|
||||
for num in range(2,len(data) - 2, 1):
|
||||
for q in range(0,len(pause_cmd)):
|
||||
if pause_cmd[q] in data[num]:
|
||||
pause_count += data[num].count(pause_cmd[q], 0, len(data[num]))
|
||||
pause_str = f"with {pause_count} pause" + ("s" if pause_count > 1 else "")
|
||||
else:
|
||||
pause_str = ""
|
||||
# This line goes in to convert seconds to hours and minutes
|
||||
lines.insert(tindex + 1, f";Cura Time Estimate: {orig_hr}hr {orig_mmm}min {pause_str}")
|
||||
data[0] = "\n".join(lines)
|
||||
if self.add_m117_line:
|
||||
data[len(data)-1] += "M117 Orig Cura Est " + str(orig_hr) + "hr " + str(orig_mmm) + "min\n"
|
||||
if self.add_m118_line:
|
||||
data[len(data)-1] += "M118 Est w/FudgeFactor " + str(speed_factor * 100) + "% was " + str(hr) + "hr " + str(mmm) + "min\n"
|
||||
if not display_total_layers or not display_remaining_time:
|
||||
base_display_text = "layer "
|
||||
else:
|
||||
base_display_text = ""
|
||||
layer = data[len(data)-1]
|
||||
data[len(data)-1] = layer.replace(";End of Gcode" + "\n", "")
|
||||
data[len(data)-1] += ";End of Gcode" + "\n"
|
||||
# Search for the number of layers and the total time from the start code
|
||||
for index in range(len(data)):
|
||||
data_section = data[index]
|
||||
# We have everything we need, save the index of the first layer and exit the loop
|
||||
if ";LAYER:" in data_section:
|
||||
first_layer_index = index
|
||||
break
|
||||
else:
|
||||
for line in data_section.split("\n"):
|
||||
if line.startswith(";LAYER_COUNT:"):
|
||||
number_of_layers = int(line.split(":")[1])
|
||||
if print_sequence == "one_at_a_time":
|
||||
number_of_layers = 1
|
||||
for lay in range(2,len(data)-1,1):
|
||||
if ";LAYER:" in data[lay]:
|
||||
number_of_layers += 1
|
||||
# for all layers...
|
||||
current_layer = 0
|
||||
for layer_counter in range(len(data)-2):
|
||||
current_layer += 1
|
||||
layer_index = first_layer_index + layer_counter
|
||||
display_text = base_display_text
|
||||
display_text += str(current_layer)
|
||||
# create a list where each element is a single line of code within the layer
|
||||
lines = data[layer_index].split("\n")
|
||||
if not ";LAYER:" in data[layer_index]:
|
||||
current_layer -= 1
|
||||
continue
|
||||
# add the total number of layers if this option is checked
|
||||
if display_total_layers:
|
||||
display_text += "/" + str(number_of_layers)
|
||||
# if display_remaining_time is checked, it is calculated in this loop
|
||||
if display_remaining_time:
|
||||
time_remaining_display = " | ET " # initialize the time display
|
||||
m = (self.time_total - time_elapsed) // 60 # estimated time in minutes
|
||||
m *= speed_factor # correct for printing time
|
||||
m = int(m)
|
||||
h, m = divmod(m, 60) # convert to hours and minutes
|
||||
# add the time remaining to the display_text
|
||||
if h > 0: # if it's more than 1 hour left, display format = xhxxm
|
||||
time_remaining_display += str(h) + "h"
|
||||
if m < 10: # add trailing zero if necessary
|
||||
time_remaining_display += "0"
|
||||
time_remaining_display += str(m) + "m"
|
||||
else:
|
||||
time_remaining_display += str(m) + "m"
|
||||
display_text += time_remaining_display
|
||||
# find time_elapsed at the end of the layer (used to calculate the remaining time of the next layer)
|
||||
if not current_layer == number_of_layers:
|
||||
for line_index in range(len(lines) - 1, -1, -1):
|
||||
line = lines[line_index]
|
||||
if line.startswith(";TIME_ELAPSED:"):
|
||||
# update time_elapsed for the NEXT layer and exit the loop
|
||||
time_elapsed = int(float(line.split(":")[1]))
|
||||
break
|
||||
# insert the text AFTER the first line of the layer (in case other scripts use ";LAYER:")
|
||||
for l_index, line in enumerate(lines):
|
||||
if line.startswith(";LAYER:"):
|
||||
if self.add_m117_line:
|
||||
lines[l_index] += "\nM117 " + display_text
|
||||
# add M73 line
|
||||
if self.add_m118_line:
|
||||
m118_text = "\nM118 "
|
||||
if self.add_m118_a1:
|
||||
m118_text += "A1 "
|
||||
if self.add_m118_p0:
|
||||
m118_text += "P0 "
|
||||
lines[l_index] += m118_text + display_text
|
||||
# add M73 line
|
||||
if display_remaining_time:
|
||||
mins = int(60 * h + m)
|
||||
if m73_time:
|
||||
lines[l_index] += "\nM73 R{}".format(mins)
|
||||
if self.add_m73_line and (self.add_m73_time or self.add_m73_percent):
|
||||
self.m73_str = ""
|
||||
if m73_time and display_remaining_time:
|
||||
self.m73_str += " R{}".format(mins)
|
||||
if m73_percent:
|
||||
lines[l_index] += "\nM73 P" + str(round(int(current_layer) / int(number_of_layers) * 100))
|
||||
if add_m118_line:
|
||||
lines[l_index] += "\nM118 " + display_text
|
||||
break
|
||||
# overwrite the layer with the modified layer
|
||||
data[layer_index] = "\n".join(lines)
|
||||
self.m73_str += " P" + str(round(int(current_layer) / int(number_of_layers) * 100))
|
||||
lines[l_index] += "\nM73" + self.m73_str
|
||||
break
|
||||
# overwrite the layer with the modified layer
|
||||
data[layer_index] = "\n".join(lines)
|
||||
|
||||
# If enabled then change the ET to TP for 'Time To Pause'
|
||||
if bool(self.getSettingValueByKey("countdown_to_pause")):
|
||||
time_list = []
|
||||
time_list.append("0")
|
||||
time_list.append("0")
|
||||
this_time = 0
|
||||
pause_index = 1
|
||||
time_list = []
|
||||
if bool(self.getSettingValueByKey("countdown_to_pause")):
|
||||
time_list.append("0")
|
||||
time_list.append("0")
|
||||
this_time = 0
|
||||
pause_index = 1
|
||||
|
||||
# Get the layer times
|
||||
for num in range(2,len(data) - 1):
|
||||
layer = data[num]
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith(";TIME_ELAPSED:"):
|
||||
this_time = (float(line.split(":")[1]))*speed_factor
|
||||
time_list.append(str(this_time))
|
||||
if "PauseAtHeight.py" in layer:
|
||||
# Get the layer times
|
||||
for num in range(2,len(data) - 1):
|
||||
layer = data[num]
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith(";TIME_ELAPSED:"):
|
||||
this_time = (float(line.split(":")[1]))*speed_factor
|
||||
time_list.append(str(this_time))
|
||||
for p_cmd in pause_cmd:
|
||||
if p_cmd in layer:
|
||||
for qnum in range(num - 1, pause_index, -1):
|
||||
time_list[qnum] = str(float(this_time) - float(time_list[qnum])) + "P"
|
||||
pause_index = num-1
|
||||
break
|
||||
|
||||
# Make the adjustments to the M117 (and M118) lines that are prior to a pause
|
||||
for num in range (2, len(data) - 1,1):
|
||||
layer = data[num]
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
# Make the adjustments to the M117 (and M118) lines that are prior to a pause
|
||||
for num in range (2, len(data) - 1,1):
|
||||
layer = data[num]
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
try:
|
||||
if line.startswith("M117") and "|" in line and "P" in time_list[num]:
|
||||
M117_line = line.split("|")[0] + "| TP "
|
||||
alt_time = time_list[num][:-1]
|
||||
hhh = int(float(alt_time) / 3600)
|
||||
if hhh > 0:
|
||||
hhr = str(hhh) + "h"
|
||||
else:
|
||||
hhr = ""
|
||||
mmm = ((float(alt_time) / 3600) - (int(float(alt_time) / 3600))) * 60
|
||||
sss = int((mmm - int(mmm)) * 60)
|
||||
mmm = str(round(mmm)) + "m"
|
||||
time_to_go = str(hhr) + str(mmm)
|
||||
if hhr == "": time_to_go = time_to_go + str(sss) + "s"
|
||||
M117_line = M117_line + time_to_go
|
||||
time_to_go = self._get_time_to_go(time_list[num])
|
||||
M117_line = line.split("|")[0] + "| TP " + time_to_go
|
||||
layer = layer.replace(line, M117_line)
|
||||
if line.startswith("M118") and "|" in line and "P" in time_list[num]:
|
||||
time_to_go = self._get_time_to_go(time_list[num])
|
||||
M118_line = line.split("|")[0] + "| TP " + time_to_go
|
||||
layer = layer.replace(line, M118_line)
|
||||
data[num] = layer
|
||||
setting_data = ""
|
||||
if bool(self.getSettingValueByKey("enable_end_message")):
|
||||
message_str = self.message_to_user(speed_factor)
|
||||
Message(title = "[Display Info on LCD] - Estimated Finish Time", text = message_str[0] + "\n\n" + message_str[1] + "\n" + message_str[2] + "\n" + message_str[3]).show()
|
||||
except:
|
||||
continue
|
||||
data[num] = layer
|
||||
if bool(self.getSettingValueByKey("enable_end_message")):
|
||||
message_str = self._message_to_user(data, speed_factor, pause_cmd)
|
||||
Message(title = "[Display Info on LCD] - Estimated Finish Time", text = message_str[0] + "\n\n" + message_str[1] + "\n" + message_str[2] + "\n" + message_str[3]).show()
|
||||
return data
|
||||
|
||||
def message_to_user(self, speed_factor: float):
|
||||
# Message the user of the projected finish time of the print
|
||||
def _message_to_user(self, data: str, speed_factor: float, pause_cmd: str) -> str:
|
||||
"""
|
||||
Message the user of the projected finish time of the print and when any pauses might occur
|
||||
"""
|
||||
print_time = Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)
|
||||
print_start_time = self.getSettingValueByKey("print_start_time")
|
||||
# If the user entered a print start time make sure it is in the correct format or ignore it.
|
||||
|
|
@ -476,8 +615,97 @@ class DisplayInfoOnLCD(Script):
|
|||
if print_start_time != "":
|
||||
print_start_str = "Print Start Time................." + str(print_start_time) + "hrs"
|
||||
else:
|
||||
print_start_str = "Print Start Time.................Now."
|
||||
print_start_str = "Print Start Time.................Now"
|
||||
estimate_str = "Cura Time Estimate.........." + str(print_time)
|
||||
adjusted_str = "Adjusted Time Estimate..." + str(time_change)
|
||||
finish_str = week_day + " " + str(mo_str) + " " + str(new_time.strftime("%d")) + ", " + str(new_time.strftime("%Y")) + " at " + str(show_hr) + str(new_time.strftime("%M")) + str(show_ampm)
|
||||
return finish_str, estimate_str, adjusted_str, print_start_str
|
||||
finish_str = f"{week_day} {mo_str} {new_time.strftime('%d')}, {new_time.strftime('%Y')} at {show_hr}{new_time.strftime('%M')}{show_ampm}"
|
||||
|
||||
# If there are pauses and if countdown is enabled, then add the time-to-pause to the message.
|
||||
if bool(self.getSettingValueByKey("countdown_to_pause")):
|
||||
num = 1
|
||||
for layer in data:
|
||||
for p_cmd in pause_cmd:
|
||||
if p_cmd in layer or "Do the actual pause" in layer:
|
||||
adjusted_str += "\n" + self._get_time_to_go(layer.split("TIME_ELAPSED:")[1].split("\n")[0]) + " ET from start to pause #" + str(num)
|
||||
num += 1
|
||||
return finish_str, estimate_str, adjusted_str, print_start_str
|
||||
|
||||
def _get_time_to_go(self, time_str: str):
|
||||
"""
|
||||
Converts a time string in seconds to a human-readable format (e.g., "2h30m").
|
||||
:param time_str: The time string in seconds.
|
||||
:return: A formatted string representing the time.
|
||||
"""
|
||||
alt_time = time_str[:-1]
|
||||
total_seconds = float(alt_time)
|
||||
hours = int(total_seconds // 3600)
|
||||
minutes = int((total_seconds % 3600) // 60)
|
||||
seconds = int(total_seconds % 60)
|
||||
time_to_go = f"{hours}h" if hours > 0 else ""
|
||||
time_to_go += f"{minutes}m"
|
||||
if hours == 0:
|
||||
time_to_go += f"{seconds}s"
|
||||
return time_to_go
|
||||
|
||||
def _add_stats(self, data: str) -> str:
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
"""
|
||||
Make a list of the models in the file.
|
||||
Add some of the filament stats to the first section of the gcode.
|
||||
"""
|
||||
model_list = []
|
||||
for mdex, layer in enumerate(data):
|
||||
layer = data[mdex].split("\n")
|
||||
for line in layer:
|
||||
if line.startswith(";MESH:") and "NONMESH" not in line:
|
||||
model_name = line.split(":")[1]
|
||||
if not model_name in model_list:
|
||||
model_list.append(model_name)
|
||||
# Filament stats
|
||||
extruder_count = global_stack.getProperty("machine_extruder_count", "value")
|
||||
layheight_0 = global_stack.getProperty("layer_height_0", "value")
|
||||
init_layer_hgt_line = ";Initial Layer Height: " + f"{layheight_0:.2f}".format(layheight_0)
|
||||
filament_line_t0 = ";Extruder 1 (T0)\n"
|
||||
filament_amount = Application.getInstance().getPrintInformation().materialLengths
|
||||
filament_line_t0 += f"; Filament used: {filament_amount[0]}m\n"
|
||||
filament_line_t0 += f"; Filament Type: {global_stack.extruderList[0].material.getMetaDataEntry("material", "")}\n"
|
||||
filament_line_t0 += f"; Filament Dia.: {global_stack.extruderList[0].getProperty("material_diameter", "value")}mm\n"
|
||||
filament_line_t0 += f"; Nozzle Size : {global_stack.extruderList[0].getProperty("machine_nozzle_size", "value")}mm\n"
|
||||
filament_line_t0 += f"; Print Temp. : {global_stack.extruderList[0].getProperty("material_print_temperature", "value")}°\n"
|
||||
filament_line_t0 += f"; Bed Temp. : {global_stack.extruderList[0].getProperty("material_bed_temperature", "value")}°"
|
||||
|
||||
# if there is more than one extruder then get the stats for the second one.
|
||||
filament_line_t1 = ""
|
||||
if extruder_count > 1:
|
||||
filament_line_t1 = "\n;Extruder 2 (T1)\n"
|
||||
filament_line_t1 += f"; Filament used: {filament_amount[1]}m\n"
|
||||
filament_line_t1 += f"; Filament Type: {global_stack.extruderList[1].material.getMetaDataEntry("material", "")}\n"
|
||||
filament_line_t1 += f"; Filament Dia.: {global_stack.extruderList[1].getProperty("material_diameter", "value")}mm\n"
|
||||
filament_line_t1 += f"; Nozzle Size : {global_stack.extruderList[1].getProperty("machine_nozzle_size", "value")}mm\n"
|
||||
filament_line_t1 += f"; Print Temp. : {global_stack.extruderList[1].getProperty("material_print_temperature", "value")}°"
|
||||
|
||||
# Calculate the cost of electricity for the print
|
||||
electricity_cost = self.getSettingValueByKey("electricity_cost")
|
||||
printer_power_usage = self.getSettingValueByKey("printer_power_usage")
|
||||
currency_unit = Application.getInstance().getPreferences().getValue("cura/currency")
|
||||
total_cost_electricity = (printer_power_usage / 1000) * (self.time_total / 3600) * electricity_cost
|
||||
|
||||
# Add the stats to the gcode file
|
||||
lines = data[0].split("\n")
|
||||
for index, line in enumerate(lines):
|
||||
if line.startswith(";Layer height:") or line.startswith(";TARGET_MACHINE.NAME:"):
|
||||
lines[index] = ";Layer height: " + f"{global_stack.getProperty("layer_height", "value")}"
|
||||
lines[index] += f"\n{init_layer_hgt_line}"
|
||||
lines[index] += f"\n;Base Quality Name : '{global_stack.quality.getMetaDataEntry("name", "")}'"
|
||||
lines[index] += f"\n;Custom Quality Name: '{global_stack.qualityChanges.getMetaDataEntry("name")}'"
|
||||
if line.startswith(";Filament used"):
|
||||
lines[index] = filament_line_t0 + filament_line_t1 + f"\n;Electric Cost: {currency_unit}{total_cost_electricity:.2f}".format(total_cost_electricity)
|
||||
# The target machine "machine_name" is actually the printer model. This adds the user defined printer name to the "TARGET_MACHINE" line.
|
||||
if line.startswith(";TARGET_MACHINE"):
|
||||
machine_model = str(global_stack.getProperty("machine_name", "value"))
|
||||
machine_name = str(global_stack.getName())
|
||||
lines[index] += f" / {machine_name}"
|
||||
if "MINX" in line or "MIN.X" in line:
|
||||
# Add the Object List
|
||||
lines[index - 1] += f"\n;Model List: {str(model_list)}"
|
||||
return "\n".join(lines)
|
||||
989
plugins/PostProcessingPlugin/scripts/TweakAtZ.py
Normal file
|
|
@ -0,0 +1,989 @@
|
|||
"""
|
||||
TweakAtZ 2.0 is a re-write of ChangeAtZ by GregValiant (Greg Foresi) March 2025
|
||||
The script name change was required to avoid conflicts with project files that might use ChangeAtZ variables.
|
||||
Differences from the previous ChangeAtZ version (5.3.0):
|
||||
~"By Height" will work with Z-hops enabled, Adaptive Layers, Scarf Z-seam, and Rafts. The changes will commence at the first layer where the height is reached or exceeded. The changes will end at the start of the layer where the End Height is reached or exceeded.
|
||||
~The user can opt to change just the print speed or both print and travel speeds. The 'F' parameters are re-calculated line-by-line using the percentage that the user inputs. Speeds can now be changed 'per extruder'. M220 is no longer used to change speeds as it affected all speeds.
|
||||
~Changing the print speed no longer affects retraction or unretract speeds.
|
||||
~The Z-hop speed is never affected.
|
||||
~The 'Output to LCD' setting is obsolete to avoid flooding the screen with messages that were quickly over-written.
|
||||
~Allows the user to select a Range of Layers (rather than just 'Single Layer' or 'To the End'.)
|
||||
~Added support for control of a single fan. This might be a Build Volume Fan, Auxilliary Fan, or a Layer Cooling Fan. It would depend on the fan circuit number that the user inputs.
|
||||
~Added support for Relative Extrusion
|
||||
~Added support for Firmware Retraction
|
||||
~Added support for 'G2' and 'G3' moves.
|
||||
~The script supports a maximum of 2 extruders.
|
||||
~'One-at-a-Time' is not supported and a kick-out is added
|
||||
|
||||
Previous contributions by:
|
||||
Original Authors and contributors to the ChangeAtZ post-processing script and the earlier TweakAtZ:
|
||||
Written by Steven Morlock,
|
||||
Modified by Ricardo Gomez, to add Bed Temperature and make it work with Cura_13.06.04+
|
||||
Modified by Stefan Heule, since V3.0
|
||||
Modified by Jaime van Kessel (Ultimaker), to make it work for 15.10 / 2.x
|
||||
Modified by Ghostkeeper (Ultimaker), to debug.
|
||||
Modified by Wes Hanney, Retract Length + Speed, Clean up
|
||||
Modified by Alex Jaxon, Added option to modify Build Volume Temperature
|
||||
Re-write by GregValiant, to work with new variables in Cura 5.x and with the changes noted above
|
||||
"""
|
||||
|
||||
from UM.Application import Application
|
||||
from ..Script import Script
|
||||
import re
|
||||
from UM.Message import Message
|
||||
from UM.Logger import Logger
|
||||
|
||||
class TweakAtZ(Script):
|
||||
version = "2.0.0"
|
||||
|
||||
def initialize(self) -> None:
|
||||
"""
|
||||
Prepare the script settings for the machine hardware configuration on Cura opening
|
||||
"""
|
||||
super().initialize()
|
||||
self.global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
self.extruder_count = int(self.global_stack.getProperty("machine_extruder_count", "value"))
|
||||
# If the printer is multi-extruder then enable the settings for T1
|
||||
if self.extruder_count == 1:
|
||||
self._instance.setProperty("multi_extruder", "value", False)
|
||||
else:
|
||||
self._instance.setProperty("multi_extruder", "value", True)
|
||||
|
||||
# Enable the build volume temperature change when a heated build volume is present
|
||||
machine_heated_build_volume = bool(self.global_stack.getProperty("machine_heated_build_volume", "value"))
|
||||
if machine_heated_build_volume:
|
||||
self._instance.setProperty("heated_build_volume", "value", True)
|
||||
else:
|
||||
self._instance.setProperty("heated_build_volume", "value", False)
|
||||
|
||||
# If it doesn't have a heated bed it is unlikely to have a Chamber or Auxiliary fan.
|
||||
has_heated_bed = bool(self.global_stack.getProperty("machine_heated_bed", "value"))
|
||||
if has_heated_bed:
|
||||
self._instance.setProperty("has_bv_fan", "value", True)
|
||||
|
||||
def getSettingDataString(self):
|
||||
return """{
|
||||
"name": "Tweak At Z (2.0)",
|
||||
"key": "TweakAtZ",
|
||||
"metadata": {},
|
||||
"version": 2,
|
||||
"settings": {
|
||||
"taz_enabled": {
|
||||
"label": "Enable Tweak at Z",
|
||||
"description": "Enables the script so it will run. You may have more than one instance of 'Tweak At Z' in the list of post processors.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"enabled": true
|
||||
},
|
||||
"by_layer_or_height": {
|
||||
"label": "'By Layer' or 'By Height'",
|
||||
"description": "Which criteria to use to start and end the changes.",
|
||||
"type": "enum",
|
||||
"options":
|
||||
{
|
||||
"by_layer": "By Layer",
|
||||
"by_height": "By Height"
|
||||
},
|
||||
"default_value": "by_layer",
|
||||
"enabled": "taz_enabled"
|
||||
},
|
||||
"a_start_layer": {
|
||||
"label": "Start Layer",
|
||||
"description": "Layer number to start the changes at. Use the Cura preview layer numbers. The changes will start at the beginning of the layer.",
|
||||
"type": "int",
|
||||
"default_value": 1,
|
||||
"minimum_value": -7,
|
||||
"minimum_value_warning": 1,
|
||||
"unit": "Layer #",
|
||||
"enabled": "taz_enabled and by_layer_or_height == 'by_layer'"
|
||||
},
|
||||
"a_end_layer": {
|
||||
"label": "End Layer",
|
||||
"description": "Use '-1' to indicate the end of the last layer. The changes will end at the end of the indicated layer. Use the Cura preview layer number. If the 'Start Layer' is equal to the 'End Layer' then the changes only affect that single layer.",
|
||||
"type": "int",
|
||||
"default_value": -1,
|
||||
"unit": "Layer #",
|
||||
"enabled": "taz_enabled and by_layer_or_height == 'by_layer'"
|
||||
},
|
||||
"a_height_start": {
|
||||
"label": "Height Start of Changes",
|
||||
"description": "Enter the 'Z-Height' to Start the changes at. The changes START at the beginning of the first layer where this height is reached (or exceeded). If the model is on a raft then this height will be from the top of the air gap (first height of the actual model print).",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"unit": "mm",
|
||||
"enabled": "taz_enabled and by_layer_or_height == 'by_height'"
|
||||
},
|
||||
"a_height_end": {
|
||||
"label": "Height End of Changes",
|
||||
"description": "Enter the 'Z-Height' to End the changes at. The changes continue until this height is reached or exceeded. If the model is on a raft then this height will be from the top of the air gap (first height of the actual model print).",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"unit": "mm",
|
||||
"enabled": "taz_enabled and by_layer_or_height == 'by_height'"
|
||||
},
|
||||
"b_change_speed": {
|
||||
"label": "Change Speeds",
|
||||
"description": "Check to enable a speed change for the Print Speeds.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "taz_enabled"
|
||||
},
|
||||
"change_speed_per_extruder": {
|
||||
"label": " Which extruder(s)",
|
||||
"description": "For multi-extruder printers the changes can be for either or both extruders.",
|
||||
"type": "enum",
|
||||
"options": {
|
||||
"ext_0": "Extruder 1 (T0)",
|
||||
"ext_1": "Extruder 2 (T1)",
|
||||
"ext_both": "Both extruders"},
|
||||
"default_value": "ext_both",
|
||||
"enabled": "taz_enabled and b_change_speed and multi_extruder"
|
||||
},
|
||||
"b_change_printspeed": {
|
||||
"label": " Include Travel Speeds",
|
||||
"description": "Check this box to change the Travel Speeds as well as the Print Speeds.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "b_change_speed and taz_enabled"
|
||||
},
|
||||
"b_speed": {
|
||||
"label": " Speed %",
|
||||
"description": "Speed factor as a percentage. The chosen speeds will be altered by this much.",
|
||||
"unit": "% ",
|
||||
"type": "int",
|
||||
"default_value": 100,
|
||||
"minimum_value": 10,
|
||||
"minimum_value_warning": 50,
|
||||
"maximum_value_warning": 200,
|
||||
"enabled": "b_change_speed and taz_enabled"
|
||||
},
|
||||
"c_change_flowrate": {
|
||||
"label": "Change Flow Rate",
|
||||
"description": "Select to change the flow rate of all extrusions in the layer range. This command uses M221 to set the flow percentage in the printer.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "taz_enabled"
|
||||
},
|
||||
"c_flowrate_t0": {
|
||||
"label": " Flow Rate % (T0)",
|
||||
"description": "Enter the new Flow Rate Percentage. For a multi-extruder printer this will apply to Extruder 1 (T0).",
|
||||
"unit": "% ",
|
||||
"type": "int",
|
||||
"default_value": 100,
|
||||
"minimum_value": 25,
|
||||
"minimum_value_warning": 50,
|
||||
"maximum_value_warning": 150,
|
||||
"maximum_value": 200,
|
||||
"enabled": "c_change_flowrate and taz_enabled"
|
||||
},
|
||||
"multi_extruder": {
|
||||
"label": "Hidden setting to enable 2nd extruder settings for multi-extruder printers.",
|
||||
"description": "Enable T1 options.",
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
"default_value": false,
|
||||
"enabled": false
|
||||
},
|
||||
"c_flowrate_t1": {
|
||||
"label": " Flow Rate % T1",
|
||||
"description": "New Flow rate percentage for Extruder 2 (T1).",
|
||||
"unit": "% ",
|
||||
"type": "int",
|
||||
"default_value": 100,
|
||||
"minimum_value": 1,
|
||||
"minimum_value_warning": 10,
|
||||
"maximum_value_warning": 200,
|
||||
"enabled": "multi_extruder and c_change_flowrate and taz_enabled"
|
||||
},
|
||||
"d_change_bed_temp": {
|
||||
"label": "Change Bed Temp",
|
||||
"description": "Select if Bed Temperature is to be changed. The bed temperature will revert at the End Layer.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "taz_enabled"
|
||||
},
|
||||
"d_bedTemp": {
|
||||
"label": " Bed Temp",
|
||||
"description": "New Bed Temperature",
|
||||
"unit": "°C ",
|
||||
"type": "int",
|
||||
"default_value": 60,
|
||||
"minimum_value": 0,
|
||||
"minimum_value_warning": 30,
|
||||
"maximum_value_warning": 120,
|
||||
"enabled": "d_change_bed_temp and taz_enabled"
|
||||
},
|
||||
"heated_build_volume": {
|
||||
"label": "Hidden setting",
|
||||
"description": "This enables the build volume settings",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": false
|
||||
},
|
||||
"e_change_build_volume_temperature": {
|
||||
"label": "Change Build Volume Temperature",
|
||||
"description": "Select if Build Volume Temperature is to be changed",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "heated_build_volume and taz_enabled"
|
||||
},
|
||||
"e_build_volume_temperature": {
|
||||
"label": " Build Volume Temperature",
|
||||
"description": "New Build Volume Temperature. This will revert at the end of the End Layer.",
|
||||
"unit": "°C ",
|
||||
"type": "int",
|
||||
"default_value": 20,
|
||||
"minimum_value": 0,
|
||||
"minimum_value_warning": 15,
|
||||
"maximum_value_warning": 80,
|
||||
"enabled": "heated_build_volume and e_change_build_volume_temperature and taz_enabled"
|
||||
},
|
||||
"f_change_extruder_temperature": {
|
||||
"label": "Change Print Temp",
|
||||
"description": "Select if the Printing Temperature is to be changed",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "taz_enabled"
|
||||
},
|
||||
"f_extruder_temperature_t0": {
|
||||
"label": " Extruder 1 Temp (T0)",
|
||||
"description": "New temperature for Extruder 1 (T0).",
|
||||
"unit": "°C ",
|
||||
"type": "int",
|
||||
"default_value": 190,
|
||||
"minimum_value": 0,
|
||||
"minimum_value_warning": 160,
|
||||
"maximum_value_warning": 250,
|
||||
"enabled": "f_change_extruder_temperature and taz_enabled"
|
||||
},
|
||||
"f_extruder_temperature_t1": {
|
||||
"label": " Extruder 2 Temp (T1)",
|
||||
"description": "New temperature for Extruder 2 (T1).",
|
||||
"unit": "°C ",
|
||||
"type": "int",
|
||||
"default_value": 190,
|
||||
"minimum_value": 0,
|
||||
"minimum_value_warning": 160,
|
||||
"maximum_value_warning": 250,
|
||||
"enabled": "multi_extruder and f_change_extruder_temperature and taz_enabled"
|
||||
},
|
||||
"g_change_retract": {
|
||||
"label": "Change Retraction Settings",
|
||||
"description": "Indicates you would like to modify retraction properties. If 'Firmware Retraction' is enabled then M207 and M208 lines are added. Your firmware must understand those commands.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "taz_enabled and not multi_extruder"
|
||||
},
|
||||
"g_change_retract_speed": {
|
||||
"label": " Change Retract/Prime Speed",
|
||||
"description": "Changes the retraction and prime speed.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "g_change_retract and taz_enabled and not multi_extruder"
|
||||
},
|
||||
"g_retract_speed": {
|
||||
"label": " Retract/Prime Speed",
|
||||
"description": "New Retract Feed Rate (mm/s). If 'Firmware Retraction' is enabled then M207 and M208 are used to change the retract and prime speeds and the distance. NOTE: the same speed will be used for both retract and prime.",
|
||||
"unit": "mm/s ",
|
||||
"type": "float",
|
||||
"default_value": 40,
|
||||
"minimum_value": 1,
|
||||
"minimum_value_warning": 0,
|
||||
"maximum_value_warning": 100,
|
||||
"enabled": "g_change_retract and g_change_retract_speed and taz_enabled and not multi_extruder"
|
||||
},
|
||||
"g_change_retract_amount": {
|
||||
"label": " Change Retraction Amount",
|
||||
"description": "Changes the retraction length during print",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "g_change_retract and taz_enabled and not multi_extruder"
|
||||
},
|
||||
"g_retract_amount": {
|
||||
"label": " Retract Amount",
|
||||
"description": "New Retraction Distance (mm). If firmware retraction is used then M207 and M208 are used to change the retract and prime amount.",
|
||||
"unit": "mm ",
|
||||
"type": "float",
|
||||
"default_value": 6.5,
|
||||
"minimum_value": 0,
|
||||
"maximum_value_warning": 20,
|
||||
"enabled": "g_change_retract and g_change_retract_amount and taz_enabled and not multi_extruder"
|
||||
},
|
||||
"enable_bv_fan_change": {
|
||||
"label": "Chamber/Aux Fan Change",
|
||||
"description": "Can alter the setting of a secondary fan when the printer is equipped with one.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "has_bv_fan and taz_enabled"
|
||||
},
|
||||
"e1_build_volume_fan_speed": {
|
||||
"label": " Chamber/Aux Fan Speed",
|
||||
"description": "New Build Volume or Auxiliary Fan Speed. This will reset to zero at the end of the 'End Layer'.",
|
||||
"unit": "%",
|
||||
"type": "int",
|
||||
"default_value": 100,
|
||||
"minimum_value": 0,
|
||||
"maximum_value": 100,
|
||||
"enabled": "has_bv_fan and enable_bv_fan_change and taz_enabled"
|
||||
},
|
||||
"bv_fan_nr": {
|
||||
"label": " Chamber/Aux Fan Number",
|
||||
"description": "The circuit number of the Auxilliary or Chamber fan. M106 will be used and the 'P' parameter (the fan number) will be the number entered here.",
|
||||
"type": "int",
|
||||
"unit": "#",
|
||||
"default_value": 3,
|
||||
"minimum_value": 0,
|
||||
"enabled": "has_bv_fan and enable_bv_fan_change and taz_enabled"
|
||||
},
|
||||
"has_bv_fan": {
|
||||
"label": "Hidden setting",
|
||||
"description": "Enables the Build Volume/Auxiliary fan speed control when 'machine_heated_bed' is true.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
def execute(self, data):
|
||||
# Exit if the script isn't enabled
|
||||
if not bool(self.getSettingValueByKey("taz_enabled")):
|
||||
data[0] += "; [Tweak at Z] is not enabled\n"
|
||||
Logger.log("i", "[Tweak at Z] is not enabled")
|
||||
return data
|
||||
|
||||
# Message the user and exit if the print sequence is 'One at a Time'
|
||||
if self.global_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
Message(title = "[Tweak at Z]", text = "One-at-a-Time mode is not supported. The script will exit without making any changes.").show()
|
||||
data[0] += "; [Tweak at Z] Did not run (One at a Time mode is not supported)\n"
|
||||
Logger.log("i", "TweakAtZ does not support 'One at a Time' mode")
|
||||
return data
|
||||
|
||||
# Exit if the gcode has been previously post-processed.
|
||||
if ";POSTPROCESSED" in data[0]:
|
||||
return data
|
||||
|
||||
# Pull some settings from Cura
|
||||
self.extruder_list = self.global_stack.extruderList
|
||||
self.firmware_retraction = bool(self.global_stack.getProperty("machine_firmware_retract", "value"))
|
||||
self.relative_extrusion = bool(self.global_stack.getProperty("relative_extrusion", "value"))
|
||||
self.initial_layer_height = self.global_stack.getProperty("layer_height_0", "value")
|
||||
self.heated_build_volume = bool(self.global_stack.getProperty("machine_heated_build_volume", "value"))
|
||||
self.heated_bed = bool(self.global_stack.getProperty("machine_heated_bed", "value"))
|
||||
self.retract_enabled = bool(self.extruder_list[0].getProperty("retraction_enable", "value"))
|
||||
self.orig_bed_temp = self.global_stack.getProperty("material_bed_temperature", "value")
|
||||
self.orig_bv_temp = self.global_stack.getProperty("build_volume_temperature", "value")
|
||||
self.z_hop_enabled = bool(self.extruder_list[0].getProperty("retraction_hop_enabled", "value"))
|
||||
self.raft_enabled = True if str(self.global_stack.getProperty("adhesion_type", "value")) == "raft" else False
|
||||
# The Start and end layer numbers are used when 'By Layer' is selected
|
||||
self.start_layer = self.getSettingValueByKey("a_start_layer") - 1
|
||||
end_layer = int(self.getSettingValueByKey("a_end_layer"))
|
||||
nbr_raft_layers = 0
|
||||
if self.raft_enabled:
|
||||
for layer in data:
|
||||
if ";LAYER:-" in layer:
|
||||
nbr_raft_layers += 1
|
||||
if ";LAYER:0\n" in layer:
|
||||
break
|
||||
|
||||
# Adjust the start layer to account for any raft layers
|
||||
self.start_layer -= nbr_raft_layers
|
||||
|
||||
# Find the indexes of the Start and End layers if 'By Layer'
|
||||
self.start_index = 0
|
||||
|
||||
# When retraction is enabled it adds a single line item to the data list
|
||||
self.end_index = len(data) - 1 - int(self.retract_enabled)
|
||||
if self.getSettingValueByKey("by_layer_or_height") == "by_layer":
|
||||
for index, layer in enumerate(data):
|
||||
if ";LAYER:" + str(self.start_layer) + "\n" in layer:
|
||||
self.start_index = index
|
||||
break
|
||||
|
||||
# If the changes continue to the top layer
|
||||
if end_layer == -1:
|
||||
if self.retract_enabled:
|
||||
self.end_index = len(data) - 2
|
||||
else:
|
||||
self.end_index = len(data) - 1
|
||||
|
||||
# If the changes end below the top layer
|
||||
else:
|
||||
|
||||
# Adjust the end layer from base1 numbering to base0 numbering
|
||||
end_layer -= 1
|
||||
|
||||
# Adjust the End Layer if it is not the top layer and if bed adhesion is 'raft'
|
||||
end_layer -= nbr_raft_layers
|
||||
for index, layer in enumerate(data):
|
||||
if ";LAYER:" + str(end_layer) + "\n" in layer:
|
||||
self.end_index = index
|
||||
break
|
||||
|
||||
# The Start and End heights are used to find the Start and End indexes when changes are 'By Height'
|
||||
elif self.getSettingValueByKey("by_layer_or_height") == "by_height":
|
||||
start_height = float(self.getSettingValueByKey("a_height_start"))
|
||||
end_height = float(self.getSettingValueByKey("a_height_end"))
|
||||
# Get the By Height start and end indexes
|
||||
self.start_index = self._is_legal_z(data, start_height)
|
||||
self.end_index = self._is_legal_z(data, end_height) - 1
|
||||
|
||||
# Exit if the Start Layer wasn't found
|
||||
if self.start_index == 0:
|
||||
Message(title = "[Tweak at Z]", text = "The 'Start Layer' is beyond the top of the print. The script did not run.").show()
|
||||
Logger.log("w", "[Tweak at Z] The 'Start Layer' is beyond the top of the print. The script did not run.")
|
||||
return data
|
||||
|
||||
# Adjust the End Index if the End Index < Start Index (required for the script to make changes)
|
||||
if self.end_index < self.start_index:
|
||||
self.start_index = self.end_index
|
||||
Message(title = "[Tweak at Z]", text = "Check the Gcode. Your 'Start Layer/Height' input is higher than the End Layer/Height input. The Start Layer has been adjusted to equal the End Layer.").show()
|
||||
|
||||
# Map settings to corresponding methods
|
||||
procedures = {
|
||||
"b_change_speed": self._change_speed,
|
||||
"c_change_flowrate": self._change_flow,
|
||||
"d_change_bed_temp": self._change_bed_temp,
|
||||
"e_change_build_volume_temperature": self._change_bv_temp,
|
||||
"f_change_extruder_temperature": self._change_hotend_temp,
|
||||
"g_change_retract": self._change_retract,
|
||||
"has_bv_fan": self._change_bv_fan_speed
|
||||
}
|
||||
|
||||
# Run the selected procedures
|
||||
for setting, method in procedures.items():
|
||||
if self.getSettingValueByKey(setting):
|
||||
method(data)
|
||||
data = self._format_lines(data)
|
||||
return data
|
||||
|
||||
def _change_speed(self, data:str)->str:
|
||||
"""
|
||||
The actual speed will be a percentage of the Cura calculated 'F' values in the gcode. The percentage can be different for each extruder. Travel speeds can also be affected dependent on the user input.
|
||||
:params:
|
||||
speed_x: The speed percentage to use
|
||||
print_speed_only: Only change speeds with extrusions (but not retract or primes)
|
||||
target_extruder: For multi-extruder printers this is the active extruder
|
||||
off_extruder: For multi-extruders this is the inactive extruder.
|
||||
"""
|
||||
# Since a single extruder changes all relevant speed settings then for a multi-extruder 'both extruders' is the same
|
||||
if self.extruder_count == 1 or self.getSettingValueByKey("change_speed_per_extruder") == "ext_both":
|
||||
speed_x = self.getSettingValueByKey("b_speed")/100
|
||||
print_speed_only = not bool(self.getSettingValueByKey("b_change_printspeed"))
|
||||
for index, layer in enumerate(data):
|
||||
if index >= self.start_index and index <= self.end_index:
|
||||
lines = layer.splitlines()
|
||||
for l_index, line in enumerate(lines):
|
||||
if self._f_x_y_not_z(line):
|
||||
f_value = self.getValue(line, "F")
|
||||
if line.startswith(("G1", "G2", "G3")):
|
||||
lines[l_index] = line.replace("F" + str(f_value), "F" + str(round(f_value * speed_x)))
|
||||
lines[l_index] += f" ; TweakAtZ: {round(speed_x * 100)}% Print Speed"
|
||||
continue
|
||||
if not print_speed_only and line.startswith("G0"):
|
||||
lines[l_index] = line.replace("F" + str(f_value), "F" + str(round(f_value * speed_x)))
|
||||
lines[l_index] += f" ; TweakAtZ: {round(speed_x * 100)}% Travel Speed"
|
||||
data[index] = "\n".join(lines) + "\n"
|
||||
elif self.extruder_count > 1:
|
||||
speed_x = self.getSettingValueByKey("b_speed")/100
|
||||
print_speed_only = not bool(self.getSettingValueByKey("b_change_printspeed"))
|
||||
target_extruder = self.getSettingValueByKey("change_speed_per_extruder")
|
||||
|
||||
# These variables are used as the 'turn changes on' and 'turn changes off' at tool changes.
|
||||
if target_extruder == "ext_0":
|
||||
target_extruder = "T0"
|
||||
off_extruder = "T1"
|
||||
elif target_extruder == "ext_1":
|
||||
target_extruder = "T1"
|
||||
off_extruder = "T0"
|
||||
|
||||
# After all of that it goes to work.
|
||||
for index, layer in enumerate(data):
|
||||
if index < self.start_index:
|
||||
lineT = layer.splitlines()
|
||||
for tline in lineT:
|
||||
if "T0" in tline:
|
||||
active_tool = "T0"
|
||||
if "T1" in tline:
|
||||
active_tool = "T1"
|
||||
if index >= self.start_index and index <= self.end_index:
|
||||
lines = layer.splitlines()
|
||||
for l_index, line in enumerate(lines):
|
||||
if active_tool == target_extruder:
|
||||
if self._f_x_y_not_z(line):
|
||||
f_value = self.getValue(line, "F")
|
||||
if line.startswith(("G1", "G2", "G3")):
|
||||
lines[l_index] = line.replace("F" + str(f_value), "F" + str(round(f_value * speed_x)))
|
||||
lines[l_index] += f" ; TweakAtZ: {round(speed_x * 100)}% Print Speed"
|
||||
continue
|
||||
if not print_speed_only and line.startswith("G0"):
|
||||
lines[l_index] = line.replace("F" + str(f_value), "F" + str(round(f_value * speed_x)))
|
||||
lines[l_index] += f" ; TweakAtZ: {round(speed_x * 100)}% Travel Speed"
|
||||
if line.startswith(off_extruder):
|
||||
active_tool = off_extruder
|
||||
if line.startswith(target_extruder):
|
||||
active_tool = target_extruder
|
||||
|
||||
data[index] = "\n".join(lines) + "\n"
|
||||
return data
|
||||
|
||||
def _change_flow(self, data:str)->str:
|
||||
"""
|
||||
M221 is used to change the flow rate.
|
||||
:params:
|
||||
new_flow_ext_0: The flowrate percentage from these script settings (for the primary extruder)
|
||||
new_flowrate_0: The string to use for the new flowrate for T0
|
||||
reset_flowrate_0: Resets the flowrate to 100% (for either extruder)
|
||||
new_flow_ext_1: The flowrate percentage from these script settings (for the secondary extruder)
|
||||
new_flowrate_1: The string to use for the new flowrate for T1
|
||||
"""
|
||||
new_flow_ext_0 = self.getSettingValueByKey("c_flowrate_t0")
|
||||
new_flowrate_0 = f"\nM221 S{new_flow_ext_0} ; TweakAtZ: Alter Flow Rate"
|
||||
reset_flowrate_0 = "\nM221 S100 ; TweakAtZ: Reset Flow Rate"
|
||||
if self.extruder_count > 1:
|
||||
new_flow_ext_1 = self.getSettingValueByKey("c_flowrate_t1")
|
||||
new_flowrate_1 = f"\nM221 S{new_flow_ext_1} ; TweakAtZ: Alter Flow Rate"
|
||||
else:
|
||||
new_flowrate_1 = ""
|
||||
|
||||
# For single extruder
|
||||
if self.extruder_count == 1:
|
||||
lines = data[self.start_index].splitlines()
|
||||
lines[0] += new_flowrate_0
|
||||
data[self.start_index] = "\n".join(lines) + "\n"
|
||||
lines = data[self.end_index].splitlines()
|
||||
lines[len(lines) - 2] += reset_flowrate_0
|
||||
data[self.end_index] = "\n".join(lines) + "\n"
|
||||
|
||||
# For dual-extruders
|
||||
elif self.extruder_count > 1:
|
||||
for index, layer in enumerate(data):
|
||||
if index < self.start_index:
|
||||
continue
|
||||
else:
|
||||
lines = layer.splitlines()
|
||||
for l_index, line in enumerate(lines):
|
||||
if line.startswith("T0"):
|
||||
lines[l_index] += new_flowrate_0 + " T0"
|
||||
if line.startswith("T1"):
|
||||
lines[l_index] += new_flowrate_1 + " T1"
|
||||
data[index] = "\n".join(lines) + "\n"
|
||||
if index == self.end_index:
|
||||
lines = data[index].splitlines()
|
||||
lines[len(lines) - 2] += "\nM221 S100 ; TweakAtZ: Reset Flow Rate"
|
||||
data[index] = "\n".join(lines) + "\n"
|
||||
break
|
||||
if index > self.end_index:
|
||||
break
|
||||
return data
|
||||
|
||||
def _change_bed_temp(self, data:str)->str:
|
||||
"""
|
||||
Change the Bed Temperature at height or layer
|
||||
:params:
|
||||
new_bed_temp: The new temperature from the settings for this script
|
||||
"""
|
||||
if not self.heated_bed:
|
||||
return data
|
||||
new_bed_temp = self.getSettingValueByKey("d_bedTemp")
|
||||
if self.start_index == 2:
|
||||
if "M140 S" in data[2]:
|
||||
data[2] = re.sub("M140 S", ";M140 S", data[2])
|
||||
if "M140 S" in data[3]:
|
||||
data[3] = re.sub("M140 S", ";M140 S", data[3])
|
||||
lines = data[self.start_index].splitlines()
|
||||
lines[0] += "\nM140 S" + str(new_bed_temp) + " ; TweakAtZ: Change Bed Temperature"
|
||||
data[self.start_index] = "\n".join(lines) + "\n"
|
||||
lines = data[self.end_index].splitlines()
|
||||
lines[len(lines) - 2] += "\nM140 S" + str(self.orig_bed_temp) + " ; TweakAtZ: Reset Bed Temperature"
|
||||
data[self.end_index] = "\n".join(lines) + "\n"
|
||||
return data
|
||||
|
||||
def _change_bv_temp(self, data:str)->str:
|
||||
"""
|
||||
Change the Build Volume temperature at height or layer
|
||||
:param:
|
||||
new_bv_temp: The new temperature from the settings for this script
|
||||
"""
|
||||
if not self.heated_build_volume:
|
||||
return data
|
||||
new_bv_temp = self.getSettingValueByKey("e_build_volume_temperature")
|
||||
lines = data[self.start_index].splitlines()
|
||||
lines[0] += "\nM141 S" + str(new_bv_temp) + " ; TweakAtZ: Change Build Volume Temperature"
|
||||
data[self.start_index] = "\n".join(lines) + "\n"
|
||||
lines = data[self.end_index].splitlines()
|
||||
lines[len(lines) - 2] += "\nM141 S" + str(self.orig_bv_temp) + " ; TweakAtZ: Reset Build Volume Temperature"
|
||||
data[self.end_index] = "\n".join(lines) + "\n"
|
||||
return data
|
||||
|
||||
def _change_hotend_temp(self, data:str)->str:
|
||||
"""
|
||||
Changes to the hot end temperature(s).
|
||||
:params:
|
||||
extruders_share_heater: Lets the script know how to handle the differences
|
||||
active_tool: Tracks the active tool through the gcode
|
||||
new_hotend_temp_0: The new temperature for the primary extruder T0
|
||||
orig_hot_end_temp_0: The print temperature for the primary extruder T0 as set in Cura
|
||||
orig_standby_temp_0: The standby temperature for the primary extruder T0 from Cura. This marks a temperature line to ignore.
|
||||
new_hotend_temp_1: The new temperature for the secondary extruder T1
|
||||
orig_hot_end_temp_1: The print temperature for the secondary extruder T1 as set in Cura
|
||||
orig_standby_temp_1: The standby temperature for the secondary extruder T1 from Cura. This marks a temperature line to ignore.
|
||||
"""
|
||||
extruders_share_heater = bool(self.global_stack.getProperty("machine_extruders_share_heater", "value"))
|
||||
self.active_tool = "T0"
|
||||
self.new_hotend_temp_0 = self.getSettingValueByKey("f_extruder_temperature_t0")
|
||||
self.orig_hot_end_temp_0 = int(self.extruder_list[0].getProperty("material_print_temperature", "value"))
|
||||
self.orig_standby_temp_0 = int(self.extruder_list[0].getProperty("material_standby_temperature", "value"))
|
||||
|
||||
# Start with single extruder machines
|
||||
if self.extruder_count == 1:
|
||||
if self.start_index == 2:
|
||||
if "M104 S" in data[2]:
|
||||
data[2] = re.sub("M104 S", ";M104 S", data[2])
|
||||
if "M104 S" in data[3]:
|
||||
data[3] = re.sub("M104 S", ";M104 S", data[3])
|
||||
|
||||
# Add the temperature change at the beginning of the start layer
|
||||
lines = data[self.start_index].splitlines()
|
||||
for index, line in enumerate(lines):
|
||||
lines[0] += "\n" + "M104 S" + str(self.new_hotend_temp_0) + " ; TweakAtZ: Change Nozzle Temperature"
|
||||
data[self.start_index] = "\n".join(lines) + "\n"
|
||||
break
|
||||
|
||||
# Revert the temperature to the Cura setting at the end of the end layer
|
||||
lines = data[self.end_index].splitlines()
|
||||
for index, line in enumerate(lines):
|
||||
lines[len(lines) - 2] += "\n" + "M104 S" + str(self.orig_hot_end_temp_0) + " ; TweakAtZ: Reset Nozzle Temperature"
|
||||
data[self.end_index] = "\n".join(lines) + "\n"
|
||||
break
|
||||
|
||||
# Multi-extruder machines
|
||||
elif self.extruder_count > 1:
|
||||
self.new_hotend_temp_1 = self.getSettingValueByKey("f_extruder_temperature_t1")
|
||||
self.orig_hot_end_temp_1 = int(self.extruder_list[1].getProperty("material_print_temperature", "value"))
|
||||
self.orig_standby_temp_1 = int(self.extruder_list[1].getProperty("material_standby_temperature", "value"))
|
||||
|
||||
# Track the tool number up to the start of the start layer
|
||||
self.getTool("T0")
|
||||
for index, layer in enumerate(data):
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith("T"):
|
||||
self.getTool(line)
|
||||
if index == self.start_index - 1:
|
||||
break
|
||||
|
||||
# Add the active extruder initial temperature change at the start of the starting layer
|
||||
data[self.start_index] = data[self.start_index].replace("\n", f"\nM104 S{self.active_print_temp} ; TweakAtZ: Start Temperature Change\n",1)
|
||||
|
||||
# At the start layer commence making the changes
|
||||
for index, layer in enumerate(data):
|
||||
if index < self.start_index:
|
||||
continue
|
||||
if index > self.end_index:
|
||||
break
|
||||
lines = layer.splitlines()
|
||||
for l_index, line in enumerate(lines):
|
||||
|
||||
# Continue to track the tool number
|
||||
if line.startswith("T"):
|
||||
self.getTool(line)
|
||||
if line.startswith("M109"):
|
||||
lines[l_index] = f"M109 S{self.active_print_temp} ; TweakAtZ: Alter temperature"
|
||||
elif line.startswith("M104"):
|
||||
if self.getValue(line, "S") == self.inactive_standby_temp:
|
||||
continue
|
||||
elif self.getValue(line, "S") == self.inactive_tool_orig_temp:
|
||||
lines[l_index] = re.sub("S(\d+|\d.+)", f"S{self.inactive_print_temp} ; TweakAtZ: Alter temperature", line)
|
||||
elif self.getValue(line, "S") == self.active_tool_orig_temp:
|
||||
lines[l_index] = re.sub("S(\d+|\d.+)", f"S{self.active_print_temp} ; TweakAtZ: Alter temperature", line)
|
||||
data[index] = "\n".join(lines) + "\n"
|
||||
|
||||
# Revert the active extruder temperature at the end of the changes
|
||||
lines = data[self.end_index].split("\n")
|
||||
lines[len(lines) - 3] += f"\nM104 {self.active_tool} S{self.active_tool_orig_temp} ; TweakAtZ: Original Temperature active tool"
|
||||
data[self.end_index] = "\n".join(lines)
|
||||
return data
|
||||
|
||||
def getTool(self, line):
|
||||
if line.startswith("T1"):
|
||||
self.active_tool = "T1"
|
||||
self.active_tool_orig_temp = self.orig_hot_end_temp_1
|
||||
self.active_print_temp = self.new_hotend_temp_1
|
||||
self.inactive_tool = "T0"
|
||||
self.inactive_tool_orig_temp = self.orig_hot_end_temp_0
|
||||
self.inactive_print_temp = self.new_hotend_temp_0
|
||||
self.inactive_standby_temp = self.orig_standby_temp_0
|
||||
else:
|
||||
self.active_tool = "T0"
|
||||
self.active_tool_orig_temp = self.orig_hot_end_temp_0
|
||||
self.active_print_temp = self.new_hotend_temp_0
|
||||
self.inactive_tool = "T1"
|
||||
self.inactive_tool_orig_temp = self.orig_hot_end_temp_1
|
||||
self.inactive_print_temp = self.new_hotend_temp_1
|
||||
self.inactive_standby_temp = self.orig_standby_temp_1
|
||||
return
|
||||
|
||||
def _change_retract(self, data:str)->str:
|
||||
"""
|
||||
This is for single extruder printers only (tool change retractions get in the way for multi-extruders).
|
||||
Depending on the selected options, this will change the Retraction Speeds and Prime Speeds, and the Retraction Distance. NOTE: The retraction and prime speeds will be the same.
|
||||
:params:
|
||||
speed_retract_0: The set retraction and prime speed from Cura.
|
||||
retract_amt_0: The set retraction distance from Cura
|
||||
change_retract_amt: Boolean to trip changing the retraction distance
|
||||
change_retract_speed: Boolean to trip changing the speeds
|
||||
new_retract_amt: The new retraction amount to use from this script settings.
|
||||
new_retract_speed: The new retract and prime speed from this script settings.
|
||||
firmware_start_str: The string to insert for changes to firmware retraction
|
||||
firmware_reset: The last insertion for firmware retraction will set the numbers back to the settings in Cura.
|
||||
is_retracted: Tracks the end of the filament location
|
||||
cur_e: The current location of the extruder
|
||||
prev_e: The location of where the extruder was before the current e
|
||||
"""
|
||||
if not self.retract_enabled:
|
||||
return
|
||||
|
||||
# Exit if neither child setting is checked.
|
||||
if not (change_retract_amt or change_retract_speed):
|
||||
return
|
||||
|
||||
speed_retract_0 = int(self.extruder_list[0].getProperty("retraction_speed", "value") * 60)
|
||||
retract_amt_0 = self.extruder_list[0].getProperty("retraction_amount", "value")
|
||||
change_retract_amt = self.getSettingValueByKey("g_change_retract_amount")
|
||||
change_retract_speed = self.getSettingValueByKey("g_change_retract_speed")
|
||||
new_retract_speed = int(self.getSettingValueByKey("g_retract_speed") * 60)
|
||||
new_retract_amt = self.getSettingValueByKey("g_retract_amount")
|
||||
|
||||
# Use M207 and M208 to adjust firmware retraction when required
|
||||
if self.firmware_retraction:
|
||||
lines = data[self.start_index].splitlines()
|
||||
firmware_start_str = "\nM207"
|
||||
firmware_reset = ""
|
||||
if change_retract_speed:
|
||||
firmware_start_str += f" F{new_retract_speed}"
|
||||
if change_retract_amt:
|
||||
firmware_start_str += f" S{new_retract_amt}"
|
||||
if change_retract_speed or change_retract_amt:
|
||||
firmware_start_str += " ; TweakAtZ: Alter Firmware Retract speed/amt"
|
||||
if change_retract_speed:
|
||||
firmware_start_str += f"\nM208 F{new_retract_speed} ; TweakAtZ: Alter Firmware Prime speed"
|
||||
lines[0] += firmware_start_str
|
||||
data[self.start_index] = "\n".join(lines) + "\n"
|
||||
lines = data[self.end_index].splitlines()
|
||||
firmware_reset = f"M207 F{speed_retract_0} S{retract_amt_0} ; TweakAtZ: Reset Firmware Retract"
|
||||
if change_retract_speed:
|
||||
firmware_reset += f"\nM208 S{speed_retract_0} ; TweakAtZ: Reset Firmware Prime"
|
||||
if len(lines) < 2:
|
||||
lines.append(firmware_reset)
|
||||
else:
|
||||
lines[len(lines) - 2] += "\n" + firmware_reset
|
||||
data[self.end_index] = "\n".join(lines) + "\n"
|
||||
return data
|
||||
|
||||
if not self.firmware_retraction:
|
||||
prev_e = 0
|
||||
cur_e = 0
|
||||
is_retracted = False
|
||||
for num in range(1, self.start_index - 1):
|
||||
lines = data[num].splitlines()
|
||||
for line in lines:
|
||||
if " E" in line:
|
||||
cur_e = self.getValue(line, "E")
|
||||
prev_e = cur_e
|
||||
for num in range(self.start_index, self.end_index):
|
||||
lines = data[num].splitlines()
|
||||
for index, line in enumerate(lines):
|
||||
if line == "G92 E0":
|
||||
cur_e = 0
|
||||
prev_e = 0
|
||||
continue
|
||||
if " E" in line and self.getValue(line, "E") is not None:
|
||||
cur_e = self.getValue(line, "E")
|
||||
if cur_e >= prev_e and " X" in line and " Y" in line:
|
||||
prev_e = cur_e
|
||||
is_retracted = False
|
||||
continue
|
||||
if " F" in line and " E" in line and not " X" in line and not " Z" in line:
|
||||
cur_speed = self.getValue(line, "F")
|
||||
if cur_e < prev_e:
|
||||
is_retracted = True
|
||||
new_e = prev_e - new_retract_amt
|
||||
if not self.relative_extrusion:
|
||||
if change_retract_amt:
|
||||
lines[index] = lines[index].replace("E" + str(cur_e), "E" + str(new_e))
|
||||
prev_e = new_e
|
||||
if change_retract_speed:
|
||||
lines[index] = lines[index].replace("F" + str(cur_speed), "F" + str(new_retract_speed))
|
||||
elif self.relative_extrusion:
|
||||
if change_retract_amt:
|
||||
lines[index] = lines[index].replace("E" + str(cur_e), "E-" + str(new_retract_amt))
|
||||
prev_e = 0
|
||||
if change_retract_speed:
|
||||
lines[index] = lines[index].replace("F" + str(cur_speed), "F" + str(new_retract_speed))
|
||||
lines[index] += " ; TweakAtZ: Alter retract"
|
||||
else:
|
||||
|
||||
# Prime line
|
||||
if change_retract_speed:
|
||||
lines[index] = lines[index].replace("F" + str(cur_speed), "F" + str(new_retract_speed))
|
||||
prev_e = cur_e
|
||||
if self.relative_extrusion:
|
||||
if change_retract_amt:
|
||||
lines[index] = lines[index].replace("E" + str(cur_e), "E" + str(new_retract_amt))
|
||||
prev_e = 0
|
||||
lines[index] += " ; TweakAtZ: Alter retract"
|
||||
is_retracted = False
|
||||
data[num] = "\n".join(lines) + "\n"
|
||||
|
||||
# If the changes end before the last layer and the filament is retracted, then adjust the first prime of the next layer so it doesn't blob.
|
||||
if is_retracted and self.getSettingValueByKey("a_end_layer") != -1:
|
||||
layer = data[self.end_index]
|
||||
lines = layer.splitlines()
|
||||
for index, line in enumerate(lines):
|
||||
if " X" in line and " Y" in line and " E" in line:
|
||||
break
|
||||
if " F" in line and " E" in line and not " X" in line and not " Z" in line:
|
||||
cur_e = self.getValue(line, "E")
|
||||
if not self.relative_extrusion:
|
||||
new_e = prev_e + new_retract_amt
|
||||
if change_retract_amt:
|
||||
lines[index] = lines[index].replace("E" + str(cur_e), "E" + str(new_e)) + " ; TweakAtZ: Alter retract"
|
||||
break
|
||||
elif self.relative_extrusion:
|
||||
if change_retract_amt:
|
||||
lines[index] = lines[index].replace("E" + str(cur_e), "E" + str(new_retract_amt)) + " ; TweakAtZ: Alter retract"
|
||||
break
|
||||
data[self.end_index] = "\n".join(lines) + "\n"
|
||||
return data
|
||||
|
||||
def _format_lines(self, temp_data: str) -> str:
|
||||
"""
|
||||
This adds '-' as padding so the setting descriptions are more readable in the gcode
|
||||
"""
|
||||
for l_index, layer in enumerate(temp_data):
|
||||
lines = layer.split("\n")
|
||||
for index, line in enumerate(lines):
|
||||
if "; TweakAtZ:" in line:
|
||||
lines[index] = lines[index].split(";")[0] + ";" + ("-" * (40 - len(lines[index].split(";")[0]))) + lines[index].split(";")[1]
|
||||
temp_data[l_index] = "\n".join(lines)
|
||||
return temp_data
|
||||
|
||||
def _change_bv_fan_speed(self, temp_data: str) -> str:
|
||||
"""
|
||||
This can be used to control any fan. Typically this would be an Auxilliary or Build Volume fan
|
||||
:params:
|
||||
bv_fan_nr: The 'P' number of the fan
|
||||
bv_fan_speed: The new speed for the fan
|
||||
orig_bv_fan_speed: The reset speed. This is currently always "0" as the fan speed may not exist in Cura, or the fan might be 'on-off' and not PWM controlled.
|
||||
"""
|
||||
if not self.getSettingValueByKey("enable_bv_fan_change"):
|
||||
return temp_data
|
||||
bv_fan_nr = self.getSettingValueByKey("bv_fan_nr")
|
||||
bv_fan_speed = self.getSettingValueByKey("e1_build_volume_fan_speed")
|
||||
orig_bv_fan_speed = 0
|
||||
if bool(self.extruder_list[0].getProperty("machine_scale_fan_speed_zero_to_one", "value")):
|
||||
bv_fan_speed = round(bv_fan_speed * 0.01, 2)
|
||||
orig_bv_fan_speed = round(orig_bv_fan_speed * 0.01, 2)
|
||||
else:
|
||||
bv_fan_speed = round(bv_fan_speed * 2.55)
|
||||
orig_bv_fan_speed = round(orig_bv_fan_speed * 2.55)
|
||||
|
||||
# Add the changes to the gcode
|
||||
for index, layer in enumerate(temp_data):
|
||||
if index == self.start_index:
|
||||
lines = layer.split("\n")
|
||||
lines.insert(1, f"M106 S{bv_fan_speed} P{bv_fan_nr} ; TweakAtZ: Change Build Volume Fan Speed")
|
||||
temp_data[index] = "\n".join(lines)
|
||||
if index == self.end_index:
|
||||
lines = layer.split("\n")
|
||||
lines.insert(len(lines) - 2, f"M106 S{orig_bv_fan_speed} P{bv_fan_nr} ; TweakAtZ: Reset Build Volume Fan Speed")
|
||||
temp_data[index] = "\n".join(lines)
|
||||
return temp_data
|
||||
|
||||
# Get the starting index or ending index of the change range when 'By Height'
|
||||
def _is_legal_z(self, data: str, the_height: float) -> int:
|
||||
"""
|
||||
When in 'By Height' mode, this will return the index of the layer where the working Z is >= the Starting Z height, or the index of the layer where the working Z >= the Ending Z height
|
||||
:params:
|
||||
max_z: The maximum Z height within the Gcode. This is used to determine the upper limit of the data list that should be returned.
|
||||
the_height: The user input height. This will be adjusted if rafts are enabled and/or Z-hops are enabled
|
||||
cur_z: Is the current Z height as tracked through the gcode
|
||||
the_index: The number to return.
|
||||
"""
|
||||
# The height passed down cannot exceed the height of the model or the search for the Z fails
|
||||
lines = data[0].split("\n")
|
||||
for line in lines:
|
||||
if "MAXZ" in line or "MAX.Z" in line:
|
||||
max_z = float(line.split(":")[1])
|
||||
break
|
||||
if the_height > max_z:
|
||||
the_height = max_z
|
||||
|
||||
starting_z = 0
|
||||
the_index = 0
|
||||
|
||||
# The start height varies depending whether or not rafts are enabled and whether Z-hops are enabled.
|
||||
if str(self.global_stack.getProperty("adhesion_type", "value")) == "raft":
|
||||
|
||||
# If z-hops are enabled then start looking for the working Z after layer:0
|
||||
if self.z_hop_enabled:
|
||||
for layer in data:
|
||||
if ";LAYER:0" in layer:
|
||||
lines = layer.splitlines()
|
||||
for index, line in enumerate(lines):
|
||||
try:
|
||||
if " Z" in line and " E" in lines[index + 1]:
|
||||
starting_z = round(float(self.getValue(line, "Z")),2)
|
||||
the_height += starting_z
|
||||
break
|
||||
|
||||
# If the layer ends without an extruder move following the Z line, then just jump out
|
||||
except IndexError:
|
||||
starting_z = round(float(self.getValue(line, "Z")),2)
|
||||
the_height += starting_z
|
||||
break
|
||||
|
||||
# If Z-hops are disabled, then look for the starting Z from the start of the raft up to Layer:0
|
||||
else:
|
||||
for layer in data:
|
||||
lines = layer.splitlines()
|
||||
for index, line in enumerate(lines):
|
||||
|
||||
# This try/except catches comments in the startup gcode
|
||||
try:
|
||||
if " Z" in line and " E" in lines[index - 1]:
|
||||
starting_z = float(self.getValue(line, "Z"))
|
||||
except TypeError:
|
||||
|
||||
# Just pass beause there will be further Z values
|
||||
pass
|
||||
if ";LAYER:0" in line:
|
||||
the_height += starting_z
|
||||
break
|
||||
|
||||
# Initialize 'cur_z'
|
||||
cur_z = self.initial_layer_height
|
||||
for index, layer in enumerate(data):
|
||||
|
||||
# Skip over the opening paragraph and StartUp Gcode
|
||||
if index < 2:
|
||||
continue
|
||||
lines = layer.splitlines()
|
||||
for z_index, line in enumerate(lines):
|
||||
if len(line) >= 3 and line[0:3] in ['G0 ', 'G1 ', 'G2 ', 'G3 '] and index <= self.end_index:
|
||||
if " Z" in line:
|
||||
cur_z = float(self.getValue(line, "Z"))
|
||||
if cur_z >= the_height and lines[z_index - 1].startswith(";TYPE:"):
|
||||
the_index = index
|
||||
break
|
||||
if the_index > 0:
|
||||
break
|
||||
|
||||
# Catch-all to insure an entry of the 'model_height'. This allows the changes to continue to the end of the top layer
|
||||
if the_height >= max_z:
|
||||
the_index = len(data) - 2
|
||||
return the_index
|
||||
|
||||
def _f_x_y_not_z(self, line):
|
||||
return " F" in line and " X" in line and " Y" in line and not " Z" in line
|
||||
564
plugins/PostProcessingPlugin/scripts/ZHopOnTravel.py
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
"""
|
||||
By GregValiant (Greg Foresi) July of 2024
|
||||
Insert Z-hops for travel moves regardless of retraction. The 'Layer Range' (or comma delimited 'Layer List'), 'Minimum Travel Distance' and the 'Hop-Height' are user defined.
|
||||
This script is compatible with Z-hops enabled in Cura. If Z-hops are enabled: There will occasionally be a hop on top of a hop, but the 'resume Z height' will be correct.
|
||||
It is not necessary to have "retractions" enabled. If retractions are disabled in Cura you may elect to have this script add retractions. The Cura retraction distance and speeds are used.
|
||||
|
||||
Compatibility:
|
||||
Multi-Extruder printers: NOTE - The retraction settings for a multi-extruder printer are always taken from Extruder 1 (T0).
|
||||
There is support for:
|
||||
Absolute and Relative Extrusion
|
||||
Firmware Retraction
|
||||
Extra Prime Amount > 0
|
||||
Adaptive Layers
|
||||
G2/G3 arc moves are supported but are treated as straight line moves.
|
||||
|
||||
Incompatibility:
|
||||
"One at a Time" mode is not supported
|
||||
|
||||
Please Note:
|
||||
This is a slow running post processor as it must check the cumulative distances of all travel moves (G0 moves) in the range of layers.
|
||||
"""
|
||||
|
||||
from UM.Application import Application
|
||||
from ..Script import Script
|
||||
import re
|
||||
from UM.Message import Message
|
||||
import math
|
||||
from UM.Logger import Logger
|
||||
|
||||
class ZHopOnTravel(Script):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def getSettingDataString(self):
|
||||
return """{
|
||||
"name": "Z-Hop on Travel",
|
||||
"key": "ZHopOnTravel",
|
||||
"metadata": {},
|
||||
"version": 2,
|
||||
"settings": {
|
||||
"zhop_travel_enabled": {
|
||||
"label": "Enable script",
|
||||
"description": "Enables the script so it will run. 'One-at-a-Time' is not supported. This script is slow running because it must check the length of all travel moves in your layer range. ",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"enabled": true
|
||||
},
|
||||
"list_or_range":
|
||||
{
|
||||
"label": "Layer List or Range",
|
||||
"description": "Using a list allows you to call out one or more individual layers delimited by commas (Ex: 23,29,35). A layer range has a start layer and an end layer.",
|
||||
"type": "enum",
|
||||
"options": {
|
||||
"range_of_layers": "Range of Layers",
|
||||
"list_of_layers": "Layer List" },
|
||||
"default_value": "range_of_layers",
|
||||
"enabled": "zhop_travel_enabled"
|
||||
},
|
||||
"layers_of_interest":
|
||||
{
|
||||
"label": "List of Layers",
|
||||
"description": "Use the Cura preview layer numbers. Enter 'individual layer' numbers delimited by commas. Spaces are not allowed.",
|
||||
"type": "str",
|
||||
"default_value": "10,28,31,54",
|
||||
"enabled": "zhop_travel_enabled and list_or_range == 'list_of_layers'"
|
||||
},
|
||||
"start_layer": {
|
||||
"label": "Start Layer",
|
||||
"description": "Layer number to start the changes at. Use the Cura preview layer numbers. The changes will start at the start of the layer.",
|
||||
"unit": "Lay# ",
|
||||
"type": "int",
|
||||
"default_value": 1,
|
||||
"minimum_value": 1,
|
||||
"enabled": "zhop_travel_enabled and list_or_range == 'range_of_layers'"
|
||||
},
|
||||
"end_layer": {
|
||||
"label": "End Layer",
|
||||
"description": "Enter '-1' to indicate the top layer, or enter a specific Layer number from the Cura preview. The changes will end at the end of this layer.",
|
||||
"unit": "Lay# ",
|
||||
"type": "int",
|
||||
"default_value": -1,
|
||||
"minimum_value": -1,
|
||||
"enabled": "zhop_travel_enabled and list_or_range == 'range_of_layers'"
|
||||
},
|
||||
"hop_height": {
|
||||
"label": "Z-Hop Height",
|
||||
"description": "The relative 'Height' that the nozzle will 'Hop' in the 'Z'.",
|
||||
"unit": "mm ",
|
||||
"type": "float",
|
||||
"default_value": 0.5,
|
||||
"minimum_value": 0,
|
||||
"maximum_value_warning": 5,
|
||||
"maximum_value": 10,
|
||||
"enabled": "zhop_travel_enabled"
|
||||
},
|
||||
"min_travel_dist": {
|
||||
"label": "Minimum Travel Distance",
|
||||
"description": "Travel distances longer than this will cause a Z-Hop to occur.",
|
||||
"unit": "mm ",
|
||||
"type": "int",
|
||||
"default_value": 10,
|
||||
"minimum_value": 1,
|
||||
"maximum_value": 200,
|
||||
"enabled": "zhop_travel_enabled"
|
||||
},
|
||||
"add_retract": {
|
||||
"label": "Add retraction when necessary",
|
||||
"description": "Depending on your travel settings there may not be a retraction prior to a travel move. When enabled, if there is no retraction prior to an added z-hop, this setting will add a retraction before the Z-hop, and a prime after returning to the working layer height. If retractions are disabled in Cura this setting is still available and will add retractions based on the Cura settings for Retraction Amount and Retract and Prime Speeds. All retraction settings are from the settings for 'Extruder 1' regardless of the number of extruders.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "zhop_travel_enabled"
|
||||
},
|
||||
"infill_only": {
|
||||
"label": "Add Z-hops to Infill Only",
|
||||
"description": "Only add Z-hops to 'Infill' within the layer range. (NOTE: For technical reasons it is not possible to add Z-hops to travel moves that start somewhere and just 'cross infill'.)",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "zhop_travel_enabled"
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
def execute(self, data):
|
||||
"""
|
||||
The script will parse the gcode and check the cumulative length of travel moves. When they exceed the "min_travel_dist" then hop-ups are added before the travel and at the end of the travel. The user may select to add retractions/primes if there were none.
|
||||
params:
|
||||
layer_list: The list of 'layers-of-interest' for both 'Layer Range' and a 'Layer List'.
|
||||
index_list: A list of the indexes within the data[] for the layers-of-interest.
|
||||
self._cur_z: The variable used to track the working Z-height through the gcode
|
||||
self._add_retract: User setting of whether to insure a retraction at inserted Z-hops
|
||||
self._is_retracted: Whether a retraction has occurred prior to the added Z-hop
|
||||
min_travel_dist: The user setting for the minimum distance of travel for Z-hops to be inserted
|
||||
start_index: The index (in data[]) of the first layer-of-interest. The Z-hops start at the beginning of this layer.
|
||||
end_index: The index (in data[]) of the last layer-of-interest. The Z-hops end at the end of this layer.
|
||||
hop_up_lines: The string to insert for 'Hop up'
|
||||
hop_down_lines: The string to insert for 'Hop down'
|
||||
hop_start: The index within a layer where a 'Hop up' is inserted
|
||||
hop_end: The index within a layer where a 'Hop down' is inserted
|
||||
extra_prime_dist: Is calculated from the Cura extra_prime_volume and if > 0 is used to reset the E location prior to unretracting.
|
||||
"""
|
||||
|
||||
# Exit if the script is not enabled
|
||||
if not self.getSettingValueByKey("zhop_travel_enabled"):
|
||||
data[0] += "; [Z-Hop on Travel] Not enabled\n"
|
||||
Logger.log("i", "[Z-Hop on Travel] Not enabled")
|
||||
return data
|
||||
|
||||
# Exit if the gcode has already been post-processed
|
||||
if ";POSTPROCESSED" in data[0]:
|
||||
return data
|
||||
|
||||
# Define the global_stack to access the Cura settings
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
# Exit if the Print Sequence is One-at-a-Time
|
||||
if global_stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
Message(title = "[ZHop On Travel]", text = "Is not compatible with 'One at a Time' print sequence.").show()
|
||||
data[0] += "; [ZHop On Travel] did not run because One at a Time is enabled"
|
||||
return data
|
||||
|
||||
# Define some variables
|
||||
extruder = global_stack.extruderList
|
||||
speed_zhop = extruder[0].getProperty("speed_z_hop", "value") * 60
|
||||
speed_travel = extruder[0].getProperty("speed_travel", "value") * 60
|
||||
retraction_enabled = extruder[0].getProperty("retraction_enable", "value")
|
||||
retraction_amount = extruder[0].getProperty("retraction_amount", "value")
|
||||
retract_speed = int(extruder[0].getProperty("retraction_retract_speed", "value")) * 60
|
||||
prime_speed = int(extruder[0].getProperty("retraction_prime_speed", "value")) * 60
|
||||
firmware_retract = global_stack.getProperty("machine_firmware_retract", "value")
|
||||
relative_extrusion = global_stack.getProperty("relative_extrusion", "value")
|
||||
self._cur_z = float(global_stack.getProperty("layer_height_0", "value"))
|
||||
filament_dia = extruder[0].getProperty("material_diameter", "value")
|
||||
extra_prime_vol = extruder[0].getProperty("retraction_extra_prime_amount", "value")
|
||||
extra_prime_dist = extra_prime_vol / (math.pi * (filament_dia / 2)**2)
|
||||
self._add_retract = self.getSettingValueByKey("add_retract")
|
||||
hop_height = round(self.getSettingValueByKey("hop_height"),2)
|
||||
list_or_range = self.getSettingValueByKey("list_or_range")
|
||||
infill_only = self.getSettingValueByKey("infill_only")
|
||||
layer_list = []
|
||||
index_list = []
|
||||
|
||||
# Get either the 'range_of_layers' or the 'list_of_layers' and convert them to 'layer_list' and then 'index_list'
|
||||
if list_or_range == "list_of_layers":
|
||||
layer_string = self.getSettingValueByKey("layers_of_interest")
|
||||
layer_list = layer_string.split(",")
|
||||
layer_list.sort()
|
||||
for layer in layer_list:
|
||||
for num in range(2, len(data) - 1):
|
||||
if ";LAYER:" + str(int(layer) - 1) + "\n" in data[num]:
|
||||
index_list.append(num)
|
||||
start_index = index_list[0]
|
||||
|
||||
elif list_or_range == "range_of_layers":
|
||||
start_layer = self.getSettingValueByKey("start_layer")
|
||||
end_layer = self.getSettingValueByKey("end_layer")
|
||||
end_index = None
|
||||
# Get the indexes for the start and end layers
|
||||
start_index = 2
|
||||
for num in range(1, len(data) - 1):
|
||||
if ";LAYER:" + str(start_layer - 1) + "\n" in data[num]:
|
||||
start_index = num
|
||||
break
|
||||
if end_layer == -1:
|
||||
if retraction_enabled:
|
||||
end_index = len(data) - 3
|
||||
else:
|
||||
end_index = len(data) - 2
|
||||
elif end_layer != -1:
|
||||
for num in range(1, len(data) - 1):
|
||||
if ";LAYER:" + str(end_layer) + "\n" in data[num]:
|
||||
end_layer = data[num].splitlines()[0].split(":")[1]
|
||||
end_index = num
|
||||
break
|
||||
if end_index is None:
|
||||
end_index = len(data)-1
|
||||
for num in range(start_index, end_index):
|
||||
index_list.append(num)
|
||||
|
||||
# Track the Z up to the starting layer
|
||||
for num in range(1, start_index):
|
||||
lines = data[num].splitlines()
|
||||
for line in lines:
|
||||
if " Z" in line and self.getValue(line, "Z"):
|
||||
self._cur_z = self.getValue(line, "Z")
|
||||
|
||||
# Use 'start_here' to avoid a zhop on the first move of the initial layer because a double-retraction may occur.
|
||||
if start_index == 2:
|
||||
start_here = False
|
||||
else:
|
||||
start_here = True
|
||||
|
||||
# Initialize variables
|
||||
self._is_retracted = False
|
||||
hop_up_lines = ""
|
||||
hop_down_lines = ""
|
||||
hop_start = 0
|
||||
hop_end = 0
|
||||
self._cur_x = 0.0
|
||||
self._cur_y = 0.0
|
||||
self._prev_x = 0.0
|
||||
self._prev_y = 0.0
|
||||
self._cur_e = 0.0
|
||||
self._prev_e = 0.0
|
||||
cmd_list = ["G0 ", "G1 ", "G2 ", "G3 "]
|
||||
self.reset_type = 0
|
||||
|
||||
# Keep track of the axes locations if the start layer > layer:0
|
||||
if start_index > 2:
|
||||
self._track_all_axes(data, cmd_list, start_index, relative_extrusion)
|
||||
|
||||
# Make the insertions
|
||||
in_the_infill = False
|
||||
for num in range(start_index, len(data)-1):
|
||||
# Leave if the num > highest index number to speed up the script.
|
||||
if num > index_list[len(index_list)-1]:
|
||||
break
|
||||
# If the num is not an "index of interest" then just track the Z through the layer
|
||||
if num not in index_list:
|
||||
lines = data[num].splitlines()
|
||||
for line in lines:
|
||||
if " Z" in line and self.getValue(line, "Z"):
|
||||
self._cur_z = self.getValue(line, "Z")
|
||||
continue
|
||||
# If the num is in the index_list then make changes
|
||||
elif num in index_list:
|
||||
lines = data[num].splitlines()
|
||||
for index, line in enumerate(lines):
|
||||
if num == 2:
|
||||
if line.startswith(";TYPE"):
|
||||
start_here = True
|
||||
if line.startswith(";") and in_the_infill == True:
|
||||
in_the_infill = False
|
||||
if line.startswith(";TYPE:FILL"):
|
||||
in_the_infill = True
|
||||
if line.startswith("G92") and " E" in line:
|
||||
self._cur_e = self.getValue(line, "E")
|
||||
self._prev_e = self._cur_e
|
||||
continue
|
||||
# Get the XYZ values from movement commands
|
||||
if line[0:3] in cmd_list:
|
||||
if " X" in line and self.getValue(line, "X"):
|
||||
self._prev_x = self._cur_x
|
||||
self._cur_x = self.getValue(line, "X")
|
||||
if " Y" in line and self.getValue(line, "Y"):
|
||||
self._prev_y = self._cur_y
|
||||
self._cur_y = self.getValue(line, "Y")
|
||||
if " Z" in line and self.getValue(line, "Z"):
|
||||
self._cur_z = self.getValue(line, "Z")
|
||||
|
||||
# Check whether retractions have occured
|
||||
if line[0:3] in ["G1 ", "G2 ", "G3 "] and "X" in line and "Y" in line and "E" in line:
|
||||
self._is_retracted = False
|
||||
self._cur_e = self.getValue(line, "E")
|
||||
elif (line.startswith("G1") and "F" in line and "E" in line and (not "X" in line or not "Y" in line)) or "G10" in line:
|
||||
if self.getValue(line, "E"):
|
||||
self._cur_e = self.getValue(line, "E")
|
||||
if not relative_extrusion:
|
||||
if self._cur_e < self._prev_e or "G10" in line:
|
||||
self._is_retracted = True
|
||||
elif relative_extrusion:
|
||||
if self._cur_e < 0 or "G10" in line:
|
||||
self._is_retracted = True
|
||||
if line.startswith(";TYPE"):
|
||||
start_here = True
|
||||
if not start_here:
|
||||
continue
|
||||
|
||||
# All travels are checked for their cumulative length
|
||||
if line.startswith("G0 ") and hop_start == 0:
|
||||
hop_indexes = self._total_travel_length(index, lines)
|
||||
hop_start = int(hop_indexes[0])
|
||||
hop_end = int(hop_indexes[1])
|
||||
if infill_only and not in_the_infill:
|
||||
hop_start = 0
|
||||
hop_end = 0
|
||||
if hop_start > 0:
|
||||
# For any lines that are XYZ moves right before layer change
|
||||
if " Z" in line:
|
||||
lines[index] = lines[index].replace("Z" + str(self._cur_z), "Z" + str(self._cur_z + hop_height))
|
||||
# If there is no 'F' in the next line then add one at the Travel Speed so the z-hop speed doesn't carry over
|
||||
if not " F" in lines[index] and lines[index].startswith("G0"):
|
||||
lines[index] = lines[index].replace("G0", f"G0 F{speed_travel}")
|
||||
if "X" in lines[index - 1] and "Y" in lines[index - 1] and "E" in lines[index - 1]:
|
||||
self._is_retracted = False
|
||||
hop_up_lines = self.get_hop_up_lines(retraction_amount, speed_zhop, retract_speed, extra_prime_dist, firmware_retract, relative_extrusion, hop_height)
|
||||
lines[index] = hop_up_lines + lines[index]
|
||||
|
||||
# Make the 'Zhop down' insertion at the correct index location (or as soon as practicable after it)
|
||||
if hop_end > 0 and index >= hop_end:
|
||||
# If there is no 'F' in the next line then add one to reinstate the Travel Speed (so the z-hop speed doesn't carry over through the travel moves)
|
||||
if not " F" in lines[index] and lines[index].startswith("G0"):
|
||||
lines[index] = lines[index].replace("G0", f"G0 F{speed_travel}")
|
||||
hop_down_lines = self.get_hop_down_lines(retraction_amount, speed_zhop, prime_speed, extra_prime_dist, firmware_retract, relative_extrusion, lines[index])
|
||||
lines[index] = hop_down_lines + lines[index]
|
||||
self._is_retracted = False
|
||||
hop_end = 0
|
||||
hop_start = 0
|
||||
hop_down_lines = ""
|
||||
if line.startswith(";"):
|
||||
continue
|
||||
self._prev_e = self._cur_e
|
||||
data[num] = "\n".join(lines) + "\n"
|
||||
|
||||
# Message to the user informing them of the number of Z-hops and retractions added
|
||||
hop_cnt = 0
|
||||
retract_cnt = 0
|
||||
try:
|
||||
for index_nr in index_list:
|
||||
hop_cnt += data[index_nr].count("; Hop Up")
|
||||
retract_cnt += data[index_nr].count("; Retract")
|
||||
msg_txt = str(hop_cnt) + " Z-Hops were added to the file\n"
|
||||
if self._add_retract:
|
||||
msg_txt += str(retract_cnt) + " Retracts and unretracts were added to the file"
|
||||
Message(title = "[Z-hop On Travel]", text = msg_txt).show()
|
||||
except:
|
||||
pass
|
||||
return data
|
||||
|
||||
def _total_travel_length(self, l_index: int, lines: str) -> int:
|
||||
"""
|
||||
This function gets the cumulative total travel distance of each individual travel move.
|
||||
:parameters:
|
||||
g_num: is the line index as passed from the calling function and when returned indicates the end of travel
|
||||
travel_total: is the cumulative travel distance
|
||||
"""
|
||||
g_num = l_index
|
||||
travel_total = 0.0
|
||||
# Total the lengths of each move and compare them to the Minimum Distance for a Z-hop to occur
|
||||
while lines[g_num].startswith("G0 "):
|
||||
travel_total += self._get_distance()
|
||||
self._prev_x = self._cur_x
|
||||
if self.getValue(lines[g_num], "X"):
|
||||
self._cur_x = self.getValue(lines[g_num], "X")
|
||||
self._prev_y = self._cur_y
|
||||
if self.getValue(lines[g_num], "Y"):
|
||||
self._cur_y = self.getValue(lines[g_num], "Y")
|
||||
g_num += 1
|
||||
if g_num == len(lines):
|
||||
break
|
||||
if travel_total > self.getSettingValueByKey("min_travel_dist"):
|
||||
return [l_index, g_num]
|
||||
else:
|
||||
return [0, 0]
|
||||
|
||||
def _get_distance(self) -> float:
|
||||
"""
|
||||
This function gets the distance from the previous location to the current location.
|
||||
"""
|
||||
try:
|
||||
dist = math.sqrt((self._prev_x - self._cur_x)**2 + (self._prev_y - self._cur_y)**2)
|
||||
except ValueError:
|
||||
return 0
|
||||
return dist
|
||||
|
||||
def get_hop_up_lines(self, retraction_amount: float, speed_zhop: str, retract_speed: str, extra_prime_dist: float, firmware_retract: bool, relative_extrusion: bool, hop_height: str) -> str:
|
||||
"""
|
||||
Determine if the hop will require a retraction
|
||||
:parameters:
|
||||
reset_type: An indicator to handle differences when Firmware Retraction, and Relative Extrusion, and Extra Prime are enabled
|
||||
up_lines: The inserted line(s) for the Z-hop Up
|
||||
front_text and back_text: Are the line splits to account for existing gcode lines that have comments in them
|
||||
"""
|
||||
hop_retraction = not self._is_retracted
|
||||
if not self._add_retract:
|
||||
hop_retraction = False
|
||||
# 'reset_type' is a bitmask representing the combination of retraction and related options:
|
||||
# Bit 0 (1): Retraction is required
|
||||
# Bit 1 (2): Firmware retraction is enabled
|
||||
# Bit 2 (4): Relative extrusion is enabled
|
||||
# Bit 3 (8): Extra prime amount is greater than 0
|
||||
# The value of 'reset_type' determines which G-code lines are inserted for the Z-hop.
|
||||
reset_type = 0
|
||||
if hop_retraction:
|
||||
reset_type += 1
|
||||
if firmware_retract and hop_retraction:
|
||||
reset_type += 2
|
||||
if relative_extrusion and hop_retraction:
|
||||
reset_type += 4
|
||||
if extra_prime_dist > 0 and hop_retraction:
|
||||
reset_type += 8
|
||||
|
||||
machine_height = Application.getInstance().getGlobalContainerStack().getProperty("machine_height", "value")
|
||||
if self._cur_z + hop_height < machine_height:
|
||||
up_lines = f"G1 F{speed_zhop} Z{round(self._cur_z + hop_height,2)} ; Hop Up"
|
||||
else:
|
||||
up_lines = f"G1 F{speed_zhop} Z{round(machine_height, 2)} ; Hop Up"
|
||||
if reset_type in [1, 9] and hop_retraction: # add retract only when necessary
|
||||
up_lines = f"G1 F{retract_speed} E{round(self._cur_e - retraction_amount, 5)} ; Retract\n" + up_lines
|
||||
self._cur_e = round(self._cur_e - retraction_amount, 5)
|
||||
if reset_type in [3, 7, 11, 15] and hop_retraction: # add retract and firmware retract
|
||||
up_lines = "G10 ; Retract\n" + up_lines
|
||||
if reset_type in [5, 13] and hop_retraction: # add retract and relative extrusion
|
||||
up_lines = f"G1 F{retract_speed} E-{retraction_amount} ; Retract\n" + up_lines
|
||||
self._cur_e = 0
|
||||
|
||||
# Format the added lines for readability
|
||||
if "\n" in up_lines: # for lines that include a retraction
|
||||
lines = up_lines.split("\n")
|
||||
for index, line in enumerate(lines):
|
||||
front_txt = lines[index].split(";")[0]
|
||||
back_txt = lines[index].split(";")[1]
|
||||
lines[index] = front_txt + str(" " * (40 - len(front_txt))) +";" + back_txt
|
||||
up_lines = "\n".join(lines) + "\n"
|
||||
else: # for lines without a retraction
|
||||
front_txt = up_lines.split(";")[0]
|
||||
back_txt = up_lines.split(";")[1]
|
||||
up_lines = front_txt + str(" " * (40 - len(front_txt))) +";" + back_txt + "\n"
|
||||
return up_lines
|
||||
|
||||
# The Zhop down may require different kinds of primes depending on the Cura settings.
|
||||
def get_hop_down_lines(self, retraction_amount: float, speed_zhop: str, prime_speed: str, extra_prime_dist: str, firmware_retract: bool, relative_extrusion: bool, next_line: str) -> str:
|
||||
"""
|
||||
Determine if the hop will require a prime
|
||||
:parameters:
|
||||
reset_type: An indicator to handle differences when Firmware Retraction, and Relative Extrusion, and Extra Prime are enabled
|
||||
dn_lines: The inserted line(s) for the Z-hop Down
|
||||
front_text and back_text: Are the line splits to account for existing gcode lines that have comments in them
|
||||
"""
|
||||
hop_retraction = not self._is_retracted
|
||||
if not self._add_retract:
|
||||
hop_retraction = False
|
||||
# Base the prime on the combination of Cura settings
|
||||
reset_type = 0
|
||||
if hop_retraction:
|
||||
reset_type += 1
|
||||
if firmware_retract and hop_retraction:
|
||||
reset_type += 2
|
||||
if relative_extrusion and hop_retraction:
|
||||
reset_type += 4
|
||||
if extra_prime_dist > 0.0 and hop_retraction:
|
||||
reset_type += 8
|
||||
dn_lines = f"G1 F{speed_zhop} Z{self._cur_z} ; Hop Down"
|
||||
# Format the line and return if the retraction option is unchecked
|
||||
if "G11" in next_line or re.search("G1 F(\d+\.\d+|\d+) E(-?\d+\.\d+|-?\d+)", next_line) and reset_type == 0:
|
||||
front_txt = dn_lines.split(";")[0]
|
||||
back_txt = dn_lines.split(";")[1]
|
||||
dn_lines = front_txt + str(" " * (40 - len(front_txt))) +";" + back_txt + "\n"
|
||||
self._is_retracted = False
|
||||
return dn_lines
|
||||
# If the retraction option is checked then determine the required unretract code for the particular combination of Cura settings.
|
||||
# Add retract 1
|
||||
if reset_type == 1 and hop_retraction:
|
||||
dn_lines += f"\nG1 F{prime_speed} E{round(self._prev_e + retraction_amount, 5)} ; Unretract"
|
||||
# Add retract 1 + firmware retract 2 and Add retract 1 + firmware retraction 2 + relative extrusion 4
|
||||
if reset_type in [3, 7] and hop_retraction:
|
||||
dn_lines += "\nG11 ; UnRetract"
|
||||
# Add retract 1 + relative extrusion 4
|
||||
if reset_type == 5 and hop_retraction:
|
||||
dn_lines += f"\nG1 F{prime_speed} E{retraction_amount} ; UnRetract"
|
||||
# Add retract 1 + extra prime 8
|
||||
if reset_type == 9 and hop_retraction:
|
||||
dn_lines += f"\nG92 E{round(self._prev_e - extra_prime_dist,5)} ; Extra prime adjustment"
|
||||
dn_lines += f"\nG1 F{prime_speed} E{round(self._prev_e + retraction_amount, 5)} ; UnRetract"
|
||||
self._cur_e = round(self._prev_e + retraction_amount, 5)
|
||||
# Add retract 1 + firmware retraction 2 + extra prime 8
|
||||
if reset_type == 11 and hop_retraction:
|
||||
dn_lines += "\nG11 ; UnRetract"
|
||||
dn_lines += "\nM83 ; Relative extrusion"
|
||||
dn_lines += f"\nG1 F{prime_speed} E{round(extra_prime_dist, 5)} ; Extra prime"
|
||||
if not relative_extrusion:
|
||||
dn_lines += "\nM82 ; Absolute extrusion"
|
||||
# Add retract 1 + relative extrusion 4 + extra prime 8
|
||||
if reset_type == 13 and hop_retraction:
|
||||
dn_lines += f"\nG1 F{prime_speed} E{round(retraction_amount + extra_prime_dist, 5)} ; Unretract with extra prime"
|
||||
# Add retract 1 + firmware retraction 2 + relative extrusion 4 + extra prime 8
|
||||
if reset_type == 15 and hop_retraction:
|
||||
dn_lines += "\nG11 ; UnRetract"
|
||||
dn_lines += f"\nG1 F{prime_speed} E{round(extra_prime_dist, 5)} ; Extra prime"
|
||||
|
||||
# Format the added lines for readability
|
||||
if "\n" in dn_lines: # for lines with primes
|
||||
lines = dn_lines.split("\n")
|
||||
for index, line in enumerate(lines):
|
||||
front_txt = lines[index].split(";")[0]
|
||||
back_txt = lines[index].split(";")[1]
|
||||
lines[index] = front_txt + str(" " * (40 - len(front_txt))) +";" + back_txt
|
||||
dn_lines = "\n".join(lines) + "\n"
|
||||
else: # for lines with no prime
|
||||
front_txt = dn_lines.split(";")[0]
|
||||
back_txt = dn_lines.split(";")[1]
|
||||
dn_lines = front_txt + str(" " * (40 - len(front_txt))) +";" + back_txt + "\n"
|
||||
self._is_retracted = False
|
||||
return dn_lines
|
||||
|
||||
def _track_all_axes(self, data: str, cmd_list: str, start_index: int, relative_extrusion: bool) -> str:
|
||||
"""
|
||||
This function tracks the XYZE locations prior to the beginning of the first 'layer-of-interest'
|
||||
|
||||
"""
|
||||
for num in range(2, start_index):
|
||||
lines = data[num].split("\n")
|
||||
for line in lines:
|
||||
# Get the XYZ values from movement commands
|
||||
if line[0:3] in cmd_list:
|
||||
if " X" in line and self.getValue(line, "X"):
|
||||
self._prev_x = self._cur_x
|
||||
self._cur_x = self.getValue(line, "X")
|
||||
if " Y" in line and self.getValue(line, "Y"):
|
||||
self._prev_y = self._cur_y
|
||||
self._cur_y = self.getValue(line, "Y")
|
||||
if " Z" in line and self.getValue(line, "Z"):
|
||||
self._cur_z = self.getValue(line, "Z")
|
||||
|
||||
# Check whether retractions have occured and track the E location
|
||||
if not relative_extrusion:
|
||||
if line.startswith("G1 ") and " X" in line and " Y" in line and " E" in line:
|
||||
self._is_retracted = False
|
||||
self._cur_e = self.getValue(line, "E")
|
||||
elif line.startswith("G1 ") and " F" in line and " E" in line and not " X" in line and not " Y" in line:
|
||||
if self.getValue(line, "E"):
|
||||
self._cur_e = self.getValue(line, "E")
|
||||
elif line.startswith("G10"):
|
||||
self._is_retracted = True
|
||||
elif line.startswith("G11"):
|
||||
self._is_retracted = False
|
||||
elif relative_extrusion:
|
||||
if self._cur_e < 0 or "G10" in line:
|
||||
self._is_retracted = True
|
||||
self._cur_e = 0
|
||||
if line.startswith("G11"):
|
||||
self._is_retracted = False
|
||||
self._cur_e = 0
|
||||
self._prev_e = self._cur_e
|
||||
return None
|
||||
|
|
@ -1,6 +1,11 @@
|
|||
# Copyright (c) 2019 Ultimaker B.V.
|
||||
# Copyright (c) 2025 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
import hashlib
|
||||
import json
|
||||
import platform
|
||||
import re
|
||||
import secrets
|
||||
from enum import StrEnum
|
||||
from json import JSONDecodeError
|
||||
from typing import Callable, List, Optional, Dict, Union, Any, Type, cast, TypeVar, Tuple
|
||||
|
||||
|
|
@ -9,6 +14,8 @@ from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkRepl
|
|||
|
||||
from UM.Logger import Logger
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from ..Models.BaseModel import BaseModel
|
||||
from ..Models.Http.ClusterPrintJobStatus import ClusterPrintJobStatus
|
||||
from ..Models.Http.ClusterPrinterStatus import ClusterPrinterStatus
|
||||
|
|
@ -20,6 +27,18 @@ ClusterApiClientModel = TypeVar("ClusterApiClientModel", bound=BaseModel)
|
|||
"""The generic type variable used to document the methods below."""
|
||||
|
||||
|
||||
class HttpRequestMethod(StrEnum):
|
||||
GET = "GET",
|
||||
HEAD = "HEAD",
|
||||
POST = "POST",
|
||||
PUT = "PUT",
|
||||
DELETE = "DELETE",
|
||||
CONNECT = "CONNECT",
|
||||
OPTIONS = "OPTIONS",
|
||||
TRACE = "TRACE",
|
||||
PATCH = "PATCH",
|
||||
|
||||
|
||||
class ClusterApiClient:
|
||||
"""The ClusterApiClient is responsible for all network calls to local network clusters."""
|
||||
|
||||
|
|
@ -27,6 +46,13 @@ class ClusterApiClient:
|
|||
PRINTER_API_PREFIX = "/api/v1"
|
||||
CLUSTER_API_PREFIX = "/cluster-api/v1"
|
||||
|
||||
AUTH_REALM = "Jedi-API"
|
||||
AUTH_QOP = "auth"
|
||||
AUTH_NONCE_LEN = 16
|
||||
AUTH_CNONCE_LEN = 8
|
||||
|
||||
AUTH_MAX_TRIES = 5
|
||||
|
||||
# In order to avoid garbage collection we keep the callbacks in this list.
|
||||
_anti_gc_callbacks = [] # type: List[Callable[[], None]]
|
||||
|
||||
|
|
@ -40,6 +66,12 @@ class ClusterApiClient:
|
|||
self._manager = QNetworkAccessManager()
|
||||
self._address = address
|
||||
self._on_error = on_error
|
||||
self._auth_id = None
|
||||
self._auth_key = None
|
||||
self._auth_tries = 0
|
||||
|
||||
self._nonce_count = 1
|
||||
self._nonce = None
|
||||
|
||||
def getSystem(self, on_finished: Callable) -> None:
|
||||
"""Get printer system information.
|
||||
|
|
@ -47,7 +79,7 @@ class ClusterApiClient:
|
|||
:param on_finished: The callback in case the response is successful.
|
||||
"""
|
||||
url = "{}/system".format(self.PRINTER_API_PREFIX)
|
||||
reply = self._manager.get(self._createEmptyRequest(url))
|
||||
reply = self._manager.get(self.createEmptyRequest(url))
|
||||
self._addCallback(reply, on_finished, PrinterSystemStatus)
|
||||
|
||||
def getMaterials(self, on_finished: Callable[[List[ClusterMaterial]], Any]) -> None:
|
||||
|
|
@ -56,7 +88,7 @@ class ClusterApiClient:
|
|||
:param on_finished: The callback in case the response is successful.
|
||||
"""
|
||||
url = "{}/materials".format(self.CLUSTER_API_PREFIX)
|
||||
reply = self._manager.get(self._createEmptyRequest(url))
|
||||
reply = self._manager.get(self.createEmptyRequest(url))
|
||||
self._addCallback(reply, on_finished, ClusterMaterial)
|
||||
|
||||
def getPrinters(self, on_finished: Callable[[List[ClusterPrinterStatus]], Any]) -> None:
|
||||
|
|
@ -65,7 +97,7 @@ class ClusterApiClient:
|
|||
:param on_finished: The callback in case the response is successful.
|
||||
"""
|
||||
url = "{}/printers".format(self.CLUSTER_API_PREFIX)
|
||||
reply = self._manager.get(self._createEmptyRequest(url))
|
||||
reply = self._manager.get(self.createEmptyRequest(url))
|
||||
self._addCallback(reply, on_finished, ClusterPrinterStatus)
|
||||
|
||||
def getPrintJobs(self, on_finished: Callable[[List[ClusterPrintJobStatus]], Any]) -> None:
|
||||
|
|
@ -74,26 +106,26 @@ class ClusterApiClient:
|
|||
:param on_finished: The callback in case the response is successful.
|
||||
"""
|
||||
url = "{}/print_jobs".format(self.CLUSTER_API_PREFIX)
|
||||
reply = self._manager.get(self._createEmptyRequest(url))
|
||||
reply = self._manager.get(self.createEmptyRequest(url))
|
||||
self._addCallback(reply, on_finished, ClusterPrintJobStatus)
|
||||
|
||||
def movePrintJobToTop(self, print_job_uuid: str) -> None:
|
||||
"""Move a print job to the top of the queue."""
|
||||
|
||||
url = "{}/print_jobs/{}/action/move".format(self.CLUSTER_API_PREFIX, print_job_uuid)
|
||||
self._manager.post(self._createEmptyRequest(url), json.dumps({"to_position": 0, "list": "queued"}).encode())
|
||||
self._manager.post(self.createEmptyRequest(url, method=HttpRequestMethod.POST), json.dumps({"to_position": 0, "list": "queued"}).encode())
|
||||
|
||||
def forcePrintJob(self, print_job_uuid: str) -> None:
|
||||
"""Override print job configuration and force it to be printed."""
|
||||
|
||||
url = "{}/print_jobs/{}".format(self.CLUSTER_API_PREFIX, print_job_uuid)
|
||||
self._manager.put(self._createEmptyRequest(url), json.dumps({"force": True}).encode())
|
||||
self._manager.put(self.createEmptyRequest(url, method=HttpRequestMethod.PUT), json.dumps({"force": True}).encode())
|
||||
|
||||
def deletePrintJob(self, print_job_uuid: str) -> None:
|
||||
"""Delete a print job from the queue."""
|
||||
|
||||
url = "{}/print_jobs/{}".format(self.CLUSTER_API_PREFIX, print_job_uuid)
|
||||
self._manager.deleteResource(self._createEmptyRequest(url))
|
||||
self._manager.deleteResource(self.createEmptyRequest(url, method=HttpRequestMethod.DELETE))
|
||||
|
||||
def setPrintJobState(self, print_job_uuid: str, state: str) -> None:
|
||||
"""Set the state of a print job."""
|
||||
|
|
@ -101,25 +133,33 @@ class ClusterApiClient:
|
|||
url = "{}/print_jobs/{}/action".format(self.CLUSTER_API_PREFIX, print_job_uuid)
|
||||
# We rewrite 'resume' to 'print' here because we are using the old print job action endpoints.
|
||||
action = "print" if state == "resume" else state
|
||||
self._manager.put(self._createEmptyRequest(url), json.dumps({"action": action}).encode())
|
||||
self._manager.put(self.createEmptyRequest(url, method=HttpRequestMethod.PUT), json.dumps({"action": action}).encode())
|
||||
|
||||
def getPrintJobPreviewImage(self, print_job_uuid: str, on_finished: Callable) -> None:
|
||||
"""Get the preview image data of a print job."""
|
||||
|
||||
url = "{}/print_jobs/{}/preview_image".format(self.CLUSTER_API_PREFIX, print_job_uuid)
|
||||
reply = self._manager.get(self._createEmptyRequest(url))
|
||||
reply = self._manager.get(self.createEmptyRequest(url))
|
||||
self._addCallback(reply, on_finished)
|
||||
|
||||
def _createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
|
||||
def createEmptyRequest(self, path: str, content_type: Optional[str] = "application/json", method: HttpRequestMethod = HttpRequestMethod.GET, skip_auth: bool = False) -> QNetworkRequest:
|
||||
"""We override _createEmptyRequest in order to add the user credentials.
|
||||
|
||||
:param url: The URL to request
|
||||
:param path: Part added to the base-endpoint forming the total request URL (the path from the endpoint to the requested resource).
|
||||
:param content_type: The type of the body contents.
|
||||
:param method: The HTTP method to use, such as GET, POST, PUT, etc.
|
||||
:param skip_auth: Skips the authentication step if set; prevents a loop on request of authentication token.
|
||||
"""
|
||||
url = QUrl("http://" + self._address + path)
|
||||
request = QNetworkRequest(url)
|
||||
if content_type:
|
||||
request.setHeader(QNetworkRequest.KnownHeaders.ContentTypeHeader, content_type)
|
||||
if self._auth_id and self._auth_key:
|
||||
digest_str = self._makeAuthDigestHeaderPart(path, method=method)
|
||||
request.setRawHeader(b"Authorization", f"Digest {digest_str}".encode("utf-8"))
|
||||
self._nonce_count += 1
|
||||
elif not skip_auth:
|
||||
self._setupAuth()
|
||||
return request
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -158,6 +198,64 @@ class ClusterApiClient:
|
|||
except (JSONDecodeError, TypeError, ValueError):
|
||||
Logger.log("e", "Could not parse response from network: %s", str(response))
|
||||
|
||||
def _makeAuthDigestHeaderPart(self, url_part: str, method: HttpRequestMethod = HttpRequestMethod.GET) -> str:
|
||||
""" Make the data-part for a Digest Authentication HTTP-header.
|
||||
|
||||
:param url_part: The part of the URL beyond the host name.
|
||||
:param method: The HTTP method to use, such as GET, POST, PUT, etc.
|
||||
:return: A string with the data, can be used as in `f"Digest {return_value}".encode()`.
|
||||
"""
|
||||
|
||||
def sha256_utf8(x: str) -> str:
|
||||
return hashlib.sha256(x.encode("utf-8")).hexdigest()
|
||||
|
||||
nonce = secrets.token_hex(ClusterApiClient.AUTH_NONCE_LEN) if self._nonce is None else self._nonce
|
||||
cnonce = secrets.token_hex(ClusterApiClient.AUTH_CNONCE_LEN)
|
||||
auth_nc = f"{self._nonce_count:08x}"
|
||||
|
||||
ha1 = sha256_utf8(f"{self._auth_id}:{ClusterApiClient.AUTH_REALM}:{self._auth_key}")
|
||||
ha2 = sha256_utf8(f"{method}:{url_part}")
|
||||
resp_digest = sha256_utf8(f"{ha1}:{nonce}:{auth_nc}:{cnonce}:{ClusterApiClient.AUTH_QOP}:{ha2}")
|
||||
return ", ".join([
|
||||
f'username="{self._auth_id}"',
|
||||
f'realm="{ClusterApiClient.AUTH_REALM}"',
|
||||
f'nonce="{nonce}"',
|
||||
f'uri="{url_part}"',
|
||||
f'nc={auth_nc}',
|
||||
f'cnonce="{cnonce}"',
|
||||
f'qop={ClusterApiClient.AUTH_QOP}',
|
||||
f'response="{resp_digest}"',
|
||||
f'algorithm="SHA-256"'
|
||||
])
|
||||
|
||||
def _setupAuth(self) -> None:
|
||||
""" Handles the setup process for authentication by making a temporary digest-token request to the printer API.
|
||||
"""
|
||||
|
||||
if self._auth_tries >= ClusterApiClient.AUTH_MAX_TRIES:
|
||||
Logger.warning("Maximum authorization temporary digest-token request tries exceeded. Is printer-firmware up to date?")
|
||||
return
|
||||
|
||||
def on_finished(resp) -> None:
|
||||
self._auth_tries += 1
|
||||
try:
|
||||
auth_info = json.loads(resp.data().decode())
|
||||
self._auth_id = auth_info["id"]
|
||||
self._auth_key = auth_info["key"]
|
||||
except Exception as ex:
|
||||
Logger.warning(f"Couldn't get temporary digest token: {str(ex)}")
|
||||
return
|
||||
self._auth_tries = 0
|
||||
|
||||
url = "{}/auth/request".format(self.PRINTER_API_PREFIX)
|
||||
request_body = json.dumps({
|
||||
"application": CuraApplication.getInstance().getApplicationDisplayName(),
|
||||
"user": f"user@{platform.node()}",
|
||||
}).encode("utf-8")
|
||||
reply = self._manager.post(self.createEmptyRequest(url, method=HttpRequestMethod.POST, skip_auth=True), request_body)
|
||||
|
||||
self._addCallback(reply, on_finished)
|
||||
|
||||
def _addCallback(self, reply: QNetworkReply, on_finished: Union[Callable[[ClusterApiClientModel], Any],
|
||||
Callable[[List[ClusterApiClientModel]], Any]], model: Type[ClusterApiClientModel] = None,
|
||||
) -> None:
|
||||
|
|
@ -179,6 +277,11 @@ class ClusterApiClient:
|
|||
return
|
||||
|
||||
if reply.error() != QNetworkReply.NetworkError.NoError:
|
||||
if reply.error() == QNetworkReply.NetworkError.AuthenticationRequiredError:
|
||||
nonce_match = re.search(r'nonce="([^"]+)', str(reply.rawHeader(b"WWW-Authenticate")))
|
||||
if nonce_match:
|
||||
self._nonce = nonce_match.group(1)
|
||||
self._nonce_count = 1
|
||||
self._on_error(reply.errorString())
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -94,15 +94,15 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
|
||||
@pyqtSlot(str, name="sendJobToTop")
|
||||
def sendJobToTop(self, print_job_uuid: str) -> None:
|
||||
self._getApiClient().movePrintJobToTop(print_job_uuid)
|
||||
self.getApiClient().movePrintJobToTop(print_job_uuid)
|
||||
|
||||
@pyqtSlot(str, name="deleteJobFromQueue")
|
||||
def deleteJobFromQueue(self, print_job_uuid: str) -> None:
|
||||
self._getApiClient().deletePrintJob(print_job_uuid)
|
||||
self.getApiClient().deletePrintJob(print_job_uuid)
|
||||
|
||||
@pyqtSlot(str, name="forceSendJob")
|
||||
def forceSendJob(self, print_job_uuid: str) -> None:
|
||||
self._getApiClient().forcePrintJob(print_job_uuid)
|
||||
self.getApiClient().forcePrintJob(print_job_uuid)
|
||||
|
||||
def setJobState(self, print_job_uuid: str, action: str) -> None:
|
||||
"""Set the remote print job state.
|
||||
|
|
@ -111,20 +111,20 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
:param action: The action to undertake ('pause', 'resume', 'abort').
|
||||
"""
|
||||
|
||||
self._getApiClient().setPrintJobState(print_job_uuid, action)
|
||||
self.getApiClient().setPrintJobState(print_job_uuid, action)
|
||||
|
||||
def _update(self) -> None:
|
||||
super()._update()
|
||||
if time() - self._time_of_last_request < self.CHECK_CLUSTER_INTERVAL:
|
||||
return # avoid calling the cluster too often
|
||||
self._getApiClient().getPrinters(self._updatePrinters)
|
||||
self._getApiClient().getPrintJobs(self._updatePrintJobs)
|
||||
self.getApiClient().getPrinters(self._updatePrinters)
|
||||
self.getApiClient().getPrintJobs(self._updatePrintJobs)
|
||||
self._updatePrintJobPreviewImages()
|
||||
|
||||
def getMaterials(self, on_finished: Callable[[List[ClusterMaterial]], Any]) -> None:
|
||||
"""Get a list of materials that are installed on the cluster host."""
|
||||
|
||||
self._getApiClient().getMaterials(on_finished = on_finished)
|
||||
self.getApiClient().getMaterials(on_finished = on_finished)
|
||||
|
||||
def sendMaterialProfiles(self) -> None:
|
||||
"""Sync the material profiles in Cura with the printer.
|
||||
|
|
@ -204,7 +204,8 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
parts.append(self._createFormPart("name=require_printer_name", bytes(unique_name, "utf-8"), "text/plain"))
|
||||
# FIXME: move form posting to API client
|
||||
self.postFormWithParts("/cluster-api/v1/print_jobs/", parts, on_finished=self._onPrintUploadCompleted,
|
||||
on_progress=self._onPrintJobUploadProgress)
|
||||
on_progress=self._onPrintJobUploadProgress,
|
||||
request=self.getApiClient().createEmptyRequest("/cluster-api/v1/print_jobs/", content_type=None, method="POST"))
|
||||
self._active_exported_job = None
|
||||
|
||||
def _onPrintJobUploadProgress(self, bytes_sent: int, bytes_total: int) -> None:
|
||||
|
|
@ -236,9 +237,9 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice):
|
|||
|
||||
for print_job in self._print_jobs:
|
||||
if print_job.getPreviewImage() is None:
|
||||
self._getApiClient().getPrintJobPreviewImage(print_job.key, print_job.updatePreviewImageData)
|
||||
self.getApiClient().getPrintJobPreviewImage(print_job.key, print_job.updatePreviewImageData)
|
||||
|
||||
def _getApiClient(self) -> ClusterApiClient:
|
||||
def getApiClient(self) -> ClusterApiClient:
|
||||
"""Get the API client instance."""
|
||||
|
||||
if not self._cluster_api:
|
||||
|
|
|
|||
|
|
@ -147,7 +147,8 @@ class SendMaterialJob(Job):
|
|||
|
||||
# FIXME: move form posting to API client
|
||||
self.device.postFormWithParts(target = "/cluster-api/v1/materials/", parts = parts,
|
||||
on_finished = self._sendingFinished)
|
||||
on_finished = self._sendingFinished,
|
||||
request=self.device.getApiClient().createEmptyRequest("/cluster-api/v1/materials/", content_type=None, method="POST"))
|
||||
|
||||
def _sendingFinished(self, reply: QNetworkReply) -> None:
|
||||
"""Check a reply from an upload to the printer and log an error when the call failed"""
|
||||
|
|
|
|||
|
|
@ -97,8 +97,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit)
|
||||
|
||||
CuraApplication.getInstance().getPreferences().addPreference("usb_printing/enabled", False)
|
||||
|
||||
# This is a callback function that checks if there is any printing in progress via USB when the application tries
|
||||
# to exit. If so, it will show a confirmation before
|
||||
def _checkActivePrintingUponAppExit(self) -> None:
|
||||
|
|
@ -146,8 +144,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
|
||||
|
||||
CuraApplication.getInstance().getPreferences().setValue("usb_printing/enabled", True)
|
||||
|
||||
#Find the g-code to print.
|
||||
gcode_textio = StringIO()
|
||||
gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter"))
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ from . import USBPrinterOutputDevice
|
|||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
USB_PRINT_PREFERENCE_KEY = "usb_printing/enabled"
|
||||
|
||||
@signalemitter
|
||||
class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
|
||||
|
|
@ -43,7 +44,9 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
|
|||
self._update_thread = threading.Thread(target = self._updateThread)
|
||||
self._update_thread.daemon = True
|
||||
|
||||
self._check_updates = True
|
||||
preferences = self._application.getPreferences()
|
||||
preferences.addPreference(USB_PRINT_PREFERENCE_KEY, False)
|
||||
self._check_updates = preferences.getValue(USB_PRINT_PREFERENCE_KEY)
|
||||
|
||||
self._application.applicationShuttingDown.connect(self.stop)
|
||||
# Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
|
||||
|
|
@ -58,7 +61,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
|
|||
device.resetDeviceSettings()
|
||||
|
||||
def start(self):
|
||||
self._check_updates = True
|
||||
self._check_updates = self._application.getPreferences().getValue(USB_PRINT_PREFERENCE_KEY)
|
||||
self._update_thread.start()
|
||||
|
||||
def stop(self, store_data: bool = True):
|
||||
|
|
|
|||
|
|
@ -145,17 +145,18 @@ class Definition(Linter):
|
|||
if "overrides" not in self._definitions[inherits_from]:
|
||||
return self._isDefinedInParent(key, value_dict, self._definitions[inherits_from]["inherits"])
|
||||
|
||||
parent = self._definitions[inherits_from]["overrides"]
|
||||
parent = self._definitions[inherits_from]
|
||||
parent_overrides = self._definitions[inherits_from]["overrides"]
|
||||
if key not in self._definitions[self.base_def]["overrides"]:
|
||||
is_number = False
|
||||
else:
|
||||
is_number = self._definitions[self.base_def]["overrides"][key]["type"] in ("float", "int")
|
||||
for child_key, child_value in value_dict.items():
|
||||
if key in parent:
|
||||
if key in parent_overrides:
|
||||
if child_key in ("default_value", "value"):
|
||||
check_values = [cv for cv in [parent[key].get("default_value", None), parent[key].get("value", None)] if cv is not None]
|
||||
check_values = [cv for cv in [parent_overrides[key].get("default_value", None), parent_overrides[key].get("value", None)] if cv is not None]
|
||||
else:
|
||||
check_values = [parent[key].get(child_key, None)]
|
||||
check_values = [parent_overrides[key].get(child_key, None)]
|
||||
for check_value in check_values:
|
||||
if is_number and child_key in ("default_value", "value"):
|
||||
try:
|
||||
|
|
@ -170,10 +171,10 @@ class Definition(Linter):
|
|||
v = child_value
|
||||
cv = check_value
|
||||
if v == cv:
|
||||
return True, child_key, child_value, parent, inherits_from
|
||||
return True, child_key, child_value, parent_overrides, inherits_from
|
||||
|
||||
if "inherits" in parent:
|
||||
return self._isDefinedInParent(key, value_dict, parent["inherits"])
|
||||
if "inherits" in parent:
|
||||
return self._isDefinedInParent(key, value_dict, parent["inherits"])
|
||||
return False, None, None, None, None
|
||||
|
||||
def _loadExperimentalSettings(self):
|
||||
|
|
|
|||
38
resources/definitions/anycubic_kobra_s1.def.json
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "Anycubic Kobra S1",
|
||||
"inherits": "fdmprinter",
|
||||
"metadata":
|
||||
{
|
||||
"visible": true,
|
||||
"author": "takanuva15",
|
||||
"manufacturer": "Anycubic",
|
||||
"file_formats": "text/x-gcode",
|
||||
"platform": "anycubic_kobra_s1_buildplate.obj",
|
||||
"has_machine_quality": true,
|
||||
"has_materials": true,
|
||||
"has_textured_buildplate": true,
|
||||
"has_variant_buildplates": false,
|
||||
"has_variants": false,
|
||||
"machine_extruder_trains": { "0": "anycubic_kobra_s1_extruder_0" },
|
||||
"platform_texture": "anycubic_kobra_s1_buildplate_texture.png",
|
||||
"preferred_quality_type": "normal",
|
||||
"preferred_variant_name": "0.4mm"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"machine_buildplate_type": { "default_value": "PEI Spring Steel" },
|
||||
"machine_depth": { "default_value": 250 },
|
||||
"machine_end_gcode": { "default_value": "; move printhead away from object\nG1 Z22.000 ; for object exclusion\nG1 E-.76675 F2400\n; fan off\nM106 S0\nM106 P2 S0\n;TYPE:Custom\n; filament end gcode\nG92 E0\nG1 E-2 F3000\nG1 Z24 F900 ; Move print head further up \nG1 F12000; present print\nG1 X44; throw_position_x\nG1 Y270; throw_position_y\nM140 S0 ; turn off heatbed\nM104 S0 ; turn off temperature\nM106 P1 S0 ; turn off fan\nM106 P2 S0\nM106 P3 S0\nM84; disable motors \n; disable stepper motors\nM106 P3 S204\n\n; CONFIG FOR SCREEN PRINT PREVIEW\n; total filament used [g] = {filament_weight}\n\n; CONFIG_BLOCK_START = begin\n; filament_type = {material_type}\n; nozzle_temperature = {material_print_temperature}\n; bed_temperature = {material_bed_temperature}\n; CONFIG_BLOCK_END = end" },
|
||||
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
"machine_height": { "default_value": 250 },
|
||||
"machine_name":
|
||||
{
|
||||
"default_value": "Anycubic Kobra S1",
|
||||
"description": "Anycubic Kobra S1"
|
||||
},
|
||||
"machine_start_gcode": { "default_value": "M106 S0\nM106 P2 S0\n;TYPE:Custom\nG9111 bedTemp={material_bed_temperature} extruderTemp={material_print_temperature}\nM117\nM106 P3 S153\nG90\nG21\nM83 ; use relative distances for extrusion\n; filament start gcode\nM900 K0.035 ; Override pressure advance value\nM106 S0\nM106 P2 S0\n\nM420 S1 ;load stored mesh to avoid auto-leveling" },
|
||||
"machine_width": { "default_value": 250 }
|
||||
}
|
||||
}
|
||||
|
|
@ -1427,11 +1427,10 @@
|
|||
"z_seam_corner":
|
||||
{
|
||||
"label": "Seam Corner Preference",
|
||||
"description": "Control whether corners on the model outline influence the position of the seam. None means that corners have no influence on the seam position. Hide Seam makes the seam more likely to occur on an inside corner. Expose Seam makes the seam more likely to occur on an outside corner. Hide or Expose Seam makes the seam more likely to occur at an inside or outside corner. Smart Hiding allows both inside and outside corners, but chooses inside corners more frequently, if appropriate.",
|
||||
"description": "Control how corners on the model outline influence the position of the seam. Hide Seam makes the seam more likely to occur on an inside corner. Expose Seam makes the seam more likely to occur on an outside corner. Hide or Expose Seam makes the seam more likely to occur at an inside or outside corner. Smart Hiding allows both inside and outside corners, but chooses inside corners more frequently, if appropriate.",
|
||||
"type": "enum",
|
||||
"options":
|
||||
{
|
||||
"z_seam_corner_none": "None",
|
||||
"z_seam_corner_inner": "Hide Seam",
|
||||
"z_seam_corner_outer": "Expose Seam",
|
||||
"z_seam_corner_any": "Hide or Expose Seam",
|
||||
|
|
@ -4712,10 +4711,9 @@
|
|||
"description": "The distance between the nozzle and already printed outer walls when travelling inside a model.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"default_value": 0.6,
|
||||
"value": "machine_nozzle_size * 1.5",
|
||||
"minimum_value": "0",
|
||||
"minimum_value_warning": "machine_nozzle_size * 0.5",
|
||||
"minimum_value": "machine_nozzle_size * 0.5",
|
||||
"maximum_value_warning": "machine_nozzle_size * 10",
|
||||
"enabled": "resolveOrValue('retraction_combing') != 'off'",
|
||||
"settable_per_mesh": false,
|
||||
|
|
@ -7915,6 +7913,35 @@
|
|||
"resolve": "max(extruderValues('interlocking_boundary_avoidance'))",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
},
|
||||
"multi_material_paint_resolution":
|
||||
{
|
||||
"label": "Multi-material Precision",
|
||||
"description": "The precision of the details when generating multi-material shapes based on painting data. A lower precision will provide more details, but increase the slicing time and memory.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"enabled": "extruders_enabled_count > 1",
|
||||
"default_value": 0.2,
|
||||
"value": "min(line_width / 2, layer_height)",
|
||||
"minimum_value": "0.05",
|
||||
"maximum_value": "5",
|
||||
"maximum_value_warning": "line_width * 2",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
},
|
||||
"multi_material_paint_deepness":
|
||||
{
|
||||
"label": "Multi-material Deepness",
|
||||
"description": "The deepness of the painted details inside the model. A higher deepness will provide a better interlocking, but increase slicing time and memory. Set a very high value to go as deep as possible. The actually calculated deepness may vary.",
|
||||
"unit": "mm",
|
||||
"type": "float",
|
||||
"enabled": "extruders_enabled_count > 1",
|
||||
"default_value": 4,
|
||||
"value": "line_width * 10",
|
||||
"minimum_value": "line_width",
|
||||
"minimum_value_warning": "line_width * 2",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -8808,7 +8835,7 @@
|
|||
"value": "line_width + support_xy_distance + 1.0",
|
||||
"enabled": "bridge_settings_enabled",
|
||||
"settable_per_mesh": true,
|
||||
"settable_per_extruder": false
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"bridge_skin_support_threshold":
|
||||
{
|
||||
|
|
|
|||
|
|
@ -74,8 +74,8 @@
|
|||
{
|
||||
"maximum_value": "max(35, min((material_bed_temperature + 20) / 2, 70))",
|
||||
"maximum_value_warning": "max(30, min((material_bed_temperature + 10) / 2, 70))",
|
||||
"minimum_value": "max((material_bed_temperature - 30) / 2, 30)",
|
||||
"minimum_value_warning": "max((material_bed_temperature - 20) / 2, 30)"
|
||||
"minimum_value": "max((material_bed_temperature - 40) / 1.5, 30)",
|
||||
"minimum_value_warning": "max((material_bed_temperature - 35) / 1.5, 30)"
|
||||
},
|
||||
"cool_min_layer_time": { "value": 3 },
|
||||
"cool_min_layer_time_fan_speed_max": { "value": "cool_min_layer_time + 12" },
|
||||
|
|
@ -86,6 +86,7 @@
|
|||
"gantry_height": { "value": 35 },
|
||||
"gradual_support_infill_steps": { "value": "3 if support_interface_enable and support_structure != 'tree' else 0" },
|
||||
"group_outer_walls": { "value": "False" },
|
||||
"infill_angles": { "value": "[-40, 50] if infill_pattern in ('grid', 'lines', 'zigzag') else [ ]" },
|
||||
"infill_before_walls": { "value": "False if infill_sparse_density > 50 else True" },
|
||||
"infill_enable_travel_optimization": { "value": "True" },
|
||||
"infill_material_flow":
|
||||
|
|
@ -94,7 +95,7 @@
|
|||
"value": "(1 + (skin_material_flow-infill_sparse_density) / 100 if infill_sparse_density > skin_material_flow else 1) * material_flow"
|
||||
},
|
||||
"infill_overlap": { "value": "0" },
|
||||
"infill_pattern": { "value": "'zigzag' if infill_sparse_density > 50 else 'triangles'" },
|
||||
"infill_pattern": { "value": "'zigzag' if infill_sparse_density > 50 else 'gyroid' if 15 < speed_infill / ( infill_line_width * 300 / infill_sparse_density ) < 25 else 'triangles'" },
|
||||
"infill_sparse_density": { "maximum_value": "100" },
|
||||
"infill_wipe_dist": { "value": "0" },
|
||||
"inset_direction": { "value": "'inside_out'" },
|
||||
|
|
@ -199,6 +200,7 @@
|
|||
"value": "skin_material_flow"
|
||||
},
|
||||
"roofing_monotonic": { "value": "True" },
|
||||
"skin_angles": { "value": "[-40, 50]" },
|
||||
"skin_material_flow":
|
||||
{
|
||||
"maximum_value": "100",
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@
|
|||
"ultimaker_bvoh_175",
|
||||
"ultimaker_cffpa_175",
|
||||
"ultimaker_cpe_175",
|
||||
"ultimaker_nylon_175",
|
||||
"ultimaker_hips_175",
|
||||
"ultimaker_pc_175",
|
||||
"ultimaker_tpu_175",
|
||||
|
|
|
|||
|
|
@ -701,7 +701,7 @@
|
|||
45
|
||||
]
|
||||
},
|
||||
"support_infill_rate": { "value": 20.0 },
|
||||
"support_infill_rate": { "value": "0 if support_structure == 'tree' else 20.0" },
|
||||
"support_infill_sparse_thickness": { "value": "layer_height" },
|
||||
"support_interface_enable": { "value": true },
|
||||
"support_interface_height": { "value": "4*support_infill_sparse_thickness" },
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@
|
|||
"zyyx_pro_",
|
||||
"octofiber_",
|
||||
"fiberlogy_",
|
||||
"ultimaker_nylon_175",
|
||||
"ultimaker_metallic_pla_175"
|
||||
],
|
||||
"has_machine_materials": true,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"author": "Ultimaker",
|
||||
"manufacturer": "Ultimaker B.V.",
|
||||
"file_formats": "application/x-makerbot-replicator_plus",
|
||||
"platform": "ultimaker_replicator_plus_platform.3MF",
|
||||
"platform": "ultimaker_replicator_plus_platform.obj",
|
||||
"exclude_materials": [
|
||||
"dsm_",
|
||||
"Essentium_",
|
||||
|
|
@ -77,9 +77,116 @@
|
|||
"acceleration_enabled":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": false
|
||||
"value": true
|
||||
},
|
||||
"acceleration_infill":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"acceleration_layer_0":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"acceleration_prime_tower":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"acceleration_print":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": 800
|
||||
},
|
||||
"acceleration_print_layer_0":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"acceleration_roofing":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"acceleration_skirt_brim":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"acceleration_support":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"acceleration_support_bottom":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_support_interface"
|
||||
},
|
||||
"acceleration_support_infill":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_support"
|
||||
},
|
||||
"acceleration_support_interface":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_support"
|
||||
},
|
||||
"acceleration_support_roof":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_support_interface"
|
||||
},
|
||||
"acceleration_topbottom":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"acceleration_travel":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": 5000
|
||||
},
|
||||
"acceleration_travel_enabled":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": true
|
||||
},
|
||||
"acceleration_travel_layer_0":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_travel"
|
||||
},
|
||||
"acceleration_wall":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"acceleration_wall_0":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_wall"
|
||||
},
|
||||
"acceleration_wall_0_roofing":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_wall"
|
||||
},
|
||||
"acceleration_wall_x":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_wall"
|
||||
},
|
||||
"acceleration_wall_x_roofing":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_wall"
|
||||
},
|
||||
"adhesion_type": { "value": "'raft'" },
|
||||
"bridge_skin_speed": { "value": 40 },
|
||||
"bridge_wall_speed": { "value": 40 },
|
||||
"brim_width": { "value": "3" },
|
||||
"cool_during_extruder_switch":
|
||||
{
|
||||
|
|
@ -89,7 +196,7 @@
|
|||
"cool_fan_full_at_height": { "value": "layer_height + layer_height_0" },
|
||||
"cool_fan_speed": { "value": 100 },
|
||||
"cool_fan_speed_0": { "value": 0 },
|
||||
"cool_min_layer_time": { "value": 5 },
|
||||
"cool_min_layer_time": { "value": 7 },
|
||||
"extruder_prime_pos_abs": { "default_value": true },
|
||||
"fill_outline_gaps": { "value": true },
|
||||
"gantry_height": { "value": "60" },
|
||||
|
|
@ -110,7 +217,112 @@
|
|||
"jerk_enabled":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": false
|
||||
"value": true
|
||||
},
|
||||
"jerk_infill":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_layer_0":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_prime_tower":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_print":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": 4
|
||||
},
|
||||
"jerk_print_layer_0":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_roofing":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_skirt_brim":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_support":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_support_bottom":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_support_interface"
|
||||
},
|
||||
"jerk_support_infill":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_support"
|
||||
},
|
||||
"jerk_support_interface":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_support"
|
||||
},
|
||||
"jerk_support_roof":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_support_interface"
|
||||
},
|
||||
"jerk_topbottom":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_travel":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_travel_enabled":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": true
|
||||
},
|
||||
"jerk_travel_layer_0":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_travel"
|
||||
},
|
||||
"jerk_wall":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_wall_0":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_wall_0_roofing":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_wall_x":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"jerk_wall_x_roofing":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"layer_height_0": { "value": "0.2 if resolveOrValue('adhesion_type') == 'raft' else 0.3" },
|
||||
"layer_start_x": { "value": "sum(extruderValues('machine_extruder_start_pos_x')) / len(extruderValues('machine_extruder_start_pos_x'))" },
|
||||
|
|
@ -178,18 +390,58 @@
|
|||
"value": "resolveOrValue('print_sequence') != 'one_at_a_time'"
|
||||
},
|
||||
"print_sequence": { "enabled": false },
|
||||
"raft_airgap": { "value": 0.3 },
|
||||
"raft_acceleration":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"raft_airgap": { "value": 0.33 },
|
||||
"raft_base_acceleration":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"raft_base_flow": { "value": 120 },
|
||||
"raft_base_infill_overlap": { "value": 25 },
|
||||
"raft_base_jerk":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"raft_base_line_spacing": { "value": 2.5 },
|
||||
"raft_base_line_width": { "value": 2 },
|
||||
"raft_base_thickness": { "value": 0.4 },
|
||||
"raft_interface_acceleration":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"raft_interface_fan_speed": { "value": 0 },
|
||||
"raft_interface_infill_overlap": { "value": 25 },
|
||||
"raft_interface_jerk":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"raft_interface_wall_count": { "value": "raft_wall_count" },
|
||||
"raft_jerk":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"raft_margin": { "value": 6.5 },
|
||||
"raft_surface_acceleration":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "acceleration_print"
|
||||
},
|
||||
"raft_surface_fan_speed": { "value": 50.0 },
|
||||
"raft_surface_infill_overlap": { "value": 35 },
|
||||
"raft_surface_jerk":
|
||||
{
|
||||
"enabled": false,
|
||||
"value": "jerk_print"
|
||||
},
|
||||
"raft_surface_wall_count": { "value": "raft_wall_count" },
|
||||
"raft_wall_count": { "value": 2 },
|
||||
"retract_at_layer_change": { "value": true },
|
||||
|
|
@ -197,9 +449,9 @@
|
|||
{
|
||||
"maximum_value": 5,
|
||||
"maximum_value_warning": 2.5,
|
||||
"value": 0.5
|
||||
"value": 1.0
|
||||
},
|
||||
"retraction_combing": { "value": "'infill'" },
|
||||
"retraction_combing": { "value": "'no_outer_surfaces'" },
|
||||
"retraction_count_max":
|
||||
{
|
||||
"maximum_value": 700,
|
||||
|
|
|
|||
|
|
@ -209,14 +209,11 @@
|
|||
"value": 35
|
||||
},
|
||||
"bridge_skin_speed_2": { "value": "speed_print*2/3" },
|
||||
"bridge_sparse_infill_max_density": { "value": 50 },
|
||||
"bridge_skin_support_threshold": { "value": 20 },
|
||||
"bridge_sparse_infill_max_density": { "value": 20 },
|
||||
"bridge_wall_material_flow": { "value": 200 },
|
||||
"bridge_wall_min_length": { "value": 2 },
|
||||
"bridge_wall_speed":
|
||||
{
|
||||
"unit": "mm/s",
|
||||
"value": 50
|
||||
},
|
||||
"bridge_wall_speed": { "value": 50 },
|
||||
"build_volume_temperature":
|
||||
{
|
||||
"force_depends_on_settings": [
|
||||
|
|
@ -237,7 +234,10 @@
|
|||
"default_material_print_temperature": { "maximum_value_warning": 320 },
|
||||
"extra_infill_lines_to_support_skins": { "value": "'walls_and_lines'" },
|
||||
"flooring_layer_count": { "value": 1 },
|
||||
"gradual_flow_enabled": { "value": false },
|
||||
"flooring_material_flow": { "value": "skin_material_flow * 110/93" },
|
||||
"flooring_monotonic": { "value": false },
|
||||
"gradual_flow_discretisation_step_size": { "value": 1 },
|
||||
"gradual_flow_enabled": { "value": true },
|
||||
"hole_xy_offset": { "value": 0.075 },
|
||||
"infill_material_flow": { "value": "material_flow if infill_sparse_density < 95 else 95" },
|
||||
"infill_overlap": { "value": 10 },
|
||||
|
|
@ -384,6 +384,7 @@
|
|||
"unit": "m/s\u00b3",
|
||||
"value": "jerk_wall_0"
|
||||
},
|
||||
"keep_retracting_during_travel": { "enabled": false },
|
||||
"machine_gcode_flavor": { "default_value": "Cheetah" },
|
||||
"machine_max_acceleration_x": { "default_value": 50000 },
|
||||
"machine_max_acceleration_y": { "default_value": 50000 },
|
||||
|
|
@ -428,26 +429,31 @@
|
|||
},
|
||||
"material_print_temperature": { "maximum_value_warning": 320 },
|
||||
"material_print_temperature_layer_0": { "maximum_value_warning": 320 },
|
||||
"max_flow_acceleration": { "value": 8.0 },
|
||||
"max_flow_acceleration": { "value": 1.5 },
|
||||
"max_skin_angle_for_expansion": { "value": 45 },
|
||||
"meshfix_maximum_resolution": { "value": 0.4 },
|
||||
"min_infill_area": { "default_value": 10 },
|
||||
"optimize_wall_printing_order": { "value": false },
|
||||
"prime_during_travel_ratio": { "enabled": false },
|
||||
"prime_tower_brim_enable": { "value": true },
|
||||
"prime_tower_min_volume": { "value": 10 },
|
||||
"prime_tower_mode": { "resolve": "'normal'" },
|
||||
"retraction_amount": { "value": 6.5 },
|
||||
"retraction_combing_avoid_distance": { "value": 1.2 },
|
||||
"retraction_combing_max_distance": { "value": 50 },
|
||||
"retraction_during_travel_ratio": { "enabled": false },
|
||||
"retraction_hop": { "value": 1 },
|
||||
"retraction_hop_after_extruder_switch_height": { "value": 2 },
|
||||
"retraction_hop_enabled": { "value": true },
|
||||
"retraction_min_travel": { "value": "2.5 if support_enable and support_structure=='tree' else line_width * 2.5" },
|
||||
"retraction_prime_speed": { "value": 15 },
|
||||
"skin_edge_support_thickness": { "value": 0 },
|
||||
"roofing_monotonic": { "value": false },
|
||||
"roofing_pattern": { "value": "'zigzag'" },
|
||||
"seam_overhang_angle": { "value": 35 },
|
||||
"skin_edge_support_thickness": { "value": 0.8 },
|
||||
"skin_material_flow": { "value": 93 },
|
||||
"skin_outline_count": { "value": 0 },
|
||||
"skin_overlap": { "value": 0 },
|
||||
"skin_overlap": { "value": 20 },
|
||||
"skin_preshrink": { "value": 0 },
|
||||
"skirt_brim_minimal_length": { "value": 1000 },
|
||||
"skirt_brim_speed":
|
||||
|
|
@ -543,12 +549,12 @@
|
|||
"speed_wall":
|
||||
{
|
||||
"maximum_value_warning": 300,
|
||||
"value": "speed_print*2/3"
|
||||
"value": "speed_print*1/2"
|
||||
},
|
||||
"speed_wall_0":
|
||||
{
|
||||
"maximum_value_warning": 300,
|
||||
"value": "speed_wall"
|
||||
"value": "speed_wall*60/75"
|
||||
},
|
||||
"speed_wall_0_flooring":
|
||||
{
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@
|
|||
"machine_max_feedrate_z": { "default_value": 40 },
|
||||
"machine_min_cool_heat_time_window": { "value": "15" },
|
||||
"machine_name": { "default_value": "MakerBot Sketch Sprint" },
|
||||
"machine_start_gcode": { "default_value": "G28\nM132 X Y Z A B\nG1 Z50.000 F420\nG161 X Y F3300\nM7 T0\nM6 T0\nM651 S255\nG1 Z0.25 F6000\nG1 E-1.5 F800\nG1 E2 F800\nG1 X111 Y111 Z0.25 F4800\nG1 X111 Y-111 E25 F1200" },
|
||||
"machine_start_gcode": { "default_value": "G28\nM132 X Y Z A B\nG1 Z50.000 F420\nG161 X Y F3300\nM7 T0\nM6 T0\nM651 S255\nSET_SERVO SERVO=my_servo ANGLE=180\nSET_FAN_SPEED FAN=external_fan SPEED=1\nSET_FAN_SPEED FAN=internal_fan SPEED=1\nG1 Z0.25 F6000\nG1 E-1.5 F800\nG1 E2 F800\nG1 X111 Y111 Z0.25 F4800\nG1 X111 Y-111 E25 F1200" },
|
||||
"machine_width": { "default_value": 221.5 },
|
||||
"material_bed_temp_wait": { "value": "False" },
|
||||
"material_bed_temperature":
|
||||
|
|
|
|||
|
|
@ -21,36 +21,37 @@
|
|||
},
|
||||
"overrides":
|
||||
{
|
||||
"acceleration_enabled": { "default_value": false },
|
||||
"acceleration_layer_0": { "value": 1800 },
|
||||
"acceleration_print": { "default_value": 2200 },
|
||||
"acceleration_roofing": { "value": 1800 },
|
||||
"acceleration_travel_layer_0": { "value": 1800 },
|
||||
"acceleration_wall_0": { "value": 1800 },
|
||||
"adhesion_type": { "default_value": "skirt" },
|
||||
"alternate_extra_perimeter": { "default_value": true },
|
||||
"bridge_fan_speed": { "default_value": 100 },
|
||||
"acceleration_enabled": { "default_value": true },
|
||||
"acceleration_layer_0": { "value": "math.ceil(acceleration_print / 10)" },
|
||||
"acceleration_print": { "default_value": 5000 },
|
||||
"acceleration_roofing": { "value": "math.ceil(acceleration_topbottom * 3000 / 5000) " },
|
||||
"acceleration_support": { "value": "math.ceil(acceleration_print / 2)" },
|
||||
"acceleration_travel": { "value": "acceleration_print if magic_spiralize else min(math.ceil(acceleration_print * 7000 / 5000), round((machine_max_acceleration_x + machine_max_acceleration_y) / 2, -2))" },
|
||||
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 3000 / 5000)" },
|
||||
"adhesion_type":
|
||||
{
|
||||
"default_value": "skirt",
|
||||
"value": "'brim' if draft_shield_enabled else 'skirt'"
|
||||
},
|
||||
"bridge_fan_speed_2": { "resolve": "max(cool_fan_speed, 50)" },
|
||||
"bridge_fan_speed_3": { "resolve": "max(cool_fan_speed, 20)" },
|
||||
"bridge_settings_enabled": { "default_value": true },
|
||||
"bridge_wall_coast": { "default_value": 10 },
|
||||
"cool_fan_full_at_height": { "value": "resolveOrValue('layer_height_0') + resolveOrValue('layer_height') * max(1, cool_fan_full_layer - 1)" },
|
||||
"cool_fan_full_layer": { "value": 4 },
|
||||
"cool_fan_speed_min": { "value": "cool_fan_speed" },
|
||||
"cool_min_layer_time": { "default_value": 15 },
|
||||
"cool_min_layer_time_fan_speed_max": { "default_value": 20 },
|
||||
"fill_outline_gaps": { "default_value": true },
|
||||
"cool_min_layer_time_fan_speed_max": { "value": "cool_min_layer_time + 5" },
|
||||
"cool_min_speed": { "value": "max(round(speed_layer_0 / 2), round(speed_wall_0 * 3 / 4) if cool_lift_head else round(speed_wall_0 / 2))" },
|
||||
"gantry_height": { "value": 30 },
|
||||
"infill_before_walls": { "default_value": false },
|
||||
"infill_enable_travel_optimization": { "default_value": true },
|
||||
"jerk_enabled": { "default_value": false },
|
||||
"jerk_roofing": { "value": 10 },
|
||||
"jerk_wall_0": { "value": 10 },
|
||||
"layer_height_0": { "resolve": "max(0.2, min(extruderValues('layer_height')))" },
|
||||
"line_width": { "value": "machine_nozzle_size * 1.125" },
|
||||
"machine_acceleration": { "default_value": 1500 },
|
||||
"infill_line_width": { "value": "machine_nozzle_size * 1.5" },
|
||||
"infill_pattern": { "value": "'zigzag' if infill_sparse_density > 80 else 'gyroid'" },
|
||||
"initial_layer_line_width_factor": { "default_value": 125.0 },
|
||||
"layer_height_0": { "resolve": "max(machine_nozzle_size / 2, min(extruderValues('layer_height')))" },
|
||||
"machine_acceleration": { "default_value": 5000 },
|
||||
"machine_depth": { "default_value": 250 },
|
||||
"machine_end_gcode": { "default_value": "print_end" },
|
||||
"machine_end_gcode": { "default_value": "PRINT_END" },
|
||||
"machine_endstop_positive_direction_x": { "default_value": true },
|
||||
"machine_endstop_positive_direction_y": { "default_value": true },
|
||||
"machine_endstop_positive_direction_z": { "default_value": false },
|
||||
|
|
@ -67,16 +68,15 @@
|
|||
},
|
||||
"machine_heated_bed": { "default_value": true },
|
||||
"machine_height": { "default_value": 250 },
|
||||
"machine_max_acceleration_x": { "default_value": 1500 },
|
||||
"machine_max_acceleration_y": { "default_value": 1500 },
|
||||
"machine_max_acceleration_z": { "default_value": 250 },
|
||||
"machine_max_acceleration_x": { "default_value": 20000 },
|
||||
"machine_max_acceleration_y": { "default_value": 20000 },
|
||||
"machine_max_acceleration_z": { "default_value": 500 },
|
||||
"machine_max_feedrate_e": { "default_value": 120 },
|
||||
"machine_max_feedrate_x": { "value": 500 },
|
||||
"machine_max_feedrate_y": { "value": 500 },
|
||||
"machine_max_feedrate_z": { "default_value": 40 },
|
||||
"machine_max_jerk_e": { "default_value": 60 },
|
||||
"machine_max_jerk_xy": { "default_value": 20 },
|
||||
"machine_max_jerk_z": { "default_value": 1 },
|
||||
"machine_name": { "default_value": "VORON2" },
|
||||
"machine_start_gcode": { "default_value": ";Nozzle diameter = {machine_nozzle_size}\n;Filament type = {material_type}\n;Filament name = {material_name}\n;Filament weight = {filament_weight}\n; M190 S{material_bed_temperature_layer_0}\n; M109 S{material_print_temperature_layer_0}\nprint_start EXTRUDER={material_print_temperature_layer_0} BED={material_bed_temperature_layer_0} CHAMBER={build_volume_temperature}" },
|
||||
"machine_start_gcode": { "default_value": ";Nozzle diameter = {machine_nozzle_size}\n;Filament type = {material_type}\n;Filament name = {material_name}\n;Filament weight = {filament_weight}\n; M190 S{material_bed_temperature_layer_0}\n; M109 S{material_print_temperature_layer_0}\nPRINT_START EXTRUDER={material_print_temperature_layer_0} BED={material_bed_temperature_layer_0} CHAMBER={build_volume_temperature}" },
|
||||
"machine_steps_per_mm_x": { "default_value": 80 },
|
||||
"machine_steps_per_mm_y": { "default_value": 80 },
|
||||
"machine_steps_per_mm_z": { "default_value": 400 },
|
||||
|
|
@ -84,40 +84,36 @@
|
|||
"meshfix_maximum_resolution": { "default_value": 0.01 },
|
||||
"min_infill_area": { "default_value": 5.0 },
|
||||
"minimum_polygon_circumference": { "default_value": 0.2 },
|
||||
"optimize_wall_printing_order": { "default_value": true },
|
||||
"retraction_amount": { "default_value": 0.75 },
|
||||
"retraction_combing": { "value": "'noskin'" },
|
||||
"retraction_combing_max_distance": { "default_value": 10 },
|
||||
"retraction_hop": { "default_value": 0.2 },
|
||||
"retraction_hop_enabled": { "default_value": true },
|
||||
"retraction_prime_speed":
|
||||
"retraction_hop":
|
||||
{
|
||||
"maximum_value_warning": 130,
|
||||
"value": "math.ceil(retraction_speed * 0.4)"
|
||||
"default_value": 0.2,
|
||||
"value": "machine_nozzle_size / 2"
|
||||
},
|
||||
"retraction_retract_speed": { "maximum_value_warning": 130 },
|
||||
"retraction_hop_enabled": { "default_value": true },
|
||||
"retraction_hop_only_when_collides": { "default_value": true },
|
||||
"retraction_prime_speed": { "maximum_value_warning": "machine_max_feedrate_e - 10" },
|
||||
"retraction_retract_speed": { "maximum_value_warning": "machine_max_feedrate_e - 10" },
|
||||
"retraction_speed":
|
||||
{
|
||||
"default_value": 30,
|
||||
"maximum_value_warning": 130
|
||||
"maximum_value_warning": "machine_max_feedrate_e - 10"
|
||||
},
|
||||
"roofing_layer_count": { "value": 1 },
|
||||
"skirt_brim_minimal_length": { "default_value": 550 },
|
||||
"speed_layer_0": { "value": "math.ceil(speed_print * 0.25)" },
|
||||
"speed_roofing": { "value": "math.ceil(speed_print * 0.33)" },
|
||||
"speed_infill": { "value": "speed_print * 1.5" },
|
||||
"speed_layer_0": { "value": "speed_print * 3 / 8" },
|
||||
"speed_print": { "value": "round(6.4 / layer_height / machine_nozzle_size, -1)" },
|
||||
"speed_slowdown_layers": { "default_value": 4 },
|
||||
"speed_topbottom": { "value": "math.ceil(speed_print * 0.33)" },
|
||||
"speed_travel":
|
||||
{
|
||||
"maximum_value_warning": 501,
|
||||
"value": 300
|
||||
"maximum_value_warning": "max(500, round((machine_max_feedrate_x + machine_max_feedrate_y) / 2, -2)) + 1",
|
||||
"value": "speed_print if magic_spiralize else max(speed_print, round((machine_max_feedrate_x + machine_max_feedrate_y) / 2, -2))"
|
||||
},
|
||||
"speed_travel_layer_0": { "value": "math.ceil(speed_travel * 0.4)" },
|
||||
"speed_wall": { "value": "math.ceil(speed_print * 0.33)" },
|
||||
"speed_wall_0": { "value": "math.ceil(speed_print * 0.33)" },
|
||||
"speed_wall_x": { "value": "math.ceil(speed_print * 0.66)" },
|
||||
"travel_avoid_other_parts": { "default_value": false },
|
||||
"wall_line_width": { "value": "machine_nozzle_size" },
|
||||
"speed_z_hop": { "value": "max(10, machine_max_feedrate_z / 2)" },
|
||||
"top_bottom_thickness": { "value": "wall_thickness" },
|
||||
"wall_overhang_angle": { "default_value": 75 },
|
||||
"wall_overhang_speed_factors":
|
||||
{
|
||||
|
|
@ -125,6 +121,11 @@
|
|||
50
|
||||
]
|
||||
},
|
||||
"zig_zaggify_infill": { "value": true }
|
||||
"wall_thickness": { "value": "wall_line_width_0 + wall_line_width_x" },
|
||||
"xy_offset_layer_0": { "value": "xy_offset - 0.1" },
|
||||
"z_seam_corner": { "value": "'z_seam_corner_weighted'" },
|
||||
"z_seam_relative": { "value": "True" },
|
||||
"zig_zaggify_infill": { "value": true },
|
||||
"zig_zaggify_support": { "value": true }
|
||||
}
|
||||
}
|
||||
|
|
@ -75,10 +75,6 @@
|
|||
"Extrudr_GreenTECPro_Silver_175",
|
||||
"Extrudr_GreenTECPro_White_175",
|
||||
"verbatim_bvoh_175",
|
||||
"Vertex_Delta_ABS",
|
||||
"Vertex_Delta_PET",
|
||||
"Vertex_Delta_PLA",
|
||||
"Vertex_Delta_TPU",
|
||||
"chromatik_pla",
|
||||
"dsm_arnitel2045_175",
|
||||
"dsm_novamid1070_175",
|
||||
|
|
@ -186,9 +182,7 @@
|
|||
"machine_extruder_trains": { "0": "zyyx_plus_extruder_0" },
|
||||
"machine_x3g_variant": "z",
|
||||
"preferred_material": "generic_pla",
|
||||
"preferred_quality_type": "normal",
|
||||
"quality_definition": "zyyx_plus",
|
||||
"setting_version": 3
|
||||
"preferred_quality_type": "normal"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
|
|
|
|||
|
|
@ -73,10 +73,6 @@
|
|||
"Extrudr_GreenTECPro_Silver_175",
|
||||
"Extrudr_GreenTECPro_White_175",
|
||||
"verbatim_bvoh_175",
|
||||
"Vertex_Delta_ABS",
|
||||
"Vertex_Delta_PET",
|
||||
"Vertex_Delta_PLA",
|
||||
"Vertex_Delta_TPU",
|
||||
"chromatik_pla",
|
||||
"dsm_arnitel2045_175",
|
||||
"dsm_novamid1070_175",
|
||||
|
|
@ -185,8 +181,6 @@
|
|||
"machine_x3g_variant": "z",
|
||||
"preferred_material": "generic_pla",
|
||||
"preferred_variant_name": "Carbon0.6",
|
||||
"quality_definition": "zyyx_pro",
|
||||
"setting_version": 3,
|
||||
"variants_name": "SwiftTool"
|
||||
},
|
||||
"overrides":
|
||||
|
|
|
|||
16
resources/extruders/anycubic_kobra_s1_extruder_0.def.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "Extruder 1",
|
||||
"inherits": "fdmextruder",
|
||||
"metadata":
|
||||
{
|
||||
"machine": "anycubic_kobra_s1",
|
||||
"position": "0"
|
||||
},
|
||||
"overrides":
|
||||
{
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
BIN
resources/images/anycubic_kobra_s1_buildplate_texture.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 329 KiB After Width: | Height: | Size: 282 KiB |
|
Before Width: | Height: | Size: 331 KiB After Width: | Height: | Size: 304 KiB |
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 305 KiB |
|
|
@ -14,5 +14,5 @@ variant = AA 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = AA 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = AA 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = AA 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = AA 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = AA 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -17,5 +17,5 @@ jerk_print = 30
|
|||
material_bed_temperature = =default_material_bed_temperature + 5
|
||||
material_print_temperature = =default_material_print_temperature + 15
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@ variant = AA 0.4
|
|||
jerk_print = 30
|
||||
material_print_temperature = =default_material_print_temperature + 10
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@ variant = CC 0.4
|
|||
jerk_print = 30
|
||||
material_print_temperature = =default_material_print_temperature + 20
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 4
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 3
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.4
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.6
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.6
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.6
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.6
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.6
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@ variant = CC 0.6
|
|||
jerk_print = 30
|
||||
material_print_temperature = =default_material_print_temperature + 20
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 4
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 3
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,5 @@ variant = CC 0.6
|
|||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,5 @@ variant = HT 0.6
|
|||
|
||||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 70
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,5 @@ variant = HT 0.6
|
|||
|
||||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 70
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@ variant = HT 0.6
|
|||
jerk_print = 30
|
||||
material_print_temperature = =default_material_print_temperature + 20
|
||||
speed_print = 80
|
||||
wall_thickness = =line_width * 4
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 3
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,5 @@ variant = HT 0.6
|
|||
|
||||
[values]
|
||||
jerk_print = 30
|
||||
speed_print = 70
|
||||
wall_thickness = =line_width * 3
|
||||
wall_thickness = =wall_line_width_0 + wall_line_width_x * 2
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
[general]
|
||||
definition = ultimaker_method
|
||||
name = Solid
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = solid
|
||||
material = ultimaker_nylon_175
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = 1A
|
||||
|
||||
[values]
|
||||
bottom_thickness = =top_bottom_thickness
|
||||
infill_angles = [45,135]
|
||||
infill_material_flow = 97
|
||||
infill_pattern = zigzag
|
||||
infill_sparse_density = 99
|
||||
top_bottom_thickness = =layer_height * 2
|
||||
top_thickness = =top_bottom_thickness
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
[general]
|
||||
definition = ultimaker_method
|
||||
name = Solid
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = solid
|
||||
material = ultimaker_nylon_175
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = 1C
|
||||
|
||||
[values]
|
||||
bottom_thickness = =top_bottom_thickness
|
||||
infill_angles = [45,135]
|
||||
infill_material_flow = 97
|
||||
infill_pattern = zigzag
|
||||
infill_sparse_density = 99
|
||||
top_bottom_thickness = =layer_height * 2
|
||||
top_thickness = =top_bottom_thickness
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
[general]
|
||||
definition = ultimaker_method
|
||||
name = Solid
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = solid
|
||||
material = ultimaker_nylon_175
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = LABS
|
||||
|
||||
[values]
|
||||
bottom_thickness = =top_bottom_thickness
|
||||
infill_angles = [45,135]
|
||||
infill_material_flow = 97
|
||||
infill_pattern = zigzag
|
||||
infill_sparse_density = 99
|
||||
top_bottom_thickness = =layer_height * 2
|
||||
top_thickness = =top_bottom_thickness
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
[general]
|
||||
definition = ultimaker_methodx
|
||||
name = Solid
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = solid
|
||||
material = ultimaker_nylon_175
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = 1A
|
||||
|
||||
[values]
|
||||
bottom_thickness = =top_bottom_thickness
|
||||
infill_angles = [45,135]
|
||||
infill_material_flow = 97
|
||||
infill_pattern = zigzag
|
||||
infill_sparse_density = 99
|
||||
top_bottom_thickness = =layer_height * 2
|
||||
top_thickness = =top_bottom_thickness
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ cool_min_temperature = 245.0
|
|||
infill_pattern = zigzag
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ infill_pattern = zigzag
|
|||
infill_sparse_density = 99
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
[general]
|
||||
definition = ultimaker_methodx
|
||||
name = Solid
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = solid
|
||||
material = ultimaker_nylon_175
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = 1C
|
||||
|
||||
[values]
|
||||
bottom_thickness = =top_bottom_thickness
|
||||
infill_angles = [45,135]
|
||||
infill_material_flow = 97
|
||||
infill_pattern = zigzag
|
||||
infill_sparse_density = 99
|
||||
top_bottom_thickness = =layer_height * 2
|
||||
top_thickness = =top_bottom_thickness
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ cool_min_temperature = 245.0
|
|||
infill_pattern = zigzag
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ infill_pattern = zigzag
|
|||
infill_sparse_density = 99
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ cool_min_temperature = 245.0
|
|||
infill_pattern = zigzag
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ infill_pattern = zigzag
|
|||
infill_sparse_density = 99
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
[general]
|
||||
definition = ultimaker_methodx
|
||||
name = Solid
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = solid
|
||||
material = ultimaker_nylon_175
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = LABS
|
||||
|
||||
[values]
|
||||
bottom_thickness = =top_bottom_thickness
|
||||
infill_angles = [45,135]
|
||||
infill_material_flow = 97
|
||||
infill_pattern = zigzag
|
||||
infill_sparse_density = 99
|
||||
top_bottom_thickness = =layer_height * 2
|
||||
top_thickness = =top_bottom_thickness
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ cool_min_temperature = 245.0
|
|||
infill_pattern = zigzag
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ infill_pattern = zigzag
|
|||
infill_sparse_density = 99
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ cool_min_temperature = 245.0
|
|||
infill_pattern = zigzag
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ infill_pattern = zigzag
|
|||
infill_sparse_density = 99
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ cool_min_temperature = 245.0
|
|||
infill_pattern = zigzag
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ infill_pattern = zigzag
|
|||
infill_sparse_density = 99
|
||||
jerk_print = 35
|
||||
speed_layer_0 = 55
|
||||
speed_prime_tower = =speed_print/5
|
||||
speed_print = 300
|
||||
speed_support = 100
|
||||
speed_support_interface = 75
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Visual
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = visual
|
||||
is_experimental = True
|
||||
material = generic_abs
|
||||
quality_type = high
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
speed_infill = 50
|
||||
top_bottom_thickness = 1.05
|
||||
z_seam_type = back
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_abs
|
||||
quality_type = fast
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Visual
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = visual
|
||||
is_experimental = True
|
||||
material = generic_abs
|
||||
quality_type = fast
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
speed_infill = 50
|
||||
top_bottom_thickness = 1.05
|
||||
z_seam_type = back
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_abs
|
||||
quality_type = normal
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Visual
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = visual
|
||||
is_experimental = True
|
||||
material = generic_abs
|
||||
quality_type = normal
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
speed_infill = 50
|
||||
top_bottom_thickness = 1.05
|
||||
z_seam_type = back
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Quick
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = quick
|
||||
is_experimental = True
|
||||
material = generic_abs
|
||||
quality_type = draft
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
infill_sparse_density = 15
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = 0.8
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_cpe_plus
|
||||
quality_type = fast
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_cpe_plus
|
||||
quality_type = normal
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_cpe
|
||||
quality_type = fast
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_cpe
|
||||
quality_type = normal
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_nylon
|
||||
quality_type = fast
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_nylon
|
||||
quality_type = normal
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_pc
|
||||
quality_type = fast
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_pc
|
||||
quality_type = normal
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_petg
|
||||
quality_type = fast
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_petg
|
||||
quality_type = normal
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Visual
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = visual
|
||||
is_experimental = True
|
||||
material = generic_pla
|
||||
quality_type = high
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
speed_infill = 50
|
||||
top_bottom_thickness = 1.05
|
||||
z_seam_type = back
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_pla
|
||||
quality_type = fast
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Visual
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = visual
|
||||
is_experimental = True
|
||||
material = generic_pla
|
||||
quality_type = fast
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
speed_infill = 50
|
||||
top_bottom_thickness = 1.05
|
||||
z_seam_type = back
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
[general]
|
||||
definition = ultimaker_s8
|
||||
name = Accurate
|
||||
version = 4
|
||||
|
||||
[metadata]
|
||||
intent_category = engineering
|
||||
is_experimental = True
|
||||
material = generic_pla
|
||||
quality_type = normal
|
||||
setting_version = 25
|
||||
type = intent
|
||||
variant = AA 0.4
|
||||
|
||||
[values]
|
||||
jerk_print = 6000
|
||||
speed_infill = =speed_print
|
||||
speed_print = 30
|
||||
speed_topbottom = =speed_print
|
||||
speed_wall = =speed_print
|
||||
speed_wall_0 = =speed_wall
|
||||
speed_wall_x = =speed_wall
|
||||
top_bottom_thickness = =wall_thickness
|
||||
wall_thickness = =line_width * 3
|
||||
|
||||