mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-17 03:37:48 -06:00
Merge branch 'master' into CURA-5334_extruder_def_for_all
This commit is contained in:
commit
3761123513
710 changed files with 272858 additions and 117101 deletions
|
@ -26,6 +26,6 @@
|
||||||
<screenshots>
|
<screenshots>
|
||||||
<screenshot type="default" width="1280" height="720">http://software.ultimaker.com/Cura.png</screenshot>
|
<screenshot type="default" width="1280" height="720">http://software.ultimaker.com/Cura.png</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
<url type="homepage">https://ultimaker.com/en/products/cura-software?utm_source=cura&utm_medium=software&utm_campaign=resources</url>
|
<url type="homepage">https://ultimaker.com/en/products/cura-software?utm_source=cura&utm_medium=software&utm_campaign=resources</url>
|
||||||
<translation type="gettext">Cura</translation>
|
<translation type="gettext">Cura</translation>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
from typing import Tuple, Optional
|
||||||
|
|
||||||
from cura.Backups.BackupsManager import BackupsManager
|
from cura.Backups.BackupsManager import BackupsManager
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +19,7 @@ class Backups:
|
||||||
## Create a new back-up using the BackupsManager.
|
## Create a new back-up using the BackupsManager.
|
||||||
# \return Tuple containing a ZIP file with the back-up data and a dict
|
# \return Tuple containing a ZIP file with the back-up data and a dict
|
||||||
# with metadata about the back-up.
|
# with metadata about the back-up.
|
||||||
def createBackup(self) -> (bytes, dict):
|
def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]:
|
||||||
return self.manager.createBackup()
|
return self.manager.createBackup()
|
||||||
|
|
||||||
## Restore a back-up using the BackupsManager.
|
## Restore a back-up using the BackupsManager.
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Math.Polygon import Polygon
|
from UM.Math.Polygon import Polygon
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from cura.Arranging.ShapeArray import ShapeArray
|
from cura.Arranging.ShapeArray import ShapeArray
|
||||||
from cura.Scene import ZOffsetDecorator
|
from cura.Scene import ZOffsetDecorator
|
||||||
|
|
||||||
|
@ -85,8 +87,7 @@ class Arrange:
|
||||||
# \param node
|
# \param node
|
||||||
# \param offset_shape_arr ShapeArray with offset, for placing the shape
|
# \param offset_shape_arr ShapeArray with offset, for placing the shape
|
||||||
# \param hull_shape_arr ShapeArray without offset, used to find location
|
# \param hull_shape_arr ShapeArray without offset, used to find location
|
||||||
def findNodePlacement(self, node, offset_shape_arr, hull_shape_arr, step = 1):
|
def findNodePlacement(self, node: SceneNode, offset_shape_arr: ShapeArray, hull_shape_arr: ShapeArray, step = 1):
|
||||||
new_node = copy.deepcopy(node)
|
|
||||||
best_spot = self.bestSpot(
|
best_spot = self.bestSpot(
|
||||||
hull_shape_arr, start_prio = self._last_priority, step = step)
|
hull_shape_arr, start_prio = self._last_priority, step = step)
|
||||||
x, y = best_spot.x, best_spot.y
|
x, y = best_spot.x, best_spot.y
|
||||||
|
@ -95,21 +96,21 @@ class Arrange:
|
||||||
self._last_priority = best_spot.priority
|
self._last_priority = best_spot.priority
|
||||||
|
|
||||||
# Ensure that the object is above the build platform
|
# Ensure that the object is above the build platform
|
||||||
new_node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
|
node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
|
||||||
if new_node.getBoundingBox():
|
if node.getBoundingBox():
|
||||||
center_y = new_node.getWorldPosition().y - new_node.getBoundingBox().bottom
|
center_y = node.getWorldPosition().y - node.getBoundingBox().bottom
|
||||||
else:
|
else:
|
||||||
center_y = 0
|
center_y = 0
|
||||||
|
|
||||||
if x is not None: # We could find a place
|
if x is not None: # We could find a place
|
||||||
new_node.setPosition(Vector(x, center_y, y))
|
node.setPosition(Vector(x, center_y, y))
|
||||||
found_spot = True
|
found_spot = True
|
||||||
self.place(x, y, offset_shape_arr) # place the object in arranger
|
self.place(x, y, offset_shape_arr) # place the object in arranger
|
||||||
else:
|
else:
|
||||||
Logger.log("d", "Could not find spot!"),
|
Logger.log("d", "Could not find spot!"),
|
||||||
found_spot = False
|
found_spot = False
|
||||||
new_node.setPosition(Vector(200, center_y, 100))
|
node.setPosition(Vector(200, center_y, 100))
|
||||||
return new_node, found_spot
|
return found_spot
|
||||||
|
|
||||||
## Fill priority, center is best. Lower value is better
|
## Fill priority, center is best. Lower value is better
|
||||||
# This is a strategy for the arranger.
|
# This is a strategy for the arranger.
|
||||||
|
|
|
@ -20,14 +20,14 @@ from typing import List
|
||||||
|
|
||||||
## Do arrangements on multiple build plates (aka builtiplexer)
|
## Do arrangements on multiple build plates (aka builtiplexer)
|
||||||
class ArrangeArray:
|
class ArrangeArray:
|
||||||
def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]):
|
def __init__(self, x: int, y: int, fixed_nodes: List[SceneNode]) -> None:
|
||||||
self._x = x
|
self._x = x
|
||||||
self._y = y
|
self._y = y
|
||||||
self._fixed_nodes = fixed_nodes
|
self._fixed_nodes = fixed_nodes
|
||||||
self._count = 0
|
self._count = 0
|
||||||
self._first_empty = None
|
self._first_empty = None
|
||||||
self._has_empty = False
|
self._has_empty = False
|
||||||
self._arrange = []
|
self._arrange = [] # type: List[Arrange]
|
||||||
|
|
||||||
def _update_first_empty(self):
|
def _update_first_empty(self):
|
||||||
for i, a in enumerate(self._arrange):
|
for i, a in enumerate(self._arrange):
|
||||||
|
@ -48,16 +48,17 @@ class ArrangeArray:
|
||||||
return self._count
|
return self._count
|
||||||
|
|
||||||
def get(self, index):
|
def get(self, index):
|
||||||
|
print(self._arrange)
|
||||||
return self._arrange[index]
|
return self._arrange[index]
|
||||||
|
|
||||||
def getFirstEmpty(self):
|
def getFirstEmpty(self):
|
||||||
if not self._is_empty:
|
if not self._has_empty:
|
||||||
self.add()
|
self.add()
|
||||||
return self._arrange[self._first_empty]
|
return self._arrange[self._first_empty]
|
||||||
|
|
||||||
|
|
||||||
class ArrangeObjectsAllBuildPlatesJob(Job):
|
class ArrangeObjectsAllBuildPlatesJob(Job):
|
||||||
def __init__(self, nodes: List[SceneNode], min_offset = 8):
|
def __init__(self, nodes: List[SceneNode], min_offset = 8) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._nodes = nodes
|
self._nodes = nodes
|
||||||
self._min_offset = min_offset
|
self._min_offset = min_offset
|
||||||
|
|
|
@ -20,7 +20,7 @@ from typing import List
|
||||||
|
|
||||||
|
|
||||||
class ArrangeObjectsJob(Job):
|
class ArrangeObjectsJob(Job):
|
||||||
def __init__(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode], min_offset = 8):
|
def __init__(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode], min_offset = 8) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._nodes = nodes
|
self._nodes = nodes
|
||||||
self._fixed_nodes = fixed_nodes
|
self._fixed_nodes = fixed_nodes
|
||||||
|
|
|
@ -28,12 +28,12 @@ class Backup:
|
||||||
# Re-use translation catalog.
|
# Re-use translation catalog.
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
def __init__(self, zip_file: bytes = None, meta_data: dict = None):
|
def __init__(self, zip_file: bytes = None, meta_data: dict = None) -> None:
|
||||||
self.zip_file = zip_file # type: Optional[bytes]
|
self.zip_file = zip_file # type: Optional[bytes]
|
||||||
self.meta_data = meta_data # type: Optional[dict]
|
self.meta_data = meta_data # type: Optional[dict]
|
||||||
|
|
||||||
## Create a back-up from the current user config folder.
|
## Create a back-up from the current user config folder.
|
||||||
def makeFromCurrent(self) -> (bool, Optional[str]):
|
def makeFromCurrent(self) -> None:
|
||||||
cura_release = CuraApplication.getInstance().getVersion()
|
cura_release = CuraApplication.getInstance().getVersion()
|
||||||
version_data_dir = Resources.getDataStoragePath()
|
version_data_dir = Resources.getDataStoragePath()
|
||||||
|
|
||||||
|
@ -54,6 +54,8 @@ class Backup:
|
||||||
# Create an empty buffer and write the archive to it.
|
# Create an empty buffer and write the archive to it.
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
archive = self._makeArchive(buffer, version_data_dir)
|
archive = self._makeArchive(buffer, version_data_dir)
|
||||||
|
if archive is None:
|
||||||
|
return
|
||||||
files = archive.namelist()
|
files = archive.namelist()
|
||||||
|
|
||||||
# Count the metadata items. We do this in a rather naive way at the moment.
|
# Count the metadata items. We do this in a rather naive way at the moment.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
from typing import Optional
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from cura.Backups.Backup import Backup
|
from cura.Backups.Backup import Backup
|
||||||
|
@ -18,7 +18,7 @@ class BackupsManager:
|
||||||
## Get a back-up of the current configuration.
|
## Get a back-up of the current configuration.
|
||||||
# \return A tuple containing a ZipFile (the actual back-up) and a dict
|
# \return A tuple containing a ZipFile (the actual back-up) and a dict
|
||||||
# containing some metadata (like version).
|
# containing some metadata (like version).
|
||||||
def createBackup(self) -> (Optional[bytes], Optional[dict]):
|
def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]:
|
||||||
self._disableAutoSave()
|
self._disableAutoSave()
|
||||||
backup = Backup()
|
backup = Backup()
|
||||||
backup.makeFromCurrent()
|
backup.makeFromCurrent()
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QUrl
|
from PyQt5.QtCore import QObject, QUrl
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from typing import List, TYPE_CHECKING
|
||||||
|
|
||||||
from UM.Event import CallFunctionEvent
|
from UM.Event import CallFunctionEvent
|
||||||
from UM.Application import Application
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Scene.Selection import Selection
|
from UM.Scene.Selection import Selection
|
||||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||||
|
@ -14,6 +14,7 @@ from UM.Operations.GroupedOperation import GroupedOperation
|
||||||
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||||
from UM.Operations.TranslateOperation import TranslateOperation
|
from UM.Operations.TranslateOperation import TranslateOperation
|
||||||
|
|
||||||
|
import cura.CuraApplication
|
||||||
from cura.Operations.SetParentOperation import SetParentOperation
|
from cura.Operations.SetParentOperation import SetParentOperation
|
||||||
from cura.MultiplyObjectsJob import MultiplyObjectsJob
|
from cura.MultiplyObjectsJob import MultiplyObjectsJob
|
||||||
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
|
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
|
||||||
|
@ -23,28 +24,30 @@ from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOper
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
|
||||||
class CuraActions(QObject):
|
class CuraActions(QObject):
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent: QObject = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def openDocumentation(self):
|
def openDocumentation(self) -> None:
|
||||||
# Starting a web browser from a signal handler connected to a menu will crash on windows.
|
# Starting a web browser from a signal handler connected to a menu will crash on windows.
|
||||||
# So instead, defer the call to the next run of the event loop, since that does work.
|
# So instead, defer the call to the next run of the event loop, since that does work.
|
||||||
# Note that weirdly enough, only signal handlers that open a web browser fail like that.
|
# Note that weirdly enough, only signal handlers that open a web browser fail like that.
|
||||||
event = CallFunctionEvent(self._openUrl, [QUrl("http://ultimaker.com/en/support/software")], {})
|
event = CallFunctionEvent(self._openUrl, [QUrl("http://ultimaker.com/en/support/software")], {})
|
||||||
Application.getInstance().functionEvent(event)
|
cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def openBugReportPage(self):
|
def openBugReportPage(self) -> None:
|
||||||
event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {})
|
event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {})
|
||||||
Application.getInstance().functionEvent(event)
|
cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
|
||||||
|
|
||||||
## Reset camera position and direction to default
|
## Reset camera position and direction to default
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def homeCamera(self) -> None:
|
def homeCamera(self) -> None:
|
||||||
scene = Application.getInstance().getController().getScene()
|
scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene()
|
||||||
camera = scene.getActiveCamera()
|
camera = scene.getActiveCamera()
|
||||||
camera.setPosition(Vector(-80, 250, 700))
|
camera.setPosition(Vector(-80, 250, 700))
|
||||||
camera.setPerspective(True)
|
camera.setPerspective(True)
|
||||||
|
@ -72,17 +75,17 @@ class CuraActions(QObject):
|
||||||
# \param count The number of times to multiply the selection.
|
# \param count The number of times to multiply the selection.
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def multiplySelection(self, count: int) -> None:
|
def multiplySelection(self, count: int) -> None:
|
||||||
min_offset = Application.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
|
min_offset = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
|
||||||
job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = max(min_offset, 8))
|
job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = max(min_offset, 8))
|
||||||
job.start()
|
job.start()
|
||||||
|
|
||||||
## Delete all selected objects.
|
## Delete all selected objects.
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def deleteSelection(self) -> None:
|
def deleteSelection(self) -> None:
|
||||||
if not Application.getInstance().getController().getToolsEnabled():
|
if not cura.CuraApplication.CuraApplication.getInstance().getController().getToolsEnabled():
|
||||||
return
|
return
|
||||||
|
|
||||||
removed_group_nodes = []
|
removed_group_nodes = [] #type: List[SceneNode]
|
||||||
op = GroupedOperation()
|
op = GroupedOperation()
|
||||||
nodes = Selection.getAllSelectedObjects()
|
nodes = Selection.getAllSelectedObjects()
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
|
@ -96,7 +99,7 @@ class CuraActions(QObject):
|
||||||
op.addOperation(RemoveSceneNodeOperation(group_node))
|
op.addOperation(RemoveSceneNodeOperation(group_node))
|
||||||
|
|
||||||
# Reset the print information
|
# Reset the print information
|
||||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
cura.CuraApplication.CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||||
|
|
||||||
op.push()
|
op.push()
|
||||||
|
|
||||||
|
@ -111,7 +114,7 @@ class CuraActions(QObject):
|
||||||
for node in Selection.getAllSelectedObjects():
|
for node in Selection.getAllSelectedObjects():
|
||||||
# If the node is a group, apply the active extruder to all children of the group.
|
# If the node is a group, apply the active extruder to all children of the group.
|
||||||
if node.callDecoration("isGroup"):
|
if node.callDecoration("isGroup"):
|
||||||
for grouped_node in BreadthFirstIterator(node):
|
for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if grouped_node.callDecoration("getActiveExtruder") == extruder_id:
|
if grouped_node.callDecoration("getActiveExtruder") == extruder_id:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -143,7 +146,7 @@ class CuraActions(QObject):
|
||||||
Logger.log("d", "Setting build plate number... %d" % build_plate_nr)
|
Logger.log("d", "Setting build plate number... %d" % build_plate_nr)
|
||||||
operation = GroupedOperation()
|
operation = GroupedOperation()
|
||||||
|
|
||||||
root = Application.getInstance().getController().getScene().getRoot()
|
root = cura.CuraApplication.CuraApplication.getInstance().getController().getScene().getRoot()
|
||||||
|
|
||||||
nodes_to_change = []
|
nodes_to_change = []
|
||||||
for node in Selection.getAllSelectedObjects():
|
for node in Selection.getAllSelectedObjects():
|
||||||
|
@ -151,7 +154,7 @@ class CuraActions(QObject):
|
||||||
while parent_node.getParent() != root:
|
while parent_node.getParent() != root:
|
||||||
parent_node = parent_node.getParent()
|
parent_node = parent_node.getParent()
|
||||||
|
|
||||||
for single_node in BreadthFirstIterator(parent_node):
|
for single_node in BreadthFirstIterator(parent_node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
nodes_to_change.append(single_node)
|
nodes_to_change.append(single_node)
|
||||||
|
|
||||||
if not nodes_to_change:
|
if not nodes_to_change:
|
||||||
|
@ -164,5 +167,5 @@ class CuraActions(QObject):
|
||||||
|
|
||||||
Selection.clear()
|
Selection.clear()
|
||||||
|
|
||||||
def _openUrl(self, url):
|
def _openUrl(self, url: QUrl) -> None:
|
||||||
QDesktopServices.openUrl(url)
|
QDesktopServices.openUrl(url)
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
@ -14,7 +13,8 @@ from PyQt5.QtGui import QColor, QIcon
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
||||||
|
|
||||||
from UM.Qt.QtApplication import QtApplication
|
from typing import cast, TYPE_CHECKING
|
||||||
|
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Scene.Camera import Camera
|
from UM.Scene.Camera import Camera
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
|
@ -28,6 +28,8 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Mesh.ReadMeshJob import ReadMeshJob
|
from UM.Mesh.ReadMeshJob import ReadMeshJob
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Preferences import Preferences
|
from UM.Preferences import Preferences
|
||||||
|
from UM.Qt.QtApplication import QtApplication #The class we're inheriting from.
|
||||||
|
from UM.View.SelectionPass import SelectionPass #For typing.
|
||||||
from UM.Scene.Selection import Selection
|
from UM.Scene.Selection import Selection
|
||||||
from UM.Scene.GroupDecorator import GroupDecorator
|
from UM.Scene.GroupDecorator import GroupDecorator
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
@ -109,8 +111,6 @@ from UM.FlameProfiler import pyqtSlot
|
||||||
|
|
||||||
numpy.seterr(all = "ignore")
|
numpy.seterr(all = "ignore")
|
||||||
|
|
||||||
MYPY = False
|
|
||||||
if not MYPY:
|
|
||||||
try:
|
try:
|
||||||
from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode
|
from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -123,7 +123,7 @@ class CuraApplication(QtApplication):
|
||||||
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
||||||
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
|
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
|
||||||
# changes of the settings.
|
# changes of the settings.
|
||||||
SettingVersion = 4
|
SettingVersion = 5
|
||||||
|
|
||||||
Created = False
|
Created = False
|
||||||
|
|
||||||
|
@ -513,7 +513,6 @@ class CuraApplication(QtApplication):
|
||||||
preferences.addPreference("cura/asked_dialog_on_project_save", False)
|
preferences.addPreference("cura/asked_dialog_on_project_save", False)
|
||||||
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
||||||
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
||||||
preferences.addPreference("cura/not_arrange_objects_on_load", False)
|
|
||||||
preferences.addPreference("cura/use_multi_build_plate", False)
|
preferences.addPreference("cura/use_multi_build_plate", False)
|
||||||
|
|
||||||
preferences.addPreference("cura/currency", "€")
|
preferences.addPreference("cura/currency", "€")
|
||||||
|
@ -670,6 +669,7 @@ class CuraApplication(QtApplication):
|
||||||
self._plugins_loaded = True
|
self._plugins_loaded = True
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
super().run()
|
||||||
container_registry = self._container_registry
|
container_registry = self._container_registry
|
||||||
|
|
||||||
Logger.log("i", "Initializing variant manager")
|
Logger.log("i", "Initializing variant manager")
|
||||||
|
@ -1603,9 +1603,7 @@ class CuraApplication(QtApplication):
|
||||||
self._currently_loading_files.remove(filename)
|
self._currently_loading_files.remove(filename)
|
||||||
|
|
||||||
self.fileLoaded.emit(filename)
|
self.fileLoaded.emit(filename)
|
||||||
arrange_objects_on_load = (
|
arrange_objects_on_load = not self.getPreferences().getValue("cura/use_multi_build_plate")
|
||||||
not self.getPreferences().getValue("cura/use_multi_build_plate") or
|
|
||||||
not self.getPreferences().getValue("cura/not_arrange_objects_on_load"))
|
|
||||||
target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
|
target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
|
||||||
|
|
||||||
root = self.getController().getScene().getRoot()
|
root = self.getController().getScene().getRoot()
|
||||||
|
@ -1678,7 +1676,7 @@ class CuraApplication(QtApplication):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
|
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
|
||||||
node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
|
arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
|
||||||
|
|
||||||
# This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy
|
# This node is deep copied from some other node which already has a BuildPlateDecorator, but the deepcopy
|
||||||
# of BuildPlateDecorator produces one that's associated with build plate -1. So, here we need to check if
|
# of BuildPlateDecorator produces one that's associated with build plate -1. So, here we need to check if
|
||||||
|
@ -1722,7 +1720,7 @@ class CuraApplication(QtApplication):
|
||||||
def _onContextMenuRequested(self, x: float, y: float) -> None:
|
def _onContextMenuRequested(self, x: float, y: float) -> None:
|
||||||
# Ensure we select the object if we request a context menu over an object without having a selection.
|
# Ensure we select the object if we request a context menu over an object without having a selection.
|
||||||
if not Selection.hasSelection():
|
if not Selection.hasSelection():
|
||||||
node = self.getController().getScene().findObject(self.getRenderer().getRenderPass("selection").getIdAtPosition(x, y))
|
node = self.getController().getScene().findObject(cast(SelectionPass, self.getRenderer().getRenderPass("selection")).getIdAtPosition(x, y))
|
||||||
if node:
|
if node:
|
||||||
while(node.getParent() and node.getParent().callDecoration("isGroup")):
|
while(node.getParent() and node.getParent().callDecoration("isGroup")):
|
||||||
node = node.getParent()
|
node = node.getParent()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional, Any, Dict, Union, TYPE_CHECKING
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
@ -9,6 +9,9 @@ from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.Machines.QualityGroup import QualityGroup
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
|
# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
|
||||||
|
@ -23,10 +26,16 @@ from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
class ContainerNode:
|
class ContainerNode:
|
||||||
__slots__ = ("metadata", "container", "children_map")
|
__slots__ = ("metadata", "container", "children_map")
|
||||||
|
|
||||||
def __init__(self, metadata: Optional[dict] = None):
|
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||||
self.metadata = metadata
|
self.metadata = metadata
|
||||||
self.container = None
|
self.container = None
|
||||||
self.children_map = OrderedDict()
|
self.children_map = OrderedDict() #type: OrderedDict[str, Union[QualityGroup, ContainerNode]]
|
||||||
|
|
||||||
|
## Get an entry value from the metadata
|
||||||
|
def getMetaDataEntry(self, entry: str, default: Any = None) -> Any:
|
||||||
|
if self.metadata is None:
|
||||||
|
return default
|
||||||
|
return self.metadata.get(entry, default)
|
||||||
|
|
||||||
def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
|
def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
|
||||||
return self.children_map.get(child_key)
|
return self.children_map.get(child_key)
|
||||||
|
@ -50,4 +59,4 @@ class ContainerNode:
|
||||||
return self.container
|
return self.container
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return "%s[%s]" % (self.__class__.__name__, self.metadata.get("id"))
|
return "%s[%s]" % (self.__class__.__name__, self.getMetaDataEntry("id"))
|
||||||
|
|
|
@ -18,10 +18,10 @@ from cura.Machines.MaterialNode import MaterialNode #For type checking.
|
||||||
class MaterialGroup:
|
class MaterialGroup:
|
||||||
__slots__ = ("name", "is_read_only", "root_material_node", "derived_material_node_list")
|
__slots__ = ("name", "is_read_only", "root_material_node", "derived_material_node_list")
|
||||||
|
|
||||||
def __init__(self, name: str, root_material_node: MaterialNode):
|
def __init__(self, name: str, root_material_node: MaterialNode) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.is_read_only = False
|
self.is_read_only = False
|
||||||
self.root_material_node = root_material_node
|
self.root_material_node = root_material_node # type: MaterialNode
|
||||||
self.derived_material_node_list = [] #type: List[MaterialNode]
|
self.derived_material_node_list = [] #type: List[MaterialNode]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
import copy
|
import copy
|
||||||
import uuid
|
import uuid
|
||||||
|
from typing import Dict
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
|
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
|
||||||
|
@ -263,7 +264,7 @@ class MaterialManager(QObject):
|
||||||
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
|
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
|
||||||
#
|
#
|
||||||
def getAvailableMaterials(self, machine_definition: "DefinitionContainer", extruder_variant_name: Optional[str],
|
def getAvailableMaterials(self, machine_definition: "DefinitionContainer", extruder_variant_name: Optional[str],
|
||||||
diameter: float) -> dict:
|
diameter: float) -> Dict[str, MaterialNode]:
|
||||||
# round the diameter to get the approximate diameter
|
# round the diameter to get the approximate diameter
|
||||||
rounded_diameter = str(round(diameter))
|
rounded_diameter = str(round(diameter))
|
||||||
if rounded_diameter not in self._diameter_machine_variant_material_map:
|
if rounded_diameter not in self._diameter_machine_variant_material_map:
|
||||||
|
@ -288,7 +289,7 @@ class MaterialManager(QObject):
|
||||||
# 3. generic material (for fdmprinter)
|
# 3. generic material (for fdmprinter)
|
||||||
machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", [])
|
machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", [])
|
||||||
|
|
||||||
material_id_metadata_dict = dict()
|
material_id_metadata_dict = dict() # type: Dict[str, MaterialNode]
|
||||||
for node in nodes_to_check:
|
for node in nodes_to_check:
|
||||||
if node is not None:
|
if node is not None:
|
||||||
# Only exclude the materials that are explicitly specified in the "exclude_materials" field.
|
# Only exclude the materials that are explicitly specified in the "exclude_materials" field.
|
||||||
|
@ -439,7 +440,7 @@ class MaterialManager(QObject):
|
||||||
|
|
||||||
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
|
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
|
||||||
for node in nodes_to_remove:
|
for node in nodes_to_remove:
|
||||||
self._container_registry.removeContainer(node.metadata["id"])
|
self._container_registry.removeContainer(node.getMetaDataEntry("id", ""))
|
||||||
|
|
||||||
#
|
#
|
||||||
# Methods for GUI
|
# Methods for GUI
|
||||||
|
@ -450,21 +451,26 @@ class MaterialManager(QObject):
|
||||||
#
|
#
|
||||||
@pyqtSlot("QVariant", str)
|
@pyqtSlot("QVariant", str)
|
||||||
def setMaterialName(self, material_node: "MaterialNode", name: str):
|
def setMaterialName(self, material_node: "MaterialNode", name: str):
|
||||||
root_material_id = material_node.metadata["base_file"]
|
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||||
|
if root_material_id is None:
|
||||||
|
return
|
||||||
if self._container_registry.isReadOnly(root_material_id):
|
if self._container_registry.isReadOnly(root_material_id):
|
||||||
Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
|
Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
material_group = self.getMaterialGroup(root_material_id)
|
material_group = self.getMaterialGroup(root_material_id)
|
||||||
if material_group:
|
if material_group:
|
||||||
material_group.root_material_node.getContainer().setName(name)
|
container = material_group.root_material_node.getContainer()
|
||||||
|
if container:
|
||||||
|
container.setName(name)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Removes the given material.
|
# Removes the given material.
|
||||||
#
|
#
|
||||||
@pyqtSlot("QVariant")
|
@pyqtSlot("QVariant")
|
||||||
def removeMaterial(self, material_node: "MaterialNode"):
|
def removeMaterial(self, material_node: "MaterialNode"):
|
||||||
root_material_id = material_node.metadata["base_file"]
|
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||||
|
if root_material_id is not None:
|
||||||
self.removeMaterialByRootId(root_material_id)
|
self.removeMaterialByRootId(root_material_id)
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -544,6 +550,10 @@ class MaterialManager(QObject):
|
||||||
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
|
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
|
||||||
material_group = self.getMaterialGroup(root_material_id)
|
material_group = self.getMaterialGroup(root_material_id)
|
||||||
|
|
||||||
|
if not material_group: # This should never happen
|
||||||
|
Logger.log("w", "Cannot get the material group of %s.", root_material_id)
|
||||||
|
return ""
|
||||||
|
|
||||||
# Create a new ID & container to hold the data.
|
# Create a new ID & container to hold the data.
|
||||||
new_id = self._container_registry.uniqueName("custom_material")
|
new_id = self._container_registry.uniqueName("custom_material")
|
||||||
new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
|
new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
from typing import Optional, Dict
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from .ContainerNode import ContainerNode
|
from .ContainerNode import ContainerNode
|
||||||
|
|
||||||
|
@ -15,7 +14,6 @@ from .ContainerNode import ContainerNode
|
||||||
class MaterialNode(ContainerNode):
|
class MaterialNode(ContainerNode):
|
||||||
__slots__ = ("material_map", "children_map")
|
__slots__ = ("material_map", "children_map")
|
||||||
|
|
||||||
def __init__(self, metadata: Optional[dict] = None):
|
def __init__(self, metadata: Optional[dict] = None) -> None:
|
||||||
super().__init__(metadata = metadata)
|
super().__init__(metadata = metadata)
|
||||||
self.material_map = {} # material_root_id -> material_node
|
self.material_map = {} # type: Dict[str, MaterialNode] # material_root_id -> material_node
|
||||||
self.children_map = {} # mapping for the child nodes
|
|
||||||
|
|
|
@ -83,7 +83,7 @@ class QualityProfilesDropDownMenuModel(ListModel):
|
||||||
|
|
||||||
self.setItems(item_list)
|
self.setItems(item_list)
|
||||||
|
|
||||||
def _fetchLayerHeight(self, quality_group: "QualityGroup"):
|
def _fetchLayerHeight(self, quality_group: "QualityGroup") -> float:
|
||||||
global_stack = self._machine_manager.activeMachine
|
global_stack = self._machine_manager.activeMachine
|
||||||
if not self._layer_height_unit:
|
if not self._layer_height_unit:
|
||||||
unit = global_stack.definition.getProperty("layer_height", "unit")
|
unit = global_stack.definition.getProperty("layer_height", "unit")
|
||||||
|
@ -94,10 +94,12 @@ class QualityProfilesDropDownMenuModel(ListModel):
|
||||||
default_layer_height = global_stack.definition.getProperty("layer_height", "value")
|
default_layer_height = global_stack.definition.getProperty("layer_height", "value")
|
||||||
|
|
||||||
# Get layer_height from the quality profile for the GlobalStack
|
# Get layer_height from the quality profile for the GlobalStack
|
||||||
|
if quality_group.node_for_global is None:
|
||||||
|
return float(default_layer_height)
|
||||||
container = quality_group.node_for_global.getContainer()
|
container = quality_group.node_for_global.getContainer()
|
||||||
|
|
||||||
layer_height = default_layer_height
|
layer_height = default_layer_height
|
||||||
if container.hasProperty("layer_height", "value"):
|
if container and container.hasProperty("layer_height", "value"):
|
||||||
layer_height = container.getProperty("layer_height", "value")
|
layer_height = container.getProperty("layer_height", "value")
|
||||||
else:
|
else:
|
||||||
# Look for layer_height in the GlobalStack from material -> definition
|
# Look for layer_height in the GlobalStack from material -> definition
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||||
|
|
||||||
from .QualityGroup import QualityGroup
|
from .QualityGroup import QualityGroup
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.Machines.QualityNode import QualityNode
|
||||||
|
|
||||||
|
|
||||||
class QualityChangesGroup(QualityGroup):
|
class QualityChangesGroup(QualityGroup):
|
||||||
def __init__(self, name: str, quality_type: str, parent = None):
|
def __init__(self, name: str, quality_type: str, parent = None) -> None:
|
||||||
super().__init__(name, quality_type, parent)
|
super().__init__(name, quality_type, parent)
|
||||||
self._container_registry = Application.getInstance().getContainerRegistry()
|
self._container_registry = Application.getInstance().getContainerRegistry()
|
||||||
|
|
||||||
def addNode(self, node: "QualityNode"):
|
def addNode(self, node: "QualityNode"):
|
||||||
extruder_position = node.metadata.get("position")
|
extruder_position = node.getMetaDataEntry("position")
|
||||||
|
|
||||||
if extruder_position is None and self.node_for_global is not None or extruder_position in self.nodes_for_extruders: #We would be overwriting another node.
|
if extruder_position is None and self.node_for_global is not None or extruder_position in self.nodes_for_extruders: #We would be overwriting another node.
|
||||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(node.metadata["id"])
|
ConfigurationErrorMessage.getInstance().addFaultyContainers(node.getMetaDataEntry("id"))
|
||||||
return
|
return
|
||||||
|
|
||||||
if extruder_position is None: #Then we're a global quality changes profile.
|
if extruder_position is None: #Then we're a global quality changes profile.
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Dict, Optional, List
|
from typing import Dict, Optional, List, Set
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtSlot
|
||||||
|
from cura.Machines.ContainerNode import ContainerNode
|
||||||
|
|
||||||
#
|
#
|
||||||
# A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used.
|
# A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used.
|
||||||
|
@ -21,11 +21,11 @@ from PyQt5.QtCore import QObject, pyqtSlot
|
||||||
#
|
#
|
||||||
class QualityGroup(QObject):
|
class QualityGroup(QObject):
|
||||||
|
|
||||||
def __init__(self, name: str, quality_type: str, parent = None):
|
def __init__(self, name: str, quality_type: str, parent = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.name = name
|
self.name = name
|
||||||
self.node_for_global = None # type: Optional["QualityGroup"]
|
self.node_for_global = None # type: Optional[ContainerNode]
|
||||||
self.nodes_for_extruders = {} # type: Dict[int, "QualityGroup"]
|
self.nodes_for_extruders = {} # type: Dict[int, ContainerNode]
|
||||||
self.quality_type = quality_type
|
self.quality_type = quality_type
|
||||||
self.is_available = False
|
self.is_available = False
|
||||||
|
|
||||||
|
@ -33,15 +33,17 @@ class QualityGroup(QObject):
|
||||||
def getName(self) -> str:
|
def getName(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def getAllKeys(self) -> set:
|
def getAllKeys(self) -> Set[str]:
|
||||||
result = set()
|
result = set() #type: Set[str]
|
||||||
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
|
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
|
||||||
if node is None:
|
if node is None:
|
||||||
continue
|
continue
|
||||||
result.update(node.getContainer().getAllKeys())
|
container = node.getContainer()
|
||||||
|
if container:
|
||||||
|
result.update(container.getAllKeys())
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def getAllNodes(self) -> List["QualityGroup"]:
|
def getAllNodes(self) -> List[ContainerNode]:
|
||||||
result = []
|
result = []
|
||||||
if self.node_for_global is not None:
|
if self.node_for_global is not None:
|
||||||
result.append(self.node_for_global)
|
result.append(self.node_for_global)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional, cast
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ class QualityManager(QObject):
|
||||||
|
|
||||||
if definition_id not in self._machine_variant_material_quality_type_to_quality_dict:
|
if definition_id not in self._machine_variant_material_quality_type_to_quality_dict:
|
||||||
self._machine_variant_material_quality_type_to_quality_dict[definition_id] = QualityNode()
|
self._machine_variant_material_quality_type_to_quality_dict[definition_id] = QualityNode()
|
||||||
machine_node = self._machine_variant_material_quality_type_to_quality_dict[definition_id]
|
machine_node = cast(QualityNode, self._machine_variant_material_quality_type_to_quality_dict[definition_id])
|
||||||
|
|
||||||
if is_global_quality:
|
if is_global_quality:
|
||||||
# For global qualities, save data in the machine node
|
# For global qualities, save data in the machine node
|
||||||
|
@ -102,7 +102,7 @@ class QualityManager(QObject):
|
||||||
# too.
|
# too.
|
||||||
if variant_name not in machine_node.children_map:
|
if variant_name not in machine_node.children_map:
|
||||||
machine_node.children_map[variant_name] = QualityNode()
|
machine_node.children_map[variant_name] = QualityNode()
|
||||||
variant_node = machine_node.children_map[variant_name]
|
variant_node = cast(QualityNode, machine_node.children_map[variant_name])
|
||||||
|
|
||||||
if root_material_id is None:
|
if root_material_id is None:
|
||||||
# If only variant_name is specified but material is not, add the quality/quality_changes metadata
|
# If only variant_name is specified but material is not, add the quality/quality_changes metadata
|
||||||
|
@ -114,7 +114,7 @@ class QualityManager(QObject):
|
||||||
# material node.
|
# material node.
|
||||||
if root_material_id not in variant_node.children_map:
|
if root_material_id not in variant_node.children_map:
|
||||||
variant_node.children_map[root_material_id] = QualityNode()
|
variant_node.children_map[root_material_id] = QualityNode()
|
||||||
material_node = variant_node.children_map[root_material_id]
|
material_node = cast(QualityNode, variant_node.children_map[root_material_id])
|
||||||
|
|
||||||
material_node.addQualityMetadata(quality_type, metadata)
|
material_node.addQualityMetadata(quality_type, metadata)
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ class QualityManager(QObject):
|
||||||
if root_material_id is not None:
|
if root_material_id is not None:
|
||||||
if root_material_id not in machine_node.children_map:
|
if root_material_id not in machine_node.children_map:
|
||||||
machine_node.children_map[root_material_id] = QualityNode()
|
machine_node.children_map[root_material_id] = QualityNode()
|
||||||
material_node = machine_node.children_map[root_material_id]
|
material_node = cast(QualityNode, machine_node.children_map[root_material_id])
|
||||||
|
|
||||||
material_node.addQualityMetadata(quality_type, metadata)
|
material_node.addQualityMetadata(quality_type, metadata)
|
||||||
|
|
||||||
|
@ -351,7 +351,7 @@ class QualityManager(QObject):
|
||||||
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"):
|
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"):
|
||||||
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
|
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
|
||||||
for node in quality_changes_group.getAllNodes():
|
for node in quality_changes_group.getAllNodes():
|
||||||
self._container_registry.removeContainer(node.metadata["id"])
|
self._container_registry.removeContainer(node.getMetaDataEntry("id"))
|
||||||
|
|
||||||
#
|
#
|
||||||
# Rename a set of quality changes containers. Returns the new name.
|
# Rename a set of quality changes containers. Returns the new name.
|
||||||
|
@ -365,7 +365,9 @@ class QualityManager(QObject):
|
||||||
|
|
||||||
new_name = self._container_registry.uniqueName(new_name)
|
new_name = self._container_registry.uniqueName(new_name)
|
||||||
for node in quality_changes_group.getAllNodes():
|
for node in quality_changes_group.getAllNodes():
|
||||||
node.getContainer().setName(new_name)
|
container = node.getContainer()
|
||||||
|
if container:
|
||||||
|
container.setName(new_name)
|
||||||
|
|
||||||
quality_changes_group.name = new_name
|
quality_changes_group.name = new_name
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional, Dict, cast
|
||||||
|
|
||||||
from .ContainerNode import ContainerNode
|
from .ContainerNode import ContainerNode
|
||||||
from .QualityChangesGroup import QualityChangesGroup
|
from .QualityChangesGroup import QualityChangesGroup
|
||||||
|
@ -12,9 +12,9 @@ from .QualityChangesGroup import QualityChangesGroup
|
||||||
#
|
#
|
||||||
class QualityNode(ContainerNode):
|
class QualityNode(ContainerNode):
|
||||||
|
|
||||||
def __init__(self, metadata: Optional[dict] = None):
|
def __init__(self, metadata: Optional[dict] = None) -> None:
|
||||||
super().__init__(metadata = metadata)
|
super().__init__(metadata = metadata)
|
||||||
self.quality_type_map = {} # quality_type -> QualityNode for InstanceContainer
|
self.quality_type_map = {} # type: Dict[str, QualityNode] # quality_type -> QualityNode for InstanceContainer
|
||||||
|
|
||||||
def addQualityMetadata(self, quality_type: str, metadata: dict):
|
def addQualityMetadata(self, quality_type: str, metadata: dict):
|
||||||
if quality_type not in self.quality_type_map:
|
if quality_type not in self.quality_type_map:
|
||||||
|
@ -32,4 +32,4 @@ class QualityNode(ContainerNode):
|
||||||
if name not in quality_type_node.children_map:
|
if name not in quality_type_node.children_map:
|
||||||
quality_type_node.children_map[name] = QualityChangesGroup(name, quality_type)
|
quality_type_node.children_map[name] = QualityChangesGroup(name, quality_type)
|
||||||
quality_changes_group = quality_type_node.children_map[name]
|
quality_changes_group = quality_type_node.children_map[name]
|
||||||
quality_changes_group.addNode(QualityNode(metadata))
|
cast(QualityChangesGroup, quality_changes_group).addNode(QualityNode(metadata))
|
||||||
|
|
|
@ -64,10 +64,11 @@ class MultiplyObjectsJob(Job):
|
||||||
arranger.resetLastPriority()
|
arranger.resetLastPriority()
|
||||||
for i in range(self._count):
|
for i in range(self._count):
|
||||||
# We do place the nodes one by one, as we want to yield in between.
|
# We do place the nodes one by one, as we want to yield in between.
|
||||||
if not node_too_big:
|
|
||||||
new_node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
|
|
||||||
else:
|
|
||||||
new_node = copy.deepcopy(node)
|
new_node = copy.deepcopy(node)
|
||||||
|
solution_found = False
|
||||||
|
if not node_too_big:
|
||||||
|
solution_found = arranger.findNodePlacement(new_node, offset_shape_arr, hull_shape_arr)
|
||||||
|
|
||||||
if node_too_big or not solution_found:
|
if node_too_big or not solution_found:
|
||||||
found_solution_for_all = False
|
found_solution_for_all = False
|
||||||
new_location = new_node.getPosition()
|
new_location = new_node.getPosition()
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
from UM.Application import Application
|
|
||||||
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
from UM.Qt.QtApplication import QtApplication
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
|
|
||||||
|
@ -10,19 +13,21 @@ from UM.View.RenderBatch import RenderBatch
|
||||||
|
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||||
|
|
||||||
## A RenderPass subclass that renders a the distance of selectable objects from the active camera to a texture.
|
## A RenderPass subclass that renders a the distance of selectable objects from the active camera to a texture.
|
||||||
# The texture is used to map a 2d location (eg the mouse location) to a world space position
|
# The texture is used to map a 2d location (eg the mouse location) to a world space position
|
||||||
#
|
#
|
||||||
# Note that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels
|
# Note that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels
|
||||||
class PickingPass(RenderPass):
|
class PickingPass(RenderPass):
|
||||||
def __init__(self, width: int, height: int):
|
def __init__(self, width: int, height: int) -> None:
|
||||||
super().__init__("picking", width, height)
|
super().__init__("picking", width, height)
|
||||||
|
|
||||||
self._renderer = Application.getInstance().getRenderer()
|
self._renderer = QtApplication.getInstance().getRenderer()
|
||||||
|
|
||||||
self._shader = None
|
self._shader = None #type: Optional[ShaderProgram]
|
||||||
self._scene = Application.getInstance().getController().getScene()
|
self._scene = QtApplication.getInstance().getController().getScene()
|
||||||
|
|
||||||
def render(self) -> None:
|
def render(self) -> None:
|
||||||
if not self._shader:
|
if not self._shader:
|
||||||
|
@ -37,7 +42,7 @@ class PickingPass(RenderPass):
|
||||||
batch = RenderBatch(self._shader)
|
batch = RenderBatch(self._shader)
|
||||||
|
|
||||||
# Fill up the batch with objects that can be sliced. `
|
# Fill up the batch with objects that can be sliced. `
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
|
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
|
||||||
batch.addItem(node.getWorldTransformation(), node.getMeshData())
|
batch.addItem(node.getWorldTransformation(), node.getMeshData())
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
|
|
||||||
|
@ -10,7 +13,8 @@ from UM.View.RenderBatch import RenderBatch
|
||||||
|
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
|
||||||
from typing import Optional
|
if TYPE_CHECKING:
|
||||||
|
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||||
|
|
||||||
MYPY = False
|
MYPY = False
|
||||||
if MYPY:
|
if MYPY:
|
||||||
|
@ -33,16 +37,16 @@ def prettier_color(color_list):
|
||||||
#
|
#
|
||||||
# This is useful to get a preview image of a scene taken from a different location as the active camera.
|
# This is useful to get a preview image of a scene taken from a different location as the active camera.
|
||||||
class PreviewPass(RenderPass):
|
class PreviewPass(RenderPass):
|
||||||
def __init__(self, width: int, height: int):
|
def __init__(self, width: int, height: int) -> None:
|
||||||
super().__init__("preview", width, height, 0)
|
super().__init__("preview", width, height, 0)
|
||||||
|
|
||||||
self._camera = None # type: Optional[Camera]
|
self._camera = None # type: Optional[Camera]
|
||||||
|
|
||||||
self._renderer = Application.getInstance().getRenderer()
|
self._renderer = Application.getInstance().getRenderer()
|
||||||
|
|
||||||
self._shader = None
|
self._shader = None #type: Optional[ShaderProgram]
|
||||||
self._non_printing_shader = None
|
self._non_printing_shader = None #type: Optional[ShaderProgram]
|
||||||
self._support_mesh_shader = None
|
self._support_mesh_shader = None #type: Optional[ShaderProgram]
|
||||||
self._scene = Application.getInstance().getController().getScene()
|
self._scene = Application.getInstance().getController().getScene()
|
||||||
|
|
||||||
# Set the camera to be used by this render pass
|
# Set the camera to be used by this render pass
|
||||||
|
@ -53,18 +57,21 @@ class PreviewPass(RenderPass):
|
||||||
def render(self) -> None:
|
def render(self) -> None:
|
||||||
if not self._shader:
|
if not self._shader:
|
||||||
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
|
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
|
||||||
|
if self._shader:
|
||||||
self._shader.setUniformValue("u_overhangAngle", 1.0)
|
self._shader.setUniformValue("u_overhangAngle", 1.0)
|
||||||
self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0])
|
self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0])
|
||||||
self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0])
|
self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0])
|
||||||
self._shader.setUniformValue("u_shininess", 20.0)
|
self._shader.setUniformValue("u_shininess", 20.0)
|
||||||
|
|
||||||
if not self._non_printing_shader:
|
if not self._non_printing_shader:
|
||||||
|
if self._non_printing_shader:
|
||||||
self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
|
self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
|
||||||
self._non_printing_shader.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 0.5])
|
self._non_printing_shader.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 0.5])
|
||||||
self._non_printing_shader.setUniformValue("u_opacity", 0.6)
|
self._non_printing_shader.setUniformValue("u_opacity", 0.6)
|
||||||
|
|
||||||
if not self._support_mesh_shader:
|
if not self._support_mesh_shader:
|
||||||
self._support_mesh_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
|
self._support_mesh_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
|
||||||
|
if self._support_mesh_shader:
|
||||||
self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
|
self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
|
||||||
self._support_mesh_shader.setUniformValue("u_width", 5.0)
|
self._support_mesh_shader.setUniformValue("u_width", 5.0)
|
||||||
|
|
||||||
|
@ -75,8 +82,8 @@ class PreviewPass(RenderPass):
|
||||||
batch = RenderBatch(self._shader)
|
batch = RenderBatch(self._shader)
|
||||||
batch_support_mesh = RenderBatch(self._support_mesh_shader)
|
batch_support_mesh = RenderBatch(self._support_mesh_shader)
|
||||||
|
|
||||||
# Fill up the batch with objects that can be sliced. `
|
# Fill up the batch with objects that can be sliced.
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
|
if node.callDecoration("isSliceable") and node.getMeshData() and node.isVisible():
|
||||||
per_mesh_stack = node.callDecoration("getStack")
|
per_mesh_stack = node.callDecoration("getStack")
|
||||||
if node.callDecoration("isNonThumbnailVisibleMesh"):
|
if node.callDecoration("isNonThumbnailVisibleMesh"):
|
||||||
|
|
|
@ -4,10 +4,9 @@
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
||||||
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
MYPY = False
|
if TYPE_CHECKING:
|
||||||
if MYPY:
|
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||||
|
|
||||||
|
@ -20,12 +19,12 @@ class ExtruderOutputModel(QObject):
|
||||||
extruderConfigurationChanged = pyqtSignal()
|
extruderConfigurationChanged = pyqtSignal()
|
||||||
isPreheatingChanged = pyqtSignal()
|
isPreheatingChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, printer: "PrinterOutputModel", position, parent=None):
|
def __init__(self, printer: "PrinterOutputModel", position, parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._printer = printer
|
self._printer = printer
|
||||||
self._position = position
|
self._position = position
|
||||||
self._target_hotend_temperature = 0
|
self._target_hotend_temperature = 0 # type: float
|
||||||
self._hotend_temperature = 0
|
self._hotend_temperature = 0 # type: float
|
||||||
self._hotend_id = ""
|
self._hotend_id = ""
|
||||||
self._active_material = None # type: Optional[MaterialOutputModel]
|
self._active_material = None # type: Optional[MaterialOutputModel]
|
||||||
self._extruder_configuration = ExtruderConfigurationModel()
|
self._extruder_configuration = ExtruderConfigurationModel()
|
||||||
|
@ -47,7 +46,7 @@ class ExtruderOutputModel(QObject):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = activeMaterialChanged)
|
@pyqtProperty(QObject, notify = activeMaterialChanged)
|
||||||
def activeMaterial(self) -> "MaterialOutputModel":
|
def activeMaterial(self) -> Optional["MaterialOutputModel"]:
|
||||||
return self._active_material
|
return self._active_material
|
||||||
|
|
||||||
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
|
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||||
from PyQt5.QtCore import QTimer
|
from PyQt5.QtCore import QTimer
|
||||||
|
|
||||||
MYPY = False
|
if TYPE_CHECKING:
|
||||||
if MYPY:
|
|
||||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
|
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||||
|
|
||||||
|
|
||||||
class GenericOutputController(PrinterOutputController):
|
class GenericOutputController(PrinterOutputController):
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.FileHandler.FileHandler import FileHandler #For typing.
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.Scene.SceneNode import SceneNode #For typing.
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply
|
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, pyqtSignal, QUrl, QCoreApplication
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Callable, Any, Optional, Dict, Tuple
|
from typing import Any, Callable, Dict, List, Optional
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import os # To get the username
|
import os # To get the username
|
||||||
import gzip
|
import gzip
|
||||||
|
@ -27,20 +28,20 @@ class AuthState(IntEnum):
|
||||||
class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
authenticationStateChanged = pyqtSignal()
|
authenticationStateChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, device_id, address: str, properties, parent = None) -> None:
|
def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], parent: QObject = None) -> None:
|
||||||
super().__init__(device_id = device_id, parent = parent)
|
super().__init__(device_id = device_id, parent = parent)
|
||||||
self._manager = None # type: QNetworkAccessManager
|
self._manager = None # type: Optional[QNetworkAccessManager]
|
||||||
self._last_manager_create_time = None # type: float
|
self._last_manager_create_time = None # type: Optional[float]
|
||||||
self._recreate_network_manager_time = 30
|
self._recreate_network_manager_time = 30
|
||||||
self._timeout_time = 10 # After how many seconds of no response should a timeout occur?
|
self._timeout_time = 10 # After how many seconds of no response should a timeout occur?
|
||||||
|
|
||||||
self._last_response_time = None # type: float
|
self._last_response_time = None # type: Optional[float]
|
||||||
self._last_request_time = None # type: float
|
self._last_request_time = None # type: Optional[float]
|
||||||
|
|
||||||
self._api_prefix = ""
|
self._api_prefix = ""
|
||||||
self._address = address
|
self._address = address
|
||||||
self._properties = properties
|
self._properties = properties
|
||||||
self._user_agent = "%s/%s " % (Application.getInstance().getApplicationName(), Application.getInstance().getVersion())
|
self._user_agent = "%s/%s " % (CuraApplication.getInstance().getApplicationName(), CuraApplication.getInstance().getVersion())
|
||||||
|
|
||||||
self._onFinishedCallbacks = {} # type: Dict[str, Callable[[QNetworkReply], None]]
|
self._onFinishedCallbacks = {} # type: Dict[str, Callable[[QNetworkReply], None]]
|
||||||
self._authentication_state = AuthState.NotAuthenticated
|
self._authentication_state = AuthState.NotAuthenticated
|
||||||
|
@ -67,16 +68,16 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
self._printer_type = value
|
self._printer_type = value
|
||||||
break
|
break
|
||||||
|
|
||||||
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs) -> None:
|
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||||
raise NotImplementedError("requestWrite needs to be implemented")
|
raise NotImplementedError("requestWrite needs to be implemented")
|
||||||
|
|
||||||
def setAuthenticationState(self, authentication_state) -> None:
|
def setAuthenticationState(self, authentication_state: AuthState) -> None:
|
||||||
if self._authentication_state != authentication_state:
|
if self._authentication_state != authentication_state:
|
||||||
self._authentication_state = authentication_state
|
self._authentication_state = authentication_state
|
||||||
self.authenticationStateChanged.emit()
|
self.authenticationStateChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(int, notify = authenticationStateChanged)
|
@pyqtProperty(int, notify = authenticationStateChanged)
|
||||||
def authenticationState(self) -> int:
|
def authenticationState(self) -> AuthState:
|
||||||
return self._authentication_state
|
return self._authentication_state
|
||||||
|
|
||||||
def _compressDataAndNotifyQt(self, data_to_append: str) -> bytes:
|
def _compressDataAndNotifyQt(self, data_to_append: str) -> bytes:
|
||||||
|
@ -121,7 +122,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
self._compressing_gcode = False
|
self._compressing_gcode = False
|
||||||
return b"".join(file_data_bytes_list)
|
return b"".join(file_data_bytes_list)
|
||||||
|
|
||||||
def _update(self) -> bool:
|
def _update(self) -> None:
|
||||||
if self._last_response_time:
|
if self._last_response_time:
|
||||||
time_since_last_response = time() - self._last_response_time
|
time_since_last_response = time() - self._last_response_time
|
||||||
else:
|
else:
|
||||||
|
@ -144,16 +145,16 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
if time_since_last_response > self._recreate_network_manager_time:
|
if time_since_last_response > self._recreate_network_manager_time:
|
||||||
if self._last_manager_create_time is None:
|
if self._last_manager_create_time is None:
|
||||||
self._createNetworkManager()
|
self._createNetworkManager()
|
||||||
if time() - self._last_manager_create_time > self._recreate_network_manager_time:
|
elif time() - self._last_manager_create_time > self._recreate_network_manager_time:
|
||||||
self._createNetworkManager()
|
self._createNetworkManager()
|
||||||
|
assert(self._manager is not None)
|
||||||
elif self._connection_state == ConnectionState.closed:
|
elif self._connection_state == ConnectionState.closed:
|
||||||
# Go out of timeout.
|
# Go out of timeout.
|
||||||
|
if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here
|
||||||
self.setConnectionState(self._connection_state_before_timeout)
|
self.setConnectionState(self._connection_state_before_timeout)
|
||||||
self._connection_state_before_timeout = None
|
self._connection_state_before_timeout = None
|
||||||
|
|
||||||
return True
|
def _createEmptyRequest(self, target: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
|
||||||
|
|
||||||
def _createEmptyRequest(self, target, content_type: Optional[str] = "application/json") -> QNetworkRequest:
|
|
||||||
url = QUrl("http://" + self._address + self._api_prefix + target)
|
url = QUrl("http://" + self._address + self._api_prefix + target)
|
||||||
request = QNetworkRequest(url)
|
request = QNetworkRequest(url)
|
||||||
if content_type is not None:
|
if content_type is not None:
|
||||||
|
@ -161,7 +162,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
|
request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
|
||||||
return request
|
return request
|
||||||
|
|
||||||
def _createFormPart(self, content_header, data, content_type = None) -> QHttpPart:
|
def _createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
|
||||||
part = QHttpPart()
|
part = QHttpPart()
|
||||||
|
|
||||||
if not content_header.startswith("form-data;"):
|
if not content_header.startswith("form-data;"):
|
||||||
|
@ -187,33 +188,33 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
if reply in self._kept_alive_multiparts:
|
if reply in self._kept_alive_multiparts:
|
||||||
del self._kept_alive_multiparts[reply]
|
del self._kept_alive_multiparts[reply]
|
||||||
|
|
||||||
def put(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None:
|
def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||||
if self._manager is None:
|
if self._manager is None:
|
||||||
self._createNetworkManager()
|
self._createNetworkManager()
|
||||||
request = self._createEmptyRequest(target)
|
request = self._createEmptyRequest(target)
|
||||||
self._last_request_time = time()
|
self._last_request_time = time()
|
||||||
reply = self._manager.put(request, data.encode())
|
reply = self._manager.put(request, data.encode())
|
||||||
self._registerOnFinishedCallback(reply, onFinished)
|
self._registerOnFinishedCallback(reply, on_finished)
|
||||||
|
|
||||||
def get(self, target: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None:
|
def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||||
if self._manager is None:
|
if self._manager is None:
|
||||||
self._createNetworkManager()
|
self._createNetworkManager()
|
||||||
request = self._createEmptyRequest(target)
|
request = self._createEmptyRequest(target)
|
||||||
self._last_request_time = time()
|
self._last_request_time = time()
|
||||||
reply = self._manager.get(request)
|
reply = self._manager.get(request)
|
||||||
self._registerOnFinishedCallback(reply, onFinished)
|
self._registerOnFinishedCallback(reply, on_finished)
|
||||||
|
|
||||||
def post(self, target: str, data: str, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None:
|
def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
|
||||||
if self._manager is None:
|
if self._manager is None:
|
||||||
self._createNetworkManager()
|
self._createNetworkManager()
|
||||||
request = self._createEmptyRequest(target)
|
request = self._createEmptyRequest(target)
|
||||||
self._last_request_time = time()
|
self._last_request_time = time()
|
||||||
reply = self._manager.post(request, data)
|
reply = self._manager.post(request, data)
|
||||||
if onProgress is not None:
|
if on_progress is not None:
|
||||||
reply.uploadProgress.connect(onProgress)
|
reply.uploadProgress.connect(on_progress)
|
||||||
self._registerOnFinishedCallback(reply, onFinished)
|
self._registerOnFinishedCallback(reply, on_finished)
|
||||||
|
|
||||||
def postFormWithParts(self, target:str, parts: List[QHttpPart], onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None:
|
def postFormWithParts(self, target:str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
|
||||||
if self._manager is None:
|
if self._manager is None:
|
||||||
self._createNetworkManager()
|
self._createNetworkManager()
|
||||||
request = self._createEmptyRequest(target, content_type=None)
|
request = self._createEmptyRequest(target, content_type=None)
|
||||||
|
@ -227,20 +228,20 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
|
|
||||||
self._kept_alive_multiparts[reply] = multi_post_part
|
self._kept_alive_multiparts[reply] = multi_post_part
|
||||||
|
|
||||||
if onProgress is not None:
|
if on_progress is not None:
|
||||||
reply.uploadProgress.connect(onProgress)
|
reply.uploadProgress.connect(on_progress)
|
||||||
self._registerOnFinishedCallback(reply, onFinished)
|
self._registerOnFinishedCallback(reply, on_finished)
|
||||||
|
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
def postForm(self, target: str, header_data: str, body_data: bytes, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None:
|
def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
|
||||||
post_part = QHttpPart()
|
post_part = QHttpPart()
|
||||||
post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
|
post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
|
||||||
post_part.setBody(body_data)
|
post_part.setBody(body_data)
|
||||||
|
|
||||||
self.postFormWithParts(target, [post_part], onFinished, onProgress)
|
self.postFormWithParts(target, [post_part], on_finished, on_progress)
|
||||||
|
|
||||||
def _onAuthenticationRequired(self, reply, authenticator) -> None:
|
def _onAuthenticationRequired(self, reply: QNetworkReply, authenticator: QAuthenticator) -> None:
|
||||||
Logger.log("w", "Request to {url} required authentication, which was not implemented".format(url = reply.url().toString()))
|
Logger.log("w", "Request to {url} required authentication, which was not implemented".format(url = reply.url().toString()))
|
||||||
|
|
||||||
def _createNetworkManager(self) -> None:
|
def _createNetworkManager(self) -> None:
|
||||||
|
@ -255,11 +256,11 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
|
self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
|
||||||
|
|
||||||
if self._properties.get(b"temporary", b"false") != b"true":
|
if self._properties.get(b"temporary", b"false") != b"true":
|
||||||
Application.getInstance().getMachineManager().checkCorrectGroupName(self.getId(), self.name)
|
CuraApplication.getInstance().getMachineManager().checkCorrectGroupName(self.getId(), self.name)
|
||||||
|
|
||||||
def _registerOnFinishedCallback(self, reply: QNetworkReply, onFinished: Optional[Callable[[Any, QNetworkReply], None]]) -> None:
|
def _registerOnFinishedCallback(self, reply: QNetworkReply, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||||
if onFinished is not None:
|
if on_finished is not None:
|
||||||
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
|
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = on_finished
|
||||||
|
|
||||||
def __handleOnFinished(self, reply: QNetworkReply) -> None:
|
def __handleOnFinished(self, reply: QNetworkReply) -> None:
|
||||||
# Due to garbage collection, we need to cache certain bits of post operations.
|
# Due to garbage collection, we need to cache certain bits of post operations.
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
||||||
from typing import Optional
|
from typing import Optional, TYPE_CHECKING
|
||||||
MYPY = False
|
|
||||||
if MYPY:
|
if TYPE_CHECKING:
|
||||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ class PrintJobOutputModel(QObject):
|
||||||
assignedPrinterChanged = pyqtSignal()
|
assignedPrinterChanged = pyqtSignal()
|
||||||
ownerChanged = pyqtSignal()
|
ownerChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None):
|
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._output_controller = output_controller
|
self._output_controller = output_controller
|
||||||
self._state = ""
|
self._state = ""
|
||||||
|
|
|
@ -27,7 +27,7 @@ class PrinterOutputModel(QObject):
|
||||||
cameraChanged = pyqtSignal()
|
cameraChanged = pyqtSignal()
|
||||||
configurationChanged = pyqtSignal()
|
configurationChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = ""):
|
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = "") -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._bed_temperature = -1 # Use -1 for no heated bed.
|
self._bed_temperature = -1 # Use -1 for no heated bed.
|
||||||
self._target_bed_temperature = 0
|
self._target_bed_temperature = 0
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from UM.Decorators import deprecated
|
from UM.Decorators import deprecated
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.OutputDevice.OutputDevice import OutputDevice
|
from UM.OutputDevice.OutputDevice import OutputDevice
|
||||||
from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal, QVariant
|
from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.FileHandler.FileHandler import FileHandler #For typing.
|
||||||
|
from UM.Scene.SceneNode import SceneNode #For typing.
|
||||||
from UM.Signal import signalemitter
|
from UM.Signal import signalemitter
|
||||||
from UM.Application import Application
|
from UM.Qt.QtApplication import QtApplication
|
||||||
|
|
||||||
from enum import IntEnum # For the connection state tracking.
|
from enum import IntEnum # For the connection state tracking.
|
||||||
from typing import List, Optional
|
from typing import Callable, List, Optional
|
||||||
|
|
||||||
MYPY = False
|
MYPY = False
|
||||||
if MYPY:
|
if MYPY:
|
||||||
|
@ -20,6 +23,16 @@ if MYPY:
|
||||||
|
|
||||||
i18n_catalog = i18nCatalog("cura")
|
i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
|
## The current processing state of the backend.
|
||||||
|
class ConnectionState(IntEnum):
|
||||||
|
closed = 0
|
||||||
|
connecting = 1
|
||||||
|
connected = 2
|
||||||
|
busy = 3
|
||||||
|
error = 4
|
||||||
|
|
||||||
|
|
||||||
## Printer output device adds extra interface options on top of output device.
|
## Printer output device adds extra interface options on top of output device.
|
||||||
#
|
#
|
||||||
# The assumption is made the printer is a FDM printer.
|
# The assumption is made the printer is a FDM printer.
|
||||||
|
@ -47,38 +60,37 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
# Signal to indicate that the configuration of one of the printers has changed.
|
# Signal to indicate that the configuration of one of the printers has changed.
|
||||||
uniqueConfigurationsChanged = pyqtSignal()
|
uniqueConfigurationsChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, device_id, parent = None):
|
def __init__(self, device_id: str, parent: QObject = None) -> None:
|
||||||
super().__init__(device_id = device_id, parent = parent)
|
super().__init__(device_id = device_id, parent = parent)
|
||||||
|
|
||||||
self._printers = [] # type: List[PrinterOutputModel]
|
self._printers = [] # type: List[PrinterOutputModel]
|
||||||
self._unique_configurations = [] # type: List[ConfigurationModel]
|
self._unique_configurations = [] # type: List[ConfigurationModel]
|
||||||
|
|
||||||
self._monitor_view_qml_path = ""
|
self._monitor_view_qml_path = "" #type: str
|
||||||
self._monitor_component = None
|
self._monitor_component = None #type: Optional[QObject]
|
||||||
self._monitor_item = None
|
self._monitor_item = None #type: Optional[QObject]
|
||||||
|
|
||||||
self._control_view_qml_path = ""
|
self._control_view_qml_path = "" #type: str
|
||||||
self._control_component = None
|
self._control_component = None #type: Optional[QObject]
|
||||||
self._control_item = None
|
self._control_item = None #type: Optional[QObject]
|
||||||
|
|
||||||
self._qml_context = None
|
self._accepts_commands = False #type: bool
|
||||||
self._accepts_commands = False
|
|
||||||
|
|
||||||
self._update_timer = QTimer()
|
self._update_timer = QTimer() #type: QTimer
|
||||||
self._update_timer.setInterval(2000) # TODO; Add preference for update interval
|
self._update_timer.setInterval(2000) # TODO; Add preference for update interval
|
||||||
self._update_timer.setSingleShot(False)
|
self._update_timer.setSingleShot(False)
|
||||||
self._update_timer.timeout.connect(self._update)
|
self._update_timer.timeout.connect(self._update)
|
||||||
|
|
||||||
self._connection_state = ConnectionState.closed
|
self._connection_state = ConnectionState.closed #type: ConnectionState
|
||||||
|
|
||||||
self._firmware_name = None
|
self._firmware_name = None #type: Optional[str]
|
||||||
self._address = ""
|
self._address = "" #type: str
|
||||||
self._connection_text = ""
|
self._connection_text = "" #type: str
|
||||||
self.printersChanged.connect(self._onPrintersChanged)
|
self.printersChanged.connect(self._onPrintersChanged)
|
||||||
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations)
|
QtApplication.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations)
|
||||||
|
|
||||||
@pyqtProperty(str, notify = connectionTextChanged)
|
@pyqtProperty(str, notify = connectionTextChanged)
|
||||||
def address(self):
|
def address(self) -> str:
|
||||||
return self._address
|
return self._address
|
||||||
|
|
||||||
def setConnectionText(self, connection_text):
|
def setConnectionText(self, connection_text):
|
||||||
|
@ -87,36 +99,36 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
self.connectionTextChanged.emit()
|
self.connectionTextChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant=True)
|
||||||
def connectionText(self):
|
def connectionText(self) -> str:
|
||||||
return self._connection_text
|
return self._connection_text
|
||||||
|
|
||||||
def materialHotendChangedMessage(self, callback):
|
def materialHotendChangedMessage(self, callback: Callable[[int], None]) -> None:
|
||||||
Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'")
|
Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'")
|
||||||
callback(QMessageBox.Yes)
|
callback(QMessageBox.Yes)
|
||||||
|
|
||||||
def isConnected(self):
|
def isConnected(self) -> bool:
|
||||||
return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error
|
return self._connection_state != ConnectionState.closed and self._connection_state != ConnectionState.error
|
||||||
|
|
||||||
def setConnectionState(self, connection_state):
|
def setConnectionState(self, connection_state: ConnectionState) -> None:
|
||||||
if self._connection_state != connection_state:
|
if self._connection_state != connection_state:
|
||||||
self._connection_state = connection_state
|
self._connection_state = connection_state
|
||||||
self.connectionStateChanged.emit(self._id)
|
self.connectionStateChanged.emit(self._id)
|
||||||
|
|
||||||
@pyqtProperty(str, notify = connectionStateChanged)
|
@pyqtProperty(str, notify = connectionStateChanged)
|
||||||
def connectionState(self):
|
def connectionState(self) -> ConnectionState:
|
||||||
return self._connection_state
|
return self._connection_state
|
||||||
|
|
||||||
def _update(self):
|
def _update(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _getPrinterByKey(self, key) -> Optional["PrinterOutputModel"]:
|
def _getPrinterByKey(self, key: str) -> Optional["PrinterOutputModel"]:
|
||||||
for printer in self._printers:
|
for printer in self._printers:
|
||||||
if printer.key == key:
|
if printer.key == key:
|
||||||
return printer
|
return printer
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None, **kwargs):
|
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||||
raise NotImplementedError("requestWrite needs to be implemented")
|
raise NotImplementedError("requestWrite needs to be implemented")
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = printersChanged)
|
@pyqtProperty(QObject, notify = printersChanged)
|
||||||
|
@ -126,11 +138,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", notify = printersChanged)
|
@pyqtProperty("QVariantList", notify = printersChanged)
|
||||||
def printers(self):
|
def printers(self) -> List["PrinterOutputModel"]:
|
||||||
return self._printers
|
return self._printers
|
||||||
|
|
||||||
@pyqtProperty(QObject, constant = True)
|
@pyqtProperty(QObject, constant = True)
|
||||||
def monitorItem(self):
|
def monitorItem(self) -> QObject:
|
||||||
# Note that we specifically only check if the monitor component is created.
|
# Note that we specifically only check if the monitor component is created.
|
||||||
# It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
|
# It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
|
||||||
# create the item (and fail) every time.
|
# create the item (and fail) every time.
|
||||||
|
@ -139,48 +151,48 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
return self._monitor_item
|
return self._monitor_item
|
||||||
|
|
||||||
@pyqtProperty(QObject, constant = True)
|
@pyqtProperty(QObject, constant = True)
|
||||||
def controlItem(self):
|
def controlItem(self) -> QObject:
|
||||||
if not self._control_component:
|
if not self._control_component:
|
||||||
self._createControlViewFromQML()
|
self._createControlViewFromQML()
|
||||||
return self._control_item
|
return self._control_item
|
||||||
|
|
||||||
def _createControlViewFromQML(self):
|
def _createControlViewFromQML(self) -> None:
|
||||||
if not self._control_view_qml_path:
|
if not self._control_view_qml_path:
|
||||||
return
|
return
|
||||||
if self._control_item is None:
|
if self._control_item is None:
|
||||||
self._control_item = Application.getInstance().createQmlComponent(self._control_view_qml_path, {"OutputDevice": self})
|
self._control_item = QtApplication.getInstance().createQmlComponent(self._control_view_qml_path, {"OutputDevice": self})
|
||||||
|
|
||||||
def _createMonitorViewFromQML(self):
|
def _createMonitorViewFromQML(self) -> None:
|
||||||
if not self._monitor_view_qml_path:
|
if not self._monitor_view_qml_path:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._monitor_item is None:
|
if self._monitor_item is None:
|
||||||
self._monitor_item = Application.getInstance().createQmlComponent(self._monitor_view_qml_path, {"OutputDevice": self})
|
self._monitor_item = QtApplication.getInstance().createQmlComponent(self._monitor_view_qml_path, {"OutputDevice": self})
|
||||||
|
|
||||||
## Attempt to establish connection
|
## Attempt to establish connection
|
||||||
def connect(self):
|
def connect(self) -> None:
|
||||||
self.setConnectionState(ConnectionState.connecting)
|
self.setConnectionState(ConnectionState.connecting)
|
||||||
self._update_timer.start()
|
self._update_timer.start()
|
||||||
|
|
||||||
## Attempt to close the connection
|
## Attempt to close the connection
|
||||||
def close(self):
|
def close(self) -> None:
|
||||||
self._update_timer.stop()
|
self._update_timer.stop()
|
||||||
self.setConnectionState(ConnectionState.closed)
|
self.setConnectionState(ConnectionState.closed)
|
||||||
|
|
||||||
## Ensure that close gets called when object is destroyed
|
## Ensure that close gets called when object is destroyed
|
||||||
def __del__(self):
|
def __del__(self) -> None:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = acceptsCommandsChanged)
|
@pyqtProperty(bool, notify = acceptsCommandsChanged)
|
||||||
def acceptsCommands(self):
|
def acceptsCommands(self) -> bool:
|
||||||
return self._accepts_commands
|
return self._accepts_commands
|
||||||
|
|
||||||
@deprecated("Please use the protected function instead", "3.2")
|
@deprecated("Please use the protected function instead", "3.2")
|
||||||
def setAcceptsCommands(self, accepts_commands):
|
def setAcceptsCommands(self, accepts_commands: bool) -> None:
|
||||||
self._setAcceptsCommands(accepts_commands)
|
self._setAcceptsCommands(accepts_commands)
|
||||||
|
|
||||||
## Set a flag to signal the UI that the printer is not (yet) ready to receive commands
|
## Set a flag to signal the UI that the printer is not (yet) ready to receive commands
|
||||||
def _setAcceptsCommands(self, accepts_commands):
|
def _setAcceptsCommands(self, accepts_commands: bool) -> None:
|
||||||
if self._accepts_commands != accepts_commands:
|
if self._accepts_commands != accepts_commands:
|
||||||
self._accepts_commands = accepts_commands
|
self._accepts_commands = accepts_commands
|
||||||
|
|
||||||
|
@ -188,15 +200,15 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
|
|
||||||
# Returns the unique configurations of the printers within this output device
|
# Returns the unique configurations of the printers within this output device
|
||||||
@pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged)
|
@pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged)
|
||||||
def uniqueConfigurations(self):
|
def uniqueConfigurations(self) -> List["ConfigurationModel"]:
|
||||||
return self._unique_configurations
|
return self._unique_configurations
|
||||||
|
|
||||||
def _updateUniqueConfigurations(self):
|
def _updateUniqueConfigurations(self) -> None:
|
||||||
self._unique_configurations = list(set([printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None]))
|
self._unique_configurations = list(set([printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None]))
|
||||||
self._unique_configurations.sort(key = lambda k: k.printerType)
|
self._unique_configurations.sort(key = lambda k: k.printerType)
|
||||||
self.uniqueConfigurationsChanged.emit()
|
self.uniqueConfigurationsChanged.emit()
|
||||||
|
|
||||||
def _onPrintersChanged(self):
|
def _onPrintersChanged(self) -> None:
|
||||||
for printer in self._printers:
|
for printer in self._printers:
|
||||||
printer.configurationChanged.connect(self._updateUniqueConfigurations)
|
printer.configurationChanged.connect(self._updateUniqueConfigurations)
|
||||||
|
|
||||||
|
@ -205,21 +217,12 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||||
|
|
||||||
## Set the device firmware name
|
## Set the device firmware name
|
||||||
#
|
#
|
||||||
# \param name \type{str} The name of the firmware.
|
# \param name The name of the firmware.
|
||||||
def _setFirmwareName(self, name):
|
def _setFirmwareName(self, name: str) -> None:
|
||||||
self._firmware_name = name
|
self._firmware_name = name
|
||||||
|
|
||||||
## Get the name of device firmware
|
## Get the name of device firmware
|
||||||
#
|
#
|
||||||
# This name can be used to define device type
|
# This name can be used to define device type
|
||||||
def getFirmwareName(self):
|
def getFirmwareName(self) -> Optional[str]:
|
||||||
return self._firmware_name
|
return self._firmware_name
|
||||||
|
|
||||||
|
|
||||||
## The current processing state of the backend.
|
|
||||||
class ConnectionState(IntEnum):
|
|
||||||
closed = 0
|
|
||||||
connecting = 1
|
|
||||||
connected = 2
|
|
||||||
busy = 3
|
|
||||||
error = 4
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ from UM.Signal import Signal
|
||||||
class CuraSceneController(QObject):
|
class CuraSceneController(QObject):
|
||||||
activeBuildPlateChanged = Signal()
|
activeBuildPlateChanged = Signal()
|
||||||
|
|
||||||
def __init__(self, objects_model: ObjectsModel, multi_build_plate_model: MultiBuildPlateModel):
|
def __init__(self, objects_model: ObjectsModel, multi_build_plate_model: MultiBuildPlateModel) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._objects_model = objects_model
|
self._objects_model = objects_model
|
||||||
|
|
|
@ -1,40 +1,47 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import List, Optional
|
from typing import cast, Dict, List, Optional
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||||
|
from UM.Math.Polygon import Polygon #For typing.
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator #To cast the deepcopy of every decorator back to SceneNodeDecorator.
|
||||||
|
|
||||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
import cura.CuraApplication #To get the build plate.
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack #For typing.
|
||||||
|
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator #For per-object settings.
|
||||||
|
|
||||||
## Scene nodes that are models are only seen when selecting the corresponding build plate
|
## Scene nodes that are models are only seen when selecting the corresponding build plate
|
||||||
# Note that many other nodes can just be UM SceneNode objects.
|
# Note that many other nodes can just be UM SceneNode objects.
|
||||||
class CuraSceneNode(SceneNode):
|
class CuraSceneNode(SceneNode):
|
||||||
def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", no_setting_override: bool = False):
|
def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", no_setting_override: bool = False) -> None:
|
||||||
super().__init__(parent = parent, visible = visible, name = name)
|
super().__init__(parent = parent, visible = visible, name = name)
|
||||||
if not no_setting_override:
|
if not no_setting_override:
|
||||||
self.addDecorator(SettingOverrideDecorator()) # now we always have a getActiveExtruderPosition, unless explicitly disabled
|
self.addDecorator(SettingOverrideDecorator()) # now we always have a getActiveExtruderPosition, unless explicitly disabled
|
||||||
self._outside_buildarea = False
|
self._outside_buildarea = False
|
||||||
|
|
||||||
def setOutsideBuildArea(self, new_value):
|
def setOutsideBuildArea(self, new_value: bool) -> None:
|
||||||
self._outside_buildarea = new_value
|
self._outside_buildarea = new_value
|
||||||
|
|
||||||
def isOutsideBuildArea(self):
|
def isOutsideBuildArea(self) -> bool:
|
||||||
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
|
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
|
||||||
|
|
||||||
def isVisible(self):
|
def isVisible(self) -> bool:
|
||||||
return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
return super().isVisible() and self.callDecoration("getBuildPlateNumber") == cura.CuraApplication.CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
|
|
||||||
def isSelectable(self) -> bool:
|
def isSelectable(self) -> bool:
|
||||||
return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == cura.CuraApplication.CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
|
|
||||||
## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned
|
## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned
|
||||||
# TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded
|
# TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded
|
||||||
def getPrintingExtruder(self):
|
def getPrintingExtruder(self) -> Optional[ExtruderStack]:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack is None:
|
||||||
|
return None
|
||||||
|
|
||||||
per_mesh_stack = self.callDecoration("getStack")
|
per_mesh_stack = self.callDecoration("getStack")
|
||||||
extruders = list(global_container_stack.extruders.values())
|
extruders = list(global_container_stack.extruders.values())
|
||||||
|
|
||||||
|
@ -79,9 +86,9 @@ class CuraSceneNode(SceneNode):
|
||||||
]
|
]
|
||||||
|
|
||||||
## Return if the provided bbox collides with the bbox of this scene node
|
## Return if the provided bbox collides with the bbox of this scene node
|
||||||
def collidesWithBbox(self, check_bbox):
|
def collidesWithBbox(self, check_bbox: AxisAlignedBox) -> bool:
|
||||||
bbox = self.getBoundingBox()
|
bbox = self.getBoundingBox()
|
||||||
|
if bbox is not None:
|
||||||
# Mark the node as outside the build volume if the bounding box test fails.
|
# Mark the node as outside the build volume if the bounding box test fails.
|
||||||
if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
||||||
return True
|
return True
|
||||||
|
@ -89,7 +96,7 @@ class CuraSceneNode(SceneNode):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
## Return if any area collides with the convex hull of this scene node
|
## Return if any area collides with the convex hull of this scene node
|
||||||
def collidesWithArea(self, areas):
|
def collidesWithArea(self, areas: List[Polygon]) -> bool:
|
||||||
convex_hull = self.callDecoration("getConvexHull")
|
convex_hull = self.callDecoration("getConvexHull")
|
||||||
if convex_hull:
|
if convex_hull:
|
||||||
if not convex_hull.isValid():
|
if not convex_hull.isValid():
|
||||||
|
@ -104,8 +111,7 @@ class CuraSceneNode(SceneNode):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
## Override of SceneNode._calculateAABB to exclude non-printing-meshes from bounding box
|
## Override of SceneNode._calculateAABB to exclude non-printing-meshes from bounding box
|
||||||
def _calculateAABB(self):
|
def _calculateAABB(self) -> None:
|
||||||
aabb = None
|
|
||||||
if self._mesh_data:
|
if self._mesh_data:
|
||||||
aabb = self._mesh_data.getExtents(self.getWorldTransformation())
|
aabb = self._mesh_data.getExtents(self.getWorldTransformation())
|
||||||
else: # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0)
|
else: # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0)
|
||||||
|
@ -123,18 +129,18 @@ class CuraSceneNode(SceneNode):
|
||||||
self._aabb = aabb
|
self._aabb = aabb
|
||||||
|
|
||||||
## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
|
## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo: Dict[int, object]) -> "CuraSceneNode":
|
||||||
copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later
|
copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later
|
||||||
copy.setTransformation(self.getLocalTransformation())
|
copy.setTransformation(self.getLocalTransformation())
|
||||||
copy.setMeshData(self._mesh_data)
|
copy.setMeshData(self._mesh_data)
|
||||||
copy.setVisible(deepcopy(self._visible, memo))
|
copy.setVisible(cast(bool, deepcopy(self._visible, memo)))
|
||||||
copy._selectable = deepcopy(self._selectable, memo)
|
copy._selectable = cast(bool, deepcopy(self._selectable, memo))
|
||||||
copy._name = deepcopy(self._name, memo)
|
copy._name = cast(str, deepcopy(self._name, memo))
|
||||||
for decorator in self._decorators:
|
for decorator in self._decorators:
|
||||||
copy.addDecorator(deepcopy(decorator, memo))
|
copy.addDecorator(cast(SceneNodeDecorator, deepcopy(decorator, memo)))
|
||||||
|
|
||||||
for child in self._children:
|
for child in self._children:
|
||||||
copy.addChild(deepcopy(child, memo))
|
copy.addChild(cast(SceneNode, deepcopy(child, memo)))
|
||||||
self.calculateBoundingBoxMesh()
|
self.calculateBoundingBoxMesh()
|
||||||
return copy
|
return copy
|
||||||
|
|
||||||
|
|
|
@ -313,7 +313,6 @@ class ContainerManager(QObject):
|
||||||
|
|
||||||
self._machine_manager.blurSettings.emit()
|
self._machine_manager.blurSettings.emit()
|
||||||
|
|
||||||
global_stack = self._machine_manager.activeMachine
|
|
||||||
current_quality_changes_name = global_stack.qualityChanges.getName()
|
current_quality_changes_name = global_stack.qualityChanges.getName()
|
||||||
current_quality_type = global_stack.quality.getMetaDataEntry("quality_type")
|
current_quality_type = global_stack.quality.getMetaDataEntry("quality_type")
|
||||||
extruder_stacks = list(global_stack.extruders.values())
|
extruder_stacks = list(global_stack.extruders.values())
|
||||||
|
@ -324,6 +323,7 @@ class ContainerManager(QObject):
|
||||||
if quality_changes.getId() == "empty_quality_changes":
|
if quality_changes.getId() == "empty_quality_changes":
|
||||||
quality_changes = self._quality_manager._createQualityChanges(current_quality_type, current_quality_changes_name,
|
quality_changes = self._quality_manager._createQualityChanges(current_quality_type, current_quality_changes_name,
|
||||||
global_stack, stack)
|
global_stack, stack)
|
||||||
|
self._container_registry.addContainer(quality_changes)
|
||||||
stack.qualityChanges = quality_changes
|
stack.qualityChanges = quality_changes
|
||||||
|
|
||||||
if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
|
if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
|
||||||
|
@ -468,7 +468,7 @@ class ContainerManager(QObject):
|
||||||
container_list = [n.getContainer() for n in quality_changes_group.getAllNodes() if n.getContainer() is not None]
|
container_list = [n.getContainer() for n in quality_changes_group.getAllNodes() if n.getContainer() is not None]
|
||||||
self._container_registry.exportQualityProfile(container_list, path, file_type)
|
self._container_registry.exportQualityProfile(container_list, path, file_type)
|
||||||
|
|
||||||
__instance = None
|
__instance = None # type: ContainerManager
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getInstance(cls, *args, **kwargs) -> "ContainerManager":
|
def getInstance(cls, *args, **kwargs) -> "ContainerManager":
|
||||||
|
|
|
@ -5,7 +5,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
from typing import Optional
|
from typing import cast, Optional
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ from UM.Resources import Resources
|
||||||
from . import ExtruderStack
|
from . import ExtruderStack
|
||||||
from . import GlobalStack
|
from . import GlobalStack
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
import cura.CuraApplication
|
||||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||||
from cura.ReaderWriters.ProfileReader import NoProfileException
|
from cura.ReaderWriters.ProfileReader import NoProfileException
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
|
|
||||||
if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()):
|
if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()):
|
||||||
# Check against setting version of the definition.
|
# Check against setting version of the definition.
|
||||||
required_setting_version = CuraApplication.SettingVersion
|
required_setting_version = cura.CuraApplication.CuraApplication.SettingVersion
|
||||||
actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0))
|
actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0))
|
||||||
if required_setting_version != actual_setting_version:
|
if required_setting_version != actual_setting_version:
|
||||||
Logger.log("w", "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}.".format(container_id = container.getId(), actual_setting_version = actual_setting_version, required_setting_version = required_setting_version))
|
Logger.log("w", "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}.".format(container_id = container.getId(), actual_setting_version = actual_setting_version, required_setting_version = required_setting_version))
|
||||||
|
@ -260,7 +260,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1))
|
profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1))
|
||||||
profile = InstanceContainer(profile_id)
|
profile = InstanceContainer(profile_id)
|
||||||
profile.setName(quality_name)
|
profile.setName(quality_name)
|
||||||
profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
profile.addMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion)
|
||||||
profile.addMetaDataEntry("type", "quality_changes")
|
profile.addMetaDataEntry("type", "quality_changes")
|
||||||
profile.addMetaDataEntry("definition", expected_machine_definition)
|
profile.addMetaDataEntry("definition", expected_machine_definition)
|
||||||
profile.addMetaDataEntry("quality_type", quality_type)
|
profile.addMetaDataEntry("quality_type", quality_type)
|
||||||
|
@ -356,13 +356,15 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
||||||
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_stack is None:
|
||||||
|
return None
|
||||||
definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition)
|
definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition)
|
||||||
profile.setDefinition(definition_id)
|
profile.setDefinition(definition_id)
|
||||||
|
|
||||||
# Check to make sure the imported profile actually makes sense in context of the current configuration.
|
# Check to make sure the imported profile actually makes sense in context of the current configuration.
|
||||||
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
|
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
|
||||||
# successfully imported but then fail to show up.
|
# successfully imported but then fail to show up.
|
||||||
quality_manager = CuraApplication.getInstance()._quality_manager
|
quality_manager = cura.CuraApplication.CuraApplication.getInstance()._quality_manager
|
||||||
quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack)
|
quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack)
|
||||||
if quality_type not in quality_group_dict:
|
if quality_type not in quality_group_dict:
|
||||||
return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)
|
return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)
|
||||||
|
@ -465,7 +467,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id, new_global_quality_changes = None, create_new_ids = True):
|
def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id, new_global_quality_changes = None, create_new_ids = True):
|
||||||
new_extruder_id = extruder_id
|
new_extruder_id = extruder_id
|
||||||
|
|
||||||
application = CuraApplication.getInstance()
|
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
|
|
||||||
extruder_definitions = self.findDefinitionContainers(id = new_extruder_id)
|
extruder_definitions = self.findDefinitionContainers(id = new_extruder_id)
|
||||||
if not extruder_definitions:
|
if not extruder_definitions:
|
||||||
|
@ -485,7 +487,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
definition_changes_name = definition_changes_id
|
definition_changes_name = definition_changes_id
|
||||||
definition_changes = InstanceContainer(definition_changes_id, parent = application)
|
definition_changes = InstanceContainer(definition_changes_id, parent = application)
|
||||||
definition_changes.setName(definition_changes_name)
|
definition_changes.setName(definition_changes_name)
|
||||||
definition_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
definition_changes.addMetaDataEntry("setting_version", application.SettingVersion)
|
||||||
definition_changes.addMetaDataEntry("type", "definition_changes")
|
definition_changes.addMetaDataEntry("type", "definition_changes")
|
||||||
definition_changes.addMetaDataEntry("definition", extruder_definition.getId())
|
definition_changes.addMetaDataEntry("definition", extruder_definition.getId())
|
||||||
|
|
||||||
|
@ -514,7 +516,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
user_container.setName(user_container_name)
|
user_container.setName(user_container_name)
|
||||||
user_container.addMetaDataEntry("type", "user")
|
user_container.addMetaDataEntry("type", "user")
|
||||||
user_container.addMetaDataEntry("machine", machine.getId())
|
user_container.addMetaDataEntry("machine", machine.getId())
|
||||||
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
user_container.addMetaDataEntry("setting_version", application.SettingVersion)
|
||||||
user_container.setDefinition(machine.definition.getId())
|
user_container.setDefinition(machine.definition.getId())
|
||||||
user_container.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
|
user_container.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
|
||||||
|
|
||||||
|
@ -587,7 +589,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
extruder_quality_changes_container = InstanceContainer(container_id, parent = application)
|
extruder_quality_changes_container = InstanceContainer(container_id, parent = application)
|
||||||
extruder_quality_changes_container.setName(container_name)
|
extruder_quality_changes_container.setName(container_name)
|
||||||
extruder_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
extruder_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
||||||
extruder_quality_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
extruder_quality_changes_container.addMetaDataEntry("setting_version", application.SettingVersion)
|
||||||
extruder_quality_changes_container.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
|
extruder_quality_changes_container.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
|
||||||
extruder_quality_changes_container.addMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type"))
|
extruder_quality_changes_container.addMetaDataEntry("quality_type", machine_quality_changes.getMetaDataEntry("quality_type"))
|
||||||
extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId())
|
extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId())
|
||||||
|
@ -675,7 +677,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
return extruder_stack
|
return extruder_stack
|
||||||
|
|
||||||
def _findQualityChangesContainerInCuraFolder(self, name):
|
def _findQualityChangesContainerInCuraFolder(self, name):
|
||||||
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityChangesInstanceContainer)
|
quality_changes_dir = Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.QualityChangesInstanceContainer)
|
||||||
|
|
||||||
instance_container = None
|
instance_container = None
|
||||||
|
|
||||||
|
@ -731,3 +733,9 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
extruder_stack.setNextStack(machines[0])
|
extruder_stack.setNextStack(machines[0])
|
||||||
else:
|
else:
|
||||||
Logger.log("w", "Could not find machine {machine} for extruder {extruder}", machine = extruder_stack.getMetaDataEntry("machine"), extruder = extruder_stack.getId())
|
Logger.log("w", "Could not find machine {machine} for extruder {extruder}", machine = extruder_stack.getMetaDataEntry("machine"), extruder = extruder_stack.getId())
|
||||||
|
|
||||||
|
#Override just for the type.
|
||||||
|
@classmethod
|
||||||
|
@override(ContainerRegistry)
|
||||||
|
def getInstance(cls, *args, **kwargs) -> "CuraContainerRegistry":
|
||||||
|
return cast(CuraContainerRegistry, super().getInstance(*args, **kwargs))
|
|
@ -1,15 +1,12 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import os.path
|
from typing import Any, cast, List, Optional, Union
|
||||||
|
|
||||||
from typing import Any, Optional
|
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
|
||||||
from UM.FlameProfiler import pyqtSlot
|
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
|
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
@ -39,19 +36,19 @@ from . import Exceptions
|
||||||
# This also means that operations on the stack that modifies the container ordering is prohibited and
|
# This also means that operations on the stack that modifies the container ordering is prohibited and
|
||||||
# will raise an exception.
|
# will raise an exception.
|
||||||
class CuraContainerStack(ContainerStack):
|
class CuraContainerStack(ContainerStack):
|
||||||
def __init__(self, container_id: str):
|
def __init__(self, container_id: str) -> None:
|
||||||
super().__init__(container_id)
|
super().__init__(container_id)
|
||||||
|
|
||||||
self._container_registry = ContainerRegistry.getInstance()
|
self._container_registry = ContainerRegistry.getInstance() #type: ContainerRegistry
|
||||||
|
|
||||||
self._empty_instance_container = self._container_registry.getEmptyInstanceContainer()
|
self._empty_instance_container = self._container_registry.getEmptyInstanceContainer() #type: InstanceContainer
|
||||||
|
|
||||||
self._empty_quality_changes = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0]
|
self._empty_quality_changes = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0] #type: InstanceContainer
|
||||||
self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
|
self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0] #type: InstanceContainer
|
||||||
self._empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0]
|
self._empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] #type: InstanceContainer
|
||||||
self._empty_variant = self._container_registry.findInstanceContainers(id = "empty_variant")[0]
|
self._empty_variant = self._container_registry.findInstanceContainers(id = "empty_variant")[0] #type: InstanceContainer
|
||||||
|
|
||||||
self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))]
|
self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] #type: List[Union[InstanceContainer, DefinitionContainer]]
|
||||||
self._containers[_ContainerIndexes.QualityChanges] = self._empty_quality_changes
|
self._containers[_ContainerIndexes.QualityChanges] = self._empty_quality_changes
|
||||||
self._containers[_ContainerIndexes.Quality] = self._empty_quality
|
self._containers[_ContainerIndexes.Quality] = self._empty_quality
|
||||||
self._containers[_ContainerIndexes.Material] = self._empty_material
|
self._containers[_ContainerIndexes.Material] = self._empty_material
|
||||||
|
@ -76,7 +73,7 @@ class CuraContainerStack(ContainerStack):
|
||||||
# \return The user changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The user changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
@pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged)
|
@pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged)
|
||||||
def userChanges(self) -> InstanceContainer:
|
def userChanges(self) -> InstanceContainer:
|
||||||
return self._containers[_ContainerIndexes.UserChanges]
|
return cast(InstanceContainer, self._containers[_ContainerIndexes.UserChanges])
|
||||||
|
|
||||||
## Set the quality changes container.
|
## Set the quality changes container.
|
||||||
#
|
#
|
||||||
|
@ -89,12 +86,12 @@ class CuraContainerStack(ContainerStack):
|
||||||
# \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
@pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged)
|
@pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged)
|
||||||
def qualityChanges(self) -> InstanceContainer:
|
def qualityChanges(self) -> InstanceContainer:
|
||||||
return self._containers[_ContainerIndexes.QualityChanges]
|
return cast(InstanceContainer, self._containers[_ContainerIndexes.QualityChanges])
|
||||||
|
|
||||||
## Set the quality container.
|
## Set the quality container.
|
||||||
#
|
#
|
||||||
# \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality".
|
# \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality".
|
||||||
def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None:
|
def setQuality(self, new_quality: InstanceContainer, postpone_emit: bool = False) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
|
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
|
||||||
|
|
||||||
## Get the quality container.
|
## Get the quality container.
|
||||||
|
@ -102,12 +99,12 @@ class CuraContainerStack(ContainerStack):
|
||||||
# \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
@pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged)
|
@pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged)
|
||||||
def quality(self) -> InstanceContainer:
|
def quality(self) -> InstanceContainer:
|
||||||
return self._containers[_ContainerIndexes.Quality]
|
return cast(InstanceContainer, self._containers[_ContainerIndexes.Quality])
|
||||||
|
|
||||||
## Set the material container.
|
## Set the material container.
|
||||||
#
|
#
|
||||||
# \param new_material The new material container. It is expected to have a "type" metadata entry with the value "material".
|
# \param new_material The new material container. It is expected to have a "type" metadata entry with the value "material".
|
||||||
def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None:
|
def setMaterial(self, new_material: InstanceContainer, postpone_emit: bool = False) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
|
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
|
||||||
|
|
||||||
## Get the material container.
|
## Get the material container.
|
||||||
|
@ -115,7 +112,7 @@ class CuraContainerStack(ContainerStack):
|
||||||
# \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
@pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged)
|
@pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged)
|
||||||
def material(self) -> InstanceContainer:
|
def material(self) -> InstanceContainer:
|
||||||
return self._containers[_ContainerIndexes.Material]
|
return cast(InstanceContainer, self._containers[_ContainerIndexes.Material])
|
||||||
|
|
||||||
## Set the variant container.
|
## Set the variant container.
|
||||||
#
|
#
|
||||||
|
@ -128,7 +125,7 @@ class CuraContainerStack(ContainerStack):
|
||||||
# \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
@pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged)
|
@pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged)
|
||||||
def variant(self) -> InstanceContainer:
|
def variant(self) -> InstanceContainer:
|
||||||
return self._containers[_ContainerIndexes.Variant]
|
return cast(InstanceContainer, self._containers[_ContainerIndexes.Variant])
|
||||||
|
|
||||||
## Set the definition changes container.
|
## Set the definition changes container.
|
||||||
#
|
#
|
||||||
|
@ -141,7 +138,7 @@ class CuraContainerStack(ContainerStack):
|
||||||
# \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
@pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged)
|
@pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged)
|
||||||
def definitionChanges(self) -> InstanceContainer:
|
def definitionChanges(self) -> InstanceContainer:
|
||||||
return self._containers[_ContainerIndexes.DefinitionChanges]
|
return cast(InstanceContainer, self._containers[_ContainerIndexes.DefinitionChanges])
|
||||||
|
|
||||||
## Set the definition container.
|
## Set the definition container.
|
||||||
#
|
#
|
||||||
|
@ -154,7 +151,7 @@ class CuraContainerStack(ContainerStack):
|
||||||
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
@pyqtProperty(QObject, fset = setDefinition, notify = pyqtContainersChanged)
|
@pyqtProperty(QObject, fset = setDefinition, notify = pyqtContainersChanged)
|
||||||
def definition(self) -> DefinitionContainer:
|
def definition(self) -> DefinitionContainer:
|
||||||
return self._containers[_ContainerIndexes.Definition]
|
return cast(DefinitionContainer, self._containers[_ContainerIndexes.Definition])
|
||||||
|
|
||||||
@override(ContainerStack)
|
@override(ContainerStack)
|
||||||
def getBottom(self) -> "DefinitionContainer":
|
def getBottom(self) -> "DefinitionContainer":
|
||||||
|
@ -189,13 +186,9 @@ class CuraContainerStack(ContainerStack):
|
||||||
# \param key The key of the setting to set.
|
# \param key The key of the setting to set.
|
||||||
# \param property_name The name of the property to set.
|
# \param property_name The name of the property to set.
|
||||||
# \param new_value The new value to set the property to.
|
# \param new_value The new value to set the property to.
|
||||||
# \param target_container The type of the container to set the property of. Defaults to "user".
|
def setProperty(self, key: str, property_name: str, property_value: Any, container: "ContainerInterface" = None, set_from_cache: bool = False) -> None:
|
||||||
def setProperty(self, key: str, property_name: str, new_value: Any, target_container: str = "user") -> None:
|
container_index = _ContainerIndexes.UserChanges
|
||||||
container_index = _ContainerIndexes.TypeIndexMap.get(target_container, -1)
|
self._containers[container_index].setProperty(key, property_name, property_value, container, set_from_cache)
|
||||||
if container_index != -1:
|
|
||||||
self._containers[container_index].setProperty(key, property_name, new_value)
|
|
||||||
else:
|
|
||||||
raise IndexError("Invalid target container {type}".format(type = target_container))
|
|
||||||
|
|
||||||
## Overridden from ContainerStack
|
## Overridden from ContainerStack
|
||||||
#
|
#
|
||||||
|
@ -310,15 +303,15 @@ class CuraContainerStack(ContainerStack):
|
||||||
#
|
#
|
||||||
# \return The ID of the definition container to use when searching for instance containers.
|
# \return The ID of the definition container to use when searching for instance containers.
|
||||||
@classmethod
|
@classmethod
|
||||||
def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainer) -> str:
|
def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainerInterface) -> str:
|
||||||
quality_definition = machine_definition.getMetaDataEntry("quality_definition")
|
quality_definition = machine_definition.getMetaDataEntry("quality_definition")
|
||||||
if not quality_definition:
|
if not quality_definition:
|
||||||
return machine_definition.id
|
return machine_definition.id #type: ignore
|
||||||
|
|
||||||
definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition)
|
definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition)
|
||||||
if not definitions:
|
if not definitions:
|
||||||
Logger.log("w", "Unable to find parent definition {parent} for machine {machine}", parent = quality_definition, machine = machine_definition.id)
|
Logger.log("w", "Unable to find parent definition {parent} for machine {machine}", parent = quality_definition, machine = machine_definition.id) #type: ignore
|
||||||
return machine_definition.id
|
return machine_definition.id #type: ignore
|
||||||
|
|
||||||
return cls._findInstanceContainerDefinitionId(definitions[0])
|
return cls._findInstanceContainerDefinitionId(definitions[0])
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
|
|
||||||
from UM.Application import Application # To get the global container stack to find the current machine.
|
import cura.CuraApplication #To get the global container stack to find the current machine.
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
@ -16,7 +16,7 @@ from UM.Settings.SettingInstance import SettingInstance
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
|
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
|
||||||
|
|
||||||
from typing import Optional, List, TYPE_CHECKING, Union
|
from typing import Optional, List, TYPE_CHECKING, Union, Dict
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
@ -36,14 +36,13 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self._application = Application.getInstance()
|
self._application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
|
|
||||||
self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
|
self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
|
||||||
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
|
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
|
||||||
self._selected_object_extruders = []
|
self._selected_object_extruders = []
|
||||||
self._addCurrentMachineExtruders()
|
self._addCurrentMachineExtruders()
|
||||||
|
|
||||||
#Application.getInstance().globalContainerStackChanged.connect(self._globalContainerStackChanged)
|
|
||||||
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
||||||
|
|
||||||
## Signal to notify other components when the list of extruders for a machine definition changes.
|
## Signal to notify other components when the list of extruders for a machine definition changes.
|
||||||
|
@ -60,29 +59,31 @@ class ExtruderManager(QObject):
|
||||||
# \return The unique ID of the currently active extruder stack.
|
# \return The unique ID of the currently active extruder stack.
|
||||||
@pyqtProperty(str, notify = activeExtruderChanged)
|
@pyqtProperty(str, notify = activeExtruderChanged)
|
||||||
def activeExtruderStackId(self) -> Optional[str]:
|
def activeExtruderStackId(self) -> Optional[str]:
|
||||||
if not Application.getInstance().getGlobalContainerStack():
|
if not self._application.getGlobalContainerStack():
|
||||||
return None # No active machine, so no active extruder.
|
return None # No active machine, so no active extruder.
|
||||||
try:
|
try:
|
||||||
return self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId()
|
return self._extruder_trains[self._application.getGlobalContainerStack().getId()][str(self._active_extruder_index)].getId()
|
||||||
except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong.
|
except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
## Return extruder count according to extruder trains.
|
## Return extruder count according to extruder trains.
|
||||||
@pyqtProperty(int, notify = extrudersChanged)
|
@pyqtProperty(int, notify = extrudersChanged)
|
||||||
def extruderCount(self):
|
def extruderCount(self):
|
||||||
if not Application.getInstance().getGlobalContainerStack():
|
if not self._application.getGlobalContainerStack():
|
||||||
return 0 # No active machine, so no extruders.
|
return 0 # No active machine, so no extruders.
|
||||||
try:
|
try:
|
||||||
return len(self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()])
|
return len(self._extruder_trains[self._application.getGlobalContainerStack().getId()])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
## Gets a dict with the extruder stack ids with the extruder number as the key.
|
## Gets a dict with the extruder stack ids with the extruder number as the key.
|
||||||
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
||||||
def extruderIds(self):
|
def extruderIds(self) -> Dict[str, str]:
|
||||||
extruder_stack_ids = {}
|
extruder_stack_ids = {}
|
||||||
|
|
||||||
global_stack_id = Application.getInstance().getGlobalContainerStack().getId()
|
global_container_stack = self._application.getGlobalContainerStack()
|
||||||
|
if global_container_stack:
|
||||||
|
global_stack_id = global_container_stack.getId()
|
||||||
|
|
||||||
if global_stack_id in self._extruder_trains:
|
if global_stack_id in self._extruder_trains:
|
||||||
for position in self._extruder_trains[global_stack_id]:
|
for position in self._extruder_trains[global_stack_id]:
|
||||||
|
@ -92,10 +93,13 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
@pyqtSlot(str, result = str)
|
@pyqtSlot(str, result = str)
|
||||||
def getQualityChangesIdByExtruderStackId(self, extruder_stack_id: str) -> str:
|
def getQualityChangesIdByExtruderStackId(self, extruder_stack_id: str) -> str:
|
||||||
for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]:
|
global_container_stack = self._application.getGlobalContainerStack()
|
||||||
extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position]
|
if global_container_stack is not None:
|
||||||
|
for position in self._extruder_trains[global_container_stack.getId()]:
|
||||||
|
extruder = self._extruder_trains[global_container_stack.getId()][position]
|
||||||
if extruder.getId() == extruder_stack_id:
|
if extruder.getId() == extruder_stack_id:
|
||||||
return extruder.qualityChanges.getId()
|
return extruder.qualityChanges.getId()
|
||||||
|
return ""
|
||||||
|
|
||||||
## Changes the active extruder by index.
|
## Changes the active extruder by index.
|
||||||
#
|
#
|
||||||
|
@ -132,7 +136,7 @@ class ExtruderManager(QObject):
|
||||||
selected_nodes = []
|
selected_nodes = []
|
||||||
for node in Selection.getAllSelectedObjects():
|
for node in Selection.getAllSelectedObjects():
|
||||||
if node.callDecoration("isGroup"):
|
if node.callDecoration("isGroup"):
|
||||||
for grouped_node in BreadthFirstIterator(node):
|
for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if grouped_node.callDecoration("isGroup"):
|
if grouped_node.callDecoration("isGroup"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -141,7 +145,7 @@ class ExtruderManager(QObject):
|
||||||
selected_nodes.append(node)
|
selected_nodes.append(node)
|
||||||
|
|
||||||
# Then, figure out which nodes are used by those selected nodes.
|
# Then, figure out which nodes are used by those selected nodes.
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = self._application.getGlobalContainerStack()
|
||||||
current_extruder_trains = self._extruder_trains.get(global_stack.getId())
|
current_extruder_trains = self._extruder_trains.get(global_stack.getId())
|
||||||
for node in selected_nodes:
|
for node in selected_nodes:
|
||||||
extruder = node.callDecoration("getActiveExtruder")
|
extruder = node.callDecoration("getActiveExtruder")
|
||||||
|
@ -164,7 +168,7 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
|
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = self._application.getGlobalContainerStack()
|
||||||
|
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
if global_container_stack.getId() in self._extruder_trains:
|
if global_container_stack.getId() in self._extruder_trains:
|
||||||
|
@ -175,7 +179,7 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
## Get an extruder stack by index
|
## Get an extruder stack by index
|
||||||
def getExtruderStack(self, index) -> Optional["ExtruderStack"]:
|
def getExtruderStack(self, index) -> Optional["ExtruderStack"]:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = self._application.getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
if global_container_stack.getId() in self._extruder_trains:
|
if global_container_stack.getId() in self._extruder_trains:
|
||||||
if str(index) in self._extruder_trains[global_container_stack.getId()]:
|
if str(index) in self._extruder_trains[global_container_stack.getId()]:
|
||||||
|
@ -186,7 +190,9 @@ class ExtruderManager(QObject):
|
||||||
def getExtruderStacks(self) -> List["ExtruderStack"]:
|
def getExtruderStacks(self) -> List["ExtruderStack"]:
|
||||||
result = []
|
result = []
|
||||||
for i in range(self.extruderCount):
|
for i in range(self.extruderCount):
|
||||||
result.append(self.getExtruderStack(i))
|
stack = self.getExtruderStack(i)
|
||||||
|
if stack:
|
||||||
|
result.append(stack)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def registerExtruder(self, extruder_train, machine_id):
|
def registerExtruder(self, extruder_train, machine_id):
|
||||||
|
@ -252,14 +258,14 @@ class ExtruderManager(QObject):
|
||||||
support_bottom_enabled = False
|
support_bottom_enabled = False
|
||||||
support_roof_enabled = False
|
support_roof_enabled = False
|
||||||
|
|
||||||
scene_root = Application.getInstance().getController().getScene().getRoot()
|
scene_root = self._application.getController().getScene().getRoot()
|
||||||
|
|
||||||
# If no extruders are registered in the extruder manager yet, return an empty array
|
# If no extruders are registered in the extruder manager yet, return an empty array
|
||||||
if len(self.extruderIds) == 0:
|
if len(self.extruderIds) == 0:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Get the extruders of all printable meshes in the scene
|
# Get the extruders of all printable meshes in the scene
|
||||||
meshes = [node for node in DepthFirstIterator(scene_root) if isinstance(node, SceneNode) and node.isSelectable()]
|
meshes = [node for node in DepthFirstIterator(scene_root) if isinstance(node, SceneNode) and node.isSelectable()] #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
for mesh in meshes:
|
for mesh in meshes:
|
||||||
extruder_stack_id = mesh.callDecoration("getActiveExtruder")
|
extruder_stack_id = mesh.callDecoration("getActiveExtruder")
|
||||||
if not extruder_stack_id:
|
if not extruder_stack_id:
|
||||||
|
@ -301,10 +307,10 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
# The platform adhesion extruder. Not used if using none.
|
# The platform adhesion extruder. Not used if using none.
|
||||||
if global_stack.getProperty("adhesion_type", "value") != "none":
|
if global_stack.getProperty("adhesion_type", "value") != "none":
|
||||||
extruder_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value"))
|
extruder_str_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value"))
|
||||||
if extruder_nr == "-1":
|
if extruder_str_nr == "-1":
|
||||||
extruder_nr = Application.getInstance().getMachineManager().defaultExtruderPosition
|
extruder_str_nr = self._application.getMachineManager().defaultExtruderPosition
|
||||||
used_extruder_stack_ids.add(self.extruderIds[extruder_nr])
|
used_extruder_stack_ids.add(self.extruderIds[extruder_str_nr])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids]
|
return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids]
|
||||||
|
@ -335,7 +341,7 @@ class ExtruderManager(QObject):
|
||||||
# The first element is the global container stack, followed by any extruder stacks.
|
# The first element is the global container stack, followed by any extruder stacks.
|
||||||
# \return \type{List[ContainerStack]}
|
# \return \type{List[ContainerStack]}
|
||||||
def getActiveGlobalAndExtruderStacks(self) -> Optional[List[Union["ExtruderStack", "GlobalStack"]]]:
|
def getActiveGlobalAndExtruderStacks(self) -> Optional[List[Union["ExtruderStack", "GlobalStack"]]]:
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = self._application.getGlobalContainerStack()
|
||||||
if not global_stack:
|
if not global_stack:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -347,7 +353,7 @@ class ExtruderManager(QObject):
|
||||||
#
|
#
|
||||||
# \return \type{List[ContainerStack]} a list of
|
# \return \type{List[ContainerStack]} a list of
|
||||||
def getActiveExtruderStacks(self) -> List["ExtruderStack"]:
|
def getActiveExtruderStacks(self) -> List["ExtruderStack"]:
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = self._application.getGlobalContainerStack()
|
||||||
if not global_stack:
|
if not global_stack:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -420,7 +426,7 @@ class ExtruderManager(QObject):
|
||||||
# If no extruder has the value, the list will contain the global value.
|
# If no extruder has the value, the list will contain the global value.
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getExtruderValues(key):
|
def getExtruderValues(key):
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
||||||
|
@ -455,7 +461,7 @@ class ExtruderManager(QObject):
|
||||||
# If no extruder has the value, the list will contain the global value.
|
# If no extruder has the value, the list will contain the global value.
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getDefaultExtruderValues(key):
|
def getDefaultExtruderValues(key):
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
context = PropertyEvaluationContext(global_stack)
|
context = PropertyEvaluationContext(global_stack)
|
||||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||||
context.context["override_operators"] = {
|
context.context["override_operators"] = {
|
||||||
|
@ -488,7 +494,7 @@ class ExtruderManager(QObject):
|
||||||
## Return the default extruder position from the machine manager
|
## Return the default extruder position from the machine manager
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getDefaultExtruderPosition() -> str:
|
def getDefaultExtruderPosition() -> str:
|
||||||
return Application.getInstance().getMachineManager().defaultExtruderPosition
|
return cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition
|
||||||
|
|
||||||
## Get all extruder values for a certain setting.
|
## Get all extruder values for a certain setting.
|
||||||
#
|
#
|
||||||
|
@ -513,7 +519,7 @@ class ExtruderManager(QObject):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getExtruderValue(extruder_index, key):
|
def getExtruderValue(extruder_index, key):
|
||||||
if extruder_index == -1:
|
if extruder_index == -1:
|
||||||
extruder_index = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
|
extruder_index = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition)
|
||||||
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
||||||
|
|
||||||
if extruder:
|
if extruder:
|
||||||
|
@ -522,7 +528,7 @@ class ExtruderManager(QObject):
|
||||||
value = value(extruder)
|
value = value(extruder)
|
||||||
else:
|
else:
|
||||||
# Just a value from global.
|
# Just a value from global.
|
||||||
value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value")
|
value = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().getProperty(key, "value")
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -551,7 +557,7 @@ class ExtruderManager(QObject):
|
||||||
if isinstance(value, SettingFunction):
|
if isinstance(value, SettingFunction):
|
||||||
value = value(extruder, context = context)
|
value = value(extruder, context = context)
|
||||||
else: # Just a value from global.
|
else: # Just a value from global.
|
||||||
value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value", context = context)
|
value = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().getProperty(key, "value", context = context)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -564,7 +570,7 @@ class ExtruderManager(QObject):
|
||||||
# \return The effective value
|
# \return The effective value
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getResolveOrValue(key):
|
def getResolveOrValue(key):
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
resolved_value = global_stack.getProperty(key, "value")
|
resolved_value = global_stack.getProperty(key, "value")
|
||||||
|
|
||||||
return resolved_value
|
return resolved_value
|
||||||
|
@ -578,7 +584,7 @@ class ExtruderManager(QObject):
|
||||||
# \return The effective value
|
# \return The effective value
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getDefaultResolveOrValue(key):
|
def getDefaultResolveOrValue(key):
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
context = PropertyEvaluationContext(global_stack)
|
context = PropertyEvaluationContext(global_stack)
|
||||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||||
context.context["override_operators"] = {
|
context.context["override_operators"] = {
|
||||||
|
@ -591,7 +597,7 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
return resolved_value
|
return resolved_value
|
||||||
|
|
||||||
__instance = None
|
__instance = None # type: ExtruderManager
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getInstance(cls, *args, **kwargs) -> "ExtruderManager":
|
def getInstance(cls, *args, **kwargs) -> "ExtruderManager":
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Any, TYPE_CHECKING, Optional
|
from typing import Any, Dict, TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
@ -13,6 +12,8 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
|
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
|
||||||
from UM.Util import parseBool
|
from UM.Util import parseBool
|
||||||
|
|
||||||
|
import cura.CuraApplication
|
||||||
|
|
||||||
from . import Exceptions
|
from . import Exceptions
|
||||||
from .CuraContainerStack import CuraContainerStack, _ContainerIndexes
|
from .CuraContainerStack import CuraContainerStack, _ContainerIndexes
|
||||||
from .ExtruderManager import ExtruderManager
|
from .ExtruderManager import ExtruderManager
|
||||||
|
@ -25,7 +26,7 @@ if TYPE_CHECKING:
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
class ExtruderStack(CuraContainerStack):
|
class ExtruderStack(CuraContainerStack):
|
||||||
def __init__(self, container_id: str):
|
def __init__(self, container_id: str) -> None:
|
||||||
super().__init__(container_id)
|
super().__init__(container_id)
|
||||||
|
|
||||||
self.addMetaDataEntry("type", "extruder_train") # For backward compatibility
|
self.addMetaDataEntry("type", "extruder_train") # For backward compatibility
|
||||||
|
@ -50,14 +51,14 @@ class ExtruderStack(CuraContainerStack):
|
||||||
def getNextStack(self) -> Optional["GlobalStack"]:
|
def getNextStack(self) -> Optional["GlobalStack"]:
|
||||||
return super().getNextStack()
|
return super().getNextStack()
|
||||||
|
|
||||||
def setEnabled(self, enabled):
|
def setEnabled(self, enabled: bool) -> None:
|
||||||
if "enabled" not in self._metadata:
|
if "enabled" not in self._metadata:
|
||||||
self.addMetaDataEntry("enabled", "True")
|
self.addMetaDataEntry("enabled", "True")
|
||||||
self.setMetaDataEntry("enabled", str(enabled))
|
self.setMetaDataEntry("enabled", str(enabled))
|
||||||
self.enabledChanged.emit()
|
self.enabledChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = enabledChanged)
|
@pyqtProperty(bool, notify = enabledChanged)
|
||||||
def isEnabled(self):
|
def isEnabled(self) -> bool:
|
||||||
return parseBool(self.getMetaDataEntry("enabled", "True"))
|
return parseBool(self.getMetaDataEntry("enabled", "True"))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -113,7 +114,7 @@ class ExtruderStack(CuraContainerStack):
|
||||||
limit_to_extruder = super().getProperty(key, "limit_to_extruder", context)
|
limit_to_extruder = super().getProperty(key, "limit_to_extruder", context)
|
||||||
if limit_to_extruder is not None:
|
if limit_to_extruder is not None:
|
||||||
if limit_to_extruder == -1:
|
if limit_to_extruder == -1:
|
||||||
limit_to_extruder = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
|
limit_to_extruder = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition)
|
||||||
limit_to_extruder = str(limit_to_extruder)
|
limit_to_extruder = str(limit_to_extruder)
|
||||||
if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder):
|
if (limit_to_extruder is not None and limit_to_extruder != "-1") and self.getMetaDataEntry("position") != str(limit_to_extruder):
|
||||||
if str(limit_to_extruder) in self.getNextStack().extruders:
|
if str(limit_to_extruder) in self.getNextStack().extruders:
|
||||||
|
@ -142,7 +143,7 @@ class ExtruderStack(CuraContainerStack):
|
||||||
if stacks:
|
if stacks:
|
||||||
self.setNextStack(stacks[0])
|
self.setNextStack(stacks[0])
|
||||||
|
|
||||||
def _onPropertiesChanged(self, key, properties):
|
def _onPropertiesChanged(self, key: str, properties: Dict[str, Any]) -> None:
|
||||||
# When there is a setting that is not settable per extruder that depends on a value from a setting that is,
|
# When there is a setting that is not settable per extruder that depends on a value from a setting that is,
|
||||||
# we do not always get properly informed that we should re-evaluate the setting. So make sure to indicate
|
# we do not always get properly informed that we should re-evaluate the setting. So make sure to indicate
|
||||||
# something changed for those settings.
|
# something changed for those settings.
|
||||||
|
|
|
@ -1,29 +1,30 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import threading
|
import threading
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional, Set, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty
|
from PyQt5.QtCore import pyqtProperty
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
|
|
||||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.SettingInstance import InstanceState
|
from UM.Settings.SettingInstance import InstanceState
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.Interfaces import PropertyEvaluationContext
|
from UM.Settings.Interfaces import PropertyEvaluationContext
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
import cura.CuraApplication
|
||||||
|
|
||||||
from . import Exceptions
|
from . import Exceptions
|
||||||
from .CuraContainerStack import CuraContainerStack
|
from .CuraContainerStack import CuraContainerStack
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
|
||||||
## Represents the Global or Machine stack and its related containers.
|
## Represents the Global or Machine stack and its related containers.
|
||||||
#
|
#
|
||||||
class GlobalStack(CuraContainerStack):
|
class GlobalStack(CuraContainerStack):
|
||||||
def __init__(self, container_id: str):
|
def __init__(self, container_id: str) -> None:
|
||||||
super().__init__(container_id)
|
super().__init__(container_id)
|
||||||
|
|
||||||
self.addMetaDataEntry("type", "machine") # For backward compatibility
|
self.addMetaDataEntry("type", "machine") # For backward compatibility
|
||||||
|
@ -34,7 +35,7 @@ class GlobalStack(CuraContainerStack):
|
||||||
# and if so, to bypass the resolve to prevent an infinite recursion that would occur
|
# and if so, to bypass the resolve to prevent an infinite recursion that would occur
|
||||||
# if the resolve function tried to access the same property it is a resolve for.
|
# if the resolve function tried to access the same property it is a resolve for.
|
||||||
# Per thread we have our own resolving_settings, or strange things sometimes occur.
|
# Per thread we have our own resolving_settings, or strange things sometimes occur.
|
||||||
self._resolving_settings = defaultdict(set) # keys are thread names
|
self._resolving_settings = defaultdict(set) #type: Dict[str, Set[str]] # keys are thread names
|
||||||
|
|
||||||
## Get the list of extruders of this stack.
|
## Get the list of extruders of this stack.
|
||||||
#
|
#
|
||||||
|
@ -94,6 +95,7 @@ class GlobalStack(CuraContainerStack):
|
||||||
context.pushContainer(self)
|
context.pushContainer(self)
|
||||||
|
|
||||||
# Handle the "resolve" property.
|
# Handle the "resolve" property.
|
||||||
|
#TODO: Why the hell does this involve threading?
|
||||||
if self._shouldResolve(key, property_name, context):
|
if self._shouldResolve(key, property_name, context):
|
||||||
current_thread = threading.current_thread()
|
current_thread = threading.current_thread()
|
||||||
self._resolving_settings[current_thread.name].add(key)
|
self._resolving_settings[current_thread.name].add(key)
|
||||||
|
@ -106,7 +108,7 @@ class GlobalStack(CuraContainerStack):
|
||||||
limit_to_extruder = super().getProperty(key, "limit_to_extruder", context)
|
limit_to_extruder = super().getProperty(key, "limit_to_extruder", context)
|
||||||
if limit_to_extruder is not None:
|
if limit_to_extruder is not None:
|
||||||
if limit_to_extruder == -1:
|
if limit_to_extruder == -1:
|
||||||
limit_to_extruder = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
|
limit_to_extruder = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition)
|
||||||
limit_to_extruder = str(limit_to_extruder)
|
limit_to_extruder = str(limit_to_extruder)
|
||||||
if limit_to_extruder is not None and limit_to_extruder != "-1" and limit_to_extruder in self._extruders:
|
if limit_to_extruder is not None and limit_to_extruder != "-1" and limit_to_extruder in self._extruders:
|
||||||
if super().getProperty(key, "settable_per_extruder", context):
|
if super().getProperty(key, "settable_per_extruder", context):
|
||||||
|
@ -155,7 +157,7 @@ class GlobalStack(CuraContainerStack):
|
||||||
|
|
||||||
## Perform some sanity checks on the global stack
|
## Perform some sanity checks on the global stack
|
||||||
# Sanity check for extruders; they must have positions 0 and up to machine_extruder_count - 1
|
# Sanity check for extruders; they must have positions 0 and up to machine_extruder_count - 1
|
||||||
def isValid(self):
|
def isValid(self) -> bool:
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = ContainerRegistry.getInstance()
|
||||||
extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = self.getId())
|
extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = self.getId())
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import time
|
import time
|
||||||
#Type hinting.
|
from typing import Any, Callable, List, Dict, TYPE_CHECKING, Optional
|
||||||
from typing import List, Dict, TYPE_CHECKING, Optional
|
|
||||||
|
|
||||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
@ -15,20 +14,22 @@ from UM.Signal import Signal
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
|
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, QTimer
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from UM import Util
|
from UM import Util
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
from UM.Signal import postponeSignals, CompressTechnique
|
from UM.Signal import postponeSignals, CompressTechnique
|
||||||
|
|
||||||
|
import cura.CuraApplication
|
||||||
|
from cura.Machines.ContainerNode import ContainerNode #For typing.
|
||||||
|
from cura.Machines.QualityChangesGroup import QualityChangesGroup #For typing.
|
||||||
|
from cura.Machines.QualityGroup import QualityGroup #For typing.
|
||||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||||
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||||
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||||
|
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
|
||||||
|
@ -40,29 +41,31 @@ catalog = i18nCatalog("cura")
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from cura.Settings.CuraContainerStack import CuraContainerStack
|
from cura.Settings.CuraContainerStack import CuraContainerStack
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
from cura.Machines.MaterialManager import MaterialManager
|
||||||
|
from cura.Machines.QualityManager import QualityManager
|
||||||
|
from cura.Machines.VariantManager import VariantManager
|
||||||
|
|
||||||
class MachineManager(QObject):
|
class MachineManager(QObject):
|
||||||
|
def __init__(self, parent: QObject = None) -> None:
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self._active_container_stack = None # type: Optional[ExtruderManager]
|
self._active_container_stack = None # type: Optional[ExtruderManager]
|
||||||
self._global_container_stack = None # type: Optional[GlobalStack]
|
self._global_container_stack = None # type: Optional[GlobalStack]
|
||||||
|
|
||||||
self._current_root_material_id = {} # type: Dict[str, str]
|
self._current_root_material_id = {} # type: Dict[str, str]
|
||||||
self._current_quality_group = None
|
self._current_quality_group = None # type: Optional[QualityGroup]
|
||||||
self._current_quality_changes_group = None
|
self._current_quality_changes_group = None # type: Optional[QualityChangesGroup]
|
||||||
|
|
||||||
self._default_extruder_position = "0" # to be updated when extruders are switched on and off
|
self._default_extruder_position = "0" # to be updated when extruders are switched on and off
|
||||||
|
|
||||||
self.machine_extruder_material_update_dict = collections.defaultdict(list)
|
self.machine_extruder_material_update_dict = collections.defaultdict(list) #type: Dict[str, List[Callable[[], None]]]
|
||||||
|
|
||||||
self._instance_container_timer = QTimer()
|
self._instance_container_timer = QTimer() #type: QTimer
|
||||||
self._instance_container_timer.setInterval(250)
|
self._instance_container_timer.setInterval(250)
|
||||||
self._instance_container_timer.setSingleShot(True)
|
self._instance_container_timer.setSingleShot(True)
|
||||||
self._instance_container_timer.timeout.connect(self.__emitChangedSignals)
|
self._instance_container_timer.timeout.connect(self.__emitChangedSignals)
|
||||||
|
|
||||||
self._application = Application.getInstance()
|
self._application = cura.CuraApplication.CuraApplication.getInstance() #type: cura.CuraApplication.CuraApplication
|
||||||
self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||||
self._application.getContainerRegistry().containerLoadComplete.connect(self._onContainersChanged)
|
self._application.getContainerRegistry().containerLoadComplete.connect(self._onContainersChanged)
|
||||||
|
|
||||||
|
@ -76,12 +79,12 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
self._stacks_have_errors = None # type: Optional[bool]
|
self._stacks_have_errors = None # type: Optional[bool]
|
||||||
|
|
||||||
self._empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
self._empty_container = CuraContainerRegistry.getInstance().getEmptyInstanceContainer() #type: InstanceContainer
|
||||||
self._empty_definition_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0]
|
self._empty_definition_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0] #type: InstanceContainer
|
||||||
self._empty_variant_container = ContainerRegistry.getInstance().findContainers(id = "empty_variant")[0]
|
self._empty_variant_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_variant")[0] #type: InstanceContainer
|
||||||
self._empty_material_container = ContainerRegistry.getInstance().findContainers(id = "empty_material")[0]
|
self._empty_material_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_material")[0] #type: InstanceContainer
|
||||||
self._empty_quality_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
|
self._empty_quality_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] #type: InstanceContainer
|
||||||
self._empty_quality_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0]
|
self._empty_quality_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0] #type: InstanceContainer
|
||||||
|
|
||||||
self._onGlobalContainerChanged()
|
self._onGlobalContainerChanged()
|
||||||
|
|
||||||
|
@ -99,8 +102,6 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
self._application.getPreferences().addPreference("cura/active_machine", "")
|
self._application.getPreferences().addPreference("cura/active_machine", "")
|
||||||
|
|
||||||
self._global_event_keys = set()
|
|
||||||
|
|
||||||
self._printer_output_devices = [] # type: List[PrinterOutputDevice]
|
self._printer_output_devices = [] # type: List[PrinterOutputDevice]
|
||||||
self._application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
self._application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
||||||
# There might already be some output devices by the time the signal is connected
|
# There might already be some output devices by the time the signal is connected
|
||||||
|
@ -116,15 +117,15 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
|
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
|
||||||
"The selected material is incompatible with the selected machine or configuration."),
|
"The selected material is incompatible with the selected machine or configuration."),
|
||||||
title = catalog.i18nc("@info:title", "Incompatible Material"))
|
title = catalog.i18nc("@info:title", "Incompatible Material")) #type: Message
|
||||||
|
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId)
|
containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) #type: List[InstanceContainer]
|
||||||
if containers:
|
if containers:
|
||||||
containers[0].nameChanged.connect(self._onMaterialNameChanged)
|
containers[0].nameChanged.connect(self._onMaterialNameChanged)
|
||||||
|
|
||||||
self._material_manager = self._application.getMaterialManager()
|
self._material_manager = self._application.getMaterialManager() #type: MaterialManager
|
||||||
self._variant_manager = self._application.getVariantManager()
|
self._variant_manager = self._application.getVariantManager() #type: VariantManager
|
||||||
self._quality_manager = self._application.getQualityManager()
|
self._quality_manager = self._application.getQualityManager() #type: QualityManager
|
||||||
|
|
||||||
# When the materials lookup table gets updated, it can mean that a material has its name changed, which should
|
# When the materials lookup table gets updated, it can mean that a material has its name changed, which should
|
||||||
# be reflected on the GUI. This signal emission makes sure that it happens.
|
# be reflected on the GUI. This signal emission makes sure that it happens.
|
||||||
|
@ -164,7 +165,7 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
def setInitialActiveMachine(self) -> None:
|
def setInitialActiveMachine(self) -> None:
|
||||||
active_machine_id = self._application.getPreferences().getValue("cura/active_machine")
|
active_machine_id = self._application.getPreferences().getValue("cura/active_machine")
|
||||||
if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id):
|
if active_machine_id != "" and CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id):
|
||||||
# An active machine was saved, so restore it.
|
# An active machine was saved, so restore it.
|
||||||
self.setActiveMachine(active_machine_id)
|
self.setActiveMachine(active_machine_id)
|
||||||
|
|
||||||
|
@ -215,7 +216,10 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
@pyqtProperty(int, constant=True)
|
@pyqtProperty(int, constant=True)
|
||||||
def totalNumberOfSettings(self) -> int:
|
def totalNumberOfSettings(self) -> int:
|
||||||
return len(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0].getAllKeys())
|
general_definition_containers = CuraContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")
|
||||||
|
if not general_definition_containers:
|
||||||
|
return 0
|
||||||
|
return len(general_definition_containers[0].getAllKeys())
|
||||||
|
|
||||||
def _onGlobalContainerChanged(self) -> None:
|
def _onGlobalContainerChanged(self) -> None:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
|
@ -355,7 +359,7 @@ class MachineManager(QObject):
|
||||||
def setActiveMachine(self, stack_id: str) -> None:
|
def setActiveMachine(self, stack_id: str) -> None:
|
||||||
self.blurSettings.emit() # Ensure no-one has focus.
|
self.blurSettings.emit() # Ensure no-one has focus.
|
||||||
|
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = CuraContainerRegistry.getInstance()
|
||||||
|
|
||||||
containers = container_registry.findContainerStacks(id = stack_id)
|
containers = container_registry.findContainerStacks(id = stack_id)
|
||||||
if not containers:
|
if not containers:
|
||||||
|
@ -381,7 +385,7 @@ class MachineManager(QObject):
|
||||||
# \param metadata_filter \type{dict} list of metadata keys and values used for filtering
|
# \param metadata_filter \type{dict} list of metadata keys and values used for filtering
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getMachine(definition_id: str, metadata_filter: Dict[str, str] = None) -> Optional["GlobalStack"]:
|
def getMachine(definition_id: str, metadata_filter: Dict[str, str] = None) -> Optional["GlobalStack"]:
|
||||||
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
if machine.definition.getId() == definition_id:
|
if machine.definition.getId() == definition_id:
|
||||||
return machine
|
return machine
|
||||||
|
@ -625,11 +629,13 @@ class MachineManager(QObject):
|
||||||
## Check if a container is read_only
|
## Check if a container is read_only
|
||||||
@pyqtSlot(str, result = bool)
|
@pyqtSlot(str, result = bool)
|
||||||
def isReadOnly(self, container_id: str) -> bool:
|
def isReadOnly(self, container_id: str) -> bool:
|
||||||
return ContainerRegistry.getInstance().isReadOnly(container_id)
|
return CuraContainerRegistry.getInstance().isReadOnly(container_id)
|
||||||
|
|
||||||
## Copy the value of the setting of the current extruder to all other extruders as well as the global container.
|
## Copy the value of the setting of the current extruder to all other extruders as well as the global container.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def copyValueToExtruders(self, key: str) -> None:
|
def copyValueToExtruders(self, key: str) -> None:
|
||||||
|
if self._active_container_stack is None or self._global_container_stack is None:
|
||||||
|
return
|
||||||
new_value = self._active_container_stack.getProperty(key, "value")
|
new_value = self._active_container_stack.getProperty(key, "value")
|
||||||
extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
|
extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
|
||||||
|
|
||||||
|
@ -641,6 +647,8 @@ class MachineManager(QObject):
|
||||||
## Copy the value of all manually changed settings of the current extruder to all other extruders.
|
## Copy the value of all manually changed settings of the current extruder to all other extruders.
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def copyAllValuesToExtruders(self) -> None:
|
def copyAllValuesToExtruders(self) -> None:
|
||||||
|
if self._active_container_stack is None or self._global_container_stack is None:
|
||||||
|
return
|
||||||
extruder_stacks = list(self._global_container_stack.extruders.values())
|
extruder_stacks = list(self._global_container_stack.extruders.values())
|
||||||
for extruder_stack in extruder_stacks:
|
for extruder_stack in extruder_stacks:
|
||||||
if extruder_stack != self._active_container_stack:
|
if extruder_stack != self._active_container_stack:
|
||||||
|
@ -704,7 +712,7 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
def renameMachine(self, machine_id: str, new_name: str) -> None:
|
def renameMachine(self, machine_id: str, new_name: str) -> None:
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = CuraContainerRegistry.getInstance()
|
||||||
machine_stack = container_registry.findContainerStacks(id = machine_id)
|
machine_stack = container_registry.findContainerStacks(id = machine_id)
|
||||||
if machine_stack:
|
if machine_stack:
|
||||||
new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].definition.getName())
|
new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].definition.getName())
|
||||||
|
@ -718,23 +726,23 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
# activate a new machine before removing a machine because this is safer
|
# activate a new machine before removing a machine because this is safer
|
||||||
if activate_new_machine:
|
if activate_new_machine:
|
||||||
machine_stacks = ContainerRegistry.getInstance().findContainerStacksMetadata(type = "machine")
|
machine_stacks = CuraContainerRegistry.getInstance().findContainerStacksMetadata(type = "machine")
|
||||||
other_machine_stacks = [s for s in machine_stacks if s["id"] != machine_id]
|
other_machine_stacks = [s for s in machine_stacks if s["id"] != machine_id]
|
||||||
if other_machine_stacks:
|
if other_machine_stacks:
|
||||||
self.setActiveMachine(other_machine_stacks[0]["id"])
|
self.setActiveMachine(other_machine_stacks[0]["id"])
|
||||||
|
|
||||||
metadata = ContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
|
metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
|
||||||
network_key = metadata["um_network_key"] if "um_network_key" in metadata else None
|
network_key = metadata["um_network_key"] if "um_network_key" in metadata else None
|
||||||
ExtruderManager.getInstance().removeMachineExtruders(machine_id)
|
ExtruderManager.getInstance().removeMachineExtruders(machine_id)
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
|
containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
|
||||||
for container in containers:
|
for container in containers:
|
||||||
ContainerRegistry.getInstance().removeContainer(container["id"])
|
CuraContainerRegistry.getInstance().removeContainer(container["id"])
|
||||||
ContainerRegistry.getInstance().removeContainer(machine_id)
|
CuraContainerRegistry.getInstance().removeContainer(machine_id)
|
||||||
|
|
||||||
# If the printer that is being removed is a network printer, the hidden printers have to be also removed
|
# If the printer that is being removed is a network printer, the hidden printers have to be also removed
|
||||||
if network_key:
|
if network_key:
|
||||||
metadata_filter = {"um_network_key": network_key}
|
metadata_filter = {"um_network_key": network_key}
|
||||||
hidden_containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||||
if hidden_containers:
|
if hidden_containers:
|
||||||
# This reuses the method and remove all printers recursively
|
# This reuses the method and remove all printers recursively
|
||||||
self.removeMachine(hidden_containers[0].getId())
|
self.removeMachine(hidden_containers[0].getId())
|
||||||
|
@ -802,14 +810,17 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
## Get the Definition ID of a machine (specified by ID)
|
## Get the Definition ID of a machine (specified by ID)
|
||||||
# \param machine_id string machine id to get the definition ID of
|
# \param machine_id string machine id to get the definition ID of
|
||||||
# \returns DefinitionID (string) if found, None otherwise
|
# \returns DefinitionID if found, None otherwise
|
||||||
@pyqtSlot(str, result = str)
|
@pyqtSlot(str, result = str)
|
||||||
def getDefinitionByMachineId(self, machine_id: str) -> str:
|
def getDefinitionByMachineId(self, machine_id: str) -> Optional[str]:
|
||||||
containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
containers = CuraContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
||||||
if containers:
|
if containers:
|
||||||
return containers[0].definition.getId()
|
return containers[0].definition.getId()
|
||||||
|
return None
|
||||||
|
|
||||||
def getIncompatibleSettingsOnEnabledExtruders(self, container: InstanceContainer) -> List[str]:
|
def getIncompatibleSettingsOnEnabledExtruders(self, container: InstanceContainer) -> List[str]:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return []
|
||||||
extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
||||||
result = [] # type: List[str]
|
result = [] # type: List[str]
|
||||||
for setting_instance in container.findInstances():
|
for setting_instance in container.findInstances():
|
||||||
|
@ -834,6 +845,8 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
## Update extruder number to a valid value when the number of extruders are changed, or when an extruder is changed
|
## Update extruder number to a valid value when the number of extruders are changed, or when an extruder is changed
|
||||||
def correctExtruderSettings(self) -> None:
|
def correctExtruderSettings(self) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
for setting_key in self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.userChanges):
|
for setting_key in self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.userChanges):
|
||||||
self._global_container_stack.userChanges.removeInstance(setting_key)
|
self._global_container_stack.userChanges.removeInstance(setting_key)
|
||||||
add_user_changes = self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.qualityChanges)
|
add_user_changes = self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.qualityChanges)
|
||||||
|
@ -851,6 +864,8 @@ class MachineManager(QObject):
|
||||||
## Set the amount of extruders on the active machine (global stack)
|
## Set the amount of extruders on the active machine (global stack)
|
||||||
# \param extruder_count int the number of extruders to set
|
# \param extruder_count int the number of extruders to set
|
||||||
def setActiveMachineExtruderCount(self, extruder_count: int) -> None:
|
def setActiveMachineExtruderCount(self, extruder_count: int) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
extruder_manager = self._application.getExtruderManager()
|
extruder_manager = self._application.getExtruderManager()
|
||||||
|
|
||||||
definition_changes_container = self._global_container_stack.definitionChanges
|
definition_changes_container = self._global_container_stack.definitionChanges
|
||||||
|
@ -869,7 +884,7 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
# Check to see if any objects are set to print with an extruder that will no longer exist
|
# Check to see if any objects are set to print with an extruder that will no longer exist
|
||||||
root_node = self._application.getController().getScene().getRoot()
|
root_node = self._application.getController().getScene().getRoot()
|
||||||
for node in DepthFirstIterator(root_node):
|
for node in DepthFirstIterator(root_node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if node.getMeshData():
|
if node.getMeshData():
|
||||||
extruder_nr = node.callDecoration("getActiveExtruderPosition")
|
extruder_nr = node.callDecoration("getActiveExtruderPosition")
|
||||||
|
|
||||||
|
@ -884,7 +899,7 @@ class MachineManager(QObject):
|
||||||
global_user_container = self._global_container_stack.userChanges
|
global_user_container = self._global_container_stack.userChanges
|
||||||
|
|
||||||
# Make sure extruder_stacks exists
|
# Make sure extruder_stacks exists
|
||||||
extruder_stacks = []
|
extruder_stacks = [] #type: List[ExtruderStack]
|
||||||
|
|
||||||
if previous_extruder_count == 1:
|
if previous_extruder_count == 1:
|
||||||
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
|
@ -912,6 +927,8 @@ class MachineManager(QObject):
|
||||||
return extruder
|
return extruder
|
||||||
|
|
||||||
def updateDefaultExtruder(self) -> None:
|
def updateDefaultExtruder(self) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
extruder_items = sorted(self._global_container_stack.extruders.items())
|
extruder_items = sorted(self._global_container_stack.extruders.items())
|
||||||
old_position = self._default_extruder_position
|
old_position = self._default_extruder_position
|
||||||
new_default_position = "0"
|
new_default_position = "0"
|
||||||
|
@ -924,6 +941,8 @@ class MachineManager(QObject):
|
||||||
self.extruderChanged.emit()
|
self.extruderChanged.emit()
|
||||||
|
|
||||||
def updateNumberExtrudersEnabled(self) -> None:
|
def updateNumberExtrudersEnabled(self) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
definition_changes_container = self._global_container_stack.definitionChanges
|
definition_changes_container = self._global_container_stack.definitionChanges
|
||||||
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
||||||
extruder_count = 0
|
extruder_count = 0
|
||||||
|
@ -936,6 +955,8 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
@pyqtProperty(int, notify = numberExtrudersEnabledChanged)
|
@pyqtProperty(int, notify = numberExtrudersEnabledChanged)
|
||||||
def numberExtrudersEnabled(self) -> int:
|
def numberExtrudersEnabled(self) -> int:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return 1
|
||||||
return self._global_container_stack.definitionChanges.getProperty("extruders_enabled_count", "value")
|
return self._global_container_stack.definitionChanges.getProperty("extruders_enabled_count", "value")
|
||||||
|
|
||||||
@pyqtProperty(str, notify = extruderChanged)
|
@pyqtProperty(str, notify = extruderChanged)
|
||||||
|
@ -945,6 +966,8 @@ class MachineManager(QObject):
|
||||||
## This will fire the propertiesChanged for all settings so they will be updated in the front-end
|
## This will fire the propertiesChanged for all settings so they will be updated in the front-end
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def forceUpdateAllSettings(self) -> None:
|
def forceUpdateAllSettings(self) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
property_names = ["value", "resolve", "validationState"]
|
property_names = ["value", "resolve", "validationState"]
|
||||||
for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
|
for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
|
||||||
|
@ -954,8 +977,9 @@ class MachineManager(QObject):
|
||||||
@pyqtSlot(int, bool)
|
@pyqtSlot(int, bool)
|
||||||
def setExtruderEnabled(self, position: int, enabled: bool) -> None:
|
def setExtruderEnabled(self, position: int, enabled: bool) -> None:
|
||||||
extruder = self.getExtruder(position)
|
extruder = self.getExtruder(position)
|
||||||
if not extruder:
|
if not extruder or self._global_container_stack is None:
|
||||||
Logger.log("w", "Could not find extruder on position %s", position)
|
Logger.log("w", "Could not find extruder on position %s", position)
|
||||||
|
return
|
||||||
|
|
||||||
extruder.setEnabled(enabled)
|
extruder.setEnabled(enabled)
|
||||||
self.updateDefaultExtruder()
|
self.updateDefaultExtruder()
|
||||||
|
@ -991,6 +1015,8 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
@pyqtSlot(str, str, str)
|
@pyqtSlot(str, str, str)
|
||||||
def setSettingForAllExtruders(self, setting_name: str, property_name: str, property_value: str) -> None:
|
def setSettingForAllExtruders(self, setting_name: str, property_name: str, property_value: str) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
for key, extruder in self._global_container_stack.extruders.items():
|
for key, extruder in self._global_container_stack.extruders.items():
|
||||||
container = extruder.userChanges
|
container = extruder.userChanges
|
||||||
container.setProperty(setting_name, property_name, property_value)
|
container.setProperty(setting_name, property_name, property_value)
|
||||||
|
@ -999,6 +1025,8 @@ class MachineManager(QObject):
|
||||||
# \param setting_name The ID of the setting to reset.
|
# \param setting_name The ID of the setting to reset.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def resetSettingForAllExtruders(self, setting_name: str) -> None:
|
def resetSettingForAllExtruders(self, setting_name: str) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
for key, extruder in self._global_container_stack.extruders.items():
|
for key, extruder in self._global_container_stack.extruders.items():
|
||||||
container = extruder.userChanges
|
container = extruder.userChanges
|
||||||
container.removeInstance(setting_name)
|
container.removeInstance(setting_name)
|
||||||
|
@ -1041,6 +1069,8 @@ class MachineManager(QObject):
|
||||||
# for all stacks in the currently active machine.
|
# for all stacks in the currently active machine.
|
||||||
#
|
#
|
||||||
def _setEmptyQuality(self) -> None:
|
def _setEmptyQuality(self) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
self._current_quality_group = None
|
self._current_quality_group = None
|
||||||
self._current_quality_changes_group = None
|
self._current_quality_changes_group = None
|
||||||
self._global_container_stack.quality = self._empty_quality_container
|
self._global_container_stack.quality = self._empty_quality_container
|
||||||
|
@ -1052,12 +1082,14 @@ class MachineManager(QObject):
|
||||||
self.activeQualityGroupChanged.emit()
|
self.activeQualityGroupChanged.emit()
|
||||||
self.activeQualityChangesGroupChanged.emit()
|
self.activeQualityChangesGroupChanged.emit()
|
||||||
|
|
||||||
def _setQualityGroup(self, quality_group, empty_quality_changes: bool = True) -> None:
|
def _setQualityGroup(self, quality_group: Optional[QualityGroup], empty_quality_changes: bool = True) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
if quality_group is None:
|
if quality_group is None:
|
||||||
self._setEmptyQuality()
|
self._setEmptyQuality()
|
||||||
return
|
return
|
||||||
|
|
||||||
if quality_group.node_for_global.getContainer() is None:
|
if quality_group.node_for_global is None or quality_group.node_for_global.getContainer() is None:
|
||||||
return
|
return
|
||||||
for node in quality_group.nodes_for_extruders.values():
|
for node in quality_group.nodes_for_extruders.values():
|
||||||
if node.getContainer() is None:
|
if node.getContainer() is None:
|
||||||
|
@ -1081,14 +1113,15 @@ class MachineManager(QObject):
|
||||||
self.activeQualityGroupChanged.emit()
|
self.activeQualityGroupChanged.emit()
|
||||||
self.activeQualityChangesGroupChanged.emit()
|
self.activeQualityChangesGroupChanged.emit()
|
||||||
|
|
||||||
def _fixQualityChangesGroupToNotSupported(self, quality_changes_group):
|
def _fixQualityChangesGroupToNotSupported(self, quality_changes_group: QualityChangesGroup) -> None:
|
||||||
nodes = [quality_changes_group.node_for_global] + list(quality_changes_group.nodes_for_extruders.values())
|
nodes = [quality_changes_group.node_for_global] + list(quality_changes_group.nodes_for_extruders.values())
|
||||||
containers = [n.getContainer() for n in nodes if n is not None]
|
containers = [n.getContainer() for n in nodes if n is not None]
|
||||||
for container in containers:
|
for container in containers:
|
||||||
|
if container:
|
||||||
container.setMetaDataEntry("quality_type", "not_supported")
|
container.setMetaDataEntry("quality_type", "not_supported")
|
||||||
quality_changes_group.quality_type = "not_supported"
|
quality_changes_group.quality_type = "not_supported"
|
||||||
|
|
||||||
def _setQualityChangesGroup(self, quality_changes_group):
|
def _setQualityChangesGroup(self, quality_changes_group: QualityChangesGroup) -> None:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return #Can't change that.
|
return #Can't change that.
|
||||||
quality_type = quality_changes_group.quality_type
|
quality_type = quality_changes_group.quality_type
|
||||||
|
@ -1132,21 +1165,25 @@ class MachineManager(QObject):
|
||||||
self.activeQualityGroupChanged.emit()
|
self.activeQualityGroupChanged.emit()
|
||||||
self.activeQualityChangesGroupChanged.emit()
|
self.activeQualityChangesGroupChanged.emit()
|
||||||
|
|
||||||
def _setVariantNode(self, position, container_node):
|
def _setVariantNode(self, position: str, container_node: ContainerNode) -> None:
|
||||||
if container_node.getContainer() is None:
|
if container_node.getContainer() is None or self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
self._global_container_stack.extruders[position].variant = container_node.getContainer()
|
self._global_container_stack.extruders[position].variant = container_node.getContainer()
|
||||||
self.activeVariantChanged.emit()
|
self.activeVariantChanged.emit()
|
||||||
|
|
||||||
def _setGlobalVariant(self, container_node):
|
def _setGlobalVariant(self, container_node: ContainerNode) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
self._global_container_stack.variant = container_node.getContainer()
|
self._global_container_stack.variant = container_node.getContainer()
|
||||||
if not self._global_container_stack.variant:
|
if not self._global_container_stack.variant:
|
||||||
self._global_container_stack.variant = self._application.empty_variant_container
|
self._global_container_stack.variant = self._application.empty_variant_container
|
||||||
|
|
||||||
def _setMaterial(self, position, container_node = None):
|
def _setMaterial(self, position: str, container_node: ContainerNode = None) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
if container_node and container_node.getContainer():
|
if container_node and container_node.getContainer():
|
||||||
self._global_container_stack.extruders[position].material = container_node.getContainer()
|
self._global_container_stack.extruders[position].material = container_node.getContainer()
|
||||||
root_material_id = container_node.metadata["base_file"]
|
root_material_id = container_node.getMetaDataEntry("base_file", None)
|
||||||
else:
|
else:
|
||||||
self._global_container_stack.extruders[position].material = self._empty_material_container
|
self._global_container_stack.extruders[position].material = self._empty_material_container
|
||||||
root_material_id = None
|
root_material_id = None
|
||||||
|
@ -1155,8 +1192,9 @@ class MachineManager(QObject):
|
||||||
self._current_root_material_id[position] = root_material_id
|
self._current_root_material_id[position] = root_material_id
|
||||||
self.rootMaterialChanged.emit()
|
self.rootMaterialChanged.emit()
|
||||||
|
|
||||||
def activeMaterialsCompatible(self):
|
def activeMaterialsCompatible(self) -> bool:
|
||||||
# check material - variant compatibility
|
# check material - variant compatibility
|
||||||
|
if self._global_container_stack is not None:
|
||||||
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
|
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
|
||||||
for position, extruder in self._global_container_stack.extruders.items():
|
for position, extruder in self._global_container_stack.extruders.items():
|
||||||
if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"):
|
if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"):
|
||||||
|
@ -1166,7 +1204,7 @@ class MachineManager(QObject):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
## Update current quality type and machine after setting material
|
## Update current quality type and machine after setting material
|
||||||
def _updateQualityWithMaterial(self, *args):
|
def _updateQualityWithMaterial(self, *args: Any) -> None:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
Logger.log("i", "Updating quality/quality_changes due to material change")
|
Logger.log("i", "Updating quality/quality_changes due to material change")
|
||||||
|
@ -1205,7 +1243,7 @@ class MachineManager(QObject):
|
||||||
current_quality_type, quality_type)
|
current_quality_type, quality_type)
|
||||||
self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True)
|
self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True)
|
||||||
|
|
||||||
def updateMaterialWithVariant(self, position: Optional[str]):
|
def updateMaterialWithVariant(self, position: Optional[str]) -> None:
|
||||||
if self._global_container_stack is None:
|
if self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
if position is None:
|
if position is None:
|
||||||
|
@ -1213,8 +1251,8 @@ class MachineManager(QObject):
|
||||||
else:
|
else:
|
||||||
position_list = [position]
|
position_list = [position]
|
||||||
|
|
||||||
for position in position_list:
|
for position_item in position_list:
|
||||||
extruder = self._global_container_stack.extruders[position]
|
extruder = self._global_container_stack.extruders[position_item]
|
||||||
|
|
||||||
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
|
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
|
||||||
current_variant_name = None
|
current_variant_name = None
|
||||||
|
@ -1232,28 +1270,28 @@ class MachineManager(QObject):
|
||||||
material_diameter)
|
material_diameter)
|
||||||
|
|
||||||
if not candidate_materials:
|
if not candidate_materials:
|
||||||
self._setMaterial(position, container_node = None)
|
self._setMaterial(position_item, container_node = None)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if current_material_base_name in candidate_materials:
|
if current_material_base_name in candidate_materials:
|
||||||
new_material = candidate_materials[current_material_base_name]
|
new_material = candidate_materials[current_material_base_name]
|
||||||
self._setMaterial(position, new_material)
|
self._setMaterial(position_item, new_material)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# The current material is not available, find the preferred one
|
# The current material is not available, find the preferred one
|
||||||
material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, position, current_variant_name)
|
material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, position, current_variant_name)
|
||||||
if material_node is not None:
|
if material_node is not None:
|
||||||
self._setMaterial(position, material_node)
|
self._setMaterial(position_item, material_node)
|
||||||
|
|
||||||
## Given a printer definition name, select the right machine instance. In case it doesn't exist, create a new
|
## Given a printer definition name, select the right machine instance. In case it doesn't exist, create a new
|
||||||
# instance with the same network key.
|
# instance with the same network key.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def switchPrinterType(self, machine_name: str) -> None:
|
def switchPrinterType(self, machine_name: str) -> None:
|
||||||
# Don't switch if the user tries to change to the same type of printer
|
# Don't switch if the user tries to change to the same type of printer
|
||||||
if self.activeMachineDefinitionName == machine_name:
|
if self._global_container_stack is None or self.self.activeMachineDefinitionName == machine_name:
|
||||||
return
|
return
|
||||||
# Get the definition id corresponding to this machine name
|
# Get the definition id corresponding to this machine name
|
||||||
machine_definition_id = ContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
|
machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
|
||||||
# Try to find a machine with the same network key
|
# Try to find a machine with the same network key
|
||||||
new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey})
|
new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey})
|
||||||
# If there is no machine, then create a new one and set it to the non-hidden instance
|
# If there is no machine, then create a new one and set it to the non-hidden instance
|
||||||
|
@ -1275,6 +1313,8 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
@pyqtSlot(QObject)
|
@pyqtSlot(QObject)
|
||||||
def applyRemoteConfiguration(self, configuration: ConfigurationModel) -> None:
|
def applyRemoteConfiguration(self, configuration: ConfigurationModel) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
self.switchPrinterType(configuration.printerType)
|
self.switchPrinterType(configuration.printerType)
|
||||||
|
@ -1309,7 +1349,7 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value'
|
## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value'
|
||||||
def replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None:
|
def replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None:
|
||||||
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
|
machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
if machine.getMetaDataEntry(key) == value:
|
if machine.getMetaDataEntry(key) == value:
|
||||||
machine.setMetaDataEntry(key, new_value)
|
machine.setMetaDataEntry(key, new_value)
|
||||||
|
@ -1322,18 +1362,18 @@ class MachineManager(QObject):
|
||||||
# Check if the connect_group_name is correct. If not, update all the containers connected to the same printer
|
# Check if the connect_group_name is correct. If not, update all the containers connected to the same printer
|
||||||
if self.activeMachineNetworkGroupName != group_name:
|
if self.activeMachineNetworkGroupName != group_name:
|
||||||
metadata_filter = {"um_network_key": self.activeMachineNetworkKey}
|
metadata_filter = {"um_network_key": self.activeMachineNetworkKey}
|
||||||
containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||||
for container in containers:
|
for container in containers:
|
||||||
container.setMetaDataEntry("connect_group_name", group_name)
|
container.setMetaDataEntry("connect_group_name", group_name)
|
||||||
|
|
||||||
## This method checks if there is an instance connected to the given network_key
|
## This method checks if there is an instance connected to the given network_key
|
||||||
def existNetworkInstances(self, network_key: str) -> bool:
|
def existNetworkInstances(self, network_key: str) -> bool:
|
||||||
metadata_filter = {"um_network_key": network_key}
|
metadata_filter = {"um_network_key": network_key}
|
||||||
containers = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||||
return bool(containers)
|
return bool(containers)
|
||||||
|
|
||||||
@pyqtSlot("QVariant")
|
@pyqtSlot("QVariant")
|
||||||
def setGlobalVariant(self, container_node):
|
def setGlobalVariant(self, container_node: ContainerNode) -> None:
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
self._setGlobalVariant(container_node)
|
self._setGlobalVariant(container_node)
|
||||||
|
@ -1341,7 +1381,9 @@ class MachineManager(QObject):
|
||||||
self._updateQualityWithMaterial()
|
self._updateQualityWithMaterial()
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
def setMaterialById(self, position, root_material_id):
|
def setMaterialById(self, position: str, root_material_id: str) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
machine_definition_id = self._global_container_stack.definition.id
|
machine_definition_id = self._global_container_stack.definition.id
|
||||||
position = str(position)
|
position = str(position)
|
||||||
extruder_stack = self._global_container_stack.extruders[position]
|
extruder_stack = self._global_container_stack.extruders[position]
|
||||||
|
@ -1364,12 +1406,14 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
def setVariantByName(self, position: str, variant_name: str) -> None:
|
def setVariantByName(self, position: str, variant_name: str) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
machine_definition_id = self._global_container_stack.definition.id
|
machine_definition_id = self._global_container_stack.definition.id
|
||||||
variant_node = self._variant_manager.getVariantNode(machine_definition_id, variant_name)
|
variant_node = self._variant_manager.getVariantNode(machine_definition_id, variant_name)
|
||||||
self.setVariant(position, variant_node)
|
self.setVariant(position, variant_node)
|
||||||
|
|
||||||
@pyqtSlot(str, "QVariant")
|
@pyqtSlot(str, "QVariant")
|
||||||
def setVariant(self, position: str, container_node):
|
def setVariant(self, position: str, container_node: ContainerNode) -> None:
|
||||||
position = str(position)
|
position = str(position)
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
|
@ -1391,7 +1435,7 @@ class MachineManager(QObject):
|
||||||
self.setQualityGroup(quality_group)
|
self.setQualityGroup(quality_group)
|
||||||
|
|
||||||
@pyqtSlot(QObject)
|
@pyqtSlot(QObject)
|
||||||
def setQualityGroup(self, quality_group, no_dialog = False):
|
def setQualityGroup(self, quality_group: QualityGroup, no_dialog: bool = False) -> None:
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
self._setQualityGroup(quality_group)
|
self._setQualityGroup(quality_group)
|
||||||
|
@ -1401,11 +1445,11 @@ class MachineManager(QObject):
|
||||||
self._application.discardOrKeepProfileChanges()
|
self._application.discardOrKeepProfileChanges()
|
||||||
|
|
||||||
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
|
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
|
||||||
def activeQualityGroup(self):
|
def activeQualityGroup(self) -> Optional[QualityGroup]:
|
||||||
return self._current_quality_group
|
return self._current_quality_group
|
||||||
|
|
||||||
@pyqtSlot(QObject)
|
@pyqtSlot(QObject)
|
||||||
def setQualityChangesGroup(self, quality_changes_group, no_dialog = False):
|
def setQualityChangesGroup(self, quality_changes_group: QualityChangesGroup, no_dialog: bool = False) -> None:
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
self._setQualityChangesGroup(quality_changes_group)
|
self._setQualityChangesGroup(quality_changes_group)
|
||||||
|
@ -1415,18 +1459,20 @@ class MachineManager(QObject):
|
||||||
self._application.discardOrKeepProfileChanges()
|
self._application.discardOrKeepProfileChanges()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def resetToUseDefaultQuality(self):
|
def resetToUseDefaultQuality(self) -> None:
|
||||||
|
if self._global_container_stack is None:
|
||||||
|
return
|
||||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||||
self._setQualityGroup(self._current_quality_group)
|
self._setQualityGroup(self._current_quality_group)
|
||||||
for stack in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
|
for stack in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
|
||||||
stack.userChanges.clear()
|
stack.userChanges.clear()
|
||||||
|
|
||||||
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
|
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
|
||||||
def activeQualityChangesGroup(self):
|
def activeQualityChangesGroup(self) -> Optional[QualityChangesGroup]:
|
||||||
return self._current_quality_changes_group
|
return self._current_quality_changes_group
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeQualityGroupChanged)
|
@pyqtProperty(str, notify = activeQualityGroupChanged)
|
||||||
def activeQualityOrQualityChangesName(self):
|
def activeQualityOrQualityChangesName(self) -> str:
|
||||||
name = self._empty_quality_container.getName()
|
name = self._empty_quality_container.getName()
|
||||||
if self._current_quality_changes_group:
|
if self._current_quality_changes_group:
|
||||||
name = self._current_quality_changes_group.name
|
name = self._current_quality_changes_group.name
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
@ -9,7 +12,6 @@ from .CuraContainerStack import CuraContainerStack
|
||||||
|
|
||||||
|
|
||||||
class PerObjectContainerStack(CuraContainerStack):
|
class PerObjectContainerStack(CuraContainerStack):
|
||||||
|
|
||||||
@override(CuraContainerStack)
|
@override(CuraContainerStack)
|
||||||
def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any:
|
def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any:
|
||||||
if context is None:
|
if context is None:
|
||||||
|
@ -20,7 +22,7 @@ class PerObjectContainerStack(CuraContainerStack):
|
||||||
|
|
||||||
# Return the user defined value if present, otherwise, evaluate the value according to the default routine.
|
# Return the user defined value if present, otherwise, evaluate the value according to the default routine.
|
||||||
if self.getContainer(0).hasProperty(key, property_name):
|
if self.getContainer(0).hasProperty(key, property_name):
|
||||||
if self.getContainer(0)._instances[key].state == InstanceState.User:
|
if self.getContainer(0).getProperty(key, "state") == InstanceState.User:
|
||||||
result = super().getProperty(key, property_name, context)
|
result = super().getProperty(key, property_name, context)
|
||||||
context.popContainer()
|
context.popContainer()
|
||||||
return result
|
return result
|
||||||
|
@ -53,13 +55,13 @@ class PerObjectContainerStack(CuraContainerStack):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@override(CuraContainerStack)
|
@override(CuraContainerStack)
|
||||||
def setNextStack(self, stack: CuraContainerStack):
|
def setNextStack(self, stack: CuraContainerStack) -> None:
|
||||||
super().setNextStack(stack)
|
super().setNextStack(stack)
|
||||||
|
|
||||||
# trigger signal to re-evaluate all default settings
|
# trigger signal to re-evaluate all default settings
|
||||||
for key, instance in self.getContainer(0)._instances.items():
|
for key in self.getContainer(0).getAllKeys():
|
||||||
# only evaluate default settings
|
# only evaluate default settings
|
||||||
if instance.state != InstanceState.Default:
|
if self.getContainer(0).getProperty(key, "state") != InstanceState.Default:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self._collectPropertyChanges(key, "value")
|
self._collectPropertyChanges(key, "value")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
|
from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
|
@ -13,6 +14,7 @@ from UM.Logger import Logger
|
||||||
# speed settings. If all the children of print_speed have a single value override, changing the speed won't
|
# speed settings. If all the children of print_speed have a single value override, changing the speed won't
|
||||||
# actually do anything, as only the 'leaf' settings are used by the engine.
|
# actually do anything, as only the 'leaf' settings are used by the engine.
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
from UM.Settings.SettingInstance import InstanceState
|
from UM.Settings.SettingInstance import InstanceState
|
||||||
|
|
||||||
|
@ -157,7 +159,7 @@ class SettingInheritanceManager(QObject):
|
||||||
stack = self._active_container_stack
|
stack = self._active_container_stack
|
||||||
if not stack: #No active container stack yet!
|
if not stack: #No active container stack yet!
|
||||||
return False
|
return False
|
||||||
containers = []
|
containers = [] # type: List[ContainerInterface]
|
||||||
|
|
||||||
## Check if the setting has a user state. If not, it is never overwritten.
|
## Check if the setting has a user state. If not, it is never overwritten.
|
||||||
has_user_state = stack.getProperty(key, "state") == InstanceState.User
|
has_user_state = stack.getProperty(key, "state") == InstanceState.User
|
||||||
|
|
|
@ -9,7 +9,6 @@ from UM.Signal import Signal, signalemitter
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Util import parseBool
|
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
|
||||||
|
@ -40,7 +39,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||||
user_container = InstanceContainer(container_id = self._generateUniqueName())
|
user_container = InstanceContainer(container_id = self._generateUniqueName())
|
||||||
user_container.addMetaDataEntry("type", "user")
|
user_container.addMetaDataEntry("type", "user")
|
||||||
self._stack.userChanges = user_container
|
self._stack.userChanges = user_container
|
||||||
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0)
|
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
|
||||||
|
|
||||||
self._is_non_printing_mesh = False
|
self._is_non_printing_mesh = False
|
||||||
self._is_non_thumbnail_visible_mesh = False
|
self._is_non_thumbnail_visible_mesh = False
|
||||||
|
@ -49,25 +48,13 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||||
|
|
||||||
Application.getInstance().getContainerRegistry().addContainer(self._stack)
|
Application.getInstance().getContainerRegistry().addContainer(self._stack)
|
||||||
|
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._onNumberOfExtrudersEnabledChanged)
|
Application.getInstance().globalContainerStackChanged.connect(self._updateNextStack)
|
||||||
Application.getInstance().getMachineManager().numberExtrudersEnabledChanged.connect(self._onNumberOfExtrudersEnabledChanged)
|
|
||||||
|
|
||||||
self.activeExtruderChanged.connect(self._updateNextStack)
|
self.activeExtruderChanged.connect(self._updateNextStack)
|
||||||
self._updateNextStack()
|
self._updateNextStack()
|
||||||
|
|
||||||
def _generateUniqueName(self):
|
def _generateUniqueName(self):
|
||||||
return "SettingOverrideInstanceContainer-%s" % uuid.uuid1()
|
return "SettingOverrideInstanceContainer-%s" % uuid.uuid1()
|
||||||
|
|
||||||
def _onNumberOfExtrudersEnabledChanged(self, *args, **kwargs):
|
|
||||||
if not parseBool(self._extruder_stack.getMetaDataEntry("enabled", "True")):
|
|
||||||
# switch to the first extruder that's available
|
|
||||||
global_stack = Application.getInstance().getMachineManager().activeMachine
|
|
||||||
for _, extruder in sorted(list(global_stack.extruders.items())):
|
|
||||||
if parseBool(extruder.getMetaDataEntry("enabled", "True")):
|
|
||||||
self._extruder_stack = extruder
|
|
||||||
self._updateNextStack()
|
|
||||||
break
|
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
## Create a fresh decorator object
|
## Create a fresh decorator object
|
||||||
deep_copy = SettingOverrideDecorator()
|
deep_copy = SettingOverrideDecorator()
|
||||||
|
@ -82,7 +69,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||||
deep_copy._stack.replaceContainer(0, instance_container)
|
deep_copy._stack.replaceContainer(0, instance_container)
|
||||||
|
|
||||||
# Properly set the right extruder on the copy
|
# Properly set the right extruder on the copy
|
||||||
deep_copy.setActiveExtruder(self._extruder_stack.getId())
|
deep_copy.setActiveExtruder(self._extruder_stack)
|
||||||
|
|
||||||
# use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh"
|
# use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh"
|
||||||
# has not been updated yet.
|
# has not been updated yet.
|
||||||
|
@ -95,7 +82,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||||
#
|
#
|
||||||
# \return An extruder's container stack.
|
# \return An extruder's container stack.
|
||||||
def getActiveExtruder(self):
|
def getActiveExtruder(self):
|
||||||
return self._extruder_stack.getId()
|
return self._extruder_stack
|
||||||
|
|
||||||
## Gets the signal that emits if the active extruder changed.
|
## Gets the signal that emits if the active extruder changed.
|
||||||
#
|
#
|
||||||
|
@ -137,16 +124,20 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||||
# kept up to date.
|
# kept up to date.
|
||||||
def _updateNextStack(self):
|
def _updateNextStack(self):
|
||||||
if self._extruder_stack:
|
if self._extruder_stack:
|
||||||
|
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = self._extruder_stack)
|
||||||
|
if extruder_stack:
|
||||||
if self._stack.getNextStack():
|
if self._stack.getNextStack():
|
||||||
old_extruder_stack_id = self._stack.getNextStack().getId()
|
old_extruder_stack_id = self._stack.getNextStack().getId()
|
||||||
else:
|
else:
|
||||||
old_extruder_stack_id = ""
|
old_extruder_stack_id = ""
|
||||||
|
|
||||||
self._stack.setNextStack(self._extruder_stack)
|
self._stack.setNextStack(extruder_stack[0])
|
||||||
# Trigger slice/need slicing if the extruder changed.
|
# Trigger slice/need slicing if the extruder changed.
|
||||||
if self._stack.getNextStack().getId() != old_extruder_stack_id:
|
if self._stack.getNextStack().getId() != old_extruder_stack_id:
|
||||||
Application.getInstance().getBackend().needsSlicing()
|
Application.getInstance().getBackend().needsSlicing()
|
||||||
Application.getInstance().getBackend().tickle()
|
Application.getInstance().getBackend().tickle()
|
||||||
|
else:
|
||||||
|
Logger.log("e", "Extruder stack %s below per-object settings does not exist.", self._extruder_stack)
|
||||||
else:
|
else:
|
||||||
self._stack.setNextStack(Application.getInstance().getGlobalContainerStack())
|
self._stack.setNextStack(Application.getInstance().getGlobalContainerStack())
|
||||||
|
|
||||||
|
@ -154,14 +145,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||||
#
|
#
|
||||||
# \param extruder_stack_id The new extruder stack to print with.
|
# \param extruder_stack_id The new extruder stack to print with.
|
||||||
def setActiveExtruder(self, extruder_stack_id):
|
def setActiveExtruder(self, extruder_stack_id):
|
||||||
if self._extruder_stack.getId() == extruder_stack_id:
|
self._extruder_stack = extruder_stack_id
|
||||||
return
|
|
||||||
|
|
||||||
global_stack = Application.getInstance().getMachineManager().activeMachine
|
|
||||||
for extruder in global_stack.extruders.values():
|
|
||||||
if extruder.getId() == extruder_stack_id:
|
|
||||||
self._extruder_stack = extruder
|
|
||||||
break
|
|
||||||
self._updateNextStack()
|
self._updateNextStack()
|
||||||
ExtruderManager.getInstance().resetSelectedObjectExtruders()
|
ExtruderManager.getInstance().resetSelectedObjectExtruders()
|
||||||
self.activeExtruderChanged.emit()
|
self.activeExtruderChanged.emit()
|
||||||
|
|
|
@ -39,12 +39,12 @@ class SimpleModeSettingsManager(QObject):
|
||||||
global_stack = self._machine_manager.activeMachine
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
|
||||||
# check user settings in the global stack
|
# check user settings in the global stack
|
||||||
user_setting_keys.update(set(global_stack.userChanges.getAllKeys()))
|
user_setting_keys.update(global_stack.userChanges.getAllKeys())
|
||||||
|
|
||||||
# check user settings in the extruder stacks
|
# check user settings in the extruder stacks
|
||||||
if global_stack.extruders:
|
if global_stack.extruders:
|
||||||
for extruder_stack in global_stack.extruders.values():
|
for extruder_stack in global_stack.extruders.values():
|
||||||
user_setting_keys.update(set(extruder_stack.userChanges.getAllKeys()))
|
user_setting_keys.update(extruder_stack.userChanges.getAllKeys())
|
||||||
|
|
||||||
# remove settings that are visible in recommended (we don't show the reset button for those)
|
# remove settings that are visible in recommended (we don't show the reset button for those)
|
||||||
for skip_key in self.__ignored_custom_setting_keys:
|
for skip_key in self.__ignored_custom_setting_keys:
|
||||||
|
@ -70,12 +70,12 @@ class SimpleModeSettingsManager(QObject):
|
||||||
global_stack = self._machine_manager.activeMachine
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
|
||||||
# check quality changes settings in the global stack
|
# check quality changes settings in the global stack
|
||||||
quality_changes_keys.update(set(global_stack.qualityChanges.getAllKeys()))
|
quality_changes_keys.update(global_stack.qualityChanges.getAllKeys())
|
||||||
|
|
||||||
# check quality changes settings in the extruder stacks
|
# check quality changes settings in the extruder stacks
|
||||||
if global_stack.extruders:
|
if global_stack.extruders:
|
||||||
for extruder_stack in global_stack.extruders.values():
|
for extruder_stack in global_stack.extruders.values():
|
||||||
quality_changes_keys.update(set(extruder_stack.qualityChanges.getAllKeys()))
|
quality_changes_keys.update(extruder_stack.qualityChanges.getAllKeys())
|
||||||
|
|
||||||
# check if the qualityChanges container is not empty (meaning it is a user created profile)
|
# check if the qualityChanges container is not empty (meaning it is a user created profile)
|
||||||
has_quality_changes = len(quality_changes_keys) > 0
|
has_quality_changes = len(quality_changes_keys) > 0
|
||||||
|
|
|
@ -7,12 +7,12 @@ from typing import List, Optional
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
|
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
|
||||||
|
|
||||||
|
from UM.Qt.QtApplication import QtApplication #For typing.
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
|
||||||
class SingleInstance:
|
class SingleInstance:
|
||||||
|
def __init__(self, application: QtApplication, files_to_open: Optional[List[str]]) -> None:
|
||||||
def __init__(self, application, files_to_open: Optional[List[str]]):
|
|
||||||
self._application = application
|
self._application = application
|
||||||
self._files_to_open = files_to_open
|
self._files_to_open = files_to_open
|
||||||
|
|
||||||
|
@ -61,17 +61,22 @@ class SingleInstance:
|
||||||
|
|
||||||
def startServer(self) -> None:
|
def startServer(self) -> None:
|
||||||
self._single_instance_server = QLocalServer()
|
self._single_instance_server = QLocalServer()
|
||||||
|
if self._single_instance_server:
|
||||||
self._single_instance_server.newConnection.connect(self._onClientConnected)
|
self._single_instance_server.newConnection.connect(self._onClientConnected)
|
||||||
self._single_instance_server.listen("ultimaker-cura")
|
self._single_instance_server.listen("ultimaker-cura")
|
||||||
|
else:
|
||||||
|
Logger.log("e", "Single instance server was not created.")
|
||||||
|
|
||||||
def _onClientConnected(self):
|
def _onClientConnected(self) -> None:
|
||||||
Logger.log("i", "New connection recevied on our single-instance server")
|
Logger.log("i", "New connection recevied on our single-instance server")
|
||||||
|
connection = None #type: Optional[QLocalSocket]
|
||||||
|
if self._single_instance_server:
|
||||||
connection = self._single_instance_server.nextPendingConnection()
|
connection = self._single_instance_server.nextPendingConnection()
|
||||||
|
|
||||||
if connection is not None:
|
if connection is not None:
|
||||||
connection.readyRead.connect(lambda c = connection: self.__readCommands(c))
|
connection.readyRead.connect(lambda c = connection: self.__readCommands(c))
|
||||||
|
|
||||||
def __readCommands(self, connection):
|
def __readCommands(self, connection: QLocalSocket) -> None:
|
||||||
line = connection.readLine()
|
line = connection.readLine()
|
||||||
while len(line) != 0: # There is also a .canReadLine()
|
while len(line) != 0: # There is also a .canReadLine()
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -134,11 +134,4 @@ import Arcus #@UnusedImport
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
app = CuraApplication()
|
app = CuraApplication()
|
||||||
app.addCommandLineOptions()
|
|
||||||
app.parseCliOptions()
|
|
||||||
app.initialize()
|
|
||||||
|
|
||||||
app.startSplashWindowPhase()
|
|
||||||
app.startPostSplashWindowPhase()
|
|
||||||
|
|
||||||
app.run()
|
app.run()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
import os.path
|
import os.path
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
@ -37,8 +38,8 @@ except ImportError:
|
||||||
|
|
||||||
## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes!
|
## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes!
|
||||||
class ThreeMFReader(MeshReader):
|
class ThreeMFReader(MeshReader):
|
||||||
def __init__(self, application):
|
def __init__(self) -> None:
|
||||||
super().__init__(application)
|
super().__init__()
|
||||||
|
|
||||||
MimeTypeDatabase.addMimeType(
|
MimeTypeDatabase.addMimeType(
|
||||||
MimeType(
|
MimeType(
|
||||||
|
@ -168,6 +169,8 @@ class ThreeMFReader(MeshReader):
|
||||||
archive = zipfile.ZipFile(file_name, "r")
|
archive = zipfile.ZipFile(file_name, "r")
|
||||||
self._base_name = os.path.basename(file_name)
|
self._base_name = os.path.basename(file_name)
|
||||||
parser = Savitar.ThreeMFParser()
|
parser = Savitar.ThreeMFParser()
|
||||||
|
with open("/tmp/test.xml", "wb") as f:
|
||||||
|
f.write(archive.open("3D/3dmodel.model").read())
|
||||||
scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read())
|
scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read())
|
||||||
self._unit = scene_3mf.getUnit()
|
self._unit = scene_3mf.getUnit()
|
||||||
for node in scene_3mf.getSceneNodes():
|
for node in scene_3mf.getSceneNodes():
|
||||||
|
@ -236,23 +239,20 @@ class ThreeMFReader(MeshReader):
|
||||||
# * inch
|
# * inch
|
||||||
# * foot
|
# * foot
|
||||||
# * meter
|
# * meter
|
||||||
def _getScaleFromUnit(self, unit):
|
def _getScaleFromUnit(self, unit: Optional[str]) -> Vector:
|
||||||
|
conversion_to_mm = {
|
||||||
|
"micron": 0.001,
|
||||||
|
"millimeter": 1,
|
||||||
|
"centimeter": 10,
|
||||||
|
"meter": 1000,
|
||||||
|
"inch": 25.4,
|
||||||
|
"foot": 304.8
|
||||||
|
}
|
||||||
if unit is None:
|
if unit is None:
|
||||||
unit = "millimeter"
|
unit = "millimeter"
|
||||||
if unit == "micron":
|
elif unit not in conversion_to_mm:
|
||||||
scale = 0.001
|
Logger.log("w", "Unrecognised unit {unit} used. Assuming mm instead.".format(unit = unit))
|
||||||
elif unit == "millimeter":
|
unit = "millimeter"
|
||||||
scale = 1
|
|
||||||
elif unit == "centimeter":
|
|
||||||
scale = 10
|
|
||||||
elif unit == "inch":
|
|
||||||
scale = 25.4
|
|
||||||
elif unit == "foot":
|
|
||||||
scale = 304.8
|
|
||||||
elif unit == "meter":
|
|
||||||
scale = 1000
|
|
||||||
else:
|
|
||||||
Logger.log("w", "Unrecognised unit %s used. Assuming mm instead", unit)
|
|
||||||
scale = 1
|
|
||||||
|
|
||||||
|
scale = conversion_to_mm[unit]
|
||||||
return Vector(scale, scale, scale)
|
return Vector(scale, scale, scale)
|
|
@ -4,7 +4,7 @@
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
import zipfile
|
import zipfile
|
||||||
import os
|
import os
|
||||||
from typing import List, Tuple
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
@ -38,7 +38,7 @@ i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
class ContainerInfo:
|
class ContainerInfo:
|
||||||
def __init__(self, file_name: str, serialized: str, parser: ConfigParser):
|
def __init__(self, file_name: str, serialized: str, parser: ConfigParser) -> None:
|
||||||
self.file_name = file_name
|
self.file_name = file_name
|
||||||
self.serialized = serialized
|
self.serialized = serialized
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
|
@ -47,14 +47,14 @@ class ContainerInfo:
|
||||||
|
|
||||||
|
|
||||||
class QualityChangesInfo:
|
class QualityChangesInfo:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.name = None
|
self.name = None
|
||||||
self.global_info = None
|
self.global_info = None
|
||||||
self.extruder_info_dict = {}
|
self.extruder_info_dict = {} # type: Dict[str, ContainerInfo]
|
||||||
|
|
||||||
|
|
||||||
class MachineInfo:
|
class MachineInfo:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.container_id = None
|
self.container_id = None
|
||||||
self.name = None
|
self.name = None
|
||||||
self.definition_id = None
|
self.definition_id = None
|
||||||
|
@ -66,11 +66,11 @@ class MachineInfo:
|
||||||
self.definition_changes_info = None
|
self.definition_changes_info = None
|
||||||
self.user_changes_info = None
|
self.user_changes_info = None
|
||||||
|
|
||||||
self.extruder_info_dict = {}
|
self.extruder_info_dict = {} # type: Dict[str, ExtruderInfo]
|
||||||
|
|
||||||
|
|
||||||
class ExtruderInfo:
|
class ExtruderInfo:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.position = None
|
self.position = None
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
self.variant_info = None
|
self.variant_info = None
|
||||||
|
@ -82,7 +82,7 @@ class ExtruderInfo:
|
||||||
|
|
||||||
## Base implementation for reading 3MF workspace files.
|
## Base implementation for reading 3MF workspace files.
|
||||||
class ThreeMFWorkspaceReader(WorkspaceReader):
|
class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
MimeTypeDatabase.addMimeType(
|
MimeTypeDatabase.addMimeType(
|
||||||
|
@ -112,28 +112,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
# - variant
|
# - variant
|
||||||
self._ignored_instance_container_types = {"quality", "variant"}
|
self._ignored_instance_container_types = {"quality", "variant"}
|
||||||
|
|
||||||
self._resolve_strategies = {}
|
self._resolve_strategies = {} # type: Dict[str, str]
|
||||||
|
|
||||||
self._id_mapping = {}
|
self._id_mapping = {} # type: Dict[str, str]
|
||||||
|
|
||||||
# In Cura 2.5 and 2.6, the empty profiles used to have those long names
|
# In Cura 2.5 and 2.6, the empty profiles used to have those long names
|
||||||
self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]}
|
self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]}
|
||||||
|
|
||||||
self._is_same_machine_type = False
|
self._is_same_machine_type = False
|
||||||
self._old_new_materials = {}
|
self._old_new_materials = {} # type: Dict[str, str]
|
||||||
self._materials_to_select = {}
|
|
||||||
self._machine_info = None
|
self._machine_info = None
|
||||||
|
|
||||||
def _clearState(self):
|
def _clearState(self):
|
||||||
self._is_same_machine_type = False
|
self._is_same_machine_type = False
|
||||||
self._id_mapping = {}
|
self._id_mapping = {}
|
||||||
self._old_new_materials = {}
|
self._old_new_materials = {}
|
||||||
self._materials_to_select = {}
|
|
||||||
self._machine_info = None
|
self._machine_info = None
|
||||||
|
|
||||||
## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
|
## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
|
||||||
# This has nothing to do with speed, but with getting consistent new naming for instances & objects.
|
# This has nothing to do with speed, but with getting consistent new naming for instances & objects.
|
||||||
def getNewId(self, old_id):
|
def getNewId(self, old_id: str):
|
||||||
if old_id not in self._id_mapping:
|
if old_id not in self._id_mapping:
|
||||||
self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
|
self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
|
||||||
return self._id_mapping[old_id]
|
return self._id_mapping[old_id]
|
||||||
|
@ -671,7 +669,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
else:
|
else:
|
||||||
material_container = materials[0]
|
material_container = materials[0]
|
||||||
old_material_root_id = material_container.getMetaDataEntry("base_file")
|
old_material_root_id = material_container.getMetaDataEntry("base_file")
|
||||||
if not self._container_registry.isReadOnly(old_material_root_id): # Only create new materials if they are not read only.
|
if old_material_root_id is not None and not self._container_registry.isReadOnly(old_material_root_id): # Only create new materials if they are not read only.
|
||||||
to_deserialize_material = True
|
to_deserialize_material = True
|
||||||
|
|
||||||
if self._resolve_strategies["material"] == "override":
|
if self._resolve_strategies["material"] == "override":
|
||||||
|
|
|
@ -187,7 +187,10 @@ class WorkspaceDialog(QObject):
|
||||||
|
|
||||||
@pyqtProperty(int, constant = True)
|
@pyqtProperty(int, constant = True)
|
||||||
def totalNumberOfSettings(self):
|
def totalNumberOfSettings(self):
|
||||||
return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())
|
general_definition_containers = ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")
|
||||||
|
if not general_definition_containers:
|
||||||
|
return 0
|
||||||
|
return len(general_definition_containers[0].getAllKeys())
|
||||||
|
|
||||||
@pyqtProperty(int, notify = numVisibleSettingsChanged)
|
@pyqtProperty(int, notify = numVisibleSettingsChanged)
|
||||||
def numVisibleSettings(self):
|
def numVisibleSettings(self):
|
||||||
|
|
|
@ -18,7 +18,7 @@ catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
def getMetaData() -> Dict:
|
def getMetaData() -> Dict:
|
||||||
# Workarround for osx not supporting double file extensions correctly.
|
# Workaround for osx not supporting double file extensions correctly.
|
||||||
if Platform.isOSX():
|
if Platform.isOSX():
|
||||||
workspace_extension = "3mf"
|
workspace_extension = "3mf"
|
||||||
else:
|
else:
|
||||||
|
@ -44,7 +44,7 @@ def getMetaData() -> Dict:
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
if "3MFReader.ThreeMFReader" in sys.modules:
|
if "3MFReader.ThreeMFReader" in sys.modules:
|
||||||
return {"mesh_reader": ThreeMFReader.ThreeMFReader(app),
|
return {"mesh_reader": ThreeMFReader.ThreeMFReader(),
|
||||||
"workspace_reader": ThreeMFWorkspaceReader.ThreeMFWorkspaceReader()}
|
"workspace_reader": ThreeMFWorkspaceReader.ThreeMFWorkspaceReader()}
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
|
@ -91,7 +91,7 @@ class ThreeMFWriter(MeshWriter):
|
||||||
# Handle per object settings (if any)
|
# Handle per object settings (if any)
|
||||||
stack = um_node.callDecoration("getStack")
|
stack = um_node.callDecoration("getStack")
|
||||||
if stack is not None:
|
if stack is not None:
|
||||||
changed_setting_keys = set(stack.getTop().getAllKeys())
|
changed_setting_keys = stack.getTop().getAllKeys()
|
||||||
|
|
||||||
# Ensure that we save the extruder used for this object in a multi-extrusion setup
|
# Ensure that we save the extruder used for this object in a multi-extrusion setup
|
||||||
if stack.getProperty("machine_extruder_count", "value") > 1:
|
if stack.getProperty("machine_extruder_count", "value") > 1:
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
import os
|
||||||
|
from PyQt5.QtCore import QObject, QTimer, pyqtSlot
|
||||||
|
import sys
|
||||||
|
from time import time
|
||||||
|
from typing import Any, cast, Dict, List, Optional, Set, TYPE_CHECKING
|
||||||
|
|
||||||
from UM.Backend.Backend import Backend, BackendState
|
from UM.Backend.Backend import Backend, BackendState
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Signal import Signal
|
from UM.Signal import Signal
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -10,29 +16,30 @@ from UM.Message import Message
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.Platform import Platform
|
from UM.Platform import Platform
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
|
||||||
from UM.Qt.Duration import DurationFormat
|
from UM.Qt.Duration import DurationFormat
|
||||||
from PyQt5.QtCore import QObject, pyqtSlot
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||||
|
from UM.Settings.SettingInstance import SettingInstance #For typing.
|
||||||
|
from UM.Tool import Tool #For typing.
|
||||||
|
|
||||||
from collections import defaultdict
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from . import ProcessSlicedLayersJob
|
from .ProcessSlicedLayersJob import ProcessSlicedLayersJob
|
||||||
from . import StartSliceJob
|
from .StartSliceJob import StartSliceJob, StartJobResult
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from time import time
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QTimer
|
|
||||||
|
|
||||||
import Arcus
|
import Arcus
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
||||||
|
from cura.Machines.MachineErrorChecker import MachineErrorChecker
|
||||||
|
from UM.Scene.Scene import Scene
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
class CuraEngineBackend(QObject, Backend):
|
class CuraEngineBackend(QObject, Backend):
|
||||||
|
|
||||||
backendError = Signal()
|
backendError = Signal()
|
||||||
|
|
||||||
## Starts the back-end plug-in.
|
## Starts the back-end plug-in.
|
||||||
|
@ -40,16 +47,16 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# This registers all the signal listeners and prepares for communication
|
# This registers all the signal listeners and prepares for communication
|
||||||
# with the back-end in general.
|
# with the back-end in general.
|
||||||
# CuraEngineBackend is exposed to qml as well.
|
# CuraEngineBackend is exposed to qml as well.
|
||||||
def __init__(self, parent = None):
|
def __init__(self) -> None:
|
||||||
super().__init__(parent = parent)
|
super().__init__()
|
||||||
# Find out where the engine is located, and how it is called.
|
# Find out where the engine is located, and how it is called.
|
||||||
# This depends on how Cura is packaged and which OS we are running on.
|
# This depends on how Cura is packaged and which OS we are running on.
|
||||||
executable_name = "CuraEngine"
|
executable_name = "CuraEngine"
|
||||||
if Platform.isWindows():
|
if Platform.isWindows():
|
||||||
executable_name += ".exe"
|
executable_name += ".exe"
|
||||||
default_engine_location = executable_name
|
default_engine_location = executable_name
|
||||||
if os.path.exists(os.path.join(Application.getInstallPrefix(), "bin", executable_name)):
|
if os.path.exists(os.path.join(CuraApplication.getInstallPrefix(), "bin", executable_name)):
|
||||||
default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", executable_name)
|
default_engine_location = os.path.join(CuraApplication.getInstallPrefix(), "bin", executable_name)
|
||||||
if hasattr(sys, "frozen"):
|
if hasattr(sys, "frozen"):
|
||||||
default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), executable_name)
|
default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), executable_name)
|
||||||
if Platform.isLinux() and not default_engine_location:
|
if Platform.isLinux() and not default_engine_location:
|
||||||
|
@ -61,9 +68,9 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
default_engine_location = execpath
|
default_engine_location = execpath
|
||||||
break
|
break
|
||||||
|
|
||||||
self._application = Application.getInstance()
|
self._application = CuraApplication.getInstance() #type: CuraApplication
|
||||||
self._multi_build_plate_model = None
|
self._multi_build_plate_model = None #type: MultiBuildPlateModel
|
||||||
self._machine_error_checker = None
|
self._machine_error_checker = None #type: MachineErrorChecker
|
||||||
|
|
||||||
if not default_engine_location:
|
if not default_engine_location:
|
||||||
raise EnvironmentError("Could not find CuraEngine")
|
raise EnvironmentError("Could not find CuraEngine")
|
||||||
|
@ -71,16 +78,16 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
Logger.log("i", "Found CuraEngine at: %s", default_engine_location)
|
Logger.log("i", "Found CuraEngine at: %s", default_engine_location)
|
||||||
|
|
||||||
default_engine_location = os.path.abspath(default_engine_location)
|
default_engine_location = os.path.abspath(default_engine_location)
|
||||||
Application.getInstance().getPreferences().addPreference("backend/location", default_engine_location)
|
self._application.getPreferences().addPreference("backend/location", default_engine_location)
|
||||||
|
|
||||||
# Workaround to disable layer view processing if layer view is not active.
|
# Workaround to disable layer view processing if layer view is not active.
|
||||||
self._layer_view_active = False
|
self._layer_view_active = False #type: bool
|
||||||
self._onActiveViewChanged()
|
self._onActiveViewChanged()
|
||||||
|
|
||||||
self._stored_layer_data = []
|
self._stored_layer_data = [] #type: List[Arcus.PythonMessage]
|
||||||
self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
|
self._stored_optimized_layer_data = {} #type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
|
||||||
|
|
||||||
self._scene = self._application.getController().getScene()
|
self._scene = self._application.getController().getScene() #type: Scene
|
||||||
self._scene.sceneChanged.connect(self._onSceneChanged)
|
self._scene.sceneChanged.connect(self._onSceneChanged)
|
||||||
|
|
||||||
# Triggers for auto-slicing. Auto-slicing is triggered as follows:
|
# Triggers for auto-slicing. Auto-slicing is triggered as follows:
|
||||||
|
@ -91,7 +98,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
|
# If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
|
||||||
# to start the auto-slicing timer again.
|
# to start the auto-slicing timer again.
|
||||||
#
|
#
|
||||||
self._global_container_stack = None
|
self._global_container_stack = None #type: Optional[ContainerStack]
|
||||||
|
|
||||||
# Listeners for receiving messages from the back-end.
|
# Listeners for receiving messages from the back-end.
|
||||||
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
|
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
|
||||||
|
@ -102,39 +109,39 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._message_handlers["cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates
|
self._message_handlers["cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates
|
||||||
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
|
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
|
||||||
|
|
||||||
self._start_slice_job = None
|
self._start_slice_job = None #type: Optional[StartSliceJob]
|
||||||
self._start_slice_job_build_plate = None
|
self._start_slice_job_build_plate = None #type: Optional[int]
|
||||||
self._slicing = False # Are we currently slicing?
|
self._slicing = False #type: bool # Are we currently slicing?
|
||||||
self._restart = False # Back-end is currently restarting?
|
self._restart = False #type: bool # Back-end is currently restarting?
|
||||||
self._tool_active = False # If a tool is active, some tasks do not have to do anything
|
self._tool_active = False #type: bool # If a tool is active, some tasks do not have to do anything
|
||||||
self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
|
self._always_restart = True #type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
|
||||||
self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers.
|
self._process_layers_job = None #type: Optional[ProcessSlicedLayersJob] # The currently active job to process layers, or None if it is not processing layers.
|
||||||
self._build_plates_to_be_sliced = [] # what needs slicing?
|
self._build_plates_to_be_sliced = [] #type: List[int] # what needs slicing?
|
||||||
self._engine_is_fresh = True # Is the newly started engine used before or not?
|
self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not?
|
||||||
|
|
||||||
self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
|
self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer
|
||||||
self._error_message = None # Pop-up message that shows errors.
|
self._error_message = None #type: Message # Pop-up message that shows errors.
|
||||||
self._last_num_objects = defaultdict(int) # Count number of objects to see if there is something changed
|
self._last_num_objects = defaultdict(int) #type: Dict[int, int] # Count number of objects to see if there is something changed
|
||||||
self._postponed_scene_change_sources = [] # scene change is postponed (by a tool)
|
self._postponed_scene_change_sources = [] #type: List[SceneNode] # scene change is postponed (by a tool)
|
||||||
|
|
||||||
self._slice_start_time = None
|
self._slice_start_time = None #type: Optional[float]
|
||||||
self._is_disabled = False
|
self._is_disabled = False #type: bool
|
||||||
|
|
||||||
Application.getInstance().getPreferences().addPreference("general/auto_slice", False)
|
self._application.getPreferences().addPreference("general/auto_slice", False)
|
||||||
|
|
||||||
self._use_timer = False
|
self._use_timer = False #type: bool
|
||||||
# When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
|
# When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
|
||||||
# This timer will group them up, and only slice for the last setting changed signal.
|
# This timer will group them up, and only slice for the last setting changed signal.
|
||||||
# TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
|
# TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
|
||||||
self._change_timer = QTimer()
|
self._change_timer = QTimer() #type: QTimer
|
||||||
self._change_timer.setSingleShot(True)
|
self._change_timer.setSingleShot(True)
|
||||||
self._change_timer.setInterval(500)
|
self._change_timer.setInterval(500)
|
||||||
self.determineAutoSlicing()
|
self.determineAutoSlicing()
|
||||||
Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
|
self._application.getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
|
||||||
|
|
||||||
self._application.initializationFinished.connect(self.initialize)
|
self._application.initializationFinished.connect(self.initialize)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self) -> None:
|
||||||
self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
|
self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
|
||||||
|
|
||||||
self._application.getController().activeViewChanged.connect(self._onActiveViewChanged)
|
self._application.getController().activeViewChanged.connect(self._onActiveViewChanged)
|
||||||
|
@ -160,16 +167,16 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
#
|
#
|
||||||
# This function should terminate the engine process.
|
# This function should terminate the engine process.
|
||||||
# Called when closing the application.
|
# Called when closing the application.
|
||||||
def close(self):
|
def close(self) -> None:
|
||||||
# Terminate CuraEngine if it is still running at this point
|
# Terminate CuraEngine if it is still running at this point
|
||||||
self._terminate()
|
self._terminate()
|
||||||
|
|
||||||
## Get the command that is used to call the engine.
|
## Get the command that is used to call the engine.
|
||||||
# This is useful for debugging and used to actually start the engine.
|
# This is useful for debugging and used to actually start the engine.
|
||||||
# \return list of commands and args / parameters.
|
# \return list of commands and args / parameters.
|
||||||
def getEngineCommand(self):
|
def getEngineCommand(self) -> List[str]:
|
||||||
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
|
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
|
||||||
return [Application.getInstance().getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
|
return [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
|
||||||
|
|
||||||
## Emitted when we get a message containing print duration and material amount.
|
## Emitted when we get a message containing print duration and material amount.
|
||||||
# This also implies the slicing has finished.
|
# This also implies the slicing has finished.
|
||||||
|
@ -184,13 +191,13 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
slicingCancelled = Signal()
|
slicingCancelled = Signal()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def stopSlicing(self):
|
def stopSlicing(self) -> None:
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.backendStateChange.emit(BackendState.NotStarted)
|
||||||
if self._slicing: # We were already slicing. Stop the old job.
|
if self._slicing: # We were already slicing. Stop the old job.
|
||||||
self._terminate()
|
self._terminate()
|
||||||
self._createSocket()
|
self._createSocket()
|
||||||
|
|
||||||
if self._process_layers_job: # We were processing layers. Stop that, the layers are going to change soon.
|
if self._process_layers_job is not None: # We were processing layers. Stop that, the layers are going to change soon.
|
||||||
Logger.log("d", "Aborting process layers job...")
|
Logger.log("d", "Aborting process layers job...")
|
||||||
self._process_layers_job.abort()
|
self._process_layers_job.abort()
|
||||||
self._process_layers_job = None
|
self._process_layers_job = None
|
||||||
|
@ -200,12 +207,12 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
|
|
||||||
## Manually triggers a reslice
|
## Manually triggers a reslice
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def forceSlice(self):
|
def forceSlice(self) -> None:
|
||||||
self.markSliceAll()
|
self.markSliceAll()
|
||||||
self.slice()
|
self.slice()
|
||||||
|
|
||||||
## Perform a slice of the scene.
|
## Perform a slice of the scene.
|
||||||
def slice(self):
|
def slice(self) -> None:
|
||||||
Logger.log("d", "Starting to slice...")
|
Logger.log("d", "Starting to slice...")
|
||||||
self._slice_start_time = time()
|
self._slice_start_time = time()
|
||||||
if not self._build_plates_to_be_sliced:
|
if not self._build_plates_to_be_sliced:
|
||||||
|
@ -218,10 +225,10 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
return
|
return
|
||||||
|
|
||||||
if not hasattr(self._scene, "gcode_dict"):
|
if not hasattr(self._scene, "gcode_dict"):
|
||||||
self._scene.gcode_dict = {}
|
self._scene.gcode_dict = {} #type: ignore #Because we are creating the missing attribute here.
|
||||||
|
|
||||||
# see if we really have to slice
|
# see if we really have to slice
|
||||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate
|
||||||
build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
|
build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
|
||||||
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
|
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
|
||||||
num_objects = self._numObjectsPerBuildPlate()
|
num_objects = self._numObjectsPerBuildPlate()
|
||||||
|
@ -230,14 +237,14 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
|
self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
|
||||||
|
|
||||||
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
|
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
|
||||||
self._scene.gcode_dict[build_plate_to_be_sliced] = []
|
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #Because we created this attribute above.
|
||||||
Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced)
|
Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced)
|
||||||
if self._build_plates_to_be_sliced:
|
if self._build_plates_to_be_sliced:
|
||||||
self.slice()
|
self.slice()
|
||||||
return
|
return
|
||||||
|
|
||||||
if Application.getInstance().getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
|
if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
|
||||||
Application.getInstance().getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
|
self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
|
||||||
|
|
||||||
if self._process is None:
|
if self._process is None:
|
||||||
self._createSocket()
|
self._createSocket()
|
||||||
|
@ -247,14 +254,14 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self.processingProgress.emit(0.0)
|
self.processingProgress.emit(0.0)
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.backendStateChange.emit(BackendState.NotStarted)
|
||||||
|
|
||||||
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #[] indexed by build plate number
|
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #[] indexed by build plate number
|
||||||
self._slicing = True
|
self._slicing = True
|
||||||
self.slicingStarted.emit()
|
self.slicingStarted.emit()
|
||||||
|
|
||||||
self.determineAutoSlicing() # Switch timer on or off if appropriate
|
self.determineAutoSlicing() # Switch timer on or off if appropriate
|
||||||
|
|
||||||
slice_message = self._socket.createMessage("cura.proto.Slice")
|
slice_message = self._socket.createMessage("cura.proto.Slice")
|
||||||
self._start_slice_job = StartSliceJob.StartSliceJob(slice_message)
|
self._start_slice_job = StartSliceJob(slice_message)
|
||||||
self._start_slice_job_build_plate = build_plate_to_be_sliced
|
self._start_slice_job_build_plate = build_plate_to_be_sliced
|
||||||
self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate)
|
self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate)
|
||||||
self._start_slice_job.start()
|
self._start_slice_job.start()
|
||||||
|
@ -262,7 +269,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
|
|
||||||
## Terminate the engine process.
|
## Terminate the engine process.
|
||||||
# Start the engine process by calling _createSocket()
|
# Start the engine process by calling _createSocket()
|
||||||
def _terminate(self):
|
def _terminate(self) -> None:
|
||||||
self._slicing = False
|
self._slicing = False
|
||||||
self._stored_layer_data = []
|
self._stored_layer_data = []
|
||||||
if self._start_slice_job_build_plate in self._stored_optimized_layer_data:
|
if self._start_slice_job_build_plate in self._stored_optimized_layer_data:
|
||||||
|
@ -274,7 +281,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self.processingProgress.emit(0)
|
self.processingProgress.emit(0)
|
||||||
Logger.log("d", "Attempting to kill the engine process")
|
Logger.log("d", "Attempting to kill the engine process")
|
||||||
|
|
||||||
if Application.getInstance().getUseExternalBackend():
|
if self._application.getUseExternalBackend():
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._process is not None:
|
if self._process is not None:
|
||||||
|
@ -295,7 +302,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# bootstrapping of a slice job.
|
# bootstrapping of a slice job.
|
||||||
#
|
#
|
||||||
# \param job The start slice job that was just finished.
|
# \param job The start slice job that was just finished.
|
||||||
def _onStartSliceCompleted(self, job):
|
def _onStartSliceCompleted(self, job: StartSliceJob) -> None:
|
||||||
if self._error_message:
|
if self._error_message:
|
||||||
self._error_message.hide()
|
self._error_message.hide()
|
||||||
|
|
||||||
|
@ -303,13 +310,13 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
if self._start_slice_job is job:
|
if self._start_slice_job is job:
|
||||||
self._start_slice_job = None
|
self._start_slice_job = None
|
||||||
|
|
||||||
if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error:
|
if job.isCancelled() or job.getError() or job.getResult() == StartJobResult.Error:
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
self.backendStateChange.emit(BackendState.Error)
|
||||||
self.backendError.emit(job)
|
self.backendError.emit(job)
|
||||||
return
|
return
|
||||||
|
|
||||||
if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible:
|
if job.getResult() == StartJobResult.MaterialIncompatible:
|
||||||
if Application.getInstance().platformActivity:
|
if self._application.platformActivity:
|
||||||
self._error_message = Message(catalog.i18nc("@info:status",
|
self._error_message = Message(catalog.i18nc("@info:status",
|
||||||
"Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice"))
|
"Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
|
@ -319,10 +326,10 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.backendStateChange.emit(BackendState.NotStarted)
|
||||||
return
|
return
|
||||||
|
|
||||||
if job.getResult() == StartSliceJob.StartJobResult.SettingError:
|
if job.getResult() == StartJobResult.SettingError:
|
||||||
if Application.getInstance().platformActivity:
|
if self._application.platformActivity:
|
||||||
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||||
error_keys = []
|
error_keys = [] #type: List[str]
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
error_keys.extend(extruder.getErrorKeys())
|
error_keys.extend(extruder.getErrorKeys())
|
||||||
if not extruders:
|
if not extruders:
|
||||||
|
@ -330,7 +337,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
error_labels = set()
|
error_labels = set()
|
||||||
for key in error_keys:
|
for key in error_keys:
|
||||||
for stack in [self._global_container_stack] + extruders: #Search all container stacks for the definition of this setting. Some are only in an extruder stack.
|
for stack in [self._global_container_stack] + extruders: #Search all container stacks for the definition of this setting. Some are only in an extruder stack.
|
||||||
definitions = stack.getBottom().findDefinitions(key = key)
|
definitions = cast(DefinitionContainerInterface, stack.getBottom()).findDefinitions(key = key)
|
||||||
if definitions:
|
if definitions:
|
||||||
break #Found it! No need to continue search.
|
break #Found it! No need to continue search.
|
||||||
else: #No stack has a definition for this setting.
|
else: #No stack has a definition for this setting.
|
||||||
|
@ -338,8 +345,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
continue
|
continue
|
||||||
error_labels.add(definitions[0].label)
|
error_labels.add(definitions[0].label)
|
||||||
|
|
||||||
error_labels = ", ".join(error_labels)
|
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(", ".join(error_labels)),
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(error_labels),
|
|
||||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
self.backendStateChange.emit(BackendState.Error)
|
||||||
|
@ -348,29 +354,27 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.backendStateChange.emit(BackendState.NotStarted)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif job.getResult() == StartSliceJob.StartJobResult.ObjectSettingError:
|
elif job.getResult() == StartJobResult.ObjectSettingError:
|
||||||
errors = {}
|
errors = {}
|
||||||
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self._application.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
stack = node.callDecoration("getStack")
|
stack = node.callDecoration("getStack")
|
||||||
if not stack:
|
if not stack:
|
||||||
continue
|
continue
|
||||||
for key in stack.getErrorKeys():
|
for key in stack.getErrorKeys():
|
||||||
definition = self._global_container_stack.getBottom().findDefinitions(key = key)
|
definition = cast(DefinitionContainerInterface, self._global_container_stack.getBottom()).findDefinitions(key = key)
|
||||||
if not definition:
|
if not definition:
|
||||||
Logger.log("e", "When checking settings for errors, unable to find definition for key {key} in per-object stack.".format(key = key))
|
Logger.log("e", "When checking settings for errors, unable to find definition for key {key} in per-object stack.".format(key = key))
|
||||||
continue
|
continue
|
||||||
definition = definition[0]
|
errors[key] = definition[0].label
|
||||||
errors[key] = definition.label
|
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = ", ".join(errors.values())),
|
||||||
error_labels = ", ".join(errors.values())
|
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = error_labels),
|
|
||||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
self.backendStateChange.emit(BackendState.Error)
|
self.backendStateChange.emit(BackendState.Error)
|
||||||
self.backendError.emit(job)
|
self.backendError.emit(job)
|
||||||
return
|
return
|
||||||
|
|
||||||
if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError:
|
if job.getResult() == StartJobResult.BuildPlateError:
|
||||||
if Application.getInstance().platformActivity:
|
if self._application.platformActivity:
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
|
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
|
||||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
|
@ -379,8 +383,16 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
else:
|
else:
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.backendStateChange.emit(BackendState.NotStarted)
|
||||||
|
|
||||||
if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice:
|
if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
|
||||||
if Application.getInstance().platformActivity:
|
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()),
|
||||||
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
|
self._error_message.show()
|
||||||
|
self.backendStateChange.emit(BackendState.Error)
|
||||||
|
self.backendError.emit(job)
|
||||||
|
return
|
||||||
|
|
||||||
|
if job.getResult() == StartJobResult.NothingToSlice:
|
||||||
|
if self._application.platformActivity:
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
|
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
|
||||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
|
@ -403,20 +415,20 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# It disables when
|
# It disables when
|
||||||
# - preference auto slice is off
|
# - preference auto slice is off
|
||||||
# - decorator isBlockSlicing is found (used in g-code reader)
|
# - decorator isBlockSlicing is found (used in g-code reader)
|
||||||
def determineAutoSlicing(self):
|
def determineAutoSlicing(self) -> bool:
|
||||||
enable_timer = True
|
enable_timer = True
|
||||||
self._is_disabled = False
|
self._is_disabled = False
|
||||||
|
|
||||||
if not Application.getInstance().getPreferences().getValue("general/auto_slice"):
|
if not self._application.getPreferences().getValue("general/auto_slice"):
|
||||||
enable_timer = False
|
enable_timer = False
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if node.callDecoration("isBlockSlicing"):
|
if node.callDecoration("isBlockSlicing"):
|
||||||
enable_timer = False
|
enable_timer = False
|
||||||
self.backendStateChange.emit(BackendState.Disabled)
|
self.backendStateChange.emit(BackendState.Disabled)
|
||||||
self._is_disabled = True
|
self._is_disabled = True
|
||||||
gcode_list = node.callDecoration("getGCodeList")
|
gcode_list = node.callDecoration("getGCodeList")
|
||||||
if gcode_list is not None:
|
if gcode_list is not None:
|
||||||
self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list
|
self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list #type: ignore #Because we generate this attribute dynamically.
|
||||||
|
|
||||||
if self._use_timer == enable_timer:
|
if self._use_timer == enable_timer:
|
||||||
return self._use_timer
|
return self._use_timer
|
||||||
|
@ -429,9 +441,9 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
## Return a dict with number of objects per build plate
|
## Return a dict with number of objects per build plate
|
||||||
def _numObjectsPerBuildPlate(self):
|
def _numObjectsPerBuildPlate(self) -> Dict[int, int]:
|
||||||
num_objects = defaultdict(int)
|
num_objects = defaultdict(int) #type: Dict[int, int]
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
# Only count sliceable objects
|
# Only count sliceable objects
|
||||||
if node.callDecoration("isSliceable"):
|
if node.callDecoration("isSliceable"):
|
||||||
build_plate_number = node.callDecoration("getBuildPlateNumber")
|
build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||||
|
@ -443,7 +455,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# This should start a slice if the scene is now ready to slice.
|
# This should start a slice if the scene is now ready to slice.
|
||||||
#
|
#
|
||||||
# \param source The scene node that was changed.
|
# \param source The scene node that was changed.
|
||||||
def _onSceneChanged(self, source):
|
def _onSceneChanged(self, source: SceneNode) -> None:
|
||||||
if not isinstance(source, SceneNode):
|
if not isinstance(source, SceneNode):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -498,8 +510,8 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
## Called when an error occurs in the socket connection towards the engine.
|
## Called when an error occurs in the socket connection towards the engine.
|
||||||
#
|
#
|
||||||
# \param error The exception that occurred.
|
# \param error The exception that occurred.
|
||||||
def _onSocketError(self, error):
|
def _onSocketError(self, error: Arcus.Error) -> None:
|
||||||
if Application.getInstance().isShuttingDown():
|
if self._application.isShuttingDown():
|
||||||
return
|
return
|
||||||
|
|
||||||
super()._onSocketError(error)
|
super()._onSocketError(error)
|
||||||
|
@ -513,19 +525,19 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
Logger.log("w", "A socket error caused the connection to be reset")
|
Logger.log("w", "A socket error caused the connection to be reset")
|
||||||
|
|
||||||
## Remove old layer data (if any)
|
## Remove old layer data (if any)
|
||||||
def _clearLayerData(self, build_plate_numbers = set()):
|
def _clearLayerData(self, build_plate_numbers: Set = None) -> None:
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if node.callDecoration("getLayerData"):
|
if node.callDecoration("getLayerData"):
|
||||||
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
|
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
|
||||||
node.getParent().removeChild(node)
|
node.getParent().removeChild(node)
|
||||||
|
|
||||||
def markSliceAll(self):
|
def markSliceAll(self) -> None:
|
||||||
for build_plate_number in range(Application.getInstance().getMultiBuildPlateModel().maxBuildPlate + 1):
|
for build_plate_number in range(self._application.getMultiBuildPlateModel().maxBuildPlate + 1):
|
||||||
if build_plate_number not in self._build_plates_to_be_sliced:
|
if build_plate_number not in self._build_plates_to_be_sliced:
|
||||||
self._build_plates_to_be_sliced.append(build_plate_number)
|
self._build_plates_to_be_sliced.append(build_plate_number)
|
||||||
|
|
||||||
## Convenient function: mark everything to slice, emit state and clear layer data
|
## Convenient function: mark everything to slice, emit state and clear layer data
|
||||||
def needsSlicing(self):
|
def needsSlicing(self) -> None:
|
||||||
self.stopSlicing()
|
self.stopSlicing()
|
||||||
self.markSliceAll()
|
self.markSliceAll()
|
||||||
self.processingProgress.emit(0.0)
|
self.processingProgress.emit(0.0)
|
||||||
|
@ -537,7 +549,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
## A setting has changed, so check if we must reslice.
|
## A setting has changed, so check if we must reslice.
|
||||||
# \param instance The setting instance that has changed.
|
# \param instance The setting instance that has changed.
|
||||||
# \param property The property of the setting instance that has changed.
|
# \param property The property of the setting instance that has changed.
|
||||||
def _onSettingChanged(self, instance, property):
|
def _onSettingChanged(self, instance: SettingInstance, property: str) -> None:
|
||||||
if property == "value": # Only reslice if the value has changed.
|
if property == "value": # Only reslice if the value has changed.
|
||||||
self.needsSlicing()
|
self.needsSlicing()
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
@ -546,7 +558,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
if self._use_timer:
|
if self._use_timer:
|
||||||
self._change_timer.stop()
|
self._change_timer.stop()
|
||||||
|
|
||||||
def _onStackErrorCheckFinished(self):
|
def _onStackErrorCheckFinished(self) -> None:
|
||||||
self.determineAutoSlicing()
|
self.determineAutoSlicing()
|
||||||
if self._is_disabled:
|
if self._is_disabled:
|
||||||
return
|
return
|
||||||
|
@ -558,13 +570,13 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
## Called when a sliced layer data message is received from the engine.
|
## Called when a sliced layer data message is received from the engine.
|
||||||
#
|
#
|
||||||
# \param message The protobuf message containing sliced layer data.
|
# \param message The protobuf message containing sliced layer data.
|
||||||
def _onLayerMessage(self, message):
|
def _onLayerMessage(self, message: Arcus.PythonMessage) -> None:
|
||||||
self._stored_layer_data.append(message)
|
self._stored_layer_data.append(message)
|
||||||
|
|
||||||
## Called when an optimized sliced layer data message is received from the engine.
|
## Called when an optimized sliced layer data message is received from the engine.
|
||||||
#
|
#
|
||||||
# \param message The protobuf message containing sliced layer data.
|
# \param message The protobuf message containing sliced layer data.
|
||||||
def _onOptimizedLayerMessage(self, message):
|
def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None:
|
||||||
if self._start_slice_job_build_plate not in self._stored_optimized_layer_data:
|
if self._start_slice_job_build_plate not in self._stored_optimized_layer_data:
|
||||||
self._stored_optimized_layer_data[self._start_slice_job_build_plate] = []
|
self._stored_optimized_layer_data[self._start_slice_job_build_plate] = []
|
||||||
self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
|
self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
|
||||||
|
@ -572,11 +584,11 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
## Called when a progress message is received from the engine.
|
## Called when a progress message is received from the engine.
|
||||||
#
|
#
|
||||||
# \param message The protobuf message containing the slicing progress.
|
# \param message The protobuf message containing the slicing progress.
|
||||||
def _onProgressMessage(self, message):
|
def _onProgressMessage(self, message: Arcus.PythonMessage) -> None:
|
||||||
self.processingProgress.emit(message.amount)
|
self.processingProgress.emit(message.amount)
|
||||||
self.backendStateChange.emit(BackendState.Processing)
|
self.backendStateChange.emit(BackendState.Processing)
|
||||||
|
|
||||||
def _invokeSlice(self):
|
def _invokeSlice(self) -> None:
|
||||||
if self._use_timer:
|
if self._use_timer:
|
||||||
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
|
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
|
||||||
# otherwise business as usual
|
# otherwise business as usual
|
||||||
|
@ -592,17 +604,17 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
## Called when the engine sends a message that slicing is finished.
|
## Called when the engine sends a message that slicing is finished.
|
||||||
#
|
#
|
||||||
# \param message The protobuf message signalling that slicing is finished.
|
# \param message The protobuf message signalling that slicing is finished.
|
||||||
def _onSlicingFinishedMessage(self, message):
|
def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None:
|
||||||
self.backendStateChange.emit(BackendState.Done)
|
self.backendStateChange.emit(BackendState.Done)
|
||||||
self.processingProgress.emit(1.0)
|
self.processingProgress.emit(1.0)
|
||||||
|
|
||||||
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate]
|
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
|
||||||
for index, line in enumerate(gcode_list):
|
for index, line in enumerate(gcode_list):
|
||||||
replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
|
replaced = line.replace("{print_time}", str(self._application.getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
|
||||||
replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths))
|
replaced = replaced.replace("{filament_amount}", str(self._application.getPrintInformation().materialLengths))
|
||||||
replaced = replaced.replace("{filament_weight}", str(Application.getInstance().getPrintInformation().materialWeights))
|
replaced = replaced.replace("{filament_weight}", str(self._application.getPrintInformation().materialWeights))
|
||||||
replaced = replaced.replace("{filament_cost}", str(Application.getInstance().getPrintInformation().materialCosts))
|
replaced = replaced.replace("{filament_cost}", str(self._application.getPrintInformation().materialCosts))
|
||||||
replaced = replaced.replace("{jobname}", str(Application.getInstance().getPrintInformation().jobName))
|
replaced = replaced.replace("{jobname}", str(self._application.getPrintInformation().jobName))
|
||||||
|
|
||||||
gcode_list[index] = replaced
|
gcode_list[index] = replaced
|
||||||
|
|
||||||
|
@ -611,7 +623,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
Logger.log("d", "Number of models per buildplate: %s", dict(self._numObjectsPerBuildPlate()))
|
Logger.log("d", "Number of models per buildplate: %s", dict(self._numObjectsPerBuildPlate()))
|
||||||
|
|
||||||
# See if we need to process the sliced layers job.
|
# See if we need to process the sliced layers job.
|
||||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate
|
||||||
if (
|
if (
|
||||||
self._layer_view_active and
|
self._layer_view_active and
|
||||||
(self._process_layers_job is None or not self._process_layers_job.isRunning()) and
|
(self._process_layers_job is None or not self._process_layers_job.isRunning()) and
|
||||||
|
@ -633,25 +645,27 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
## Called when a g-code message is received from the engine.
|
## Called when a g-code message is received from the engine.
|
||||||
#
|
#
|
||||||
# \param message The protobuf message containing g-code, encoded as UTF-8.
|
# \param message The protobuf message containing g-code, encoded as UTF-8.
|
||||||
def _onGCodeLayerMessage(self, message):
|
def _onGCodeLayerMessage(self, message: Arcus.PythonMessage) -> None:
|
||||||
self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace"))
|
self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
|
||||||
|
|
||||||
## Called when a g-code prefix message is received from the engine.
|
## Called when a g-code prefix message is received from the engine.
|
||||||
#
|
#
|
||||||
# \param message The protobuf message containing the g-code prefix,
|
# \param message The protobuf message containing the g-code prefix,
|
||||||
# encoded as UTF-8.
|
# encoded as UTF-8.
|
||||||
def _onGCodePrefixMessage(self, message):
|
def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None:
|
||||||
self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace"))
|
self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
|
||||||
|
|
||||||
## Creates a new socket connection.
|
## Creates a new socket connection.
|
||||||
def _createSocket(self):
|
def _createSocket(self, protocol_file: str = None) -> None:
|
||||||
super()._createSocket(os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto")))
|
if not protocol_file:
|
||||||
|
protocol_file = os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto"))
|
||||||
|
super()._createSocket(protocol_file)
|
||||||
self._engine_is_fresh = True
|
self._engine_is_fresh = True
|
||||||
|
|
||||||
## Called when anything has changed to the stuff that needs to be sliced.
|
## Called when anything has changed to the stuff that needs to be sliced.
|
||||||
#
|
#
|
||||||
# This indicates that we should probably re-slice soon.
|
# This indicates that we should probably re-slice soon.
|
||||||
def _onChanged(self, *args, **kwargs):
|
def _onChanged(self, *args: Any, **kwargs: Any) -> None:
|
||||||
self.needsSlicing()
|
self.needsSlicing()
|
||||||
if self._use_timer:
|
if self._use_timer:
|
||||||
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
|
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
|
||||||
|
@ -669,7 +683,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
#
|
#
|
||||||
# \param message The protobuf message containing the print time per feature and
|
# \param message The protobuf message containing the print time per feature and
|
||||||
# material amount per extruder
|
# material amount per extruder
|
||||||
def _onPrintTimeMaterialEstimates(self, message):
|
def _onPrintTimeMaterialEstimates(self, message: Arcus.PythonMessage) -> None:
|
||||||
material_amounts = []
|
material_amounts = []
|
||||||
for index in range(message.repeatedMessageCount("materialEstimates")):
|
for index in range(message.repeatedMessageCount("materialEstimates")):
|
||||||
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
|
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
|
||||||
|
@ -680,7 +694,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
## Called for parsing message to retrieve estimated time per feature
|
## Called for parsing message to retrieve estimated time per feature
|
||||||
#
|
#
|
||||||
# \param message The protobuf message containing the print time per feature
|
# \param message The protobuf message containing the print time per feature
|
||||||
def _parseMessagePrintTimes(self, message):
|
def _parseMessagePrintTimes(self, message: Arcus.PythonMessage) -> Dict[str, float]:
|
||||||
result = {
|
result = {
|
||||||
"inset_0": message.time_inset_0,
|
"inset_0": message.time_inset_0,
|
||||||
"inset_x": message.time_inset_x,
|
"inset_x": message.time_inset_x,
|
||||||
|
@ -697,7 +711,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
## Called when the back-end connects to the front-end.
|
## Called when the back-end connects to the front-end.
|
||||||
def _onBackendConnected(self):
|
def _onBackendConnected(self) -> None:
|
||||||
if self._restart:
|
if self._restart:
|
||||||
self._restart = False
|
self._restart = False
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
@ -708,7 +722,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# continuously slicing while the user is dragging some tool handle.
|
# continuously slicing while the user is dragging some tool handle.
|
||||||
#
|
#
|
||||||
# \param tool The tool that the user is using.
|
# \param tool The tool that the user is using.
|
||||||
def _onToolOperationStarted(self, tool):
|
def _onToolOperationStarted(self, tool: Tool) -> None:
|
||||||
self._tool_active = True # Do not react on scene change
|
self._tool_active = True # Do not react on scene change
|
||||||
self.disableTimer()
|
self.disableTimer()
|
||||||
# Restart engine as soon as possible, we know we want to slice afterwards
|
# Restart engine as soon as possible, we know we want to slice afterwards
|
||||||
|
@ -721,7 +735,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# This indicates that we can safely start slicing again.
|
# This indicates that we can safely start slicing again.
|
||||||
#
|
#
|
||||||
# \param tool The tool that the user was using.
|
# \param tool The tool that the user was using.
|
||||||
def _onToolOperationStopped(self, tool):
|
def _onToolOperationStopped(self, tool: Tool) -> None:
|
||||||
self._tool_active = False # React on scene change again
|
self._tool_active = False # React on scene change again
|
||||||
self.determineAutoSlicing() # Switch timer on if appropriate
|
self.determineAutoSlicing() # Switch timer on if appropriate
|
||||||
# Process all the postponed scene changes
|
# Process all the postponed scene changes
|
||||||
|
@ -729,18 +743,17 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
source = self._postponed_scene_change_sources.pop(0)
|
source = self._postponed_scene_change_sources.pop(0)
|
||||||
self._onSceneChanged(source)
|
self._onSceneChanged(source)
|
||||||
|
|
||||||
def _startProcessSlicedLayersJob(self, build_plate_number):
|
def _startProcessSlicedLayersJob(self, build_plate_number: int) -> None:
|
||||||
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number])
|
self._process_layers_job = ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number])
|
||||||
self._process_layers_job.setBuildPlate(build_plate_number)
|
self._process_layers_job.setBuildPlate(build_plate_number)
|
||||||
self._process_layers_job.finished.connect(self._onProcessLayersFinished)
|
self._process_layers_job.finished.connect(self._onProcessLayersFinished)
|
||||||
self._process_layers_job.start()
|
self._process_layers_job.start()
|
||||||
|
|
||||||
## Called when the user changes the active view mode.
|
## Called when the user changes the active view mode.
|
||||||
def _onActiveViewChanged(self):
|
def _onActiveViewChanged(self) -> None:
|
||||||
application = Application.getInstance()
|
view = self._application.getController().getActiveView()
|
||||||
view = application.getController().getActiveView()
|
|
||||||
if view:
|
if view:
|
||||||
active_build_plate = application.getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate
|
||||||
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
|
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
|
||||||
self._layer_view_active = True
|
self._layer_view_active = True
|
||||||
# There is data and we're not slicing at the moment
|
# There is data and we're not slicing at the moment
|
||||||
|
@ -758,14 +771,14 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
## Called when the back-end self-terminates.
|
## Called when the back-end self-terminates.
|
||||||
#
|
#
|
||||||
# We should reset our state and start listening for new connections.
|
# We should reset our state and start listening for new connections.
|
||||||
def _onBackendQuit(self):
|
def _onBackendQuit(self) -> None:
|
||||||
if not self._restart:
|
if not self._restart:
|
||||||
if self._process:
|
if self._process:
|
||||||
Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait())
|
Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait())
|
||||||
self._process = None
|
self._process = None
|
||||||
|
|
||||||
## Called when the global container stack changes
|
## Called when the global container stack changes
|
||||||
def _onGlobalStackChanged(self):
|
def _onGlobalStackChanged(self) -> None:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged)
|
self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged)
|
||||||
self._global_container_stack.containersChanged.disconnect(self._onChanged)
|
self._global_container_stack.containersChanged.disconnect(self._onChanged)
|
||||||
|
@ -775,7 +788,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
extruder.propertyChanged.disconnect(self._onSettingChanged)
|
extruder.propertyChanged.disconnect(self._onSettingChanged)
|
||||||
extruder.containersChanged.disconnect(self._onChanged)
|
extruder.containersChanged.disconnect(self._onChanged)
|
||||||
|
|
||||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
self._global_container_stack = self._application.getGlobalContainerStack()
|
||||||
|
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
|
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
|
||||||
|
@ -786,26 +799,26 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
extruder.containersChanged.connect(self._onChanged)
|
extruder.containersChanged.connect(self._onChanged)
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
||||||
def _onProcessLayersFinished(self, job):
|
def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob) -> None:
|
||||||
del self._stored_optimized_layer_data[job.getBuildPlate()]
|
del self._stored_optimized_layer_data[job.getBuildPlate()]
|
||||||
self._process_layers_job = None
|
self._process_layers_job = None
|
||||||
Logger.log("d", "See if there is more to slice(2)...")
|
Logger.log("d", "See if there is more to slice(2)...")
|
||||||
self._invokeSlice()
|
self._invokeSlice()
|
||||||
|
|
||||||
## Connect slice function to timer.
|
## Connect slice function to timer.
|
||||||
def enableTimer(self):
|
def enableTimer(self) -> None:
|
||||||
if not self._use_timer:
|
if not self._use_timer:
|
||||||
self._change_timer.timeout.connect(self.slice)
|
self._change_timer.timeout.connect(self.slice)
|
||||||
self._use_timer = True
|
self._use_timer = True
|
||||||
|
|
||||||
## Disconnect slice function from timer.
|
## Disconnect slice function from timer.
|
||||||
# This means that slicing will not be triggered automatically
|
# This means that slicing will not be triggered automatically
|
||||||
def disableTimer(self):
|
def disableTimer(self) -> None:
|
||||||
if self._use_timer:
|
if self._use_timer:
|
||||||
self._use_timer = False
|
self._use_timer = False
|
||||||
self._change_timer.timeout.disconnect(self.slice)
|
self._change_timer.timeout.disconnect(self.slice)
|
||||||
|
|
||||||
def _onPreferencesChanged(self, preference):
|
def _onPreferencesChanged(self, preference: str) -> None:
|
||||||
if preference != "general/auto_slice":
|
if preference != "general/auto_slice":
|
||||||
return
|
return
|
||||||
auto_slice = self.determineAutoSlicing()
|
auto_slice = self.determineAutoSlicing()
|
||||||
|
@ -813,11 +826,11 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._change_timer.start()
|
self._change_timer.start()
|
||||||
|
|
||||||
## Tickle the backend so in case of auto slicing, it starts the timer.
|
## Tickle the backend so in case of auto slicing, it starts the timer.
|
||||||
def tickle(self):
|
def tickle(self) -> None:
|
||||||
if self._use_timer:
|
if self._use_timer:
|
||||||
self._change_timer.start()
|
self._change_timer.start()
|
||||||
|
|
||||||
def _extruderChanged(self):
|
def _extruderChanged(self) -> None:
|
||||||
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
|
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
|
||||||
if build_plate_number not in self._build_plates_to_be_sliced:
|
if build_plate_number not in self._build_plates_to_be_sliced:
|
||||||
self._build_plates_to_be_sliced.append(build_plate_number)
|
self._build_plates_to_be_sliced.append(build_plate_number)
|
||||||
|
|
|
@ -1,21 +1,25 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
import time
|
import time
|
||||||
|
from typing import Any, Dict, List, Optional, Set
|
||||||
import re
|
import re
|
||||||
|
import Arcus #For typing.
|
||||||
|
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack #For typing.
|
||||||
|
from UM.Settings.SettingRelation import SettingRelation #For typing.
|
||||||
|
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
from UM.Scene.Scene import Scene #For typing.
|
||||||
from UM.Settings.Validator import ValidatorState
|
from UM.Settings.Validator import ValidatorState
|
||||||
from UM.Settings.SettingRelation import RelationType
|
from UM.Settings.SettingRelation import RelationType
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
from cura.OneAtATimeIterator import OneAtATimeIterator
|
from cura.OneAtATimeIterator import OneAtATimeIterator
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
@ -32,21 +36,22 @@ class StartJobResult(IntEnum):
|
||||||
MaterialIncompatible = 5
|
MaterialIncompatible = 5
|
||||||
BuildPlateError = 6
|
BuildPlateError = 6
|
||||||
ObjectSettingError = 7 #When an error occurs in per-object settings.
|
ObjectSettingError = 7 #When an error occurs in per-object settings.
|
||||||
|
ObjectsWithDisabledExtruder = 8
|
||||||
|
|
||||||
|
|
||||||
## Formatter class that handles token expansion in start/end gcod
|
## Formatter class that handles token expansion in start/end gcode
|
||||||
class GcodeStartEndFormatter(Formatter):
|
class GcodeStartEndFormatter(Formatter):
|
||||||
def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class]
|
def get_value(self, key: str, *args: str, **kwargs) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
|
||||||
# The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key),
|
# The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key),
|
||||||
# and a default_extruder_nr to use when no extruder_nr is specified
|
# and a default_extruder_nr to use when no extruder_nr is specified
|
||||||
|
|
||||||
if isinstance(key, str):
|
if isinstance(key, str):
|
||||||
try:
|
try:
|
||||||
extruder_nr = kwargs["default_extruder_nr"]
|
extruder_nr = int(kwargs["default_extruder_nr"])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
extruder_nr = -1
|
extruder_nr = -1
|
||||||
|
|
||||||
key_fragments = [fragment.strip() for fragment in key.split(',')]
|
key_fragments = [fragment.strip() for fragment in key.split(",")]
|
||||||
if len(key_fragments) == 2:
|
if len(key_fragments) == 2:
|
||||||
try:
|
try:
|
||||||
extruder_nr = int(key_fragments[1])
|
extruder_nr = int(key_fragments[1])
|
||||||
|
@ -73,25 +78,25 @@ class GcodeStartEndFormatter(Formatter):
|
||||||
|
|
||||||
## Job class that builds up the message of scene data to send to CuraEngine.
|
## Job class that builds up the message of scene data to send to CuraEngine.
|
||||||
class StartSliceJob(Job):
|
class StartSliceJob(Job):
|
||||||
def __init__(self, slice_message):
|
def __init__(self, slice_message: Arcus.PythonMessage) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._scene = Application.getInstance().getController().getScene()
|
self._scene = CuraApplication.getInstance().getController().getScene() #type: Scene
|
||||||
self._slice_message = slice_message
|
self._slice_message = slice_message #type: Arcus.PythonMessage
|
||||||
self._is_cancelled = False
|
self._is_cancelled = False #type: bool
|
||||||
self._build_plate_number = None
|
self._build_plate_number = None #type: Optional[int]
|
||||||
|
|
||||||
self._all_extruders_settings = None # cache for all setting values from all stacks (global & extruder) for the current machine
|
self._all_extruders_settings = None #type: Optional[Dict[str, Any]] # cache for all setting values from all stacks (global & extruder) for the current machine
|
||||||
|
|
||||||
def getSliceMessage(self):
|
def getSliceMessage(self) -> Arcus.PythonMessage:
|
||||||
return self._slice_message
|
return self._slice_message
|
||||||
|
|
||||||
def setBuildPlate(self, build_plate_number):
|
def setBuildPlate(self, build_plate_number: int) -> None:
|
||||||
self._build_plate_number = build_plate_number
|
self._build_plate_number = build_plate_number
|
||||||
|
|
||||||
## Check if a stack has any errors.
|
## Check if a stack has any errors.
|
||||||
## returns true if it has errors, false otherwise.
|
## returns true if it has errors, false otherwise.
|
||||||
def _checkStackForErrors(self, stack):
|
def _checkStackForErrors(self, stack: ContainerStack) -> bool:
|
||||||
if stack is None:
|
if stack is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -104,28 +109,28 @@ class StartSliceJob(Job):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
## Runs the job that initiates the slicing.
|
## Runs the job that initiates the slicing.
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
if self._build_plate_number is None:
|
if self._build_plate_number is None:
|
||||||
self.setResult(StartJobResult.Error)
|
self.setResult(StartJobResult.Error)
|
||||||
return
|
return
|
||||||
|
|
||||||
stack = Application.getInstance().getGlobalContainerStack()
|
stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if not stack:
|
if not stack:
|
||||||
self.setResult(StartJobResult.Error)
|
self.setResult(StartJobResult.Error)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Don't slice if there is a setting with an error value.
|
# Don't slice if there is a setting with an error value.
|
||||||
if Application.getInstance().getMachineManager().stacksHaveErrors:
|
if CuraApplication.getInstance().getMachineManager().stacksHaveErrors:
|
||||||
self.setResult(StartJobResult.SettingError)
|
self.setResult(StartJobResult.SettingError)
|
||||||
return
|
return
|
||||||
|
|
||||||
if Application.getInstance().getBuildVolume().hasErrors():
|
if CuraApplication.getInstance().getBuildVolume().hasErrors():
|
||||||
self.setResult(StartJobResult.BuildPlateError)
|
self.setResult(StartJobResult.BuildPlateError)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Don't slice if the buildplate or the nozzle type is incompatible with the materials
|
# Don't slice if the buildplate or the nozzle type is incompatible with the materials
|
||||||
if not Application.getInstance().getMachineManager().variantBuildplateCompatible and \
|
if not CuraApplication.getInstance().getMachineManager().variantBuildplateCompatible and \
|
||||||
not Application.getInstance().getMachineManager().variantBuildplateUsable:
|
not CuraApplication.getInstance().getMachineManager().variantBuildplateUsable:
|
||||||
self.setResult(StartJobResult.MaterialIncompatible)
|
self.setResult(StartJobResult.MaterialIncompatible)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -140,7 +145,7 @@ class StartSliceJob(Job):
|
||||||
|
|
||||||
|
|
||||||
# Don't slice if there is a per object setting with an error value.
|
# Don't slice if there is a per object setting with an error value.
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if not isinstance(node, CuraSceneNode) or not node.isSelectable():
|
if not isinstance(node, CuraSceneNode) or not node.isSelectable():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -150,7 +155,7 @@ class StartSliceJob(Job):
|
||||||
|
|
||||||
with self._scene.getSceneLock():
|
with self._scene.getSceneLock():
|
||||||
# Remove old layer data.
|
# Remove old layer data.
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
|
if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
|
||||||
node.getParent().removeChild(node)
|
node.getParent().removeChild(node)
|
||||||
break
|
break
|
||||||
|
@ -158,7 +163,7 @@ class StartSliceJob(Job):
|
||||||
# Get the objects in their groups to print.
|
# Get the objects in their groups to print.
|
||||||
object_groups = []
|
object_groups = []
|
||||||
if stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
if stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||||
for node in OneAtATimeIterator(self._scene.getRoot()):
|
for node in OneAtATimeIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
temp_list = []
|
temp_list = []
|
||||||
|
|
||||||
# Node can't be printed, so don't bother sending it.
|
# Node can't be printed, so don't bother sending it.
|
||||||
|
@ -184,7 +189,7 @@ class StartSliceJob(Job):
|
||||||
else:
|
else:
|
||||||
temp_list = []
|
temp_list = []
|
||||||
has_printing_mesh = False
|
has_printing_mesh = False
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
if node.callDecoration("isSliceable") and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
if node.callDecoration("isSliceable") and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
||||||
per_object_stack = node.callDecoration("getStack")
|
per_object_stack = node.callDecoration("getStack")
|
||||||
is_non_printing_mesh = False
|
is_non_printing_mesh = False
|
||||||
|
@ -211,18 +216,28 @@ class StartSliceJob(Job):
|
||||||
if temp_list:
|
if temp_list:
|
||||||
object_groups.append(temp_list)
|
object_groups.append(temp_list)
|
||||||
|
|
||||||
extruders_enabled = {position: stack.isEnabled for position, stack in Application.getInstance().getGlobalContainerStack().extruders.items()}
|
extruders_enabled = {position: stack.isEnabled for position, stack in CuraApplication.getInstance().getGlobalContainerStack().extruders.items()}
|
||||||
filtered_object_groups = []
|
filtered_object_groups = []
|
||||||
|
has_model_with_disabled_extruders = False
|
||||||
|
associated_disabled_extruders = set()
|
||||||
for group in object_groups:
|
for group in object_groups:
|
||||||
stack = Application.getInstance().getGlobalContainerStack()
|
stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
skip_group = False
|
skip_group = False
|
||||||
for node in group:
|
for node in group:
|
||||||
if not extruders_enabled[node.callDecoration("getActiveExtruderPosition")]:
|
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
||||||
|
if not extruders_enabled[extruder_position]:
|
||||||
skip_group = True
|
skip_group = True
|
||||||
break
|
has_model_with_disabled_extruders = True
|
||||||
|
associated_disabled_extruders.add(extruder_position)
|
||||||
if not skip_group:
|
if not skip_group:
|
||||||
filtered_object_groups.append(group)
|
filtered_object_groups.append(group)
|
||||||
|
|
||||||
|
if has_model_with_disabled_extruders:
|
||||||
|
self.setResult(StartJobResult.ObjectsWithDisabledExtruder)
|
||||||
|
associated_disabled_extruders = [str(c) for c in sorted([int(p) + 1 for p in associated_disabled_extruders])]
|
||||||
|
self.setMessage(", ".join(associated_disabled_extruders))
|
||||||
|
return
|
||||||
|
|
||||||
# There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
|
# There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
|
||||||
# able to find a possible sequence or because there are no objects on the build plate (or they are outside
|
# able to find a possible sequence or because there are no objects on the build plate (or they are outside
|
||||||
# the build volume)
|
# the build volume)
|
||||||
|
@ -272,11 +287,11 @@ class StartSliceJob(Job):
|
||||||
|
|
||||||
self.setResult(StartJobResult.Finished)
|
self.setResult(StartJobResult.Finished)
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self) -> None:
|
||||||
super().cancel()
|
super().cancel()
|
||||||
self._is_cancelled = True
|
self._is_cancelled = True
|
||||||
|
|
||||||
def isCancelled(self):
|
def isCancelled(self) -> bool:
|
||||||
return self._is_cancelled
|
return self._is_cancelled
|
||||||
|
|
||||||
## Creates a dictionary of tokens to replace in g-code pieces.
|
## Creates a dictionary of tokens to replace in g-code pieces.
|
||||||
|
@ -286,7 +301,7 @@ class StartSliceJob(Job):
|
||||||
# with.
|
# with.
|
||||||
# \return A dictionary of replacement tokens to the values they should be
|
# \return A dictionary of replacement tokens to the values they should be
|
||||||
# replaced with.
|
# replaced with.
|
||||||
def _buildReplacementTokens(self, stack) -> dict:
|
def _buildReplacementTokens(self, stack: ContainerStack) -> Dict[str, Any]:
|
||||||
result = {}
|
result = {}
|
||||||
for key in stack.getAllKeys():
|
for key in stack.getAllKeys():
|
||||||
value = stack.getProperty(key, "value")
|
value = stack.getProperty(key, "value")
|
||||||
|
@ -299,7 +314,7 @@ class StartSliceJob(Job):
|
||||||
result["date"] = time.strftime("%d-%m-%Y")
|
result["date"] = time.strftime("%d-%m-%Y")
|
||||||
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
|
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
|
||||||
|
|
||||||
initial_extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
|
initial_extruder_stack = CuraApplication.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
|
||||||
initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
|
initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
|
||||||
result["initial_extruder_nr"] = initial_extruder_nr
|
result["initial_extruder_nr"] = initial_extruder_nr
|
||||||
|
|
||||||
|
@ -308,9 +323,9 @@ class StartSliceJob(Job):
|
||||||
## Replace setting tokens in a piece of g-code.
|
## Replace setting tokens in a piece of g-code.
|
||||||
# \param value A piece of g-code to replace tokens in.
|
# \param value A piece of g-code to replace tokens in.
|
||||||
# \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
|
# \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
|
||||||
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1):
|
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str:
|
||||||
if not self._all_extruders_settings:
|
if not self._all_extruders_settings:
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
# NB: keys must be strings for the string formatter
|
# NB: keys must be strings for the string formatter
|
||||||
self._all_extruders_settings = {
|
self._all_extruders_settings = {
|
||||||
|
@ -332,7 +347,7 @@ class StartSliceJob(Job):
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
## Create extruder message from stack
|
## Create extruder message from stack
|
||||||
def _buildExtruderMessage(self, stack):
|
def _buildExtruderMessage(self, stack: ContainerStack) -> None:
|
||||||
message = self._slice_message.addRepeatedMessage("extruders")
|
message = self._slice_message.addRepeatedMessage("extruders")
|
||||||
message.id = int(stack.getMetaDataEntry("position"))
|
message.id = int(stack.getMetaDataEntry("position"))
|
||||||
|
|
||||||
|
@ -359,7 +374,7 @@ class StartSliceJob(Job):
|
||||||
#
|
#
|
||||||
# The settings are taken from the global stack. This does not include any
|
# The settings are taken from the global stack. This does not include any
|
||||||
# per-extruder settings or per-object settings.
|
# per-extruder settings or per-object settings.
|
||||||
def _buildGlobalSettingsMessage(self, stack):
|
def _buildGlobalSettingsMessage(self, stack: ContainerStack) -> None:
|
||||||
settings = self._buildReplacementTokens(stack)
|
settings = self._buildReplacementTokens(stack)
|
||||||
|
|
||||||
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
|
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
|
||||||
|
@ -373,7 +388,7 @@ class StartSliceJob(Job):
|
||||||
|
|
||||||
# Replace the setting tokens in start and end g-code.
|
# Replace the setting tokens in start and end g-code.
|
||||||
# Use values from the first used extruder by default so we get the expected temperatures
|
# Use values from the first used extruder by default so we get the expected temperatures
|
||||||
initial_extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
|
initial_extruder_stack = CuraApplication.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
|
||||||
initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
|
initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
|
||||||
|
|
||||||
settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], initial_extruder_nr)
|
settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], initial_extruder_nr)
|
||||||
|
@ -394,7 +409,7 @@ class StartSliceJob(Job):
|
||||||
#
|
#
|
||||||
# \param stack The global stack with all settings, from which to read the
|
# \param stack The global stack with all settings, from which to read the
|
||||||
# limit_to_extruder property.
|
# limit_to_extruder property.
|
||||||
def _buildGlobalInheritsStackMessage(self, stack):
|
def _buildGlobalInheritsStackMessage(self, stack: ContainerStack) -> None:
|
||||||
for key in stack.getAllKeys():
|
for key in stack.getAllKeys():
|
||||||
extruder_position = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
|
extruder_position = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
|
||||||
if extruder_position >= 0: # Set to a specific extruder.
|
if extruder_position >= 0: # Set to a specific extruder.
|
||||||
|
@ -404,9 +419,9 @@ class StartSliceJob(Job):
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
## Check if a node has per object settings and ensure that they are set correctly in the message
|
## Check if a node has per object settings and ensure that they are set correctly in the message
|
||||||
# \param node \type{SceneNode} Node to check.
|
# \param node Node to check.
|
||||||
# \param message object_lists message to put the per object settings in
|
# \param message object_lists message to put the per object settings in
|
||||||
def _handlePerObjectSettings(self, node, message):
|
def _handlePerObjectSettings(self, node: CuraSceneNode, message: Arcus.PythonMessage):
|
||||||
stack = node.callDecoration("getStack")
|
stack = node.callDecoration("getStack")
|
||||||
|
|
||||||
# Check if the node has a stack attached to it and the stack has any settings in the top container.
|
# Check if the node has a stack attached to it and the stack has any settings in the top container.
|
||||||
|
@ -415,7 +430,7 @@ class StartSliceJob(Job):
|
||||||
|
|
||||||
# Check all settings for relations, so we can also calculate the correct values for dependent settings.
|
# Check all settings for relations, so we can also calculate the correct values for dependent settings.
|
||||||
top_of_stack = stack.getTop() # Cache for efficiency.
|
top_of_stack = stack.getTop() # Cache for efficiency.
|
||||||
changed_setting_keys = set(top_of_stack.getAllKeys())
|
changed_setting_keys = top_of_stack.getAllKeys()
|
||||||
|
|
||||||
# Add all relations to changed settings as well.
|
# Add all relations to changed settings as well.
|
||||||
for key in top_of_stack.getAllKeys():
|
for key in top_of_stack.getAllKeys():
|
||||||
|
@ -444,9 +459,9 @@ class StartSliceJob(Job):
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
## Recursive function to put all settings that require each other for value changes in a list
|
## Recursive function to put all settings that require each other for value changes in a list
|
||||||
# \param relations_set \type{set} Set of keys (strings) of settings that are influenced
|
# \param relations_set Set of keys of settings that are influenced
|
||||||
# \param relations list of relation objects that need to be checked.
|
# \param relations list of relation objects that need to be checked.
|
||||||
def _addRelations(self, relations_set, relations):
|
def _addRelations(self, relations_set: Set[str], relations: List[SettingRelation]):
|
||||||
for relation in filter(lambda r: r.role == "value" or r.role == "limit_to_extruder", relations):
|
for relation in filter(lambda r: r.role == "value" or r.role == "limit_to_extruder", relations):
|
||||||
if relation.type == RelationType.RequiresTarget:
|
if relation.type == RelationType.RequiresTarget:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -11,9 +11,8 @@ from UM.PluginRegistry import PluginRegistry
|
||||||
#
|
#
|
||||||
# If you're zipping g-code, you might as well use gzip!
|
# If you're zipping g-code, you might as well use gzip!
|
||||||
class GCodeGzReader(MeshReader):
|
class GCodeGzReader(MeshReader):
|
||||||
|
def __init__(self) -> None:
|
||||||
def __init__(self, application):
|
super().__init__()
|
||||||
super().__init__(application)
|
|
||||||
self._supported_extensions = [".gcode.gz"]
|
self._supported_extensions = [".gcode.gz"]
|
||||||
|
|
||||||
def _read(self, file_name):
|
def _read(self, file_name):
|
||||||
|
|
|
@ -19,6 +19,7 @@ def getMetaData():
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
app.addNonSliceableExtension(".gz")
|
app.addNonSliceableExtension(".gz")
|
||||||
return { "mesh_reader": GCodeGzReader.GCodeGzReader(app) }
|
return {"mesh_reader": GCodeGzReader.GCodeGzReader()}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import gzip
|
import gzip
|
||||||
from io import StringIO, BufferedIOBase #To write the g-code to a temporary buffer, and for typing.
|
from io import StringIO, BufferedIOBase #To write the g-code to a temporary buffer, and for typing.
|
||||||
from typing import List
|
from typing import cast, List
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Mesh.MeshWriter import MeshWriter #The class we're extending/implementing.
|
from UM.Mesh.MeshWriter import MeshWriter #The class we're extending/implementing.
|
||||||
|
@ -32,7 +32,7 @@ class GCodeGzWriter(MeshWriter):
|
||||||
|
|
||||||
#Get the g-code from the g-code writer.
|
#Get the g-code from the g-code writer.
|
||||||
gcode_textio = StringIO() #We have to convert the g-code into bytes.
|
gcode_textio = StringIO() #We have to convert the g-code into bytes.
|
||||||
success = PluginRegistry.getInstance().getPluginObject("GCodeWriter").write(gcode_textio, None)
|
success = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter")).write(gcode_textio, None)
|
||||||
if not success: #Writing the g-code failed. Then I can also not write the gzipped g-code.
|
if not success: #Writing the g-code failed. Then I can also not write the gzipped g-code.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Backend import Backend
|
from UM.Backend import Backend
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
|
@ -13,7 +11,8 @@ from UM.i18n import i18nCatalog
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
from cura import LayerDataBuilder
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from cura.LayerDataBuilder import LayerDataBuilder
|
||||||
from cura.LayerDataDecorator import LayerDataDecorator
|
from cura.LayerDataDecorator import LayerDataDecorator
|
||||||
from cura.LayerPolygon import LayerPolygon
|
from cura.LayerPolygon import LayerPolygon
|
||||||
from cura.Scene.GCodeListDecorator import GCodeListDecorator
|
from cura.Scene.GCodeListDecorator import GCodeListDecorator
|
||||||
|
@ -23,16 +22,16 @@ import numpy
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
from typing import Dict, List, NamedTuple, Optional, Union
|
from typing import Dict, List, NamedTuple, Optional, Union
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", float)])
|
PositionOptional = NamedTuple("Position", [("x", Optional[float]), ("y", Optional[float]), ("z", Optional[float]), ("f", Optional[float]), ("e", Optional[float])])
|
||||||
|
Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", List[float])])
|
||||||
|
|
||||||
## This parser is intended to interpret the common firmware codes among all the
|
## This parser is intended to interpret the common firmware codes among all the
|
||||||
# different flavors
|
# different flavors
|
||||||
class FlavorParser:
|
class FlavorParser:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
Application.getInstance().hideMessageSignal.connect(self._onHideMessage)
|
CuraApplication.getInstance().hideMessageSignal.connect(self._onHideMessage)
|
||||||
self._cancelled = False
|
self._cancelled = False
|
||||||
self._message = None
|
self._message = None
|
||||||
self._layer_number = 0
|
self._layer_number = 0
|
||||||
|
@ -40,21 +39,21 @@ class FlavorParser:
|
||||||
self._clearValues()
|
self._clearValues()
|
||||||
self._scene_node = None
|
self._scene_node = None
|
||||||
# X, Y, Z position, F feedrate and E extruder values are stored
|
# X, Y, Z position, F feedrate and E extruder values are stored
|
||||||
self._position = namedtuple('Position', ['x', 'y', 'z', 'f', 'e'])
|
self._position = Position
|
||||||
self._is_layers_in_file = False # Does the Gcode have the layers comment?
|
self._is_layers_in_file = False # Does the Gcode have the layers comment?
|
||||||
self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
|
self._extruder_offsets = {} # type: Dict[int, List[float]] # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
|
||||||
self._current_layer_thickness = 0.2 # default
|
self._current_layer_thickness = 0.2 # default
|
||||||
self._filament_diameter = 2.85 # default
|
self._filament_diameter = 2.85 # default
|
||||||
|
|
||||||
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
|
CuraApplication.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
|
||||||
|
|
||||||
def _clearValues(self) -> None:
|
def _clearValues(self) -> None:
|
||||||
self._extruder_number = 0
|
self._extruder_number = 0
|
||||||
self._extrusion_length_offset = [0]
|
self._extrusion_length_offset = [0] # type: List[float]
|
||||||
self._layer_type = LayerPolygon.Inset0Type
|
self._layer_type = LayerPolygon.Inset0Type
|
||||||
self._layer_number = 0
|
self._layer_number = 0
|
||||||
self._previous_z = 0
|
self._previous_z = 0 # type: float
|
||||||
self._layer_data_builder = LayerDataBuilder.LayerDataBuilder()
|
self._layer_data_builder = LayerDataBuilder()
|
||||||
self._is_absolute_positioning = True # It can be absolute (G90) or relative (G91)
|
self._is_absolute_positioning = True # It can be absolute (G90) or relative (G91)
|
||||||
self._is_absolute_extrusion = True # It can become absolute (M82, default) or relative (M83)
|
self._is_absolute_extrusion = True # It can become absolute (M82, default) or relative (M83)
|
||||||
|
|
||||||
|
@ -77,14 +76,14 @@ class FlavorParser:
|
||||||
def _getInt(self, line: str, code: str) -> Optional[int]:
|
def _getInt(self, line: str, code: str) -> Optional[int]:
|
||||||
value = self._getValue(line, code)
|
value = self._getValue(line, code)
|
||||||
try:
|
try:
|
||||||
return int(value)
|
return int(value) # type: ignore
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _getFloat(self, line: str, code: str) -> Optional[float]:
|
def _getFloat(self, line: str, code: str) -> Optional[float]:
|
||||||
value = self._getValue(line, code)
|
value = self._getValue(line, code)
|
||||||
try:
|
try:
|
||||||
return float(value)
|
return float(value) # type: ignore
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -92,10 +91,6 @@ class FlavorParser:
|
||||||
if message == self._message:
|
if message == self._message:
|
||||||
self._cancelled = True
|
self._cancelled = True
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _getNullBoundingBox() -> AxisAlignedBox:
|
|
||||||
return AxisAlignedBox(minimum=Vector(0, 0, 0), maximum=Vector(10, 10, 10))
|
|
||||||
|
|
||||||
def _createPolygon(self, layer_thickness: float, path: List[List[Union[float, int]]], extruder_offsets: List[float]) -> bool:
|
def _createPolygon(self, layer_thickness: float, path: List[List[Union[float, int]]], extruder_offsets: List[float]) -> bool:
|
||||||
countvalid = 0
|
countvalid = 0
|
||||||
for point in path:
|
for point in path:
|
||||||
|
@ -169,7 +164,7 @@ class FlavorParser:
|
||||||
return 0.35
|
return 0.35
|
||||||
return line_width
|
return line_width
|
||||||
|
|
||||||
def _gCode0(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
|
def _gCode0(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position:
|
||||||
x, y, z, f, e = position
|
x, y, z, f, e = position
|
||||||
|
|
||||||
if self._is_absolute_positioning:
|
if self._is_absolute_positioning:
|
||||||
|
@ -205,7 +200,7 @@ class FlavorParser:
|
||||||
_gCode1 = _gCode0
|
_gCode1 = _gCode0
|
||||||
|
|
||||||
## Home the head.
|
## Home the head.
|
||||||
def _gCode28(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
|
def _gCode28(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position:
|
||||||
return self._position(
|
return self._position(
|
||||||
params.x if params.x is not None else position.x,
|
params.x if params.x is not None else position.x,
|
||||||
params.y if params.y is not None else position.y,
|
params.y if params.y is not None else position.y,
|
||||||
|
@ -214,20 +209,20 @@ class FlavorParser:
|
||||||
position.e)
|
position.e)
|
||||||
|
|
||||||
## Set the absolute positioning
|
## Set the absolute positioning
|
||||||
def _gCode90(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
|
def _gCode90(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position:
|
||||||
self._is_absolute_positioning = True
|
self._is_absolute_positioning = True
|
||||||
self._is_absolute_extrusion = True
|
self._is_absolute_extrusion = True
|
||||||
return position
|
return position
|
||||||
|
|
||||||
## Set the relative positioning
|
## Set the relative positioning
|
||||||
def _gCode91(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
|
def _gCode91(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position:
|
||||||
self._is_absolute_positioning = False
|
self._is_absolute_positioning = False
|
||||||
self._is_absolute_extrusion = False
|
self._is_absolute_extrusion = False
|
||||||
return position
|
return position
|
||||||
|
|
||||||
## Reset the current position to the values specified.
|
## Reset the current position to the values specified.
|
||||||
# For example: G92 X10 will set the X to 10 without any physical motion.
|
# For example: G92 X10 will set the X to 10 without any physical motion.
|
||||||
def _gCode92(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
|
def _gCode92(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position:
|
||||||
if params.e is not None:
|
if params.e is not None:
|
||||||
# Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width
|
# Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width
|
||||||
self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e
|
self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e
|
||||||
|
@ -260,7 +255,7 @@ class FlavorParser:
|
||||||
f = float(item[1:]) / 60
|
f = float(item[1:]) / 60
|
||||||
if item[0] == "E":
|
if item[0] == "E":
|
||||||
e = float(item[1:])
|
e = float(item[1:])
|
||||||
params = self._position(x, y, z, f, e)
|
params = PositionOptional(x, y, z, f, e)
|
||||||
return func(position, params, path)
|
return func(position, params, path)
|
||||||
return position
|
return position
|
||||||
|
|
||||||
|
@ -290,13 +285,10 @@ class FlavorParser:
|
||||||
Logger.log("d", "Preparing to load GCode")
|
Logger.log("d", "Preparing to load GCode")
|
||||||
self._cancelled = False
|
self._cancelled = False
|
||||||
# We obtain the filament diameter from the selected extruder to calculate line widths
|
# We obtain the filament diameter from the selected extruder to calculate line widths
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value")
|
self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value")
|
||||||
|
|
||||||
scene_node = CuraSceneNode()
|
scene_node = CuraSceneNode()
|
||||||
# Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no
|
|
||||||
# real data to calculate it from.
|
|
||||||
scene_node.getBoundingBox = self._getNullBoundingBox
|
|
||||||
|
|
||||||
gcode_list = []
|
gcode_list = []
|
||||||
self._is_layers_in_file = False
|
self._is_layers_in_file = False
|
||||||
|
@ -322,13 +314,14 @@ class FlavorParser:
|
||||||
lifetime=0,
|
lifetime=0,
|
||||||
title = catalog.i18nc("@info:title", "G-code Details"))
|
title = catalog.i18nc("@info:title", "G-code Details"))
|
||||||
|
|
||||||
|
assert(self._message is not None) # use for typing purposes
|
||||||
self._message.setProgress(0)
|
self._message.setProgress(0)
|
||||||
self._message.show()
|
self._message.show()
|
||||||
|
|
||||||
Logger.log("d", "Parsing Gcode...")
|
Logger.log("d", "Parsing Gcode...")
|
||||||
|
|
||||||
current_position = self._position(0, 0, 0, 0, [0])
|
current_position = Position(0, 0, 0, 0, [0])
|
||||||
current_path = []
|
current_path = [] #type: List[List[float]]
|
||||||
min_layer_number = 0
|
min_layer_number = 0
|
||||||
negative_layers = 0
|
negative_layers = 0
|
||||||
previous_layer = 0
|
previous_layer = 0
|
||||||
|
@ -443,9 +436,9 @@ class FlavorParser:
|
||||||
scene_node.addDecorator(gcode_list_decorator)
|
scene_node.addDecorator(gcode_list_decorator)
|
||||||
|
|
||||||
# gcode_dict stores gcode_lists for a number of build plates.
|
# gcode_dict stores gcode_lists for a number of build plates.
|
||||||
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
gcode_dict = {active_build_plate_id: gcode_list}
|
gcode_dict = {active_build_plate_id: gcode_list}
|
||||||
Application.getInstance().getController().getScene().gcode_dict = gcode_dict
|
CuraApplication.getInstance().getController().getScene().gcode_dict = gcode_dict #type: ignore #Because gcode_dict is generated dynamically.
|
||||||
|
|
||||||
Logger.log("d", "Finished parsing Gcode")
|
Logger.log("d", "Finished parsing Gcode")
|
||||||
self._message.hide()
|
self._message.hide()
|
||||||
|
@ -453,7 +446,7 @@ class FlavorParser:
|
||||||
if self._layer_number == 0:
|
if self._layer_number == 0:
|
||||||
Logger.log("w", "File doesn't contain any valid layers")
|
Logger.log("w", "File doesn't contain any valid layers")
|
||||||
|
|
||||||
settings = Application.getInstance().getGlobalContainerStack()
|
settings = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if not settings.getProperty("machine_center_is_zero", "value"):
|
if not settings.getProperty("machine_center_is_zero", "value"):
|
||||||
machine_width = settings.getProperty("machine_width", "value")
|
machine_width = settings.getProperty("machine_width", "value")
|
||||||
machine_depth = settings.getProperty("machine_depth", "value")
|
machine_depth = settings.getProperty("machine_depth", "value")
|
||||||
|
@ -461,7 +454,7 @@ class FlavorParser:
|
||||||
|
|
||||||
Logger.log("d", "GCode loading finished")
|
Logger.log("d", "GCode loading finished")
|
||||||
|
|
||||||
if Application.getInstance().getPreferences().getValue("gcodereader/show_caution"):
|
if CuraApplication.getInstance().getPreferences().getValue("gcodereader/show_caution"):
|
||||||
caution_message = Message(catalog.i18nc(
|
caution_message = Message(catalog.i18nc(
|
||||||
"@info:generic",
|
"@info:generic",
|
||||||
"Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."),
|
"Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."),
|
||||||
|
@ -470,7 +463,7 @@ class FlavorParser:
|
||||||
caution_message.show()
|
caution_message.show()
|
||||||
|
|
||||||
# The "save/print" button's state is bound to the backend state.
|
# The "save/print" button's state is bound to the backend state.
|
||||||
backend = Application.getInstance().getBackend()
|
backend = CuraApplication.getInstance().getBackend()
|
||||||
backend.backendStateChange.emit(Backend.BackendState.Disabled)
|
backend.backendStateChange.emit(Backend.BackendState.Disabled)
|
||||||
|
|
||||||
return scene_node
|
return scene_node
|
||||||
|
|
|
@ -19,16 +19,16 @@ MimeTypeDatabase.addMimeType(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Class for loading and parsing G-code files
|
# Class for loading and parsing G-code files
|
||||||
class GCodeReader(MeshReader):
|
class GCodeReader(MeshReader):
|
||||||
|
|
||||||
_flavor_default = "Marlin"
|
_flavor_default = "Marlin"
|
||||||
_flavor_keyword = ";FLAVOR:"
|
_flavor_keyword = ";FLAVOR:"
|
||||||
_flavor_readers_dict = {"RepRap" : RepRapFlavorParser.RepRapFlavorParser(),
|
_flavor_readers_dict = {"RepRap" : RepRapFlavorParser.RepRapFlavorParser(),
|
||||||
"Marlin" : MarlinFlavorParser.MarlinFlavorParser()}
|
"Marlin" : MarlinFlavorParser.MarlinFlavorParser()}
|
||||||
|
|
||||||
def __init__(self, application):
|
def __init__(self) -> None:
|
||||||
super(GCodeReader, self).__init__(application)
|
super().__init__()
|
||||||
self._supported_extensions = [".gcode", ".g"]
|
self._supported_extensions = [".gcode", ".g"]
|
||||||
self._flavor_reader = None
|
self._flavor_reader = None
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,8 @@ def getMetaData():
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
app.addNonSliceableExtension(".gcode")
|
app.addNonSliceableExtension(".gcode")
|
||||||
app.addNonSliceableExtension(".g")
|
app.addNonSliceableExtension(".g")
|
||||||
return { "mesh_reader": GCodeReader.GCodeReader(app) }
|
return {"mesh_reader": GCodeReader.GCodeReader()}
|
||||||
|
|
|
@ -140,7 +140,7 @@ class GCodeWriter(MeshWriter):
|
||||||
serialized = flat_global_container.serialize()
|
serialized = flat_global_container.serialize()
|
||||||
data = {"global_quality": serialized}
|
data = {"global_quality": serialized}
|
||||||
|
|
||||||
all_setting_keys = set(flat_global_container.getAllKeys())
|
all_setting_keys = flat_global_container.getAllKeys()
|
||||||
for extruder in sorted(stack.extruders.values(), key = lambda k: int(k.getMetaDataEntry("position"))):
|
for extruder in sorted(stack.extruders.values(), key = lambda k: int(k.getMetaDataEntry("position"))):
|
||||||
extruder_quality = extruder.qualityChanges
|
extruder_quality = extruder.qualityChanges
|
||||||
if extruder_quality.getId() == "empty_quality_changes":
|
if extruder_quality.getId() == "empty_quality_changes":
|
||||||
|
@ -167,7 +167,7 @@ class GCodeWriter(MeshWriter):
|
||||||
extruder_serialized = flat_extruder_quality.serialize()
|
extruder_serialized = flat_extruder_quality.serialize()
|
||||||
data.setdefault("extruder_quality", []).append(extruder_serialized)
|
data.setdefault("extruder_quality", []).append(extruder_serialized)
|
||||||
|
|
||||||
all_setting_keys.update(set(flat_extruder_quality.getAllKeys()))
|
all_setting_keys.update(flat_extruder_quality.getAllKeys())
|
||||||
|
|
||||||
# Check if there is any profiles
|
# Check if there is any profiles
|
||||||
if not all_setting_keys:
|
if not all_setting_keys:
|
||||||
|
|
|
@ -17,8 +17,8 @@ from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
|
||||||
|
|
||||||
|
|
||||||
class ImageReader(MeshReader):
|
class ImageReader(MeshReader):
|
||||||
def __init__(self, application):
|
def __init__(self) -> None:
|
||||||
super(ImageReader, self).__init__(application)
|
super().__init__()
|
||||||
self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"]
|
self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"]
|
||||||
self._ui = ImageReaderUI(self)
|
self._ui = ImageReaderUI(self)
|
||||||
|
|
||||||
|
|
|
@ -32,5 +32,6 @@ def getMetaData():
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return { "mesh_reader": ImageReader.ImageReader(app) }
|
return {"mesh_reader": ImageReader.ImageReader()}
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
# This PostProcessing Plugin script is released
|
# This PostProcessing Plugin script is released
|
||||||
# under the terms of the AGPLv3 or higher
|
# under the terms of the AGPLv3 or higher
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
from ..Script import Script
|
from ..Script import Script
|
||||||
|
|
||||||
class FilamentChange(Script):
|
class FilamentChange(Script):
|
||||||
|
|
||||||
|
_layer_keyword = ";LAYER:"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
|
@ -64,11 +69,26 @@ class FilamentChange(Script):
|
||||||
if len(layer_targets) > 0:
|
if len(layer_targets) > 0:
|
||||||
for layer_num in layer_targets:
|
for layer_num in layer_targets:
|
||||||
layer_num = int(layer_num.strip())
|
layer_num = int(layer_num.strip())
|
||||||
if layer_num < len(data):
|
if layer_num <= len(data):
|
||||||
layer = data[layer_num - 1]
|
index, layer_data = self._searchLayerData(data, layer_num - 1)
|
||||||
lines = layer.split("\n")
|
if layer_data is None:
|
||||||
|
Logger.log("e", "Could not found the layer")
|
||||||
|
continue
|
||||||
|
lines = layer_data.split("\n")
|
||||||
lines.insert(2, color_change)
|
lines.insert(2, color_change)
|
||||||
final_line = "\n".join(lines)
|
final_line = "\n".join(lines)
|
||||||
data[layer_num - 1] = final_line
|
data[index] = final_line
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
## This method returns the data corresponding with the indicated layer number, looking in the gcode for
|
||||||
|
# the occurrence of this layer number.
|
||||||
|
def _searchLayerData(self, data: list, layer_num: int) -> Tuple[int, Optional[str]]:
|
||||||
|
for index, layer_data in enumerate(data):
|
||||||
|
first_line = layer_data.split("\n")[0]
|
||||||
|
# The first line should contain the layer number at the beginning.
|
||||||
|
if first_line[:len(self._layer_keyword)] == self._layer_keyword:
|
||||||
|
# If found the layer that we are looking for, then return the data
|
||||||
|
if first_line[len(self._layer_keyword):] == str(layer_num):
|
||||||
|
return index, layer_data
|
||||||
|
return 0, None
|
|
@ -105,14 +105,6 @@ class PauseAtHeight(Script):
|
||||||
"unit": "°C",
|
"unit": "°C",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"default_value": 0
|
"default_value": 0
|
||||||
},
|
|
||||||
"resume_temperature":
|
|
||||||
{
|
|
||||||
"label": "Resume Temperature",
|
|
||||||
"description": "Change the temperature after the pause",
|
|
||||||
"unit": "°C",
|
|
||||||
"type": "int",
|
|
||||||
"default_value": 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
|
@ -144,7 +136,6 @@ class PauseAtHeight(Script):
|
||||||
layers_started = False
|
layers_started = False
|
||||||
redo_layers = self.getSettingValueByKey("redo_layers")
|
redo_layers = self.getSettingValueByKey("redo_layers")
|
||||||
standby_temperature = self.getSettingValueByKey("standby_temperature")
|
standby_temperature = self.getSettingValueByKey("standby_temperature")
|
||||||
resume_temperature = self.getSettingValueByKey("resume_temperature")
|
|
||||||
|
|
||||||
# T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
|
# T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
|
||||||
|
|
||||||
|
@ -152,6 +143,8 @@ class PauseAtHeight(Script):
|
||||||
layer_0_z = 0.
|
layer_0_z = 0.
|
||||||
current_z = 0
|
current_z = 0
|
||||||
got_first_g_cmd_on_layer_0 = False
|
got_first_g_cmd_on_layer_0 = False
|
||||||
|
current_t = 0 #Tracks the current extruder for tracking the target temperature.
|
||||||
|
target_temperature = {} #Tracks the current target temperature for each extruder.
|
||||||
|
|
||||||
nbr_negative_layers = 0
|
nbr_negative_layers = 0
|
||||||
|
|
||||||
|
@ -169,6 +162,16 @@ class PauseAtHeight(Script):
|
||||||
if not layers_started:
|
if not layers_started:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
#Track the latest printing temperature in order to resume at the correct temperature.
|
||||||
|
if line.startswith("T"):
|
||||||
|
current_t = self.getValue(line, "T")
|
||||||
|
m = self.getValue(line, "M")
|
||||||
|
if m is not None and (m == 104 or m == 109) and self.getValue(line, "S") is not None:
|
||||||
|
extruder = current_t
|
||||||
|
if self.getValue(line, "T") is not None:
|
||||||
|
extruder = self.getValue(line, "T")
|
||||||
|
target_temperature[extruder] = self.getValue(line, "S")
|
||||||
|
|
||||||
# If a Z instruction is in the line, read the current Z
|
# If a Z instruction is in the line, read the current Z
|
||||||
if self.getValue(line, "Z") is not None:
|
if self.getValue(line, "Z") is not None:
|
||||||
current_z = self.getValue(line, "Z")
|
current_z = self.getValue(line, "Z")
|
||||||
|
@ -262,9 +265,6 @@ class PauseAtHeight(Script):
|
||||||
if current_z < 15:
|
if current_z < 15:
|
||||||
prepend_gcode += self.putValue(G=1, Z=15, F=300) + "\n"
|
prepend_gcode += self.putValue(G=1, Z=15, F=300) + "\n"
|
||||||
|
|
||||||
# Disable the E steppers
|
|
||||||
prepend_gcode += self.putValue(M=84, E=0) + "\n"
|
|
||||||
|
|
||||||
# Set extruder standby temperature
|
# Set extruder standby temperature
|
||||||
prepend_gcode += self.putValue(M=104, S=standby_temperature) + "; standby temperature\n"
|
prepend_gcode += self.putValue(M=104, S=standby_temperature) + "; standby temperature\n"
|
||||||
|
|
||||||
|
@ -272,7 +272,7 @@ class PauseAtHeight(Script):
|
||||||
prepend_gcode += self.putValue(M=0) + ";Do the actual pause\n"
|
prepend_gcode += self.putValue(M=0) + ";Do the actual pause\n"
|
||||||
|
|
||||||
# Set extruder resume temperature
|
# Set extruder resume temperature
|
||||||
prepend_gcode += self.putValue(M=109, S=resume_temperature) + "; resume temperature\n"
|
prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, default = 0))) + "; resume temperature\n"
|
||||||
|
|
||||||
# Push the filament back,
|
# Push the filament back,
|
||||||
if retraction_amount != 0:
|
if retraction_amount != 0:
|
||||||
|
|
|
@ -53,7 +53,7 @@ UM.PointingRectangle {
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
width: 40 * screenScaleFactor
|
width: maximumValue.toString().length * 12 * screenScaleFactor
|
||||||
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
|
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
|
||||||
horizontalAlignment: TextInput.AlignRight
|
horizontalAlignment: TextInput.AlignRight
|
||||||
|
|
||||||
|
@ -77,6 +77,7 @@ UM.PointingRectangle {
|
||||||
if (valueLabel.text != "") {
|
if (valueLabel.text != "") {
|
||||||
// -startFrom because we need to convert back to an array structure
|
// -startFrom because we need to convert back to an array structure
|
||||||
sliderLabelRoot.setValue(parseInt(valueLabel.text) - startFrom)
|
sliderLabelRoot.setValue(parseInt(valueLabel.text) - startFrom)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QTimer
|
from PyQt5.QtCore import Qt, QTimer
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Tool import Tool
|
from UM.Tool import Tool
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Event import Event, MouseEvent
|
from UM.Event import Event, MouseEvent
|
||||||
|
|
||||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||||
from UM.Scene.Selection import Selection
|
from UM.Scene.Selection import Selection
|
||||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
from cura.PickingPass import PickingPass
|
from cura.PickingPass import PickingPass
|
||||||
|
|
||||||
from UM.Operations.GroupedOperation import GroupedOperation
|
from UM.Operations.GroupedOperation import GroupedOperation
|
||||||
|
@ -26,8 +21,6 @@ from cura.Operations.SetParentOperation import SetParentOperation
|
||||||
|
|
||||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||||
from UM.Scene.GroupDecorator import GroupDecorator
|
|
||||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
|
||||||
|
|
||||||
from UM.Settings.SettingInstance import SettingInstance
|
from UM.Settings.SettingInstance import SettingInstance
|
||||||
|
|
||||||
|
@ -38,7 +31,7 @@ class SupportEraser(Tool):
|
||||||
self._controller = self.getController()
|
self._controller = self.getController()
|
||||||
|
|
||||||
self._selection_pass = None
|
self._selection_pass = None
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._updateEnabled)
|
CuraApplication.getInstance().globalContainerStackChanged.connect(self._updateEnabled)
|
||||||
|
|
||||||
# Note: if the selection is cleared with this tool active, there is no way to switch to
|
# Note: if the selection is cleared with this tool active, there is no way to switch to
|
||||||
# another tool than to reselect an object (by clicking it) because the tool buttons in the
|
# another tool than to reselect an object (by clicking it) because the tool buttons in the
|
||||||
|
@ -106,7 +99,7 @@ class SupportEraser(Tool):
|
||||||
mesh.addCube(10,10,10)
|
mesh.addCube(10,10,10)
|
||||||
node.setMeshData(mesh.build())
|
node.setMeshData(mesh.build())
|
||||||
|
|
||||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||||
node.addDecorator(SliceableObjectDecorator())
|
node.addDecorator(SliceableObjectDecorator())
|
||||||
|
|
||||||
|
@ -126,7 +119,7 @@ class SupportEraser(Tool):
|
||||||
op.push()
|
op.push()
|
||||||
node.setPosition(position, CuraSceneNode.TransformSpace.World)
|
node.setPosition(position, CuraSceneNode.TransformSpace.World)
|
||||||
|
|
||||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||||
|
|
||||||
def _removeEraserMesh(self, node: CuraSceneNode):
|
def _removeEraserMesh(self, node: CuraSceneNode):
|
||||||
parent = node.getParent()
|
parent = node.getParent()
|
||||||
|
@ -139,16 +132,16 @@ class SupportEraser(Tool):
|
||||||
if parent and not Selection.isSelected(parent):
|
if parent and not Selection.isSelected(parent):
|
||||||
Selection.add(parent)
|
Selection.add(parent)
|
||||||
|
|
||||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||||
|
|
||||||
def _updateEnabled(self):
|
def _updateEnabled(self):
|
||||||
plugin_enabled = False
|
plugin_enabled = False
|
||||||
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
plugin_enabled = global_container_stack.getProperty("anti_overhang_mesh", "enabled")
|
plugin_enabled = global_container_stack.getProperty("anti_overhang_mesh", "enabled")
|
||||||
|
|
||||||
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, plugin_enabled)
|
CuraApplication.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, plugin_enabled)
|
||||||
|
|
||||||
def _onSelectionChanged(self):
|
def _onSelectionChanged(self):
|
||||||
# When selection is passed from one object to another object, first the selection is cleared
|
# When selection is passed from one object to another object, first the selection is cleared
|
||||||
|
|
|
@ -9,4 +9,4 @@ def getMetaData():
|
||||||
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return {"extension": Toolbox.Toolbox()}
|
return {"extension": Toolbox.Toolbox(app)}
|
||||||
|
|
|
@ -92,6 +92,12 @@ Item
|
||||||
font: UM.Theme.getFont("very_small")
|
font: UM.Theme.getFont("very_small")
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
}
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@label", "Downloads") + ":"
|
||||||
|
font: UM.Theme.getFont("very_small")
|
||||||
|
color: UM.Theme.getColor("text_medium")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
|
@ -138,6 +144,12 @@ Item
|
||||||
linkColor: UM.Theme.getColor("text_link")
|
linkColor: UM.Theme.getColor("text_link")
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
}
|
}
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: details.download_count || catalog.i18nc("@label", "Unknown")
|
||||||
|
font: UM.Theme.getFont("very_small")
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,7 +12,7 @@ Column
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: UM.Theme.getSize("default_margin").height
|
spacing: UM.Theme.getSize("default_margin").height
|
||||||
/* Hidden for 3.4
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: heading
|
id: heading
|
||||||
|
@ -21,7 +21,6 @@ Column
|
||||||
color: UM.Theme.getColor("text_medium")
|
color: UM.Theme.getColor("text_medium")
|
||||||
font: UM.Theme.getFont("medium")
|
font: UM.Theme.getFont("medium")
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
GridLayout
|
GridLayout
|
||||||
{
|
{
|
||||||
id: grid
|
id: grid
|
||||||
|
|
|
@ -32,6 +32,7 @@ class PackagesModel(ListModel):
|
||||||
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
|
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
|
||||||
self.addRoleName(Qt.UserRole + 16, "has_configs")
|
self.addRoleName(Qt.UserRole + 16, "has_configs")
|
||||||
self.addRoleName(Qt.UserRole + 17, "supported_configs")
|
self.addRoleName(Qt.UserRole + 17, "supported_configs")
|
||||||
|
self.addRoleName(Qt.UserRole + 18, "download_count")
|
||||||
|
|
||||||
# List of filters for queries. The result is the union of the each list of results.
|
# List of filters for queries. The result is the union of the each list of results.
|
||||||
self._filter = {} # type: Dict[str, str]
|
self._filter = {} # type: Dict[str, str]
|
||||||
|
@ -76,7 +77,9 @@ class PackagesModel(ListModel):
|
||||||
"is_enabled": package["is_enabled"] if "is_enabled" in package else False,
|
"is_enabled": package["is_enabled"] if "is_enabled" in package else False,
|
||||||
"is_installed": package["is_installed"] if "is_installed" in package else False,
|
"is_installed": package["is_installed"] if "is_installed" in package else False,
|
||||||
"has_configs": has_configs,
|
"has_configs": has_configs,
|
||||||
"supported_configs": configs_model
|
"supported_configs": configs_model,
|
||||||
|
"download_count": package["download_count"] if "download_count" in package else 0
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Filter on all the key-word arguments.
|
# Filter on all the key-word arguments.
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Toolbox is released under the terms of the LGPLv3 or higher.
|
# Toolbox is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Dict, Optional, Union, Any
|
from typing import Dict, Optional, Union, Any, cast
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import platform
|
import platform
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.Extension import Extension
|
from UM.Extension import Extension
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Version import Version
|
from UM.Version import Version
|
||||||
|
|
||||||
|
@ -27,44 +28,39 @@ i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
## The Toolbox class is responsible of communicating with the server through the API
|
## The Toolbox class is responsible of communicating with the server through the API
|
||||||
class Toolbox(QObject, Extension):
|
class Toolbox(QObject, Extension):
|
||||||
|
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" #type: str
|
||||||
|
DEFAULT_CLOUD_API_VERSION = 1 #type: int
|
||||||
|
|
||||||
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com"
|
def __init__(self, application: CuraApplication) -> None:
|
||||||
DEFAULT_CLOUD_API_VERSION = 1
|
super().__init__()
|
||||||
|
|
||||||
def __init__(self, parent=None) -> None:
|
self._application = application #type: CuraApplication
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
self._application = Application.getInstance()
|
self._sdk_version = None # type: Optional[int]
|
||||||
self._package_manager = None
|
self._cloud_api_version = None # type: Optional[int]
|
||||||
self._plugin_registry = Application.getInstance().getPluginRegistry()
|
self._cloud_api_root = None # type: Optional[str]
|
||||||
|
self._api_url = None # type: Optional[str]
|
||||||
self._sdk_version = None
|
|
||||||
self._cloud_api_version = None
|
|
||||||
self._cloud_api_root = None
|
|
||||||
self._api_url = None
|
|
||||||
|
|
||||||
# Network:
|
# Network:
|
||||||
self._get_packages_request = None
|
self._download_request = None #type: Optional[QNetworkRequest]
|
||||||
self._get_showcase_request = None
|
self._download_reply = None #type: Optional[QNetworkReply]
|
||||||
self._download_request = None
|
self._download_progress = 0 #type: float
|
||||||
self._download_reply = None
|
self._is_downloading = False #type: bool
|
||||||
self._download_progress = 0
|
self._network_manager = None #type: Optional[QNetworkAccessManager]
|
||||||
self._is_downloading = False
|
|
||||||
self._network_manager = None
|
|
||||||
self._request_header = [
|
self._request_header = [
|
||||||
b"User-Agent",
|
b"User-Agent",
|
||||||
str.encode(
|
str.encode(
|
||||||
"%s/%s (%s %s)" % (
|
"%s/%s (%s %s)" % (
|
||||||
Application.getInstance().getApplicationName(),
|
self._application.getApplicationName(),
|
||||||
Application.getInstance().getVersion(),
|
self._application.getVersion(),
|
||||||
platform.system(),
|
platform.system(),
|
||||||
platform.machine(),
|
platform.machine(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
self._request_urls = {}
|
self._request_urls = {} # type: Dict[str, QUrl]
|
||||||
self._to_update = [] # Package_ids that are waiting to be updated
|
self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
|
||||||
self._old_plugin_ids = []
|
self._old_plugin_ids = [] # type: List[str]
|
||||||
|
|
||||||
# Data:
|
# Data:
|
||||||
self._metadata = {
|
self._metadata = {
|
||||||
|
@ -76,7 +72,7 @@ class Toolbox(QObject, Extension):
|
||||||
"materials_showcase": [],
|
"materials_showcase": [],
|
||||||
"materials_available": [],
|
"materials_available": [],
|
||||||
"materials_installed": []
|
"materials_installed": []
|
||||||
}
|
} # type: Dict[str, List[Any]]
|
||||||
|
|
||||||
# Models:
|
# Models:
|
||||||
self._models = {
|
self._models = {
|
||||||
|
@ -88,33 +84,33 @@ class Toolbox(QObject, Extension):
|
||||||
"materials_showcase": AuthorsModel(self),
|
"materials_showcase": AuthorsModel(self),
|
||||||
"materials_available": PackagesModel(self),
|
"materials_available": PackagesModel(self),
|
||||||
"materials_installed": PackagesModel(self)
|
"materials_installed": PackagesModel(self)
|
||||||
}
|
} # type: Dict[str, ListModel]
|
||||||
|
|
||||||
# These properties are for keeping track of the UI state:
|
# These properties are for keeping track of the UI state:
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
# View category defines which filter to use, and therefore effectively
|
# View category defines which filter to use, and therefore effectively
|
||||||
# which category is currently being displayed. For example, possible
|
# which category is currently being displayed. For example, possible
|
||||||
# values include "plugin" or "material", but also "installed".
|
# values include "plugin" or "material", but also "installed".
|
||||||
self._view_category = "plugin"
|
self._view_category = "plugin" #type: str
|
||||||
|
|
||||||
# View page defines which type of page layout to use. For example,
|
# View page defines which type of page layout to use. For example,
|
||||||
# possible values include "overview", "detail" or "author".
|
# possible values include "overview", "detail" or "author".
|
||||||
self._view_page = "loading"
|
self._view_page = "loading" #type: str
|
||||||
|
|
||||||
# Active package refers to which package is currently being downloaded,
|
# Active package refers to which package is currently being downloaded,
|
||||||
# installed, or otherwise modified.
|
# installed, or otherwise modified.
|
||||||
self._active_package = None
|
self._active_package = None # type: Optional[Dict[str, Any]]
|
||||||
|
|
||||||
self._dialog = None
|
self._dialog = None #type: Optional[QObject]
|
||||||
self._restart_required = False
|
self._restart_required = False #type: bool
|
||||||
|
|
||||||
# variables for the license agreement dialog
|
# variables for the license agreement dialog
|
||||||
self._license_dialog_plugin_name = ""
|
self._license_dialog_plugin_name = "" #type: str
|
||||||
self._license_dialog_license_content = ""
|
self._license_dialog_license_content = "" #type: str
|
||||||
self._license_dialog_plugin_file_location = ""
|
self._license_dialog_plugin_file_location = "" #type: str
|
||||||
self._restart_dialog_message = ""
|
self._restart_dialog_message = "" #type: str
|
||||||
|
|
||||||
Application.getInstance().initializationFinished.connect(self._onAppInitialized)
|
self._application.initializationFinished.connect(self._onAppInitialized)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,7 +152,8 @@ class Toolbox(QObject, Extension):
|
||||||
# This is a plugin, so most of the components required are not ready when
|
# This is a plugin, so most of the components required are not ready when
|
||||||
# this is initialized. Therefore, we wait until the application is ready.
|
# this is initialized. Therefore, we wait until the application is ready.
|
||||||
def _onAppInitialized(self) -> None:
|
def _onAppInitialized(self) -> None:
|
||||||
self._package_manager = Application.getInstance().getPackageManager()
|
self._plugin_registry = self._application.getPluginRegistry()
|
||||||
|
self._package_manager = self._application.getPackageManager()
|
||||||
self._sdk_version = self._getSDKVersion()
|
self._sdk_version = self._getSDKVersion()
|
||||||
self._cloud_api_version = self._getCloudAPIVersion()
|
self._cloud_api_version = self._getCloudAPIVersion()
|
||||||
self._cloud_api_root = self._getCloudAPIRoot()
|
self._cloud_api_root = self._getCloudAPIRoot()
|
||||||
|
@ -178,38 +175,38 @@ class Toolbox(QObject, Extension):
|
||||||
def _getCloudAPIRoot(self) -> str:
|
def _getCloudAPIRoot(self) -> str:
|
||||||
if not hasattr(cura, "CuraVersion"):
|
if not hasattr(cura, "CuraVersion"):
|
||||||
return self.DEFAULT_CLOUD_API_ROOT
|
return self.DEFAULT_CLOUD_API_ROOT
|
||||||
if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"):
|
if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"): # type: ignore
|
||||||
return self.DEFAULT_CLOUD_API_ROOT
|
return self.DEFAULT_CLOUD_API_ROOT
|
||||||
if not cura.CuraVersion.CuraCloudAPIRoot:
|
if not cura.CuraVersion.CuraCloudAPIRoot: # type: ignore
|
||||||
return self.DEFAULT_CLOUD_API_ROOT
|
return self.DEFAULT_CLOUD_API_ROOT
|
||||||
return cura.CuraVersion.CuraCloudAPIRoot
|
return cura.CuraVersion.CuraCloudAPIRoot # type: ignore
|
||||||
|
|
||||||
# Get the cloud API version from CuraVersion
|
# Get the cloud API version from CuraVersion
|
||||||
def _getCloudAPIVersion(self) -> int:
|
def _getCloudAPIVersion(self) -> int:
|
||||||
if not hasattr(cura, "CuraVersion"):
|
if not hasattr(cura, "CuraVersion"):
|
||||||
return self.DEFAULT_CLOUD_API_VERSION
|
return self.DEFAULT_CLOUD_API_VERSION
|
||||||
if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"):
|
if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"): # type: ignore
|
||||||
return self.DEFAULT_CLOUD_API_VERSION
|
return self.DEFAULT_CLOUD_API_VERSION
|
||||||
if not cura.CuraVersion.CuraCloudAPIVersion:
|
if not cura.CuraVersion.CuraCloudAPIVersion: # type: ignore
|
||||||
return self.DEFAULT_CLOUD_API_VERSION
|
return self.DEFAULT_CLOUD_API_VERSION
|
||||||
return cura.CuraVersion.CuraCloudAPIVersion
|
return cura.CuraVersion.CuraCloudAPIVersion # type: ignore
|
||||||
|
|
||||||
# Get the packages version depending on Cura version settings.
|
# Get the packages version depending on Cura version settings.
|
||||||
def _getSDKVersion(self) -> int:
|
def _getSDKVersion(self) -> int:
|
||||||
if not hasattr(cura, "CuraVersion"):
|
if not hasattr(cura, "CuraVersion"):
|
||||||
return self._plugin_registry.APIVersion
|
return self._plugin_registry.APIVersion
|
||||||
if not hasattr(cura.CuraVersion, "CuraSDKVersion"):
|
if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore
|
||||||
return self._plugin_registry.APIVersion
|
return self._plugin_registry.APIVersion
|
||||||
if not cura.CuraVersion.CuraSDKVersion:
|
if not cura.CuraVersion.CuraSDKVersion: # type: ignore
|
||||||
return self._plugin_registry.APIVersion
|
return self._plugin_registry.APIVersion
|
||||||
return cura.CuraVersion.CuraSDKVersion
|
return cura.CuraVersion.CuraSDKVersion # type: ignore
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def browsePackages(self) -> None:
|
def browsePackages(self) -> None:
|
||||||
# Create the network manager:
|
# Create the network manager:
|
||||||
# This was formerly its own function but really had no reason to be as
|
# This was formerly its own function but really had no reason to be as
|
||||||
# it was never called more than once ever.
|
# it was never called more than once ever.
|
||||||
if self._network_manager:
|
if self._network_manager is not None:
|
||||||
self._network_manager.finished.disconnect(self._onRequestFinished)
|
self._network_manager.finished.disconnect(self._onRequestFinished)
|
||||||
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged)
|
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged)
|
||||||
self._network_manager = QNetworkAccessManager()
|
self._network_manager = QNetworkAccessManager()
|
||||||
|
@ -235,11 +232,11 @@ class Toolbox(QObject, Extension):
|
||||||
def _createDialog(self, qml_name: str) -> Optional[QObject]:
|
def _createDialog(self, qml_name: str) -> Optional[QObject]:
|
||||||
Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name)
|
Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name)
|
||||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "resources", "qml", qml_name)
|
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "resources", "qml", qml_name)
|
||||||
dialog = Application.getInstance().createQmlComponent(path, {"toolbox": self})
|
dialog = self._application.createQmlComponent(path, {"toolbox": self})
|
||||||
return dialog
|
return dialog
|
||||||
|
|
||||||
|
|
||||||
def _convertPluginMetadata(self, plugin: dict) -> dict:
|
def _convertPluginMetadata(self, plugin: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
formatted = {
|
formatted = {
|
||||||
"package_id": plugin["id"],
|
"package_id": plugin["id"],
|
||||||
"package_type": "plugin",
|
"package_type": "plugin",
|
||||||
|
@ -257,7 +254,6 @@ class Toolbox(QObject, Extension):
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _updateInstalledModels(self) -> None:
|
def _updateInstalledModels(self) -> None:
|
||||||
|
|
||||||
# This is moved here to avoid code duplication and so that after installing plugins they get removed from the
|
# This is moved here to avoid code duplication and so that after installing plugins they get removed from the
|
||||||
# list of old plugins
|
# list of old plugins
|
||||||
old_plugin_ids = self._plugin_registry.getInstalledPlugins()
|
old_plugin_ids = self._plugin_registry.getInstalledPlugins()
|
||||||
|
@ -265,7 +261,7 @@ class Toolbox(QObject, Extension):
|
||||||
scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs()
|
scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs()
|
||||||
|
|
||||||
self._old_plugin_ids = []
|
self._old_plugin_ids = []
|
||||||
self._old_plugin_metadata = []
|
self._old_plugin_metadata = [] # type: List[Dict[str, Any]]
|
||||||
|
|
||||||
for plugin_id in old_plugin_ids:
|
for plugin_id in old_plugin_ids:
|
||||||
# Neither the installed packages nor the packages that are scheduled to remove are old plugins
|
# Neither the installed packages nor the packages that are scheduled to remove are old plugins
|
||||||
|
@ -353,8 +349,8 @@ class Toolbox(QObject, Extension):
|
||||||
return self._restart_required
|
return self._restart_required
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def restart(self):
|
def restart(self) -> None:
|
||||||
CuraApplication.getInstance().windowClosed()
|
self._application.windowClosed()
|
||||||
|
|
||||||
def getRemotePackage(self, package_id: str) -> Optional[Dict]:
|
def getRemotePackage(self, package_id: str) -> Optional[Dict]:
|
||||||
# TODO: make the lookup in a dict, not a loop. canUpdate is called for every item.
|
# TODO: make the lookup in a dict, not a loop. canUpdate is called for every item.
|
||||||
|
@ -432,6 +428,7 @@ class Toolbox(QObject, Extension):
|
||||||
Logger.log("i", "Toolbox: Requesting %s metadata from server.", type)
|
Logger.log("i", "Toolbox: Requesting %s metadata from server.", type)
|
||||||
request = QNetworkRequest(self._request_urls[type])
|
request = QNetworkRequest(self._request_urls[type])
|
||||||
request.setRawHeader(*self._request_header)
|
request.setRawHeader(*self._request_header)
|
||||||
|
if self._network_manager:
|
||||||
self._network_manager.get(request)
|
self._network_manager.get(request)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
|
@ -441,15 +438,15 @@ class Toolbox(QObject, Extension):
|
||||||
self._download_request = QNetworkRequest(url)
|
self._download_request = QNetworkRequest(url)
|
||||||
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
|
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
|
||||||
# Patch for Qt 5.6-5.8
|
# Patch for Qt 5.6-5.8
|
||||||
self._download_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
|
cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
|
||||||
if hasattr(QNetworkRequest, "RedirectPolicyAttribute"):
|
if hasattr(QNetworkRequest, "RedirectPolicyAttribute"):
|
||||||
# Patch for Qt 5.9+
|
# Patch for Qt 5.9+
|
||||||
self._download_request.setAttribute(QNetworkRequest.RedirectPolicyAttribute, True)
|
cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.RedirectPolicyAttribute, True)
|
||||||
self._download_request.setRawHeader(*self._request_header)
|
cast(QNetworkRequest, self._download_request).setRawHeader(*self._request_header)
|
||||||
self._download_reply = self._network_manager.get(self._download_request)
|
self._download_reply = cast(QNetworkAccessManager, self._network_manager).get(self._download_request)
|
||||||
self.setDownloadProgress(0)
|
self.setDownloadProgress(0)
|
||||||
self.setIsDownloading(True)
|
self.setIsDownloading(True)
|
||||||
self._download_reply.downloadProgress.connect(self._onDownloadProgress)
|
cast(QNetworkReply, self._download_reply).downloadProgress.connect(self._onDownloadProgress)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def cancelDownload(self) -> None:
|
def cancelDownload(self) -> None:
|
||||||
|
@ -475,7 +472,6 @@ class Toolbox(QObject, Extension):
|
||||||
self.resetDownload()
|
self.resetDownload()
|
||||||
|
|
||||||
def _onRequestFinished(self, reply: QNetworkReply) -> None:
|
def _onRequestFinished(self, reply: QNetworkReply) -> None:
|
||||||
|
|
||||||
if reply.error() == QNetworkReply.TimeoutError:
|
if reply.error() == QNetworkReply.TimeoutError:
|
||||||
Logger.log("w", "Got a timeout.")
|
Logger.log("w", "Got a timeout.")
|
||||||
self.setViewPage("errored")
|
self.setViewPage("errored")
|
||||||
|
@ -551,12 +547,12 @@ class Toolbox(QObject, Extension):
|
||||||
self.setDownloadProgress(new_progress)
|
self.setDownloadProgress(new_progress)
|
||||||
if bytes_sent == bytes_total:
|
if bytes_sent == bytes_total:
|
||||||
self.setIsDownloading(False)
|
self.setIsDownloading(False)
|
||||||
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
cast(QNetworkReply, self._download_reply).downloadProgress.disconnect(self._onDownloadProgress)
|
||||||
# Must not delete the temporary file on Windows
|
# Must not delete the temporary file on Windows
|
||||||
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
||||||
file_path = self._temp_plugin_file.name
|
file_path = self._temp_plugin_file.name
|
||||||
# Write first and close, otherwise on Windows, it cannot read the file
|
# Write first and close, otherwise on Windows, it cannot read the file
|
||||||
self._temp_plugin_file.write(self._download_reply.readAll())
|
self._temp_plugin_file.write(cast(QNetworkReply, self._download_reply).readAll())
|
||||||
self._temp_plugin_file.close()
|
self._temp_plugin_file.close()
|
||||||
self._onDownloadComplete(file_path)
|
self._onDownloadComplete(file_path)
|
||||||
|
|
||||||
|
@ -577,13 +573,13 @@ class Toolbox(QObject, Extension):
|
||||||
|
|
||||||
# Getter & Setters for Properties:
|
# Getter & Setters for Properties:
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
def setDownloadProgress(self, progress: Union[int, float]) -> None:
|
def setDownloadProgress(self, progress: float) -> None:
|
||||||
if progress != self._download_progress:
|
if progress != self._download_progress:
|
||||||
self._download_progress = progress
|
self._download_progress = progress
|
||||||
self.onDownloadProgressChanged.emit()
|
self.onDownloadProgressChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(int, fset = setDownloadProgress, notify = onDownloadProgressChanged)
|
@pyqtProperty(int, fset = setDownloadProgress, notify = onDownloadProgressChanged)
|
||||||
def downloadProgress(self) -> int:
|
def downloadProgress(self) -> float:
|
||||||
return self._download_progress
|
return self._download_progress
|
||||||
|
|
||||||
def setIsDownloading(self, is_downloading: bool) -> None:
|
def setIsDownloading(self, is_downloading: bool) -> None:
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Any, cast, Set, Tuple, Union
|
||||||
|
|
||||||
|
from UM.FileHandler.FileHandler import FileHandler
|
||||||
from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary).
|
from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary).
|
||||||
from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer asynchronously.
|
from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer asynchronously.
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
|
@ -13,6 +15,7 @@ from UM.OutputDevice import OutputDeviceError #To show that something went wrong
|
||||||
from UM.Scene.SceneNode import SceneNode #For typing.
|
from UM.Scene.SceneNode import SceneNode #For typing.
|
||||||
from UM.Version import Version #To check against firmware versions for support.
|
from UM.Version import Version #To check against firmware versions for support.
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||||
|
@ -25,7 +28,7 @@ from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
|
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
|
||||||
|
|
||||||
from time import time, sleep
|
from time import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, Dict, List
|
from typing import Optional, Dict, List
|
||||||
|
|
||||||
|
@ -44,15 +47,15 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
# Inheritance doesn't seem to work. Tying them together does work, but i'm open for better suggestions.
|
# Inheritance doesn't seem to work. Tying them together does work, but i'm open for better suggestions.
|
||||||
clusterPrintersChanged = pyqtSignal()
|
clusterPrintersChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, device_id, address, properties, parent = None):
|
def __init__(self, device_id, address, properties, parent = None) -> None:
|
||||||
super().__init__(device_id = device_id, address = address, properties=properties, parent = parent)
|
super().__init__(device_id = device_id, address = address, properties=properties, parent = parent)
|
||||||
self._api_prefix = "/cluster-api/v1/"
|
self._api_prefix = "/cluster-api/v1/"
|
||||||
|
|
||||||
self._number_of_extruders = 2
|
self._number_of_extruders = 2
|
||||||
|
|
||||||
self._dummy_lambdas = set()
|
self._dummy_lambdas = ("", {}, io.BytesIO()) #type: Tuple[str, Dict, Union[io.StringIO, io.BytesIO]]
|
||||||
|
|
||||||
self._print_jobs = []
|
self._print_jobs = [] # type: List[PrintJobOutputModel]
|
||||||
|
|
||||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
|
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
|
||||||
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")
|
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")
|
||||||
|
@ -60,18 +63,18 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
# See comments about this hack with the clusterPrintersChanged signal
|
# See comments about this hack with the clusterPrintersChanged signal
|
||||||
self.printersChanged.connect(self.clusterPrintersChanged)
|
self.printersChanged.connect(self.clusterPrintersChanged)
|
||||||
|
|
||||||
self._accepts_commands = True
|
self._accepts_commands = True #type: bool
|
||||||
|
|
||||||
# Cluster does not have authentication, so default to authenticated
|
# Cluster does not have authentication, so default to authenticated
|
||||||
self._authentication_state = AuthState.Authenticated
|
self._authentication_state = AuthState.Authenticated
|
||||||
|
|
||||||
self._error_message = None
|
self._error_message = None #type: Optional[Message]
|
||||||
self._write_job_progress_message = None
|
self._write_job_progress_message = None #type: Optional[Message]
|
||||||
self._progress_message = None
|
self._progress_message = None #type: Optional[Message]
|
||||||
|
|
||||||
self._active_printer = None # type: Optional[PrinterOutputModel]
|
self._active_printer = None # type: Optional[PrinterOutputModel]
|
||||||
|
|
||||||
self._printer_selection_dialog = None
|
self._printer_selection_dialog = None #type: QObject
|
||||||
|
|
||||||
self.setPriority(3) # Make sure the output device gets selected above local file output
|
self.setPriority(3) # Make sure the output device gets selected above local file output
|
||||||
self.setName(self._id)
|
self.setName(self._id)
|
||||||
|
@ -80,25 +83,25 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network"))
|
self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network"))
|
||||||
|
|
||||||
self._printer_uuid_to_unique_name_mapping = {}
|
self._printer_uuid_to_unique_name_mapping = {} # type: Dict[str, str]
|
||||||
|
|
||||||
self._finished_jobs = []
|
self._finished_jobs = [] # type: List[PrintJobOutputModel]
|
||||||
|
|
||||||
self._cluster_size = int(properties.get(b"cluster_size", 0))
|
self._cluster_size = int(properties.get(b"cluster_size", 0))
|
||||||
|
|
||||||
self._latest_reply_handler = None
|
self._latest_reply_handler = None #type: Optional[QNetworkReply]
|
||||||
|
|
||||||
def requestWrite(self, nodes: List[SceneNode], file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
|
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||||
self.writeStarted.emit(self)
|
self.writeStarted.emit(self)
|
||||||
|
|
||||||
#Formats supported by this application (file types that we can actually write).
|
#Formats supported by this application (file types that we can actually write).
|
||||||
if file_handler:
|
if file_handler:
|
||||||
file_formats = file_handler.getSupportedFileTypesWrite()
|
file_formats = file_handler.getSupportedFileTypesWrite()
|
||||||
else:
|
else:
|
||||||
file_formats = Application.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
|
file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
|
||||||
|
|
||||||
#Create a list from the supported file formats string.
|
#Create a list from the supported file formats string.
|
||||||
machine_file_formats = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("file_formats").split(";")
|
machine_file_formats = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("file_formats").split(";")
|
||||||
machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
|
machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
|
||||||
#Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
|
#Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
|
||||||
if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"):
|
if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"):
|
||||||
|
@ -115,9 +118,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
#Just take the first file format available.
|
#Just take the first file format available.
|
||||||
if file_handler is not None:
|
if file_handler is not None:
|
||||||
writer = file_handler.getWriterByMimeType(preferred_format["mime_type"])
|
writer = file_handler.getWriterByMimeType(cast(str, preferred_format["mime_type"]))
|
||||||
else:
|
else:
|
||||||
writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(preferred_format["mime_type"])
|
writer = CuraApplication.getInstance().getMeshFileHandler().getWriterByMimeType(cast(str, preferred_format["mime_type"]))
|
||||||
|
|
||||||
#This function pauses with the yield, waiting on instructions on which printer it needs to print with.
|
#This function pauses with the yield, waiting on instructions on which printer it needs to print with.
|
||||||
self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
|
self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
|
||||||
|
@ -133,7 +136,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
def _spawnPrinterSelectionDialog(self):
|
def _spawnPrinterSelectionDialog(self):
|
||||||
if self._printer_selection_dialog is None:
|
if self._printer_selection_dialog is None:
|
||||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml")
|
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml")
|
||||||
self._printer_selection_dialog = Application.getInstance().createQmlComponent(path, {"OutputDevice": self})
|
self._printer_selection_dialog = CuraApplication.getInstance().createQmlComponent(path, {"OutputDevice": self})
|
||||||
if self._printer_selection_dialog is not None:
|
if self._printer_selection_dialog is not None:
|
||||||
self._printer_selection_dialog.show()
|
self._printer_selection_dialog.show()
|
||||||
|
|
||||||
|
@ -179,10 +182,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
target_printer = yield #Potentially wait on the user to select a target printer.
|
target_printer = yield #Potentially wait on the user to select a target printer.
|
||||||
|
|
||||||
# Using buffering greatly reduces the write time for many lines of gcode
|
# Using buffering greatly reduces the write time for many lines of gcode
|
||||||
|
|
||||||
|
stream = io.BytesIO() # type: Union[io.BytesIO, io.StringIO]# Binary mode.
|
||||||
if preferred_format["mode"] == FileWriter.OutputMode.TextMode:
|
if preferred_format["mode"] == FileWriter.OutputMode.TextMode:
|
||||||
stream = io.StringIO()
|
stream = io.StringIO()
|
||||||
else: #Binary mode.
|
|
||||||
stream = io.BytesIO()
|
|
||||||
|
|
||||||
job = WriteFileJob(writer, stream, nodes, preferred_format["mode"])
|
job = WriteFileJob(writer, stream, nodes, preferred_format["mode"])
|
||||||
|
|
||||||
|
@ -198,9 +201,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
yield True #Return that we had success!
|
yield True #Return that we had success!
|
||||||
yield #To prevent having to catch the StopIteration exception.
|
yield #To prevent having to catch the StopIteration exception.
|
||||||
|
|
||||||
from cura.Utils.Threading import call_on_qt_thread
|
def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None:
|
||||||
|
|
||||||
def _sendPrintJobWaitOnWriteJobFinished(self, job):
|
|
||||||
self._write_job_progress_message.hide()
|
self._write_job_progress_message.hide()
|
||||||
|
|
||||||
self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1,
|
self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1,
|
||||||
|
@ -221,40 +222,41 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
# Add user name to the print_job
|
# Add user name to the print_job
|
||||||
parts.append(self._createFormPart("name=owner", bytes(self._getUserName(), "utf-8"), "text/plain"))
|
parts.append(self._createFormPart("name=owner", bytes(self._getUserName(), "utf-8"), "text/plain"))
|
||||||
|
|
||||||
file_name = Application.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"]
|
file_name = CuraApplication.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"]
|
||||||
|
|
||||||
output = stream.getvalue() #Either str or bytes depending on the output mode.
|
output = stream.getvalue() #Either str or bytes depending on the output mode.
|
||||||
if isinstance(stream, io.StringIO):
|
if isinstance(stream, io.StringIO):
|
||||||
output = output.encode("utf-8")
|
output = cast(str, output).encode("utf-8")
|
||||||
|
output = cast(bytes, output)
|
||||||
|
|
||||||
parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, output))
|
parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, output))
|
||||||
|
|
||||||
self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress)
|
self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, on_finished = self._onPostPrintJobFinished, on_progress = self._onUploadPrintJobProgress)
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = activePrinterChanged)
|
@pyqtProperty(QObject, notify = activePrinterChanged)
|
||||||
def activePrinter(self) -> Optional[PrinterOutputModel]:
|
def activePrinter(self) -> Optional[PrinterOutputModel]:
|
||||||
return self._active_printer
|
return self._active_printer
|
||||||
|
|
||||||
@pyqtSlot(QObject)
|
@pyqtSlot(QObject)
|
||||||
def setActivePrinter(self, printer: Optional[PrinterOutputModel]):
|
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
|
||||||
if self._active_printer != printer:
|
if self._active_printer != printer:
|
||||||
if self._active_printer and self._active_printer.camera:
|
if self._active_printer and self._active_printer.camera:
|
||||||
self._active_printer.camera.stop()
|
self._active_printer.camera.stop()
|
||||||
self._active_printer = printer
|
self._active_printer = printer
|
||||||
self.activePrinterChanged.emit()
|
self.activePrinterChanged.emit()
|
||||||
|
|
||||||
def _onPostPrintJobFinished(self, reply):
|
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
|
||||||
self._progress_message.hide()
|
self._progress_message.hide()
|
||||||
self._compressing_gcode = False
|
self._compressing_gcode = False
|
||||||
self._sending_gcode = False
|
self._sending_gcode = False
|
||||||
|
|
||||||
def _onUploadPrintJobProgress(self, bytes_sent:int, bytes_total:int):
|
def _onUploadPrintJobProgress(self, bytes_sent: int, bytes_total: int) -> None:
|
||||||
if bytes_total > 0:
|
if bytes_total > 0:
|
||||||
new_progress = bytes_sent / bytes_total * 100
|
new_progress = bytes_sent / bytes_total * 100
|
||||||
# Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
|
# Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
|
||||||
# timeout responses if this happens.
|
# timeout responses if this happens.
|
||||||
self._last_response_time = time()
|
self._last_response_time = time()
|
||||||
if new_progress > self._progress_message.getProgress():
|
if self._progress_message and new_progress > self._progress_message.getProgress():
|
||||||
self._progress_message.show() # Ensure that the message is visible.
|
self._progress_message.show() # Ensure that the message is visible.
|
||||||
self._progress_message.setProgress(bytes_sent / bytes_total * 100)
|
self._progress_message.setProgress(bytes_sent / bytes_total * 100)
|
||||||
|
|
||||||
|
@ -271,16 +273,18 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._success_message.actionTriggered.connect(self._successMessageActionTriggered)
|
self._success_message.actionTriggered.connect(self._successMessageActionTriggered)
|
||||||
self._success_message.show()
|
self._success_message.show()
|
||||||
else:
|
else:
|
||||||
|
if self._progress_message is not None:
|
||||||
self._progress_message.setProgress(0)
|
self._progress_message.setProgress(0)
|
||||||
self._progress_message.hide()
|
self._progress_message.hide()
|
||||||
|
|
||||||
def _progressMessageActionTriggered(self, message_id: Optional[str] = None, action_id: Optional[str] = None) -> None:
|
def _progressMessageActionTriggered(self, message_id: Optional[str] = None, action_id: Optional[str] = None) -> None:
|
||||||
if action_id == "Abort":
|
if action_id == "Abort":
|
||||||
Logger.log("d", "User aborted sending print to remote.")
|
Logger.log("d", "User aborted sending print to remote.")
|
||||||
|
if self._progress_message is not None:
|
||||||
self._progress_message.hide()
|
self._progress_message.hide()
|
||||||
self._compressing_gcode = False
|
self._compressing_gcode = False
|
||||||
self._sending_gcode = False
|
self._sending_gcode = False
|
||||||
Application.getInstance().getController().setActiveStage("PrepareStage")
|
CuraApplication.getInstance().getController().setActiveStage("PrepareStage")
|
||||||
|
|
||||||
# After compressing the sliced model Cura sends data to printer, to stop receiving updates from the request
|
# After compressing the sliced model Cura sends data to printer, to stop receiving updates from the request
|
||||||
# the "reply" should be disconnected
|
# the "reply" should be disconnected
|
||||||
|
@ -290,7 +294,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
def _successMessageActionTriggered(self, message_id: Optional[str] = None, action_id: Optional[str] = None) -> None:
|
def _successMessageActionTriggered(self, message_id: Optional[str] = None, action_id: Optional[str] = None) -> None:
|
||||||
if action_id == "View":
|
if action_id == "View":
|
||||||
Application.getInstance().getController().setActiveStage("MonitorStage")
|
CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def openPrintJobControlPanel(self) -> None:
|
def openPrintJobControlPanel(self) -> None:
|
||||||
|
@ -315,8 +319,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"]
|
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"]
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", notify = clusterPrintersChanged)
|
@pyqtProperty("QVariantList", notify = clusterPrintersChanged)
|
||||||
def connectedPrintersTypeCount(self) -> List[PrinterOutputModel]:
|
def connectedPrintersTypeCount(self) -> List[Dict[str, str]]:
|
||||||
printer_count = {}
|
printer_count = {} # type: Dict[str, int]
|
||||||
for printer in self._printers:
|
for printer in self._printers:
|
||||||
if printer.type in printer_count:
|
if printer.type in printer_count:
|
||||||
printer_count[printer.type] += 1
|
printer_count[printer.type] += 1
|
||||||
|
@ -324,7 +328,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
printer_count[printer.type] = 1
|
printer_count[printer.type] = 1
|
||||||
result = []
|
result = []
|
||||||
for machine_type in printer_count:
|
for machine_type in printer_count:
|
||||||
result.append({"machine_type": machine_type, "count": printer_count[machine_type]})
|
result.append({"machine_type": machine_type, "count": str(printer_count[machine_type])})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@pyqtSlot(int, result = str)
|
@pyqtSlot(int, result = str)
|
||||||
|
@ -367,10 +371,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._finished_jobs = finished_jobs
|
self._finished_jobs = finished_jobs
|
||||||
|
|
||||||
def _update(self) -> None:
|
def _update(self) -> None:
|
||||||
if not super()._update():
|
super()._update()
|
||||||
return
|
self.get("printers/", on_finished = self._onGetPrintersDataFinished)
|
||||||
self.get("printers/", onFinished=self._onGetPrintersDataFinished)
|
self.get("print_jobs/", on_finished = self._onGetPrintJobsFinished)
|
||||||
self.get("print_jobs/", onFinished=self._onGetPrintJobsFinished)
|
|
||||||
|
|
||||||
def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
|
def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
|
||||||
if not checkValidGetReply(reply):
|
if not checkValidGetReply(reply):
|
||||||
|
@ -409,7 +412,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
removed_jobs = [print_job for print_job in self._print_jobs if print_job not in print_jobs_seen]
|
removed_jobs = [print_job for print_job in self._print_jobs if print_job not in print_jobs_seen]
|
||||||
|
|
||||||
for removed_job in removed_jobs:
|
for removed_job in removed_jobs:
|
||||||
job_list_changed |= self._removeJob(removed_job)
|
job_list_changed = job_list_changed or self._removeJob(removed_job)
|
||||||
|
|
||||||
if job_list_changed:
|
if job_list_changed:
|
||||||
self.printJobsChanged.emit() # Do a single emit for all print job changes.
|
self.printJobsChanged.emit() # Do a single emit for all print job changes.
|
||||||
|
@ -443,27 +446,27 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
if removed_printers or printer_list_changed:
|
if removed_printers or printer_list_changed:
|
||||||
self.printersChanged.emit()
|
self.printersChanged.emit()
|
||||||
|
|
||||||
def _createPrinterModel(self, data: Dict) -> PrinterOutputModel:
|
def _createPrinterModel(self, data: Dict[str, Any]) -> PrinterOutputModel:
|
||||||
printer = PrinterOutputModel(output_controller = ClusterUM3PrinterOutputController(self),
|
printer = PrinterOutputModel(output_controller = ClusterUM3PrinterOutputController(self),
|
||||||
number_of_extruders = self._number_of_extruders)
|
number_of_extruders = self._number_of_extruders)
|
||||||
printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream"))
|
printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream"))
|
||||||
self._printers.append(printer)
|
self._printers.append(printer)
|
||||||
return printer
|
return printer
|
||||||
|
|
||||||
def _createPrintJobModel(self, data: Dict) -> PrintJobOutputModel:
|
def _createPrintJobModel(self, data: Dict[str, Any]) -> PrintJobOutputModel:
|
||||||
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
|
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
|
||||||
key=data["uuid"], name= data["name"])
|
key=data["uuid"], name= data["name"])
|
||||||
print_job.stateChanged.connect(self._printJobStateChanged)
|
print_job.stateChanged.connect(self._printJobStateChanged)
|
||||||
self._print_jobs.append(print_job)
|
self._print_jobs.append(print_job)
|
||||||
return print_job
|
return print_job
|
||||||
|
|
||||||
def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict) -> None:
|
def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict[str, Any]) -> None:
|
||||||
print_job.updateTimeTotal(data["time_total"])
|
print_job.updateTimeTotal(data["time_total"])
|
||||||
print_job.updateTimeElapsed(data["time_elapsed"])
|
print_job.updateTimeElapsed(data["time_elapsed"])
|
||||||
print_job.updateState(data["status"])
|
print_job.updateState(data["status"])
|
||||||
print_job.updateOwner(data["owner"])
|
print_job.updateOwner(data["owner"])
|
||||||
|
|
||||||
def _updatePrinter(self, printer: PrinterOutputModel, data: Dict) -> None:
|
def _updatePrinter(self, printer: PrinterOutputModel, data: Dict[str, Any]) -> None:
|
||||||
# For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
|
# For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
|
||||||
# Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
|
# Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
|
||||||
self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"]
|
self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"]
|
||||||
|
@ -518,7 +521,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
brand=brand, color=color, name=name)
|
brand=brand, color=color, name=name)
|
||||||
extruder.updateActiveMaterial(material)
|
extruder.updateActiveMaterial(material)
|
||||||
|
|
||||||
def _removeJob(self, job: PrintJobOutputModel):
|
def _removeJob(self, job: PrintJobOutputModel) -> bool:
|
||||||
if job not in self._print_jobs:
|
if job not in self._print_jobs:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -529,23 +532,23 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _removePrinter(self, printer: PrinterOutputModel):
|
def _removePrinter(self, printer: PrinterOutputModel) -> None:
|
||||||
self._printers.remove(printer)
|
self._printers.remove(printer)
|
||||||
if self._active_printer == printer:
|
if self._active_printer == printer:
|
||||||
self._active_printer = None
|
self._active_printer = None
|
||||||
self.activePrinterChanged.emit()
|
self.activePrinterChanged.emit()
|
||||||
|
|
||||||
|
|
||||||
def loadJsonFromReply(reply):
|
def loadJsonFromReply(reply: QNetworkReply) -> Optional[List[Dict[str, Any]]]:
|
||||||
try:
|
try:
|
||||||
result = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
result = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
Logger.logException("w", "Unable to decode JSON from reply.")
|
Logger.logException("w", "Unable to decode JSON from reply.")
|
||||||
return
|
return None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def checkValidGetReply(reply):
|
def checkValidGetReply(reply: QNetworkReply) -> bool:
|
||||||
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||||
|
|
||||||
if status_code != 200:
|
if status_code != 200:
|
||||||
|
@ -554,7 +557,8 @@ def checkValidGetReply(reply):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def findByKey(list, key):
|
def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
|
||||||
for item in list:
|
for item in list:
|
||||||
if item.key == key:
|
if item.key == key:
|
||||||
return item
|
return item
|
||||||
|
return None
|
|
@ -1,43 +1,47 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import time
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.MachineAction import MachineAction
|
from cura.MachineAction import MachineAction
|
||||||
|
|
||||||
|
from .UM3OutputDevicePlugin import UM3OutputDevicePlugin
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
class DiscoverUM3Action(MachineAction):
|
class DiscoverUM3Action(MachineAction):
|
||||||
discoveredDevicesChanged = pyqtSignal()
|
discoveredDevicesChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__("DiscoverUM3Action", catalog.i18nc("@action","Connect via Network"))
|
super().__init__("DiscoverUM3Action", catalog.i18nc("@action","Connect via Network"))
|
||||||
self._qml_url = "DiscoverUM3Action.qml"
|
self._qml_url = "DiscoverUM3Action.qml"
|
||||||
|
|
||||||
self._network_plugin = None
|
self._network_plugin = None #type: Optional[UM3OutputDevicePlugin]
|
||||||
|
|
||||||
self.__additional_components_context = None
|
self.__additional_components_view = None #type: Optional[QObject]
|
||||||
self.__additional_component = None
|
|
||||||
self.__additional_components_view = None
|
|
||||||
|
|
||||||
Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView)
|
CuraApplication.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView)
|
||||||
|
|
||||||
self._last_zero_conf_event_time = time.time()
|
self._last_zero_conf_event_time = time.time() #type: float
|
||||||
|
|
||||||
# Time to wait after a zero-conf service change before allowing a zeroconf reset
|
# Time to wait after a zero-conf service change before allowing a zeroconf reset
|
||||||
self._zero_conf_change_grace_period = 0.25
|
self._zero_conf_change_grace_period = 0.25 #type: float
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def startDiscovery(self):
|
def startDiscovery(self):
|
||||||
if not self._network_plugin:
|
if not self._network_plugin:
|
||||||
Logger.log("d", "Starting device discovery.")
|
Logger.log("d", "Starting device discovery.")
|
||||||
self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
|
self._network_plugin = CuraApplication.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
|
||||||
self._network_plugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged)
|
self._network_plugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged)
|
||||||
self.discoveredDevicesChanged.emit()
|
self.discoveredDevicesChanged.emit()
|
||||||
|
|
||||||
|
@ -93,16 +97,16 @@ class DiscoverUM3Action(MachineAction):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def setGroupName(self, group_name):
|
def setGroupName(self, group_name: str) -> None:
|
||||||
Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name)
|
Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name)
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
meta_data = global_container_stack.getMetaData()
|
meta_data = global_container_stack.getMetaData()
|
||||||
if "connect_group_name" in meta_data:
|
if "connect_group_name" in meta_data:
|
||||||
previous_connect_group_name = meta_data["connect_group_name"]
|
previous_connect_group_name = meta_data["connect_group_name"]
|
||||||
global_container_stack.setMetaDataEntry("connect_group_name", group_name)
|
global_container_stack.setMetaDataEntry("connect_group_name", group_name)
|
||||||
# Find all the places where there is the same group name and change it accordingly
|
# Find all the places where there is the same group name and change it accordingly
|
||||||
Application.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name)
|
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name)
|
||||||
else:
|
else:
|
||||||
global_container_stack.addMetaDataEntry("connect_group_name", group_name)
|
global_container_stack.addMetaDataEntry("connect_group_name", group_name)
|
||||||
global_container_stack.addMetaDataEntry("hidden", False)
|
global_container_stack.addMetaDataEntry("hidden", False)
|
||||||
|
@ -112,9 +116,9 @@ class DiscoverUM3Action(MachineAction):
|
||||||
self._network_plugin.reCheckConnections()
|
self._network_plugin.reCheckConnections()
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def setKey(self, key):
|
def setKey(self, key: str) -> None:
|
||||||
Logger.log("d", "Attempting to set the network key of the active machine to %s", key)
|
Logger.log("d", "Attempting to set the network key of the active machine to %s", key)
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
meta_data = global_container_stack.getMetaData()
|
meta_data = global_container_stack.getMetaData()
|
||||||
if "um_network_key" in meta_data:
|
if "um_network_key" in meta_data:
|
||||||
|
@ -124,7 +128,7 @@ class DiscoverUM3Action(MachineAction):
|
||||||
Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key)
|
Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key)
|
||||||
global_container_stack.removeMetaDataEntry("network_authentication_id")
|
global_container_stack.removeMetaDataEntry("network_authentication_id")
|
||||||
global_container_stack.removeMetaDataEntry("network_authentication_key")
|
global_container_stack.removeMetaDataEntry("network_authentication_key")
|
||||||
Application.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key)
|
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key)
|
||||||
else:
|
else:
|
||||||
global_container_stack.addMetaDataEntry("um_network_key", key)
|
global_container_stack.addMetaDataEntry("um_network_key", key)
|
||||||
|
|
||||||
|
@ -134,7 +138,7 @@ class DiscoverUM3Action(MachineAction):
|
||||||
|
|
||||||
@pyqtSlot(result = str)
|
@pyqtSlot(result = str)
|
||||||
def getStoredKey(self) -> str:
|
def getStoredKey(self) -> str:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
meta_data = global_container_stack.getMetaData()
|
meta_data = global_container_stack.getMetaData()
|
||||||
if "um_network_key" in meta_data:
|
if "um_network_key" in meta_data:
|
||||||
|
@ -149,12 +153,12 @@ class DiscoverUM3Action(MachineAction):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtSlot(str, result = bool)
|
@pyqtSlot(str, result = bool)
|
||||||
def existsKey(self, key) -> bool:
|
def existsKey(self, key: str) -> bool:
|
||||||
return Application.getInstance().getMachineManager().existNetworkInstances(network_key = key)
|
return CuraApplication.getInstance().getMachineManager().existNetworkInstances(network_key = key)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def loadConfigurationFromPrinter(self):
|
def loadConfigurationFromPrinter(self) -> None:
|
||||||
machine_manager = Application.getInstance().getMachineManager()
|
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||||
hotend_ids = machine_manager.printerOutputDevices[0].hotendIds
|
hotend_ids = machine_manager.printerOutputDevices[0].hotendIds
|
||||||
for index in range(len(hotend_ids)):
|
for index in range(len(hotend_ids)):
|
||||||
machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotend_ids[index])
|
machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotend_ids[index])
|
||||||
|
@ -162,16 +166,16 @@ class DiscoverUM3Action(MachineAction):
|
||||||
for index in range(len(material_ids)):
|
for index in range(len(material_ids)):
|
||||||
machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, material_ids[index])
|
machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, material_ids[index])
|
||||||
|
|
||||||
def _createAdditionalComponentsView(self):
|
def _createAdditionalComponentsView(self) -> None:
|
||||||
Logger.log("d", "Creating additional ui components for UM3.")
|
Logger.log("d", "Creating additional ui components for UM3.")
|
||||||
|
|
||||||
# Create networking dialog
|
# Create networking dialog
|
||||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml")
|
path = os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml")
|
||||||
self.__additional_components_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
|
||||||
if not self.__additional_components_view:
|
if not self.__additional_components_view:
|
||||||
Logger.log("w", "Could not create ui components for UM3.")
|
Logger.log("w", "Could not create ui components for UM3.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create extra components
|
# Create extra components
|
||||||
Application.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton"))
|
CuraApplication.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton"))
|
||||||
Application.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo"))
|
CuraApplication.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo"))
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from UM.FileHandler.FileHandler import FileHandler
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||||
|
@ -9,12 +14,11 @@ from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Application import Application
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QNetworkRequest
|
from PyQt5.QtNetwork import QNetworkRequest
|
||||||
from PyQt5.QtCore import QTimer, QCoreApplication
|
from PyQt5.QtCore import QTimer
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from .LegacyUM3PrinterOutputController import LegacyUM3PrinterOutputController
|
from .LegacyUM3PrinterOutputController import LegacyUM3PrinterOutputController
|
||||||
|
@ -39,7 +43,7 @@ i18n_catalog = i18nCatalog("cura")
|
||||||
# 4. At this point the machine either has the state Authenticated or AuthenticationDenied.
|
# 4. At this point the machine either has the state Authenticated or AuthenticationDenied.
|
||||||
# 5. As a final step, we verify the authentication, as this forces the QT manager to setup the authenticator.
|
# 5. As a final step, we verify the authentication, as this forces the QT manager to setup the authenticator.
|
||||||
class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
def __init__(self, device_id, address: str, properties, parent = None):
|
def __init__(self, device_id, address: str, properties, parent = None) -> None:
|
||||||
super().__init__(device_id = device_id, address = address, properties = properties, parent = parent)
|
super().__init__(device_id = device_id, address = address, properties = properties, parent = parent)
|
||||||
self._api_prefix = "/api/v1/"
|
self._api_prefix = "/api/v1/"
|
||||||
self._number_of_extruders = 2
|
self._number_of_extruders = 2
|
||||||
|
@ -125,7 +129,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
def connect(self):
|
def connect(self):
|
||||||
super().connect()
|
super().connect()
|
||||||
self._setupMessages()
|
self._setupMessages()
|
||||||
global_container = Application.getInstance().getGlobalContainerStack()
|
global_container = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if global_container:
|
if global_container:
|
||||||
self._authentication_id = global_container.getMetaDataEntry("network_authentication_id", None)
|
self._authentication_id = global_container.getMetaDataEntry("network_authentication_id", None)
|
||||||
self._authentication_key = global_container.getMetaDataEntry("network_authentication_key", None)
|
self._authentication_key = global_container.getMetaDataEntry("network_authentication_key", None)
|
||||||
|
@ -168,7 +172,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
# NotImplementedError. We can simply ignore these.
|
# NotImplementedError. We can simply ignore these.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
|
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||||
if not self.activePrinter:
|
if not self.activePrinter:
|
||||||
# No active printer. Unable to write
|
# No active printer. Unable to write
|
||||||
return
|
return
|
||||||
|
@ -183,8 +187,8 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
self.writeStarted.emit(self)
|
self.writeStarted.emit(self)
|
||||||
|
|
||||||
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
|
gcode_dict = getattr(CuraApplication.getInstance().getController().getScene(), "gcode_dict", [])
|
||||||
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
gcode_list = gcode_dict[active_build_plate_id]
|
gcode_list = gcode_dict[active_build_plate_id]
|
||||||
|
|
||||||
if not gcode_list:
|
if not gcode_list:
|
||||||
|
@ -203,7 +207,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
for error in errors:
|
for error in errors:
|
||||||
detailed_text += error + "\n"
|
detailed_text += error + "\n"
|
||||||
|
|
||||||
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
|
CuraApplication.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
|
||||||
text,
|
text,
|
||||||
informative_text,
|
informative_text,
|
||||||
detailed_text,
|
detailed_text,
|
||||||
|
@ -225,7 +229,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
for warning in warnings:
|
for warning in warnings:
|
||||||
detailed_text += warning + "\n"
|
detailed_text += warning + "\n"
|
||||||
|
|
||||||
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
|
CuraApplication.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
|
||||||
text,
|
text,
|
||||||
informative_text,
|
informative_text,
|
||||||
detailed_text,
|
detailed_text,
|
||||||
|
@ -239,7 +243,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._startPrint()
|
self._startPrint()
|
||||||
|
|
||||||
# Notify the UI that a switch to the print monitor should happen
|
# Notify the UI that a switch to the print monitor should happen
|
||||||
Application.getInstance().getController().setActiveStage("MonitorStage")
|
CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
|
||||||
|
|
||||||
def _startPrint(self):
|
def _startPrint(self):
|
||||||
Logger.log("i", "Sending print job to printer.")
|
Logger.log("i", "Sending print job to printer.")
|
||||||
|
@ -264,7 +268,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
# Abort was called.
|
# Abort was called.
|
||||||
return
|
return
|
||||||
|
|
||||||
file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName
|
file_name = "%s.gcode.gz" % CuraApplication.getInstance().getPrintInformation().jobName
|
||||||
self.postForm("print_job", "form-data; name=\"file\";filename=\"%s\"" % file_name, compressed_gcode,
|
self.postForm("print_job", "form-data; name=\"file\";filename=\"%s\"" % file_name, compressed_gcode,
|
||||||
onFinished=self._onPostPrintJobFinished)
|
onFinished=self._onPostPrintJobFinished)
|
||||||
|
|
||||||
|
@ -276,7 +280,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._progress_message.hide()
|
self._progress_message.hide()
|
||||||
self._compressing_gcode = False
|
self._compressing_gcode = False
|
||||||
self._sending_gcode = False
|
self._sending_gcode = False
|
||||||
Application.getInstance().getController().setActiveStage("PrepareStage")
|
CuraApplication.getInstance().getController().setActiveStage("PrepareStage")
|
||||||
|
|
||||||
def _onPostPrintJobFinished(self, reply):
|
def _onPostPrintJobFinished(self, reply):
|
||||||
self._progress_message.hide()
|
self._progress_message.hide()
|
||||||
|
@ -301,7 +305,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
if button == QMessageBox.Yes:
|
if button == QMessageBox.Yes:
|
||||||
self._startPrint()
|
self._startPrint()
|
||||||
else:
|
else:
|
||||||
Application.getInstance().getController().setActiveStage("PrepareStage")
|
CuraApplication.getInstance().getController().setActiveStage("PrepareStage")
|
||||||
# For some unknown reason Cura on OSX will hang if we do the call back code
|
# For some unknown reason Cura on OSX will hang if we do the call back code
|
||||||
# immediately without first returning and leaving QML's event system.
|
# immediately without first returning and leaving QML's event system.
|
||||||
|
|
||||||
|
@ -309,7 +313,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
def _checkForErrors(self):
|
def _checkForErrors(self):
|
||||||
errors = []
|
errors = []
|
||||||
print_information = Application.getInstance().getPrintInformation()
|
print_information = CuraApplication.getInstance().getPrintInformation()
|
||||||
if not print_information.materialLengths:
|
if not print_information.materialLengths:
|
||||||
Logger.log("w", "There is no material length information. Unable to check for errors.")
|
Logger.log("w", "There is no material length information. Unable to check for errors.")
|
||||||
return errors
|
return errors
|
||||||
|
@ -329,7 +333,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
def _checkForWarnings(self):
|
def _checkForWarnings(self):
|
||||||
warnings = []
|
warnings = []
|
||||||
print_information = Application.getInstance().getPrintInformation()
|
print_information = CuraApplication.getInstance().getPrintInformation()
|
||||||
|
|
||||||
if not print_information.materialLengths:
|
if not print_information.materialLengths:
|
||||||
Logger.log("w", "There is no material length information. Unable to check for warnings.")
|
Logger.log("w", "There is no material length information. Unable to check for warnings.")
|
||||||
|
@ -452,7 +456,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._authentication_failed_message.show()
|
self._authentication_failed_message.show()
|
||||||
|
|
||||||
def _saveAuthentication(self):
|
def _saveAuthentication(self):
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
if "network_authentication_key" in global_container_stack.getMetaData():
|
if "network_authentication_key" in global_container_stack.getMetaData():
|
||||||
global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key)
|
global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key)
|
||||||
|
@ -465,7 +469,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id)
|
global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id)
|
||||||
|
|
||||||
# Force save so we are sure the data is not lost.
|
# Force save so we are sure the data is not lost.
|
||||||
Application.getInstance().saveStack(global_container_stack)
|
CuraApplication.getInstance().saveStack(global_container_stack)
|
||||||
Logger.log("i", "Authentication succeeded for id %s and key %s", self._authentication_id,
|
Logger.log("i", "Authentication succeeded for id %s and key %s", self._authentication_id,
|
||||||
self._getSafeAuthKey())
|
self._getSafeAuthKey())
|
||||||
else:
|
else:
|
||||||
|
@ -496,7 +500,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._authentication_id = None
|
self._authentication_id = None
|
||||||
|
|
||||||
self.post("auth/request",
|
self.post("auth/request",
|
||||||
json.dumps({"application": "Cura-" + Application.getInstance().getVersion(),
|
json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(),
|
||||||
"user": self._getUserName()}).encode(),
|
"user": self._getUserName()}).encode(),
|
||||||
onFinished=self._onRequestAuthenticationFinished)
|
onFinished=self._onRequestAuthenticationFinished)
|
||||||
|
|
||||||
|
@ -542,7 +546,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
"Got status code {status_code} while trying to get printer data".format(status_code=status_code))
|
"Got status code {status_code} while trying to get printer data".format(status_code=status_code))
|
||||||
|
|
||||||
def materialHotendChangedMessage(self, callback):
|
def materialHotendChangedMessage(self, callback):
|
||||||
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Sync with your printer"),
|
CuraApplication.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Sync with your printer"),
|
||||||
i18n_catalog.i18nc("@label",
|
i18n_catalog.i18nc("@label",
|
||||||
"Would you like to use your current printer configuration in Cura?"),
|
"Would you like to use your current printer configuration in Cura?"),
|
||||||
i18n_catalog.i18nc("@label",
|
i18n_catalog.i18nc("@label",
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Qt.Duration import DurationFormat
|
from UM.Qt.Duration import DurationFormat
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||||
|
@ -22,7 +22,7 @@ from threading import Thread, Event
|
||||||
from time import time, sleep
|
from time import time, sleep
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import Union, Optional, List
|
from typing import Union, Optional, List, cast
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import functools # Used for reduce
|
import functools # Used for reduce
|
||||||
|
@ -35,7 +35,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
firmwareProgressChanged = pyqtSignal()
|
firmwareProgressChanged = pyqtSignal()
|
||||||
firmwareUpdateStateChanged = pyqtSignal()
|
firmwareUpdateStateChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, serial_port: str, baud_rate: Optional[int] = None):
|
def __init__(self, serial_port: str, baud_rate: Optional[int] = None) -> None:
|
||||||
super().__init__(serial_port)
|
super().__init__(serial_port)
|
||||||
self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
|
self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
|
||||||
self.setShortDescription(catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print via USB"))
|
self.setShortDescription(catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print via USB"))
|
||||||
|
@ -68,7 +68,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
self._is_printing = False # A print is being sent.
|
self._is_printing = False # A print is being sent.
|
||||||
|
|
||||||
## Set when print is started in order to check running time.
|
## Set when print is started in order to check running time.
|
||||||
self._print_start_time = None # type: Optional[int]
|
self._print_start_time = None # type: Optional[float]
|
||||||
self._print_estimated_time = None # type: Optional[int]
|
self._print_estimated_time = None # type: Optional[int]
|
||||||
|
|
||||||
self._accepts_commands = True
|
self._accepts_commands = True
|
||||||
|
@ -83,7 +83,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB"))
|
self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB"))
|
||||||
|
|
||||||
# Queue for commands that need to be sent.
|
# Queue for commands that need to be sent.
|
||||||
self._command_queue = Queue()
|
self._command_queue = Queue() # type: Queue
|
||||||
# Event to indicate that an "ok" was received from the printer after sending a command.
|
# Event to indicate that an "ok" was received from the printer after sending a command.
|
||||||
self._command_received = Event()
|
self._command_received = Event()
|
||||||
self._command_received.set()
|
self._command_received.set()
|
||||||
|
@ -107,11 +107,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
# cancel any ongoing preheat timer before starting a print
|
# cancel any ongoing preheat timer before starting a print
|
||||||
self._printers[0].getController().stopPreheatTimers()
|
self._printers[0].getController().stopPreheatTimers()
|
||||||
|
|
||||||
Application.getInstance().getController().setActiveStage("MonitorStage")
|
CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
|
||||||
|
|
||||||
# find the G-code for the active build plate to print
|
# find the G-code for the active build plate to print
|
||||||
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict")
|
gcode_dict = getattr(CuraApplication.getInstance().getController().getScene(), "gcode_dict")
|
||||||
gcode_list = gcode_dict[active_build_plate_id]
|
gcode_list = gcode_dict[active_build_plate_id]
|
||||||
|
|
||||||
self._printGCode(gcode_list)
|
self._printGCode(gcode_list)
|
||||||
|
@ -121,7 +121,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
def showFirmwareInterface(self):
|
def showFirmwareInterface(self):
|
||||||
if self._firmware_view is None:
|
if self._firmware_view is None:
|
||||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
|
path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
|
||||||
self._firmware_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
|
||||||
|
|
||||||
self._firmware_view.show()
|
self._firmware_view.show()
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
self.setFirmwareUpdateState(FirmwareUpdateState.completed)
|
self.setFirmwareUpdateState(FirmwareUpdateState.completed)
|
||||||
|
|
||||||
# Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later.
|
# Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later.
|
||||||
Application.getInstance().callLater(self.connect)
|
CuraApplication.getInstance().callLater(self.connect)
|
||||||
|
|
||||||
@pyqtProperty(float, notify = firmwareProgressChanged)
|
@pyqtProperty(float, notify = firmwareProgressChanged)
|
||||||
def firmwareProgress(self):
|
def firmwareProgress(self):
|
||||||
|
@ -214,7 +214,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
self._gcode_position = 0
|
self._gcode_position = 0
|
||||||
self._print_start_time = time()
|
self._print_start_time = time()
|
||||||
|
|
||||||
self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))
|
self._print_estimated_time = int(CuraApplication.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))
|
||||||
|
|
||||||
for i in range(0, 4): # Push first 4 entries before accepting other inputs
|
for i in range(0, 4): # Push first 4 entries before accepting other inputs
|
||||||
self._sendNextGcodeLine()
|
self._sendNextGcodeLine()
|
||||||
|
@ -250,7 +250,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
except SerialException:
|
except SerialException:
|
||||||
Logger.log("w", "An exception occured while trying to create serial connection")
|
Logger.log("w", "An exception occured while trying to create serial connection")
|
||||||
return
|
return
|
||||||
container_stack = Application.getInstance().getGlobalContainerStack()
|
container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||||
num_extruders = container_stack.getProperty("machine_extruder_count", "value")
|
num_extruders = container_stack.getProperty("machine_extruder_count", "value")
|
||||||
# Ensure that a printer is created.
|
# Ensure that a printer is created.
|
||||||
self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)]
|
self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)]
|
||||||
|
@ -277,13 +277,12 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
if self._serial is None or self._connection_state != ConnectionState.connected:
|
if self._serial is None or self._connection_state != ConnectionState.connected:
|
||||||
return
|
return
|
||||||
|
|
||||||
if type(command == str):
|
new_command = cast(bytes, command) if type(command) is bytes else cast(str, command).encode() # type: bytes
|
||||||
command = command.encode()
|
if not new_command.endswith(b"\n"):
|
||||||
if not command.endswith(b"\n"):
|
new_command += b"\n"
|
||||||
command += b"\n"
|
|
||||||
try:
|
try:
|
||||||
self._command_received.clear()
|
self._command_received.clear()
|
||||||
self._serial.write(command)
|
self._serial.write(new_command)
|
||||||
except SerialTimeoutException:
|
except SerialTimeoutException:
|
||||||
Logger.log("w", "Timeout when sending command to printer via USB.")
|
Logger.log("w", "Timeout when sending command to printer via USB.")
|
||||||
self._command_received.set()
|
self._command_received.set()
|
||||||
|
|
|
@ -179,7 +179,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
|
||||||
|
|
||||||
return list(base_list)
|
return list(base_list)
|
||||||
|
|
||||||
__instance = None
|
__instance = None # type: USBPrinterOutputDeviceManager
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getInstance(cls, *args, **kwargs) -> "USBPrinterOutputDeviceManager":
|
def getInstance(cls, *args, **kwargs) -> "USBPrinterOutputDeviceManager":
|
||||||
|
|
|
@ -44,20 +44,18 @@ class Profile:
|
||||||
|
|
||||||
# Parse the general section.
|
# Parse the general section.
|
||||||
self._name = parser.get("general", "name")
|
self._name = parser.get("general", "name")
|
||||||
self._type = parser.get("general", "type", fallback = None)
|
self._type = parser.get("general", "type")
|
||||||
|
self._weight = None
|
||||||
if "weight" in parser["general"]:
|
if "weight" in parser["general"]:
|
||||||
self._weight = int(parser.get("general", "weight"))
|
self._weight = int(parser.get("general", "weight"))
|
||||||
else:
|
self._machine_type_id = parser.get("general", "machine_type")
|
||||||
self._weight = None
|
self._machine_variant_name = parser.get("general", "machine_variant")
|
||||||
self._machine_type_id = parser.get("general", "machine_type", fallback = None)
|
self._machine_instance_name = parser.get("general", "machine_instance")
|
||||||
self._machine_variant_name = parser.get("general", "machine_variant", fallback = None)
|
self._material_name = None
|
||||||
self._machine_instance_name = parser.get("general", "machine_instance", fallback = None)
|
|
||||||
if "material" in parser["general"]: #Note: Material name is unused in this upgrade.
|
if "material" in parser["general"]: #Note: Material name is unused in this upgrade.
|
||||||
self._material_name = parser.get("general", "material")
|
self._material_name = parser.get("general", "material")
|
||||||
elif self._type == "material":
|
elif self._type == "material":
|
||||||
self._material_name = parser.get("general", "name", fallback = None)
|
self._material_name = parser.get("general", "name")
|
||||||
else:
|
|
||||||
self._material_name = None
|
|
||||||
|
|
||||||
# Parse the settings.
|
# Parse the settings.
|
||||||
self._settings = {} # type: Dict[str,str]
|
self._settings = {} # type: Dict[str,str]
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import io
|
||||||
|
|
||||||
|
from UM.VersionUpgrade import VersionUpgrade
|
||||||
|
|
||||||
|
|
||||||
|
## Upgrades configurations from the state they were in at version 3.4 to the
|
||||||
|
# state they should be in at version 4.0.
|
||||||
|
class VersionUpgrade34to40(VersionUpgrade):
|
||||||
|
|
||||||
|
## Gets the version number from a CFG file in Uranium's 3.3 format.
|
||||||
|
#
|
||||||
|
# Since the format may change, this is implemented for the 3.3 format only
|
||||||
|
# and needs to be included in the version upgrade system rather than
|
||||||
|
# globally in Uranium.
|
||||||
|
#
|
||||||
|
# \param serialised The serialised form of a CFG file.
|
||||||
|
# \return The version number stored in the CFG file.
|
||||||
|
# \raises ValueError The format of the version number in the file is
|
||||||
|
# incorrect.
|
||||||
|
# \raises KeyError The format of the file is incorrect.
|
||||||
|
def getCfgVersion(self, serialised):
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialised)
|
||||||
|
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
|
||||||
|
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
|
||||||
|
return format_version * 1000000 + setting_version
|
||||||
|
|
||||||
|
## Upgrades Preferences to have the new version number.
|
||||||
|
def upgradePreferences(self, serialized, filename):
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialized)
|
||||||
|
|
||||||
|
# Update version number.
|
||||||
|
parser["general"]["version"] = "4"
|
||||||
|
if "metadata" not in parser:
|
||||||
|
parser["metadata"] = {}
|
||||||
|
parser["metadata"]["setting_version"] = "5"
|
||||||
|
|
||||||
|
result = io.StringIO()
|
||||||
|
parser.write(result)
|
||||||
|
return [filename], [result.getvalue()]
|
||||||
|
|
||||||
|
## Upgrades stacks to have the new version number.
|
||||||
|
def upgradeStack(self, serialized, filename):
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialized)
|
||||||
|
|
||||||
|
# Update version number.
|
||||||
|
parser["general"]["version"] = "4"
|
||||||
|
parser["metadata"]["setting_version"] = "5"
|
||||||
|
|
||||||
|
result = io.StringIO()
|
||||||
|
parser.write(result)
|
||||||
|
return [filename], [result.getvalue()]
|
||||||
|
|
||||||
|
## Upgrades instance containers to have the new version
|
||||||
|
# number.
|
||||||
|
def upgradeInstanceContainer(self, serialized, filename):
|
||||||
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
|
parser.read_string(serialized)
|
||||||
|
|
||||||
|
# Update version number.
|
||||||
|
parser["general"]["version"] = "4"
|
||||||
|
parser["metadata"]["setting_version"] = "5"
|
||||||
|
|
||||||
|
self._resetConcentric3DInfillPattern(parser)
|
||||||
|
|
||||||
|
result = io.StringIO()
|
||||||
|
parser.write(result)
|
||||||
|
return [filename], [result.getvalue()]
|
||||||
|
|
||||||
|
def _resetConcentric3DInfillPattern(self, parser):
|
||||||
|
if "values" not in parser:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Reset the patterns which are concentric 3d
|
||||||
|
for key in ("infill_pattern",
|
||||||
|
"support_pattern",
|
||||||
|
"support_interface_pattern",
|
||||||
|
"support_roof_pattern",
|
||||||
|
"support_bottom_pattern",
|
||||||
|
):
|
||||||
|
if key not in parser["values"]:
|
||||||
|
continue
|
||||||
|
if parser["values"][key] == "concentric_3d":
|
||||||
|
del parser["values"][key]
|
||||||
|
|
52
plugins/VersionUpgrade/VersionUpgrade34to40/__init__.py
Normal file
52
plugins/VersionUpgrade/VersionUpgrade34to40/__init__.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from . import VersionUpgrade34to40
|
||||||
|
|
||||||
|
upgrade = VersionUpgrade34to40.VersionUpgrade34to40()
|
||||||
|
|
||||||
|
|
||||||
|
def getMetaData():
|
||||||
|
return {
|
||||||
|
"version_upgrade": {
|
||||||
|
# From To Upgrade function
|
||||||
|
("preferences", 6000004): ("preferences", 6000005, upgrade.upgradePreferences),
|
||||||
|
|
||||||
|
("definition_changes", 4000004): ("definition_changes", 4000005, upgrade.upgradeInstanceContainer),
|
||||||
|
("quality_changes", 4000004): ("quality_changes", 4000005, upgrade.upgradeInstanceContainer),
|
||||||
|
("user", 4000004): ("user", 4000005, upgrade.upgradeInstanceContainer),
|
||||||
|
|
||||||
|
("machine_stack", 4000005): ("machine_stack", 4000005, upgrade.upgradeStack),
|
||||||
|
("extruder_train", 4000005): ("extruder_train", 4000005, upgrade.upgradeStack),
|
||||||
|
},
|
||||||
|
"sources": {
|
||||||
|
"preferences": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"."}
|
||||||
|
},
|
||||||
|
"machine_stack": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./machine_instances"}
|
||||||
|
},
|
||||||
|
"extruder_train": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./extruders"}
|
||||||
|
},
|
||||||
|
"definition_changes": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./definition_changes"}
|
||||||
|
},
|
||||||
|
"quality_changes": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./quality_changes"}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"get_version": upgrade.getCfgVersion,
|
||||||
|
"location": {"./user"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def register(app):
|
||||||
|
return { "version_upgrade": upgrade }
|
8
plugins/VersionUpgrade/VersionUpgrade34to40/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade34to40/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"name": "Version Upgrade 3.4 to 4.0",
|
||||||
|
"author": "Ultimaker B.V.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Upgrades configurations from Cura 3.4 to Cura 4.0.",
|
||||||
|
"api": 4,
|
||||||
|
"i18n-catalog": "cura"
|
||||||
|
}
|
|
@ -26,8 +26,8 @@ except ImportError:
|
||||||
DEFAULT_SUBDIV = 16 # Default subdivision factor for spheres, cones, and cylinders
|
DEFAULT_SUBDIV = 16 # Default subdivision factor for spheres, cones, and cylinders
|
||||||
EPSILON = 0.000001
|
EPSILON = 0.000001
|
||||||
|
|
||||||
class Shape:
|
|
||||||
|
|
||||||
|
class Shape:
|
||||||
# Expects verts in MeshBuilder-ready format, as a n by 3 mdarray
|
# Expects verts in MeshBuilder-ready format, as a n by 3 mdarray
|
||||||
# with vertices stored in rows
|
# with vertices stored in rows
|
||||||
def __init__(self, verts, faces, index_base, name):
|
def __init__(self, verts, faces, index_base, name):
|
||||||
|
@ -37,9 +37,10 @@ class Shape:
|
||||||
self.index_base = index_base
|
self.index_base = index_base
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
|
||||||
class X3DReader(MeshReader):
|
class X3DReader(MeshReader):
|
||||||
def __init__(self, application):
|
def __init__(self) -> None:
|
||||||
super().__init__(application)
|
super().__init__()
|
||||||
self._supported_extensions = [".x3d"]
|
self._supported_extensions = [".x3d"]
|
||||||
self._namespaces = {}
|
self._namespaces = {}
|
||||||
|
|
||||||
|
|
|
@ -15,5 +15,6 @@ def getMetaData():
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return { "mesh_reader": X3DReader.X3DReader(app) }
|
return {"mesh_reader": X3DReader.X3DReader()}
|
||||||
|
|
|
@ -6,8 +6,10 @@ import io
|
||||||
import json #To parse the product-to-id mapping file.
|
import json #To parse the product-to-id mapping file.
|
||||||
import os.path #To find the product-to-id mapping.
|
import os.path #To find the product-to-id mapping.
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional, cast
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Iterator
|
||||||
|
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -132,7 +134,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
"version": self.CurrentFdmMaterialVersion})
|
"version": self.CurrentFdmMaterialVersion})
|
||||||
|
|
||||||
## Begin Metadata Block
|
## Begin Metadata Block
|
||||||
builder.start("metadata")
|
builder.start("metadata") # type: ignore
|
||||||
|
|
||||||
metadata = copy.deepcopy(self.getMetaData())
|
metadata = copy.deepcopy(self.getMetaData())
|
||||||
# setting_version is derived from the "version" tag in the schema, so don't serialize it into a file
|
# setting_version is derived from the "version" tag in the schema, so don't serialize it into a file
|
||||||
|
@ -156,21 +158,21 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
metadata.pop("name", "")
|
metadata.pop("name", "")
|
||||||
|
|
||||||
## Begin Name Block
|
## Begin Name Block
|
||||||
builder.start("name")
|
builder.start("name") # type: ignore
|
||||||
|
|
||||||
builder.start("brand")
|
builder.start("brand") # type: ignore
|
||||||
builder.data(metadata.pop("brand", ""))
|
builder.data(metadata.pop("brand", ""))
|
||||||
builder.end("brand")
|
builder.end("brand")
|
||||||
|
|
||||||
builder.start("material")
|
builder.start("material") # type: ignore
|
||||||
builder.data(metadata.pop("material", ""))
|
builder.data(metadata.pop("material", ""))
|
||||||
builder.end("material")
|
builder.end("material")
|
||||||
|
|
||||||
builder.start("color")
|
builder.start("color") # type: ignore
|
||||||
builder.data(metadata.pop("color_name", ""))
|
builder.data(metadata.pop("color_name", ""))
|
||||||
builder.end("color")
|
builder.end("color")
|
||||||
|
|
||||||
builder.start("label")
|
builder.start("label") # type: ignore
|
||||||
builder.data(self.getName())
|
builder.data(self.getName())
|
||||||
builder.end("label")
|
builder.end("label")
|
||||||
|
|
||||||
|
@ -178,7 +180,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
## End Name Block
|
## End Name Block
|
||||||
|
|
||||||
for key, value in metadata.items():
|
for key, value in metadata.items():
|
||||||
builder.start(key)
|
builder.start(key) # type: ignore
|
||||||
if value is not None: #Nones get handled well by the builder.
|
if value is not None: #Nones get handled well by the builder.
|
||||||
#Otherwise the builder always expects a string.
|
#Otherwise the builder always expects a string.
|
||||||
#Deserialize expects the stringified version.
|
#Deserialize expects the stringified version.
|
||||||
|
@ -190,10 +192,10 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
## End Metadata Block
|
## End Metadata Block
|
||||||
|
|
||||||
## Begin Properties Block
|
## Begin Properties Block
|
||||||
builder.start("properties")
|
builder.start("properties") # type: ignore
|
||||||
|
|
||||||
for key, value in properties.items():
|
for key, value in properties.items():
|
||||||
builder.start(key)
|
builder.start(key) # type: ignore
|
||||||
builder.data(value)
|
builder.data(value)
|
||||||
builder.end(key)
|
builder.end(key)
|
||||||
|
|
||||||
|
@ -201,14 +203,14 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
## End Properties Block
|
## End Properties Block
|
||||||
|
|
||||||
## Begin Settings Block
|
## Begin Settings Block
|
||||||
builder.start("settings")
|
builder.start("settings") # type: ignore
|
||||||
|
|
||||||
if self.getMetaDataEntry("definition") == "fdmprinter":
|
if self.getMetaDataEntry("definition") == "fdmprinter":
|
||||||
for instance in self.findInstances():
|
for instance in self.findInstances():
|
||||||
self._addSettingElement(builder, instance)
|
self._addSettingElement(builder, instance)
|
||||||
|
|
||||||
machine_container_map = {}
|
machine_container_map = {} # type: Dict[str, InstanceContainer]
|
||||||
machine_variant_map = {}
|
machine_variant_map = {} # type: Dict[str, Dict[str, Any]]
|
||||||
|
|
||||||
variant_manager = CuraApplication.getInstance().getVariantManager()
|
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||||
|
|
||||||
|
@ -248,7 +250,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
product = product_name
|
product = product_name
|
||||||
break
|
break
|
||||||
|
|
||||||
builder.start("machine")
|
builder.start("machine") # type: ignore
|
||||||
builder.start("machine_identifier", {
|
builder.start("machine_identifier", {
|
||||||
"manufacturer": container.getMetaDataEntry("machine_manufacturer",
|
"manufacturer": container.getMetaDataEntry("machine_manufacturer",
|
||||||
definition_metadata.get("manufacturer", "Unknown")),
|
definition_metadata.get("manufacturer", "Unknown")),
|
||||||
|
@ -264,7 +266,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
self._addSettingElement(builder, instance)
|
self._addSettingElement(builder, instance)
|
||||||
|
|
||||||
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
|
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
|
||||||
buildplate_dict = {}
|
buildplate_dict = {} # type: Dict[str, Any]
|
||||||
for variant_name, variant_dict in machine_variant_map[definition_id].items():
|
for variant_name, variant_dict in machine_variant_map[definition_id].items():
|
||||||
variant_type = variant_dict["variant_node"].metadata["hardware_type"]
|
variant_type = variant_dict["variant_node"].metadata["hardware_type"]
|
||||||
from cura.Machines.VariantManager import VariantType
|
from cura.Machines.VariantManager import VariantType
|
||||||
|
@ -812,11 +814,14 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
if label is not None and label.text is not None:
|
if label is not None and label.text is not None:
|
||||||
base_metadata["name"] = label.text
|
base_metadata["name"] = label.text
|
||||||
else:
|
else:
|
||||||
|
if material is not None and color is not None:
|
||||||
base_metadata["name"] = cls._profile_name(material.text, color.text)
|
base_metadata["name"] = cls._profile_name(material.text, color.text)
|
||||||
|
else:
|
||||||
|
base_metadata["name"] = "Unknown Material"
|
||||||
|
|
||||||
base_metadata["brand"] = brand.text if brand.text is not None else "Unknown Brand"
|
base_metadata["brand"] = brand.text if brand is not None and brand.text is not None else "Unknown Brand"
|
||||||
base_metadata["material"] = material.text if material.text is not None else "Unknown Type"
|
base_metadata["material"] = material.text if material is not None and material.text is not None else "Unknown Type"
|
||||||
base_metadata["color_name"] = color.text if color.text is not None else "Unknown Color"
|
base_metadata["color_name"] = color.text if color is not None and color.text is not None else "Unknown Color"
|
||||||
continue
|
continue
|
||||||
|
|
||||||
#Setting_version is derived from the "version" tag in the schema earlier, so don't set it here.
|
#Setting_version is derived from the "version" tag in the schema earlier, so don't set it here.
|
||||||
|
@ -836,13 +841,13 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
tag_name = _tag_without_namespace(entry)
|
tag_name = _tag_without_namespace(entry)
|
||||||
property_values[tag_name] = entry.text
|
property_values[tag_name] = entry.text
|
||||||
|
|
||||||
base_metadata["approximate_diameter"] = str(round(float(property_values.get("diameter", 2.85)))) # In mm
|
base_metadata["approximate_diameter"] = str(round(float(cast(float, property_values.get("diameter", 2.85))))) # In mm
|
||||||
base_metadata["properties"] = property_values
|
base_metadata["properties"] = property_values
|
||||||
base_metadata["definition"] = "fdmprinter"
|
base_metadata["definition"] = "fdmprinter"
|
||||||
|
|
||||||
compatible_entries = data.iterfind("./um:settings/um:setting[@key='hardware compatible']", cls.__namespaces)
|
compatible_entries = data.iterfind("./um:settings/um:setting[@key='hardware compatible']", cls.__namespaces)
|
||||||
try:
|
try:
|
||||||
common_compatibility = cls._parseCompatibleValue(next(compatible_entries).text)
|
common_compatibility = cls._parseCompatibleValue(next(compatible_entries).text) # type: ignore
|
||||||
except StopIteration: #No 'hardware compatible' setting.
|
except StopIteration: #No 'hardware compatible' setting.
|
||||||
common_compatibility = True
|
common_compatibility = True
|
||||||
base_metadata["compatible"] = common_compatibility
|
base_metadata["compatible"] = common_compatibility
|
||||||
|
@ -856,6 +861,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
for entry in machine.iterfind("./um:setting", cls.__namespaces):
|
for entry in machine.iterfind("./um:setting", cls.__namespaces):
|
||||||
key = entry.get("key")
|
key = entry.get("key")
|
||||||
if key == "hardware compatible":
|
if key == "hardware compatible":
|
||||||
|
if entry.text is not None:
|
||||||
machine_compatibility = cls._parseCompatibleValue(entry.text)
|
machine_compatibility = cls._parseCompatibleValue(entry.text)
|
||||||
|
|
||||||
for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces):
|
for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces):
|
||||||
|
@ -864,11 +870,11 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
machine_id_list = cls.getPossibleDefinitionIDsFromName(identifier.get("product"))
|
machine_id_list = cls.getPossibleDefinitionIDsFromName(identifier.get("product"))
|
||||||
|
|
||||||
for machine_id in machine_id_list:
|
for machine_id in machine_id_list:
|
||||||
definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
|
definition_metadatas = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
|
||||||
if not definition_metadata:
|
if not definition_metadatas:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
definition_metadata = definition_metadata[0]
|
definition_metadata = definition_metadatas[0]
|
||||||
|
|
||||||
machine_manufacturer = identifier.get("manufacturer", definition_metadata.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
|
machine_manufacturer = identifier.get("manufacturer", definition_metadata.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
|
||||||
|
|
||||||
|
@ -891,7 +897,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
result_metadata.append(new_material_metadata)
|
result_metadata.append(new_material_metadata)
|
||||||
|
|
||||||
buildplates = machine.iterfind("./um:buildplate", cls.__namespaces)
|
buildplates = machine.iterfind("./um:buildplate", cls.__namespaces)
|
||||||
buildplate_map = {}
|
buildplate_map = {} # type: Dict[str, Dict[str, bool]]
|
||||||
buildplate_map["buildplate_compatible"] = {}
|
buildplate_map["buildplate_compatible"] = {}
|
||||||
buildplate_map["buildplate_recommended"] = {}
|
buildplate_map["buildplate_recommended"] = {}
|
||||||
for buildplate in buildplates:
|
for buildplate in buildplates:
|
||||||
|
@ -912,6 +918,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
buildplate_recommended = True
|
buildplate_recommended = True
|
||||||
for entry in settings:
|
for entry in settings:
|
||||||
key = entry.get("key")
|
key = entry.get("key")
|
||||||
|
if entry.text is not None:
|
||||||
if key == "hardware compatible":
|
if key == "hardware compatible":
|
||||||
buildplate_compatibility = cls._parseCompatibleValue(entry.text)
|
buildplate_compatibility = cls._parseCompatibleValue(entry.text)
|
||||||
elif key == "hardware recommended":
|
elif key == "hardware recommended":
|
||||||
|
@ -929,6 +936,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
|
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
|
||||||
key = entry.get("key")
|
key = entry.get("key")
|
||||||
if key == "hardware compatible":
|
if key == "hardware compatible":
|
||||||
|
if entry.text is not None:
|
||||||
hotend_compatibility = cls._parseCompatibleValue(entry.text)
|
hotend_compatibility = cls._parseCompatibleValue(entry.text)
|
||||||
|
|
||||||
new_hotend_specific_material_id = container_id + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
|
new_hotend_specific_material_id = container_id + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
|
||||||
|
|
5
resources/definitions/creality_cr10s.def.json
Normal file
5
resources/definitions/creality_cr10s.def.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "Creality CR-10S",
|
||||||
|
"version": 2,
|
||||||
|
"inherits": "creality_cr10"
|
||||||
|
}
|
|
@ -1627,7 +1627,6 @@
|
||||||
"tetrahedral": "Octet",
|
"tetrahedral": "Octet",
|
||||||
"quarter_cubic": "Quarter Cubic",
|
"quarter_cubic": "Quarter Cubic",
|
||||||
"concentric": "Concentric",
|
"concentric": "Concentric",
|
||||||
"concentric_3d": "Concentric 3D",
|
|
||||||
"zigzag": "Zig Zag",
|
"zigzag": "Zig Zag",
|
||||||
"cross": "Cross",
|
"cross": "Cross",
|
||||||
"cross_3d": "Cross 3D"
|
"cross_3d": "Cross 3D"
|
||||||
|
@ -1655,7 +1654,7 @@
|
||||||
"description": "A list of integer line directions to use. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees for the lines and zig zag patterns and 45 degrees for all other patterns).",
|
"description": "A list of integer line directions to use. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees for the lines and zig zag patterns and 45 degrees for all other patterns).",
|
||||||
"type": "[int]",
|
"type": "[int]",
|
||||||
"default_value": "[ ]",
|
"default_value": "[ ]",
|
||||||
"enabled": "infill_pattern != 'concentric' and infill_pattern != 'concentric_3d' and infill_pattern != 'cubicsubdiv' and infill_sparse_density > 0",
|
"enabled": "infill_pattern != 'concentric' and infill_pattern != 'cubicsubdiv' and infill_sparse_density > 0",
|
||||||
"limit_to_extruder": "infill_extruder_nr",
|
"limit_to_extruder": "infill_extruder_nr",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
|
@ -1791,7 +1790,7 @@
|
||||||
"default_value": 0,
|
"default_value": 0,
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"maximum_value_warning": "1 if (infill_pattern == 'cross' or infill_pattern == 'cross_3d' or support_pattern == 'concentric' or support_pattern == 'concentric_3d') else 5",
|
"maximum_value_warning": "1 if (infill_pattern == 'cross' or infill_pattern == 'cross_3d' or support_pattern == 'concentric') else 5",
|
||||||
"maximum_value": "0 if spaghetti_infill_enabled else (999999 if infill_line_distance == 0 else (20 - math.log(infill_line_distance) / math.log(2)))",
|
"maximum_value": "0 if spaghetti_infill_enabled else (999999 if infill_line_distance == 0 else (20 - math.log(infill_line_distance) / math.log(2)))",
|
||||||
"enabled": "infill_sparse_density > 0 and infill_pattern != 'cubicsubdiv' and not spaghetti_infill_enabled",
|
"enabled": "infill_sparse_density > 0 and infill_pattern != 'cubicsubdiv' and not spaghetti_infill_enabled",
|
||||||
"limit_to_extruder": "infill_extruder_nr",
|
"limit_to_extruder": "infill_extruder_nr",
|
||||||
|
@ -3736,7 +3735,6 @@
|
||||||
"grid": "Grid",
|
"grid": "Grid",
|
||||||
"triangles": "Triangles",
|
"triangles": "Triangles",
|
||||||
"concentric": "Concentric",
|
"concentric": "Concentric",
|
||||||
"concentric_3d": "Concentric 3D",
|
|
||||||
"zigzag": "Zig Zag",
|
"zigzag": "Zig Zag",
|
||||||
"cross": "Cross"
|
"cross": "Cross"
|
||||||
},
|
},
|
||||||
|
@ -3976,7 +3974,7 @@
|
||||||
"default_value": 0,
|
"default_value": 0,
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"maximum_value_warning": "1 if (support_pattern == 'cross' or support_pattern == 'lines' or support_pattern == 'zigzag' or support_pattern == 'concentric' or support_pattern == 'concentric_3d') else 5",
|
"maximum_value_warning": "1 if (support_pattern == 'cross' or support_pattern == 'lines' or support_pattern == 'zigzag' or support_pattern == 'concentric') else 5",
|
||||||
"maximum_value": "999999 if support_line_distance == 0 else (20 - math.log(support_line_distance) / math.log(2))",
|
"maximum_value": "999999 if support_line_distance == 0 else (20 - math.log(support_line_distance) / math.log(2))",
|
||||||
"enabled": "(support_enable or support_tree_enable) and support_infill_rate > 0",
|
"enabled": "(support_enable or support_tree_enable) and support_infill_rate > 0",
|
||||||
"limit_to_extruder": "support_infill_extruder_nr",
|
"limit_to_extruder": "support_infill_extruder_nr",
|
||||||
|
@ -4183,7 +4181,6 @@
|
||||||
"grid": "Grid",
|
"grid": "Grid",
|
||||||
"triangles": "Triangles",
|
"triangles": "Triangles",
|
||||||
"concentric": "Concentric",
|
"concentric": "Concentric",
|
||||||
"concentric_3d": "Concentric 3D",
|
|
||||||
"zigzag": "Zig Zag"
|
"zigzag": "Zig Zag"
|
||||||
},
|
},
|
||||||
"default_value": "concentric",
|
"default_value": "concentric",
|
||||||
|
@ -4204,7 +4201,6 @@
|
||||||
"grid": "Grid",
|
"grid": "Grid",
|
||||||
"triangles": "Triangles",
|
"triangles": "Triangles",
|
||||||
"concentric": "Concentric",
|
"concentric": "Concentric",
|
||||||
"concentric_3d": "Concentric 3D",
|
|
||||||
"zigzag": "Zig Zag"
|
"zigzag": "Zig Zag"
|
||||||
},
|
},
|
||||||
"default_value": "concentric",
|
"default_value": "concentric",
|
||||||
|
@ -4225,7 +4221,6 @@
|
||||||
"grid": "Grid",
|
"grid": "Grid",
|
||||||
"triangles": "Triangles",
|
"triangles": "Triangles",
|
||||||
"concentric": "Concentric",
|
"concentric": "Concentric",
|
||||||
"concentric_3d": "Concentric 3D",
|
|
||||||
"zigzag": "Zig Zag"
|
"zigzag": "Zig Zag"
|
||||||
},
|
},
|
||||||
"default_value": "concentric",
|
"default_value": "concentric",
|
||||||
|
@ -5424,7 +5419,7 @@
|
||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"minimum_value": "0.001",
|
"minimum_value": "0.001",
|
||||||
"default_value": 4,
|
"default_value": 1,
|
||||||
"limit_to_extruder": "support_infill_extruder_nr",
|
"limit_to_extruder": "support_infill_extruder_nr",
|
||||||
"enabled": "support_tree_enable",
|
"enabled": "support_tree_enable",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
|
|
101
resources/definitions/tevo_tornado.def.json
Normal file
101
resources/definitions/tevo_tornado.def.json
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
{
|
||||||
|
"name": "Tevo Tornado",
|
||||||
|
"version": 2,
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "nean",
|
||||||
|
"manufacturer": "Tevo",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"icon": "icon_ultimaker2.png",
|
||||||
|
"has_materials": true
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": {
|
||||||
|
"default_value": "Tevo Tornado"
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 300
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 400
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 300
|
||||||
|
},
|
||||||
|
"machine_center_is_zero": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"machine_head_polygon": {
|
||||||
|
"default_value": [
|
||||||
|
[-30, 34],
|
||||||
|
[-30, -32],
|
||||||
|
[30, -32],
|
||||||
|
[30, 34]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"material_diameter": {
|
||||||
|
"default_value": 1.75
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"top_bottom_thickness": {
|
||||||
|
"default_value": 1.2
|
||||||
|
},
|
||||||
|
"top_bottom_pattern": {
|
||||||
|
"default_value": "lines"
|
||||||
|
},
|
||||||
|
"infill_pattern": {
|
||||||
|
"value": "'triangles'"
|
||||||
|
},
|
||||||
|
"retraction_enable": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"retraction_amount": {
|
||||||
|
"default_value": 6.8
|
||||||
|
},
|
||||||
|
"retraction_speed": {
|
||||||
|
"default_value": 40
|
||||||
|
},
|
||||||
|
"cool_min_layer_time": {
|
||||||
|
"default_value": 10
|
||||||
|
},
|
||||||
|
"adhesion_type": {
|
||||||
|
"default_value": "skirt"
|
||||||
|
},
|
||||||
|
"skirt_line_count": {
|
||||||
|
"default_value": 4
|
||||||
|
},
|
||||||
|
"skirt_gap": {
|
||||||
|
"default_value": 10
|
||||||
|
},
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"gantry_height": {
|
||||||
|
"default_value": 30
|
||||||
|
},
|
||||||
|
"acceleration_enabled": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"machine_acceleration": {
|
||||||
|
"default_value": 1500
|
||||||
|
},
|
||||||
|
"jerk_enabled": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"machine_max_jerk_xy": {
|
||||||
|
"default_value": 6
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap (Marlin/Sprinter)"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "; start_gcode\nM117 Start Clean ; Indicate nozzle clean in progress on LCD\n;\nM104 S[extruder0_temperature] \nM109 S[extruder0_temperature] \nM109 R[extruder0_temperature] \n;\nM107 ; Turn layer fan off\nG21 ; Set to metric [change to G20 if you want Imperial]\nG90 ; Force coordinates to be absolute relative to the origin\nG28 ; Home X/Y/Z axis\n;\nG1 X3 Y1 Z15 F9000 ; Move safe Z height to shear strings\nG0 X1 Y1 Z0.2 F9000 ; Move in 1mm from edge and up [z] 0.2mm\nG92 E0 ; Set extruder to [0] zero\nG1 X100 E12 F500 ; Extrude 30mm filiment along X axis 100mm long to prime and clean the nozzle\nG92 E0 ; Reset extruder to [0] zero end of cleaning run\nG1 E-1 F500 ; Retract filiment by 1 mm to reduce string effect\nG1 X180 F4000 ; quick wipe away from the filament line / purge\nM117 End Clean ; Indicate nozzle clean in progress on LCD\n;\nM117 Printing...\n; Begin printing with sliced GCode after here\n;"
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": ";\n; end_gcode\nG92 E0 ; zero the extruded length\nG1 E-5 F9000 ; retract\nM104 S0 ; turn off temperature\nM140 S0 ; turn off bed\nG91 ; relative positioning\nG1 E-1 F300 ; retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+20 E-5 X-20 Y-20 F7200 ; move Z up a bit and retract filament even more\nG1 X320 Y150 F10000 ; move right mid\nM107 ; turn off layer fan\nM84 ; disable motors\nG90 ; absolute positioning\n;\n;EOF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
"platform_offset": [9, 0, 0],
|
"platform_offset": [9, 0, 0],
|
||||||
"has_materials": false,
|
"has_materials": false,
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "generic_pva", "generic_tough_pla"],
|
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
|
||||||
"first_start_actions": ["UM2UpgradeSelection"],
|
"first_start_actions": ["UM2UpgradeSelection"],
|
||||||
"supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"],
|
"supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"],
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
"icon": "icon_ultimaker.png",
|
"icon": "icon_ultimaker.png",
|
||||||
"platform": "ultimaker_platform.stl",
|
"platform": "ultimaker_platform.stl",
|
||||||
"has_materials": true,
|
"has_materials": true,
|
||||||
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "generic_pva", "generic_tough_pla"],
|
"has_machine_quality": true,
|
||||||
|
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
|
||||||
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
||||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"],
|
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"],
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
"icon": "icon_ultimaker.png",
|
"icon": "icon_ultimaker.png",
|
||||||
"platform": "ultimaker_platform.stl",
|
"platform": "ultimaker_platform.stl",
|
||||||
"has_materials": true,
|
"has_materials": true,
|
||||||
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "generic_pva", "generic_tough_pla"],
|
"has_machine_quality": true,
|
||||||
|
"quality_definition": "ultimaker_original",
|
||||||
|
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
{
|
{
|
||||||
"0": "ultimaker_original_dual_1st",
|
"0": "ultimaker_original_dual_1st",
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"icon": "icon_ultimaker.png",
|
"icon": "icon_ultimaker.png",
|
||||||
"platform": "ultimaker2_platform.obj",
|
"platform": "ultimaker2_platform.obj",
|
||||||
"platform_texture": "UltimakerPlusbackplate.png",
|
"platform_texture": "UltimakerPlusbackplate.png",
|
||||||
|
"quality_definition": "ultimaker_original",
|
||||||
"first_start_actions": ["UMOCheckup", "BedLevel"],
|
"first_start_actions": ["UMOCheckup", "BedLevel"],
|
||||||
"supported_actions": ["UMOCheckup", "BedLevel", "UpgradeFirmware"],
|
"supported_actions": ["UMOCheckup", "BedLevel", "UpgradeFirmware"],
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
|
|
42
resources/definitions/uni_print_3d.def.json
Normal file
42
resources/definitions/uni_print_3d.def.json
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"id": "uni_print_3d",
|
||||||
|
"name": "UNI-PRINT-3D",
|
||||||
|
"version": 2,
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata":
|
||||||
|
{
|
||||||
|
"visible": true,
|
||||||
|
"author": "Alexander Rössler",
|
||||||
|
"category": "Other",
|
||||||
|
"manufacturer": "TheCoolTool",
|
||||||
|
"file_formats": "text/x-ngc;text/x-gcode",
|
||||||
|
"platform": "uni_print_3d_platform.stl",
|
||||||
|
"platform_offset": [0, 0, 0]
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": { "default_value": "UNI-PRINT-3D" },
|
||||||
|
"machine_heated_bed": { "default_value": true },
|
||||||
|
"machine_width": { "default_value": 186 },
|
||||||
|
"machine_height": { "default_value": 230 },
|
||||||
|
"machine_depth": { "default_value": 220 },
|
||||||
|
"machine_center_is_zero": { "default_value": true },
|
||||||
|
"machine_nozzle_size": { "default_value": 0.4 },
|
||||||
|
"material_diameter": { "default_value": 1.75 },
|
||||||
|
"machine_nozzle_heat_up_speed": { "default_value": 2.0 },
|
||||||
|
"machine_nozzle_cool_down_speed": { "default_value": 2.0 },
|
||||||
|
"machine_head_shape_min_x": { "default_value": 75 },
|
||||||
|
"machine_head_shape_min_y": { "default_value": 18 },
|
||||||
|
"machine_head_shape_max_x": { "default_value": 18 },
|
||||||
|
"machine_head_shape_max_y": { "default_value": 35 },
|
||||||
|
"machine_nozzle_gantry_distance": { "default_value": 55 },
|
||||||
|
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
||||||
|
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "M53; enable feed-hold\nG0 Z2.0; always start from the same height to compensate backlash\nG28; move extruder to 0\nM420 R0.0 E0.0 D0.0 P0.1 ; turn the lights on\nM107; turn off fan\nG64 P0.05 Q0.05; path blending settings\nG23; unretract"
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 P0 ; turn off hotend\nG0 X-80 Y100; move the extruder out of the way\nM420 R0.0 E0.1 D0.0 P0.6 ; signalize end of print\nM140 P0 ; turn off heatbed\nM65 P16 ; turn off external fan\nM65 P15 ; turn off power"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
resources/definitions/wanhao_d4s.def.json
Normal file
52
resources/definitions/wanhao_d4s.def.json
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"name": "Wanhao Duplicator 4S",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Ricardo Snoek",
|
||||||
|
"manufacturer": "Wanhao",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"icon": "wanhao-icon.png",
|
||||||
|
"has_materials": true,
|
||||||
|
"platform": "wanhao_225_145_platform.obj",
|
||||||
|
"platform_texture": "Wanhaobackplate.png",
|
||||||
|
"platform_offset": [
|
||||||
|
0,
|
||||||
|
-28,
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": {
|
||||||
|
"default_value": "Wanhao Duplicator 4S"
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 225
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 150
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 150
|
||||||
|
},
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap (Marlin/Sprinter)"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
|
||||||
|
},
|
||||||
|
"material_diameter": {
|
||||||
|
"default_value": 1.75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
resources/definitions/wanhao_d6.def.json
Normal file
58
resources/definitions/wanhao_d6.def.json
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"name": "Wanhao Duplicator 6",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Ricardo Snoek",
|
||||||
|
"manufacturer": "Wanhao",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"icon": "wanhao-icon.png",
|
||||||
|
"has_materials": true,
|
||||||
|
"platform": "wanhao_200_200_platform.obj",
|
||||||
|
"platform_texture": "Wanhaobackplate.png",
|
||||||
|
"platform_offset": [
|
||||||
|
0,
|
||||||
|
-28,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"supported_actions": [
|
||||||
|
"UpgradeFirmware"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": {
|
||||||
|
"default_value": "Wanhao Duplicator 6"
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 180
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"gantry_height": {
|
||||||
|
"default_value": 55
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap (Marlin/Sprinter)"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
|
||||||
|
},
|
||||||
|
"material_diameter": {
|
||||||
|
"default_value": 1.75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
resources/definitions/wanhao_d6_plus.def.json
Normal file
52
resources/definitions/wanhao_d6_plus.def.json
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"name": "Wanhao Duplicator 6 Plus",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Ricardo Snoek",
|
||||||
|
"manufacturer": "Wanhao",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"icon": "wanhao-icon.png",
|
||||||
|
"has_materials": true,
|
||||||
|
"platform": "wanhao_200_200_platform.obj",
|
||||||
|
"platform_texture": "Wanhaobackplate.png",
|
||||||
|
"platform_offset": [
|
||||||
|
0,
|
||||||
|
-28,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": {
|
||||||
|
"default_value": "Wanhao Duplicator 6 Plus"
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 180
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap (Marlin/Sprinter)"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
|
||||||
|
},
|
||||||
|
"material_diameter": {
|
||||||
|
"default_value": 1.75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
resources/definitions/wanhao_duplicator5S.def.json
Normal file
55
resources/definitions/wanhao_duplicator5S.def.json
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"name": "Wanhao Duplicator 5S",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Ricardo Snoek",
|
||||||
|
"manufacturer": "Wanhao",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"icon": "wanhao-icon.png",
|
||||||
|
"has_materials": true,
|
||||||
|
"platform": "wanhao_300_200_platform.obj",
|
||||||
|
"platform_texture": "Wanhaobackplate.png",
|
||||||
|
"platform_offset": [
|
||||||
|
0,
|
||||||
|
-28,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": {
|
||||||
|
"default_value": "Wanhao Duplicator 5S"
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 300
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 600
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_center_is_zero": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap (Marlin/Sprinter)"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
|
||||||
|
},
|
||||||
|
"material_diameter": {
|
||||||
|
"default_value": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
resources/definitions/wanhao_duplicator5Smini.def.json
Normal file
52
resources/definitions/wanhao_duplicator5Smini.def.json
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"name": "Wanhao Duplicator 5S Mini",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Ricardo Snoek",
|
||||||
|
"manufacturer": "Wanhao",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"icon": "wanhao-icon.png",
|
||||||
|
"has_materials": true,
|
||||||
|
"platform": "wanhao_300_200_platform.obj",
|
||||||
|
"platform_texture": "Wanhaobackplate.png",
|
||||||
|
"platform_offset": [
|
||||||
|
0,
|
||||||
|
-28,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": {
|
||||||
|
"default_value": "Wanhao Duplicator 5S Mini"
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 300
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 180
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap (Marlin/Sprinter)"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
|
||||||
|
},
|
||||||
|
"material_diameter": {
|
||||||
|
"default_value": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
resources/definitions/wanhao_i3.def.json
Normal file
52
resources/definitions/wanhao_i3.def.json
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"name": "Wanhao Duplicator i3",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Ricardo Snoek",
|
||||||
|
"manufacturer": "Wanhao",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"icon": "wanhao-icon.png",
|
||||||
|
"has_materials": true,
|
||||||
|
"platform": "wanhao_200_200_platform.obj",
|
||||||
|
"platform_texture": "Wanhaobackplate.png",
|
||||||
|
"platform_offset": [
|
||||||
|
0,
|
||||||
|
-28,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": {
|
||||||
|
"default_value": "Wanhao Duplicator i3"
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 180
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap (Marlin/Sprinter)"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
|
||||||
|
},
|
||||||
|
"material_diameter": {
|
||||||
|
"default_value": 1.75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
resources/definitions/wanhao_i3mini.def.json
Normal file
52
resources/definitions/wanhao_i3mini.def.json
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"name": "Wanhao Duplicator i3 Mini",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Ricardo Snoek",
|
||||||
|
"manufacturer": "Wanhao",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"icon": "wanhao-icon.png",
|
||||||
|
"has_materials": true,
|
||||||
|
"platform": "wanhao_110_110_platform.obj",
|
||||||
|
"platform_texture": "Wanhaobackplate.png",
|
||||||
|
"platform_offset": [
|
||||||
|
0,
|
||||||
|
-15,
|
||||||
|
7
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": {
|
||||||
|
"default_value": "Wanhao Duplicator i3 Mini"
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 110
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 110
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 110
|
||||||
|
},
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap (Marlin/Sprinter)"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
|
||||||
|
},
|
||||||
|
"material_diameter": {
|
||||||
|
"default_value": 1.75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
resources/definitions/wanhao_i3plus.def.json
Normal file
52
resources/definitions/wanhao_i3plus.def.json
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"name": "Wanhao Duplicator i3 Plus",
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Ricardo Snoek",
|
||||||
|
"manufacturer": "Wanhao",
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"icon": "wanhao-icon.png",
|
||||||
|
"has_materials": true,
|
||||||
|
"platform": "wanhao_200_200_platform.obj",
|
||||||
|
"platform_texture": "Wanhaobackplate.png",
|
||||||
|
"platform_offset": [
|
||||||
|
0,
|
||||||
|
-28,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": {
|
||||||
|
"default_value": "Wanhao Duplicator i3 Plus"
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 180
|
||||||
|
},
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap (Marlin/Sprinter)"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{travel_speed} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E6 ;extrude 6 mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{travel_speed} \n ;Put printing message on LCD screen\n M117 Printing..."
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning"
|
||||||
|
},
|
||||||
|
"material_diameter": {
|
||||||
|
"default_value": 1.75
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,7 @@ msgstr "G-Code-Datei"
|
||||||
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:30
|
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:30
|
||||||
msgctxt "@info:title"
|
msgctxt "@info:title"
|
||||||
msgid "3D Model Assistant"
|
msgid "3D Model Assistant"
|
||||||
msgstr ""
|
msgstr "3D-Modell-Assistent"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:80
|
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:80
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -53,7 +53,7 @@ msgid ""
|
||||||
"<p>{model_names}</p>\n"
|
"<p>{model_names}</p>\n"
|
||||||
"<p>Find out how to ensure the best possible print quality and reliability.</p>\n"
|
"<p>Find out how to ensure the best possible print quality and reliability.</p>\n"
|
||||||
"<p><a href=\"https://ultimaker.com/3D-model-assistant\">View print quality guide</a></p>"
|
"<p><a href=\"https://ultimaker.com/3D-model-assistant\">View print quality guide</a></p>"
|
||||||
msgstr ""
|
msgstr "<p>Ein oder mehrere 3D-Modelle können möglicherweise aufgrund der Modellgröße und Materialkonfiguration nicht optimal gedruckt werden:</p>\n<p>{model_names}</p>\n<p>Erfahren Sie, wie Sie die bestmögliche Druckqualität und Zuverlässigkeit sicherstellen.</p>\n<p><a href=“https://ultimaker.com/3D-model-assistant“>Leitfaden zu Druckqualität anzeigen</a></p>"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65
|
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -155,17 +155,17 @@ msgstr "Über USB verbunden"
|
||||||
#: /home/ruben/Projects/Cura/plugins/X3GWriter/__init__.py:15
|
#: /home/ruben/Projects/Cura/plugins/X3GWriter/__init__.py:15
|
||||||
msgctxt "X3G Writer File Description"
|
msgctxt "X3G Writer File Description"
|
||||||
msgid "X3G File"
|
msgid "X3G File"
|
||||||
msgstr "X3D-Datei"
|
msgstr "X3G-Datei"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/GPX-prefix/src/GPX/slicerplugins/cura15.06/X3gWriter/__init__.py:16
|
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/GPX-prefix/src/GPX/slicerplugins/cura15.06/X3gWriter/__init__.py:16
|
||||||
msgctxt "X3g Writer Plugin Description"
|
msgctxt "X3g Writer Plugin Description"
|
||||||
msgid "Writes X3g to files"
|
msgid "Writes X3g to files"
|
||||||
msgstr ""
|
msgstr "Schreibt X3g in Dateien"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/GPX-prefix/src/GPX/slicerplugins/cura15.06/X3gWriter/__init__.py:21
|
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/GPX-prefix/src/GPX/slicerplugins/cura15.06/X3gWriter/__init__.py:21
|
||||||
msgctxt "X3g Writer File Description"
|
msgctxt "X3g Writer File Description"
|
||||||
msgid "X3g File"
|
msgid "X3g File"
|
||||||
msgstr ""
|
msgstr "X3g-Datei"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/GCodeGzWriter/__init__.py:17
|
#: /home/ruben/Projects/Cura/plugins/GCodeGzWriter/__init__.py:17
|
||||||
#: /home/ruben/Projects/Cura/plugins/GCodeGzReader/__init__.py:17
|
#: /home/ruben/Projects/Cura/plugins/GCodeGzReader/__init__.py:17
|
||||||
|
@ -186,13 +186,13 @@ msgstr "Vorbereiten"
|
||||||
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
|
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
|
||||||
msgctxt "@action:button Preceded by 'Ready to'."
|
msgctxt "@action:button Preceded by 'Ready to'."
|
||||||
msgid "Save to Removable Drive"
|
msgid "Save to Removable Drive"
|
||||||
msgstr "Speichern auf Datenträger"
|
msgstr "Speichern auf Wechseldatenträger"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:24
|
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:24
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "@item:inlistbox"
|
msgctxt "@item:inlistbox"
|
||||||
msgid "Save to Removable Drive {0}"
|
msgid "Save to Removable Drive {0}"
|
||||||
msgstr "Auf Datenträger speichern {0}"
|
msgstr "Auf Wechseldatenträger speichern {0}"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:64
|
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:64
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:113
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:113
|
||||||
|
@ -204,7 +204,7 @@ msgstr "Es sind keine Dateiformate zum Schreiben vorhanden!"
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "@info:progress Don't translate the XML tags <filename>!"
|
msgctxt "@info:progress Don't translate the XML tags <filename>!"
|
||||||
msgid "Saving to Removable Drive <filename>{0}</filename>"
|
msgid "Saving to Removable Drive <filename>{0}</filename>"
|
||||||
msgstr "Wird auf Datenträger gespeichert <filename>{0}</filename>"
|
msgstr "Wird auf Wechseldatenträger gespeichert <filename>{0}</filename>"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:94
|
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:94
|
||||||
msgctxt "@info:title"
|
msgctxt "@info:title"
|
||||||
|
@ -229,7 +229,7 @@ msgstr "Bei dem Versuch, auf {device} zu schreiben, wurde ein Dateiname nicht ge
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
msgid "Could not save to removable drive {0}: {1}"
|
msgid "Could not save to removable drive {0}: {1}"
|
||||||
msgstr "Konnte nicht auf dem Datenträger gespeichert werden {0}: {1}"
|
msgstr "Konnte nicht auf dem Wechseldatenträger gespeichert werden {0}: {1}"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:137
|
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:137
|
||||||
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:133
|
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:133
|
||||||
|
@ -243,7 +243,7 @@ msgstr "Fehler"
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
msgid "Saved to Removable Drive {0} as {1}"
|
msgid "Saved to Removable Drive {0} as {1}"
|
||||||
msgstr "Auf Datenträger {0} gespeichert als {1}"
|
msgstr "Auf Wechseldatenträger {0} gespeichert als {1}"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:145
|
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:145
|
||||||
msgctxt "@info:title"
|
msgctxt "@info:title"
|
||||||
|
@ -259,7 +259,7 @@ msgstr "Auswerfen"
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "@action"
|
msgctxt "@action"
|
||||||
msgid "Eject removable device {0}"
|
msgid "Eject removable device {0}"
|
||||||
msgstr "Datenträger auswerfen {0}"
|
msgstr "Wechseldatenträger auswerfen {0}"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:151
|
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:151
|
||||||
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:163
|
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:163
|
||||||
|
@ -289,7 +289,7 @@ msgstr "Auswurf fehlgeschlagen {0}. Möglicherweise wird das Laufwerk von einem
|
||||||
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py:68
|
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py:68
|
||||||
msgctxt "@item:intext"
|
msgctxt "@item:intext"
|
||||||
msgid "Removable Drive"
|
msgid "Removable Drive"
|
||||||
msgstr "Datenträger"
|
msgstr "Wechseldatenträger"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:70
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py:70
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:78
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py:78
|
||||||
|
@ -570,12 +570,12 @@ msgstr "Daten werden erfasst"
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "More info"
|
msgid "More info"
|
||||||
msgstr ""
|
msgstr "Mehr Infos"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
|
||||||
msgctxt "@action:tooltip"
|
msgctxt "@action:tooltip"
|
||||||
msgid "See more information on what data Cura sends."
|
msgid "See more information on what data Cura sends."
|
||||||
msgstr ""
|
msgstr "Siehe mehr Informationen dazu, was Cura sendet."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -602,9 +602,7 @@ msgctxt "@info:status"
|
||||||
msgid ""
|
msgid ""
|
||||||
"Could not export using \"{}\" quality!\n"
|
"Could not export using \"{}\" quality!\n"
|
||||||
"Felt back to \"{}\"."
|
"Felt back to \"{}\"."
|
||||||
msgstr ""
|
msgstr "Exportieren in \"{}\" Qualität nicht möglich!\nZurückgeschaltet auf \"{}\"."
|
||||||
"Exportieren in \"{}\" Qualität nicht möglich!\n"
|
|
||||||
"Zurückgeschaltet auf \"{}\"."
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14
|
#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14
|
||||||
msgctxt "@item:inlistbox"
|
msgctxt "@item:inlistbox"
|
||||||
|
@ -1014,22 +1012,22 @@ msgstr "Produktabmessungen"
|
||||||
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:99
|
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:99
|
||||||
msgctxt "@info:backup_failed"
|
msgctxt "@info:backup_failed"
|
||||||
msgid "Could not create archive from user data directory: {}"
|
msgid "Could not create archive from user data directory: {}"
|
||||||
msgstr ""
|
msgstr "Konnte kein Archiv von Benutzer-Datenverzeichnis {} erstellen"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:104
|
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:104
|
||||||
msgctxt "@info:title"
|
msgctxt "@info:title"
|
||||||
msgid "Backup"
|
msgid "Backup"
|
||||||
msgstr ""
|
msgstr "Backup"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:116
|
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:116
|
||||||
msgctxt "@info:backup_failed"
|
msgctxt "@info:backup_failed"
|
||||||
msgid "Tried to restore a Cura backup without having proper data or meta data."
|
msgid "Tried to restore a Cura backup without having proper data or meta data."
|
||||||
msgstr ""
|
msgstr "Versucht, ein Cura-Backup-Verzeichnis ohne entsprechende Daten oder Metadaten wiederherzustellen."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:126
|
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:126
|
||||||
msgctxt "@info:backup_failed"
|
msgctxt "@info:backup_failed"
|
||||||
msgid "Tried to restore a Cura backup that does not match your current version."
|
msgid "Tried to restore a Cura backup that does not match your current version."
|
||||||
msgstr ""
|
msgstr "Versucht, ein Cura-Backup zu erstellen, das nicht Ihrer aktuellen Version entspricht."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:27
|
#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:27
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
|
@ -1080,12 +1078,7 @@ msgid ""
|
||||||
" <p>Backups can be found in the configuration folder.</p>\n"
|
" <p>Backups can be found in the configuration folder.</p>\n"
|
||||||
" <p>Please send us this Crash Report to fix the problem.</p>\n"
|
" <p>Please send us this Crash Report to fix the problem.</p>\n"
|
||||||
" "
|
" "
|
||||||
msgstr ""
|
msgstr "<p><b>Hoppla, bei Ultimaker Cura ist ein Problem aufgetreten.</p></b>\n <p>Beim Start ist ein nicht behebbarer Fehler aufgetreten. Er wurde möglicherweise durch einige falsche Konfigurationsdateien verursacht. Wir empfehlen ein Backup und Reset Ihrer Konfiguration.</p>\n <p>Backups sind im Konfigurationsordner abgelegt.</p>\n <p>Senden Sie uns diesen Absturzbericht bitte, um das Problem zu beheben.</p>\n "
|
||||||
"<p><b>Hoppla, bei Ultimaker Cura ist ein Problem aufgetreten.</p></b>\n"
|
|
||||||
" <p>Beim Start ist ein nicht behebbarer Fehler aufgetreten. Er wurde möglicherweise durch einige falsche Konfigurationsdateien verursacht. Wir empfehlen ein Backup und Reset Ihrer Konfiguration.</p>\n"
|
|
||||||
" <p>Backups sind im Konfigurationsordner abgelegt.</p>\n"
|
|
||||||
" <p>Senden Sie uns diesen Absturzbericht bitte, um das Problem zu beheben.</p>\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:102
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:102
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -1118,10 +1111,7 @@ msgid ""
|
||||||
"<p><b>A fatal error has occurred in Cura. Please send us this Crash Report to fix the problem</p></b>\n"
|
"<p><b>A fatal error has occurred in Cura. Please send us this Crash Report to fix the problem</p></b>\n"
|
||||||
" <p>Please use the \"Send report\" button to post a bug report automatically to our servers</p>\n"
|
" <p>Please use the \"Send report\" button to post a bug report automatically to our servers</p>\n"
|
||||||
" "
|
" "
|
||||||
msgstr ""
|
msgstr "<p><b>Ein schwerer Fehler ist in Cura aufgetreten. Senden Sie uns diesen Absturzbericht, um das Problem zu beheben</p></b>\n <p>Verwenden Sie bitte die Schaltfläche „Bericht senden“, um den Fehlerbericht automatisch an unsere Server zu senden</p>\n "
|
||||||
"<p><b>Ein schwerer Fehler ist in Cura aufgetreten. Senden Sie uns diesen Absturzbericht, um das Problem zu beheben</p></b>\n"
|
|
||||||
" <p>Verwenden Sie bitte die Schaltfläche „Bericht senden“, um den Fehlerbericht automatisch an unsere Server zu senden</p>\n"
|
|
||||||
" "
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:177
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:177
|
||||||
msgctxt "@title:groupbox"
|
msgctxt "@title:groupbox"
|
||||||
|
@ -1435,7 +1425,7 @@ msgstr "Installiert"
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxErrorPage.qml:16
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxErrorPage.qml:16
|
||||||
msgctxt "@info"
|
msgctxt "@info"
|
||||||
msgid "Could not connect to the Cura Package database. Please check your connection."
|
msgid "Could not connect to the Cura Package database. Please check your connection."
|
||||||
msgstr ""
|
msgstr "Verbindung zur Cura Paket-Datenbank konnte nicht hergestellt werden. Bitte überprüfen Sie Ihre Verbindung."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:35
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:35
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:26
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:26
|
||||||
|
@ -1446,17 +1436,17 @@ msgstr "Plugins"
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:79
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:79
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Version"
|
msgid "Version"
|
||||||
msgstr ""
|
msgstr "Version"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:85
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:85
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Last updated"
|
msgid "Last updated"
|
||||||
msgstr ""
|
msgstr "Zuletzt aktualisiert"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:91
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:91
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Author"
|
msgid "Author"
|
||||||
msgstr ""
|
msgstr "Autor"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:109
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:109
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:269
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:269
|
||||||
|
@ -1474,53 +1464,53 @@ msgstr "Aktualisierung"
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:31
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:31
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Updating"
|
msgid "Updating"
|
||||||
msgstr ""
|
msgstr "Aktualisierung wird durchgeführt"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml:46
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml:46
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:32
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:32
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Updated"
|
msgid "Updated"
|
||||||
msgstr ""
|
msgstr "Aktualisiert"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/Toolbox.qml:13
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/Toolbox.qml:13
|
||||||
msgctxt "@title"
|
msgctxt "@title"
|
||||||
msgid "Toolbox"
|
msgid "Toolbox"
|
||||||
msgstr ""
|
msgstr "Toolbox"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxBackColumn.qml:25
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxBackColumn.qml:25
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr ""
|
msgstr "Zurück"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:17
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:17
|
||||||
msgctxt "@info"
|
msgctxt "@info"
|
||||||
msgid "You will need to restart Cura before changes in packages have effect."
|
msgid "You will need to restart Cura before changes in packages have effect."
|
||||||
msgstr ""
|
msgstr "Cura muss neu gestartet werden, um die Änderungen der Pakete zu übernehmen."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:32
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:32
|
||||||
msgctxt "@info:button"
|
msgctxt "@info:button"
|
||||||
msgid "Quit Cura"
|
msgid "Quit Cura"
|
||||||
msgstr ""
|
msgstr "Quit Cura"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:54
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:54
|
||||||
msgctxt "@title:tab"
|
msgctxt "@title:tab"
|
||||||
msgid "Installed"
|
msgid "Installed"
|
||||||
msgstr ""
|
msgstr "Installiert"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:19
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:19
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Will install upon restarting"
|
msgid "Will install upon restarting"
|
||||||
msgstr ""
|
msgstr "Installiert nach Neustart"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:51
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:51
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Downgrade"
|
msgid "Downgrade"
|
||||||
msgstr ""
|
msgstr "Downgraden"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:51
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:51
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Uninstall"
|
msgid "Uninstall"
|
||||||
msgstr ""
|
msgstr "Deinstallieren"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml:16
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml:16
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
|
@ -1533,10 +1523,7 @@ msgid ""
|
||||||
"This plugin contains a license.\n"
|
"This plugin contains a license.\n"
|
||||||
"You need to accept this license to install this plugin.\n"
|
"You need to accept this license to install this plugin.\n"
|
||||||
"Do you agree with the terms below?"
|
"Do you agree with the terms below?"
|
||||||
msgstr ""
|
msgstr "Dieses Plugin enthält eine Lizenz.\nSie müssen diese Lizenz akzeptieren, um das Plugin zu installieren.\nStimmen Sie den nachfolgenden Bedingungen zu?"
|
||||||
"Dieses Plugin enthält eine Lizenz.\n"
|
|
||||||
"Sie müssen diese Lizenz akzeptieren, um das Plugin zu installieren.\n"
|
|
||||||
"Stimmen Sie den nachfolgenden Bedingungen zu?"
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml:54
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml:54
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -1551,22 +1538,22 @@ msgstr "Ablehnen"
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:17
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:17
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Featured"
|
msgid "Featured"
|
||||||
msgstr ""
|
msgstr "Unterstützter"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:20
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:20
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Compatibility"
|
msgid "Compatibility"
|
||||||
msgstr ""
|
msgstr "Kompatibilität"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml:16
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml:16
|
||||||
msgctxt "@info"
|
msgctxt "@info"
|
||||||
msgid "Fetching packages..."
|
msgid "Fetching packages..."
|
||||||
msgstr ""
|
msgstr "Pakete werden abgeholt..."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:87
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:87
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Contact"
|
msgid "Contact"
|
||||||
msgstr ""
|
msgstr "Kontakt"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.qml:22
|
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.qml:22
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
|
@ -1651,10 +1638,7 @@ msgid ""
|
||||||
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
|
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Select your printer from the list below:"
|
"Select your printer from the list below:"
|
||||||
msgstr ""
|
msgstr "Um über das Netzwerk direkt auf Ihrem Drucker zu drucken, stellen Sie bitte sicher, dass der Drucker mit dem Netzwerkkabel verbunden ist oder verbinden Sie Ihren Drucker mit Ihrem WLAN-Netzwerk. Wenn Sie Cura nicht mit Ihrem Drucker verbinden, können Sie dennoch ein USB-Laufwerk für die Übertragung von G-Code-Dateien auf Ihren Drucker verwenden.\n\nWählen Sie Ihren Drucker aus der folgenden Liste:"
|
||||||
"Um über das Netzwerk direkt auf Ihrem Drucker zu drucken, stellen Sie bitte sicher, dass der Drucker mit dem Netzwerkkabel verbunden ist oder verbinden Sie Ihren Drucker mit Ihrem WLAN-Netzwerk. Wenn Sie Cura nicht mit Ihrem Drucker verbinden, können Sie dennoch ein USB-Laufwerk für die Übertragung von G-Code-Dateien auf Ihren Drucker verwenden.\n"
|
|
||||||
"\n"
|
|
||||||
"Wählen Sie Ihren Drucker aus der folgenden Liste:"
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:42
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:42
|
||||||
|
@ -2015,22 +1999,22 @@ msgstr "Aktive Skripts Nachbearbeitung ändern"
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:16
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:16
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
msgid "More information on anonymous data collection"
|
msgid "More information on anonymous data collection"
|
||||||
msgstr ""
|
msgstr "Weitere Informationen zur anonymen Datenerfassung"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:66
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:66
|
||||||
msgctxt "@text:window"
|
msgctxt "@text:window"
|
||||||
msgid "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent."
|
msgid "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent."
|
||||||
msgstr ""
|
msgstr "Cura sendet anonyme Daten an Ultimaker, um die Druckqualität und Benutzererfahrung zu steigern. Nachfolgend ist ein Beispiel aller Daten, die gesendet werden."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:101
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:101
|
||||||
msgctxt "@text:window"
|
msgctxt "@text:window"
|
||||||
msgid "I don't want to send these data"
|
msgid "I don't want to send these data"
|
||||||
msgstr ""
|
msgstr "Ich möchte diese Daten nicht senden."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:111
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:111
|
||||||
msgctxt "@text:window"
|
msgctxt "@text:window"
|
||||||
msgid "Allow sending these data to Ultimaker and help us improve Cura"
|
msgid "Allow sending these data to Ultimaker and help us improve Cura"
|
||||||
msgstr ""
|
msgstr "Ich erlaube das Senden dieser Daten an Ultimaker, um Cura zu verbessern"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:19
|
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:19
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
|
@ -2544,9 +2528,7 @@ msgctxt "@text:window"
|
||||||
msgid ""
|
msgid ""
|
||||||
"You have customized some profile settings.\n"
|
"You have customized some profile settings.\n"
|
||||||
"Would you like to keep or discard those settings?"
|
"Would you like to keep or discard those settings?"
|
||||||
msgstr ""
|
msgstr "Sie haben einige Profileinstellungen angepasst.\nMöchten Sie diese Einstellungen übernehmen oder verwerfen?"
|
||||||
"Sie haben einige Profileinstellungen angepasst.\n"
|
|
||||||
"Möchten Sie diese Einstellungen übernehmen oder verwerfen?"
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:110
|
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:110
|
||||||
msgctxt "@title:column"
|
msgctxt "@title:column"
|
||||||
|
@ -2609,7 +2591,7 @@ msgstr "Änderung Durchmesser bestätigen"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:95
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:95
|
||||||
msgctxt "@label (%1 is a number)"
|
msgctxt "@label (%1 is a number)"
|
||||||
msgid "The new filament diameter is set to %1 mm, which is not compatible with the current extruder. Do you wish to continue?"
|
msgid "The new filament diameter is set to %1 mm, which is not compatible with the current extruder. Do you wish to continue?"
|
||||||
msgstr ""
|
msgstr "Der neue Filament-Durchmesser wurde auf %1 mm eingestellt, was nicht kompatibel mit dem aktuellen Extruder ist. Möchten Sie fortfahren?"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:128
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:128
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -2880,12 +2862,12 @@ msgstr "Extrem kleine Modelle skalieren"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:507
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:507
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
msgid "Should models be selected after they are loaded?"
|
msgid "Should models be selected after they are loaded?"
|
||||||
msgstr ""
|
msgstr "Sollten Modelle gewählt werden, nachdem sie geladen wurden?"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
|
||||||
msgctxt "@option:check"
|
msgctxt "@option:check"
|
||||||
msgid "Select models when loaded"
|
msgid "Select models when loaded"
|
||||||
msgstr ""
|
msgstr "Modelle wählen, nachdem sie geladen wurden"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
|
@ -2970,7 +2952,7 @@ msgstr "(Anonyme) Druckinformationen senden"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:708
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:708
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "More information"
|
msgid "More information"
|
||||||
msgstr ""
|
msgstr "Mehr Informationen"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:726
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:726
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3246,9 +3228,7 @@ msgctxt "@info:credit"
|
||||||
msgid ""
|
msgid ""
|
||||||
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
|
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
|
||||||
"Cura proudly uses the following open source projects:"
|
"Cura proudly uses the following open source projects:"
|
||||||
msgstr ""
|
msgstr "Cura wurde von Ultimaker B.V. in Zusammenarbeit mit der Community entwickelt.\nCura verwendet mit Stolz die folgenden Open Source-Projekte:"
|
||||||
"Cura wurde von Ultimaker B.V. in Zusammenarbeit mit der Community entwickelt.\n"
|
|
||||||
"Cura verwendet mit Stolz die folgenden Open Source-Projekte:"
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
|
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3361,10 +3341,7 @@ msgid ""
|
||||||
"Some setting/override values are different from the values stored in the profile.\n"
|
"Some setting/override values are different from the values stored in the profile.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Click to open the profile manager."
|
"Click to open the profile manager."
|
||||||
msgstr ""
|
msgstr "Einige Einstellungs-/Überschreibungswerte unterscheiden sich von den im Profil gespeicherten Werten.\n\nKlicken Sie, um den Profilmanager zu öffnen."
|
||||||
"Einige Einstellungs-/Überschreibungswerte unterscheiden sich von den im Profil gespeicherten Werten.\n"
|
|
||||||
"\n"
|
|
||||||
"Klicken Sie, um den Profilmanager zu öffnen."
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:199
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:199
|
||||||
msgctxt "@label:textbox"
|
msgctxt "@label:textbox"
|
||||||
|
@ -3405,12 +3382,12 @@ msgstr "Sichtbarkeit einstellen wird konfiguriert..."
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:621
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:621
|
||||||
msgctxt "@action:inmenu"
|
msgctxt "@action:inmenu"
|
||||||
msgid "Collapse All"
|
msgid "Collapse All"
|
||||||
msgstr ""
|
msgstr "Alle verkleinern"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:626
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:626
|
||||||
msgctxt "@action:inmenu"
|
msgctxt "@action:inmenu"
|
||||||
msgid "Expand All"
|
msgid "Expand All"
|
||||||
msgstr ""
|
msgstr "Alle vergrößern"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:249
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:249
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3418,10 +3395,7 @@ msgid ""
|
||||||
"Some hidden settings use values different from their normal calculated value.\n"
|
"Some hidden settings use values different from their normal calculated value.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Click to make these settings visible."
|
"Click to make these settings visible."
|
||||||
msgstr ""
|
msgstr "Einige ausgeblendete Einstellungen verwenden Werte, die von ihren normalen, berechneten Werten abweichen.\n\nKlicken Sie, um diese Einstellungen sichtbar zu machen."
|
||||||
"Einige ausgeblendete Einstellungen verwenden Werte, die von ihren normalen, berechneten Werten abweichen.\n"
|
|
||||||
"\n"
|
|
||||||
"Klicken Sie, um diese Einstellungen sichtbar zu machen."
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:61
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:61
|
||||||
msgctxt "@label Header for list of settings."
|
msgctxt "@label Header for list of settings."
|
||||||
|
@ -3449,10 +3423,7 @@ msgid ""
|
||||||
"This setting has a value that is different from the profile.\n"
|
"This setting has a value that is different from the profile.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Click to restore the value of the profile."
|
"Click to restore the value of the profile."
|
||||||
msgstr ""
|
msgstr "Diese Einstellung hat einen vom Profil abweichenden Wert.\n\nKlicken Sie, um den Wert des Profils wiederherzustellen."
|
||||||
"Diese Einstellung hat einen vom Profil abweichenden Wert.\n"
|
|
||||||
"\n"
|
|
||||||
"Klicken Sie, um den Wert des Profils wiederherzustellen."
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:286
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:286
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3460,10 +3431,7 @@ msgid ""
|
||||||
"This setting is normally calculated, but it currently has an absolute value set.\n"
|
"This setting is normally calculated, but it currently has an absolute value set.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Click to restore the calculated value."
|
"Click to restore the calculated value."
|
||||||
msgstr ""
|
msgstr "Diese Einstellung wird normalerweise berechnet; aktuell ist jedoch ein Absolutwert eingestellt.\n\nKlicken Sie, um den berechneten Wert wiederherzustellen."
|
||||||
"Diese Einstellung wird normalerweise berechnet; aktuell ist jedoch ein Absolutwert eingestellt.\n"
|
|
||||||
"\n"
|
|
||||||
"Klicken Sie, um den berechneten Wert wiederherzustellen."
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/ManualPrinterControl.qml:129
|
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/ManualPrinterControl.qml:129
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3607,7 +3575,7 @@ msgstr "&Druckplatte"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:13
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:13
|
||||||
msgctxt "@action:inmenu"
|
msgctxt "@action:inmenu"
|
||||||
msgid "Visible Settings"
|
msgid "Visible Settings"
|
||||||
msgstr "Sichtbare Einstellungen:"
|
msgstr "Sichtbare Einstellungen"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:43
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:43
|
||||||
msgctxt "@action:inmenu"
|
msgctxt "@action:inmenu"
|
||||||
|
@ -3651,12 +3619,12 @@ msgstr "Extruder"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/ConfigurationMenu/SyncButton.qml:16
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/ConfigurationMenu/SyncButton.qml:16
|
||||||
msgctxt "@label:extruder label"
|
msgctxt "@label:extruder label"
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
msgstr ""
|
msgstr "Ja"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/ConfigurationMenu/SyncButton.qml:16
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/ConfigurationMenu/SyncButton.qml:16
|
||||||
msgctxt "@label:extruder label"
|
msgctxt "@label:extruder label"
|
||||||
msgid "No"
|
msgid "No"
|
||||||
msgstr ""
|
msgstr "Nein"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/RecentFilesMenu.qml:13
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/RecentFilesMenu.qml:13
|
||||||
msgctxt "@title:menu menubar:file"
|
msgctxt "@title:menu menubar:file"
|
||||||
|
@ -3673,14 +3641,12 @@ msgctxt "@label:listbox"
|
||||||
msgid ""
|
msgid ""
|
||||||
"Print Setup disabled\n"
|
"Print Setup disabled\n"
|
||||||
"G-code files cannot be modified"
|
"G-code files cannot be modified"
|
||||||
msgstr ""
|
msgstr "Druckeinrichtung deaktiviert\nG-Code-Dateien können nicht geändert werden"
|
||||||
"Druckeinrichtung deaktiviert\n"
|
|
||||||
"G-Code-Dateien können nicht geändert werden"
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:341
|
#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:341
|
||||||
msgctxt "@label Hours and minutes"
|
msgctxt "@label Hours and minutes"
|
||||||
msgid "00h 00min"
|
msgid "00h 00min"
|
||||||
msgstr "00 St. 00 M."
|
msgstr "00 Stunden 00 Minuten"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:359
|
#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:359
|
||||||
msgctxt "@tooltip"
|
msgctxt "@tooltip"
|
||||||
|
@ -3954,7 +3920,7 @@ msgstr "Konfigurationsordner anzeigen"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:433
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:433
|
||||||
msgctxt "@action:menu"
|
msgctxt "@action:menu"
|
||||||
msgid "Browse packages..."
|
msgid "Browse packages..."
|
||||||
msgstr ""
|
msgstr "Pakete durchsuchen..."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:440
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:440
|
||||||
msgctxt "@action:inmenu menubar:view"
|
msgctxt "@action:inmenu menubar:view"
|
||||||
|
@ -4112,7 +4078,7 @@ msgstr "Er&weiterungen"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:274
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:274
|
||||||
msgctxt "@title:menu menubar:toplevel"
|
msgctxt "@title:menu menubar:toplevel"
|
||||||
msgid "&Toolbox"
|
msgid "&Toolbox"
|
||||||
msgstr ""
|
msgstr "&Toolbox"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:281
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:281
|
||||||
msgctxt "@title:menu menubar:toplevel"
|
msgctxt "@title:menu menubar:toplevel"
|
||||||
|
@ -4127,7 +4093,7 @@ msgstr "&Hilfe"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:335
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:335
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "This package will be installed after restarting."
|
msgid "This package will be installed after restarting."
|
||||||
msgstr ""
|
msgstr "Dieses Paket wird nach einem Neustart installiert."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:364
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:364
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -4152,7 +4118,7 @@ msgstr "Möchten Sie wirklich ein neues Projekt beginnen? Damit werden das Druck
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:814
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:814
|
||||||
msgctxt "@window:title"
|
msgctxt "@window:title"
|
||||||
msgid "Install Package"
|
msgid "Install Package"
|
||||||
msgstr ""
|
msgstr "Paket installieren"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:821
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:821
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
|
@ -4319,7 +4285,7 @@ msgstr "Engine-Protokoll"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:70
|
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:70
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Printer type"
|
msgid "Printer type"
|
||||||
msgstr "Druckertyp:"
|
msgstr "Druckertyp"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
|
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -4360,7 +4326,7 @@ msgstr "An aktueller Druckplatte ausrichten"
|
||||||
#: MachineSettingsAction/plugin.json
|
#: MachineSettingsAction/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc.)."
|
msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc.)."
|
||||||
msgstr ""
|
msgstr "Beschreibt die Durchführung der Geräteeinstellung (z. B. Druckabmessung, Düsengröße usw.)"
|
||||||
|
|
||||||
#: MachineSettingsAction/plugin.json
|
#: MachineSettingsAction/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
|
@ -4370,12 +4336,12 @@ msgstr "Beschreibung Geräteeinstellungen"
|
||||||
#: Toolbox/plugin.json
|
#: Toolbox/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Find, manage and install new Cura packages."
|
msgid "Find, manage and install new Cura packages."
|
||||||
msgstr ""
|
msgstr "Neue Cura Pakete finden, verwalten und installieren."
|
||||||
|
|
||||||
#: Toolbox/plugin.json
|
#: Toolbox/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "Toolbox"
|
msgid "Toolbox"
|
||||||
msgstr ""
|
msgstr "Toolbox"
|
||||||
|
|
||||||
#: XRayView/plugin.json
|
#: XRayView/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
@ -4470,7 +4436,7 @@ msgstr "USB-Drucken"
|
||||||
#: UserAgreement/plugin.json
|
#: UserAgreement/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Ask the user once if he/she agrees with our license."
|
msgid "Ask the user once if he/she agrees with our license."
|
||||||
msgstr ""
|
msgstr "Den Benutzer einmalig fragen, ob er unsere Lizenz akzeptiert."
|
||||||
|
|
||||||
#: UserAgreement/plugin.json
|
#: UserAgreement/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
|
@ -4480,12 +4446,12 @@ msgstr "UserAgreement"
|
||||||
#: X3GWriter/plugin.json
|
#: X3GWriter/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Allows saving the resulting slice as an X3G file, to support printers that read this format (Malyan, Makerbot and other Sailfish-based printers)."
|
msgid "Allows saving the resulting slice as an X3G file, to support printers that read this format (Malyan, Makerbot and other Sailfish-based printers)."
|
||||||
msgstr ""
|
msgstr "Ermöglicht das Speichern des resultierenden Slices als X3G-Datei, um Drucker zu unterstützen, die dieses Format lesen (Malyan, Makerbot und andere Sailfish-basierte Drucker)."
|
||||||
|
|
||||||
#: X3GWriter/plugin.json
|
#: X3GWriter/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "X3GWriter"
|
msgid "X3GWriter"
|
||||||
msgstr ""
|
msgstr "X3G-Writer"
|
||||||
|
|
||||||
#: GCodeGzWriter/plugin.json
|
#: GCodeGzWriter/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
@ -4530,17 +4496,17 @@ msgstr "Live-Scripting-Tool"
|
||||||
#: RemovableDriveOutputDevice/plugin.json
|
#: RemovableDriveOutputDevice/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Provides removable drive hotplugging and writing support."
|
msgid "Provides removable drive hotplugging and writing support."
|
||||||
msgstr "Ermöglicht Hotplugging des Datenträgers und Beschreiben."
|
msgstr "Ermöglicht Hotplugging des Wechseldatenträgers und Beschreiben."
|
||||||
|
|
||||||
#: RemovableDriveOutputDevice/plugin.json
|
#: RemovableDriveOutputDevice/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "Removable Drive Output Device Plugin"
|
msgid "Removable Drive Output Device Plugin"
|
||||||
msgstr "Ausgabegerät-Plugin für Datenträger"
|
msgstr "Ausgabegerät-Plugin für Wechseldatenträger"
|
||||||
|
|
||||||
#: UM3NetworkPrinting/plugin.json
|
#: UM3NetworkPrinting/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Manages network connections to Ultimaker 3 printers."
|
msgid "Manages network connections to Ultimaker 3 printers."
|
||||||
msgstr ""
|
msgstr "Verwaltet Netzwerkverbindungen zu Ultimaker 3-Druckern"
|
||||||
|
|
||||||
#: UM3NetworkPrinting/plugin.json
|
#: UM3NetworkPrinting/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
|
@ -4670,12 +4636,12 @@ msgstr "Upgrade von Version 3.2 auf 3.3"
|
||||||
#: VersionUpgrade/VersionUpgrade33to34/plugin.json
|
#: VersionUpgrade/VersionUpgrade33to34/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Upgrades configurations from Cura 3.3 to Cura 3.4."
|
msgid "Upgrades configurations from Cura 3.3 to Cura 3.4."
|
||||||
msgstr ""
|
msgstr "Aktualisiert Konfigurationen von Cura 3.3 auf Cura 3.4."
|
||||||
|
|
||||||
#: VersionUpgrade/VersionUpgrade33to34/plugin.json
|
#: VersionUpgrade/VersionUpgrade33to34/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "Version Upgrade 3.3 to 3.4"
|
msgid "Version Upgrade 3.3 to 3.4"
|
||||||
msgstr ""
|
msgstr "Upgrade von Version 3.3 auf 3.4"
|
||||||
|
|
||||||
#: VersionUpgrade/VersionUpgrade25to26/plugin.json
|
#: VersionUpgrade/VersionUpgrade25to26/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
@ -4820,7 +4786,7 @@ msgstr "3MF-Writer"
|
||||||
#: UltimakerMachineActions/plugin.json
|
#: UltimakerMachineActions/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.)."
|
msgid "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.)."
|
||||||
msgstr ""
|
msgstr "Ermöglicht Maschinenabläufe für Ultimaker-Maschinen (z. B. Assistent für Bettnivellierung, Auswahl von Upgrades usw.)"
|
||||||
|
|
||||||
#: UltimakerMachineActions/plugin.json
|
#: UltimakerMachineActions/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
|
|
|
@ -56,9 +56,7 @@ msgctxt "machine_start_gcode description"
|
||||||
msgid ""
|
msgid ""
|
||||||
"G-code commands to be executed at the very start - separated by \n"
|
"G-code commands to be executed at the very start - separated by \n"
|
||||||
"."
|
"."
|
||||||
msgstr ""
|
msgstr "G-Code-Befehle, die zu Beginn ausgeführt werden sollen – getrennt durch \n."
|
||||||
"G-Code-Befehle, die zu Beginn ausgeführt werden sollen – getrennt durch \n"
|
|
||||||
"."
|
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "machine_end_gcode label"
|
msgctxt "machine_end_gcode label"
|
||||||
|
@ -70,9 +68,7 @@ msgctxt "machine_end_gcode description"
|
||||||
msgid ""
|
msgid ""
|
||||||
"G-code commands to be executed at the very end - separated by \n"
|
"G-code commands to be executed at the very end - separated by \n"
|
||||||
"."
|
"."
|
||||||
msgstr ""
|
msgstr "G-Code-Befehle, die am Ende ausgeführt werden sollen – getrennt durch \n."
|
||||||
"G-Code-Befehle, die am Ende ausgeführt werden sollen – getrennt durch \n"
|
|
||||||
"."
|
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "material_guid label"
|
msgctxt "material_guid label"
|
||||||
|
@ -1087,7 +1083,7 @@ msgstr "Reihenfolge des Wanddrucks optimieren"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "optimize_wall_printing_order description"
|
msgctxt "optimize_wall_printing_order description"
|
||||||
msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization. First layer is not optimized when choosing brim as build plate adhesion type."
|
msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization. First layer is not optimized when choosing brim as build plate adhesion type."
|
||||||
msgstr ""
|
msgstr "Optimieren Sie die Reihenfolge, in der die Wände gedruckt werden, um die Anzahl der Einzüge und die zurückgelegten Distanzen zu reduzieren. Dieser Schritt bringt für die meisten Teile Vorteile, allerdings werden einige möglicherweise länger benötigen. Vergleichen Sie deshalb bitte die Schätzung der Druckzeiten mit und ohne Optimierung. Bei Wahl eines Brims als Druckplattenhaftungstyp ist die erste Schicht nicht optimiert."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "outer_inset_first label"
|
msgctxt "outer_inset_first label"
|
||||||
|
@ -1677,22 +1673,22 @@ msgstr "Keine Füllungsbereiche generieren, die kleiner als dieser sind (stattde
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_support_enabled label"
|
msgctxt "infill_support_enabled label"
|
||||||
msgid "Infill Support"
|
msgid "Infill Support"
|
||||||
msgstr ""
|
msgstr "Füllstruktur"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_support_enabled description"
|
msgctxt "infill_support_enabled description"
|
||||||
msgid "Print infill structures only where tops of the model should be supported. Enabling this reduces print time and material usage, but leads to ununiform object strength."
|
msgid "Print infill structures only where tops of the model should be supported. Enabling this reduces print time and material usage, but leads to ununiform object strength."
|
||||||
msgstr ""
|
msgstr "Drucken Sie Füllstrukturen nur dort, wo das Modell gestützt werden soll. Die Aktivierung dieser Option reduziert die Druckdauer und den Materialverbrauch, führt jedoch zu einer ungleichmäßigen Objektdicke."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_support_angle label"
|
msgctxt "infill_support_angle label"
|
||||||
msgid "Infill Overhang Angle"
|
msgid "Infill Overhang Angle"
|
||||||
msgstr ""
|
msgstr "Füllung für Überhänge Stützstruktur"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_support_angle description"
|
msgctxt "infill_support_angle description"
|
||||||
msgid "The minimum angle of internal overhangs for which infill is added. At a value of 0° objects are totally filled with infill, 90° will not provide any infill."
|
msgid "The minimum angle of internal overhangs for which infill is added. At a value of 0° objects are totally filled with infill, 90° will not provide any infill."
|
||||||
msgstr ""
|
msgstr "Der Mindestwinkel für Überhänge, für welche eine Stützstruktur zugefügt wird. Bei einem Wert von 0° werden Objekte komplett gefüllt, bei 90° wird keine Füllung ausgeführt."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "skin_preshrink label"
|
msgctxt "skin_preshrink label"
|
||||||
|
@ -2037,12 +2033,12 @@ msgstr "Das Fenster, in dem die maximale Anzahl von Einzügen durchgeführt wird
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "limit_support_retractions label"
|
msgctxt "limit_support_retractions label"
|
||||||
msgid "Limit Support Retractions"
|
msgid "Limit Support Retractions"
|
||||||
msgstr ""
|
msgstr "Stützstruktur-Einzüge einschränken"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "limit_support_retractions description"
|
msgctxt "limit_support_retractions description"
|
||||||
msgid "Omit retraction when moving from support to support in a straight line. Enabling this setting saves print time, but can lead to excesive stringing within the support structure."
|
msgid "Omit retraction when moving from support to support in a straight line. Enabling this setting saves print time, but can lead to excesive stringing within the support structure."
|
||||||
msgstr ""
|
msgstr "Lassen Sie den Einzug beim Vorgehen von Stützstruktur zu Stützstruktur in einer geraden Linie aus. Die Aktivierung dieser Einstellung spart Druckzeit, kann jedoch zu übermäßigem Fadenziehen innerhalb der Stützstruktur führen."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "material_standby_temperature label"
|
msgctxt "material_standby_temperature label"
|
||||||
|
@ -2737,17 +2733,17 @@ msgstr "Alle"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "retraction_combing option noskin"
|
msgctxt "retraction_combing option noskin"
|
||||||
msgid "Not in Skin"
|
msgid "Not in Skin"
|
||||||
msgstr ""
|
msgstr "Nicht in Außenhaut"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "retraction_combing_max_distance label"
|
msgctxt "retraction_combing_max_distance label"
|
||||||
msgid "Max Comb Distance With No Retract"
|
msgid "Max Comb Distance With No Retract"
|
||||||
msgstr ""
|
msgstr "Max. Kammentfernung ohne Einziehen"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "retraction_combing_max_distance description"
|
msgctxt "retraction_combing_max_distance description"
|
||||||
msgid "When non-zero, combing travel moves that are longer than this distance will use retraction."
|
msgid "When non-zero, combing travel moves that are longer than this distance will use retraction."
|
||||||
msgstr ""
|
msgstr "Bei Nicht-Null verwenden die Combing-Fahrbewegungen, die länger als die Distanz sind, die Einziehfunktion."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "travel_retract_before_outer_wall label"
|
msgctxt "travel_retract_before_outer_wall label"
|
||||||
|
@ -2772,12 +2768,12 @@ msgstr "Die Düse vermeidet bei der Bewegung bereits gedruckte Teile. Diese Opti
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "travel_avoid_supports label"
|
msgctxt "travel_avoid_supports label"
|
||||||
msgid "Avoid Supports When Traveling"
|
msgid "Avoid Supports When Traveling"
|
||||||
msgstr ""
|
msgstr "Stützstrukturen bei Bewegung umgehen"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "travel_avoid_supports description"
|
msgctxt "travel_avoid_supports description"
|
||||||
msgid "The nozzle avoids already printed supports when traveling. This option is only available when combing is enabled."
|
msgid "The nozzle avoids already printed supports when traveling. This option is only available when combing is enabled."
|
||||||
msgstr ""
|
msgstr "Die Düse vermeidet bei der Bewegung bereits gedruckte Stützstrukturen. Diese Option ist nur verfügbar, wenn Combing aktiviert ist."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "travel_avoid_distance label"
|
msgctxt "travel_avoid_distance label"
|
||||||
|
@ -3137,12 +3133,12 @@ msgstr "Quer"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_wall_count label"
|
msgctxt "support_wall_count label"
|
||||||
msgid "Support Wall Line Count"
|
msgid "Support Wall Line Count"
|
||||||
msgstr ""
|
msgstr "Anzahl der Wandlinien der Stützstruktur"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_wall_count description"
|
msgctxt "support_wall_count description"
|
||||||
msgid "The number of walls with which to surround support infill. Adding a wall can make support print more reliably and can support overhangs better, but increases print time and material used."
|
msgid "The number of walls with which to surround support infill. Adding a wall can make support print more reliably and can support overhangs better, but increases print time and material used."
|
||||||
msgstr ""
|
msgstr "Die Anzahl der Wände, mit denen die Stützstruktur-Füllung umgeben wird. Das Hinzufügen einer Wand kann den Druck der Stützstruktur zuverlässiger machen und Überhänge besser unterstützen. Es erhöht jedoch die Druckzeit und den Materialverbrauch."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "zig_zaggify_support label"
|
msgctxt "zig_zaggify_support label"
|
||||||
|
@ -3714,9 +3710,7 @@ msgctxt "skirt_gap description"
|
||||||
msgid ""
|
msgid ""
|
||||||
"The horizontal distance between the skirt and the first layer of the print.\n"
|
"The horizontal distance between the skirt and the first layer of the print.\n"
|
||||||
"This is the minimum distance. Multiple skirt lines will extend outwards from this distance."
|
"This is the minimum distance. Multiple skirt lines will extend outwards from this distance."
|
||||||
msgstr ""
|
msgstr "Der horizontale Abstand zwischen dem Skirt und der ersten Schicht des Drucks.\nEs handelt sich dabei um den Mindestabstand. Ab diesem Abstand werden mehrere Skirt-Linien in äußerer Richtung angebracht."
|
||||||
"Der horizontale Abstand zwischen dem Skirt und der ersten Schicht des Drucks.\n"
|
|
||||||
"Es handelt sich dabei um den Mindestabstand. Ab diesem Abstand werden mehrere Skirt-Linien in äußerer Richtung angebracht."
|
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "skirt_brim_minimal_length label"
|
msgctxt "skirt_brim_minimal_length label"
|
||||||
|
@ -4676,12 +4670,12 @@ msgstr "Die Mindestgröße eines Linienabschnitts nach dem Slicen. Wenn Sie dies
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "meshfix_maximum_travel_resolution label"
|
msgctxt "meshfix_maximum_travel_resolution label"
|
||||||
msgid "Maximum Travel Resolution"
|
msgid "Maximum Travel Resolution"
|
||||||
msgstr ""
|
msgstr "Maximale Bewegungsauflösung"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "meshfix_maximum_travel_resolution description"
|
msgctxt "meshfix_maximum_travel_resolution description"
|
||||||
msgid "The minimum size of a travel line segment after slicing. If you increase this, the travel moves will have less smooth corners. This may allow the printer to keep up with the speed it has to process g-code, but it may cause model avoidance to become less accurate."
|
msgid "The minimum size of a travel line segment after slicing. If you increase this, the travel moves will have less smooth corners. This may allow the printer to keep up with the speed it has to process g-code, but it may cause model avoidance to become less accurate."
|
||||||
msgstr ""
|
msgstr "Die maximale Größe eines Bewegungsliniensegments nach dem Slicen. Wenn Sie diesen Wert erhöhen, weisen die Fahrtbewegungen weniger glatte Kanten aus. Das ermöglicht dem Drucker, die für die Verarbeitung eines G-Codes erforderliche Geschwindigkeit aufrechtzuerhalten, allerdings kann das Modell damit auch weniger akkurat werden."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_skip_some_zags label"
|
msgctxt "support_skip_some_zags label"
|
||||||
|
@ -4846,22 +4840,22 @@ msgstr "Die Größe der Taschen bei Überkreuzung im 3D-Quermuster bei Höhen, i
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "cross_infill_density_image label"
|
msgctxt "cross_infill_density_image label"
|
||||||
msgid "Cross Infill Density Image"
|
msgid "Cross Infill Density Image"
|
||||||
msgstr ""
|
msgstr "Querfülldichte Bild"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "cross_infill_density_image description"
|
msgctxt "cross_infill_density_image description"
|
||||||
msgid "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the infill of the print."
|
msgid "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the infill of the print."
|
||||||
msgstr ""
|
msgstr "Die Dateiposition eines Bildes, von dem die Helligkeitswerte die minimale Dichte an der entsprechenden Position in der Füllung des Drucks bestimmen."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "cross_support_density_image label"
|
msgctxt "cross_support_density_image label"
|
||||||
msgid "Cross Fill Density Image for Support"
|
msgid "Cross Fill Density Image for Support"
|
||||||
msgstr ""
|
msgstr "Querfülldichte Bild für Stützstruktur"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "cross_support_density_image description"
|
msgctxt "cross_support_density_image description"
|
||||||
msgid "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the support."
|
msgid "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the support."
|
||||||
msgstr ""
|
msgstr "Die Dateiposition eines Bildes, von dem die Helligkeitswerte die minimale Dichte an der entsprechenden Position in der Stützstruktur bestimmen."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "spaghetti_infill_enabled label"
|
msgctxt "spaghetti_infill_enabled label"
|
||||||
|
@ -5173,9 +5167,7 @@ msgctxt "wireframe_up_half_speed description"
|
||||||
msgid ""
|
msgid ""
|
||||||
"Distance of an upward move which is extruded with half speed.\n"
|
"Distance of an upward move which is extruded with half speed.\n"
|
||||||
"This can cause better adhesion to previous layers, while not heating the material in those layers too much. Only applies to Wire Printing."
|
"This can cause better adhesion to previous layers, while not heating the material in those layers too much. Only applies to Wire Printing."
|
||||||
msgstr ""
|
msgstr "Die Strecke einer Aufwärtsbewegung, die mit halber Geschwindigkeit extrudiert wird.\nDies kann zu einer besseren Haftung an vorhergehenden Schichten führen, während gleichzeitig ein Überhitzen des Materials in diesen Schichten vermieden wird. Dies gilt nur für das Drucken mit Drahtstruktur."
|
||||||
"Die Strecke einer Aufwärtsbewegung, die mit halber Geschwindigkeit extrudiert wird.\n"
|
|
||||||
"Dies kann zu einer besseren Haftung an vorhergehenden Schichten führen, während gleichzeitig ein Überhitzen des Materials in diesen Schichten vermieden wird. Dies gilt nur für das Drucken mit Drahtstruktur."
|
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "wireframe_top_jump label"
|
msgctxt "wireframe_top_jump label"
|
||||||
|
@ -5300,7 +5292,7 @@ msgstr "Maximale Abweichung für Anpassschichten"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_variation description"
|
msgctxt "adaptive_layer_height_variation description"
|
||||||
msgid "The maximum allowed height different from the base layer height."
|
msgid "The maximum allowed height different from the base layer height."
|
||||||
msgstr ""
|
msgstr "Die max. zulässige Höhendifferenz von der Basisschichthöhe."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_variation_step label"
|
msgctxt "adaptive_layer_height_variation_step label"
|
||||||
|
@ -5535,7 +5527,7 @@ msgstr "Diese Einstellungen werden nur verwendet, wenn CuraEngine nicht seitens
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "center_object label"
|
msgctxt "center_object label"
|
||||||
msgid "Center Object"
|
msgid "Center Object"
|
||||||
msgstr ""
|
msgstr "Objekt zentrieren"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "center_object description"
|
msgctxt "center_object description"
|
||||||
|
@ -5545,7 +5537,7 @@ msgstr "Ermöglicht das Zentrieren des Objekts in der Mitte eines Druckbetts (0,
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_x label"
|
msgctxt "mesh_position_x label"
|
||||||
msgid "Mesh Position X"
|
msgid "Mesh Position X"
|
||||||
msgstr ""
|
msgstr "Netzposition X"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_x description"
|
msgctxt "mesh_position_x description"
|
||||||
|
@ -5555,7 +5547,7 @@ msgstr "Verwendeter Versatz für das Objekt in X-Richtung."
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_y label"
|
msgctxt "mesh_position_y label"
|
||||||
msgid "Mesh Position Y"
|
msgid "Mesh Position Y"
|
||||||
msgstr ""
|
msgstr "Netzposition Y"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_y description"
|
msgctxt "mesh_position_y description"
|
||||||
|
@ -5565,7 +5557,7 @@ msgstr "Verwendeter Versatz für das Objekt in Y-Richtung."
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_z label"
|
msgctxt "mesh_position_z label"
|
||||||
msgid "Mesh Position Z"
|
msgid "Mesh Position Z"
|
||||||
msgstr ""
|
msgstr "Netzposition Z"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_z description"
|
msgctxt "mesh_position_z description"
|
||||||
|
|
|
@ -43,7 +43,7 @@ msgstr "Archivo GCode"
|
||||||
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:30
|
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:30
|
||||||
msgctxt "@info:title"
|
msgctxt "@info:title"
|
||||||
msgid "3D Model Assistant"
|
msgid "3D Model Assistant"
|
||||||
msgstr ""
|
msgstr "Asistente del modelo 3D"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:80
|
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.py:80
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -53,7 +53,10 @@ msgid ""
|
||||||
"<p>{model_names}</p>\n"
|
"<p>{model_names}</p>\n"
|
||||||
"<p>Find out how to ensure the best possible print quality and reliability.</p>\n"
|
"<p>Find out how to ensure the best possible print quality and reliability.</p>\n"
|
||||||
"<p><a href=\"https://ultimaker.com/3D-model-assistant\">View print quality guide</a></p>"
|
"<p><a href=\"https://ultimaker.com/3D-model-assistant\">View print quality guide</a></p>"
|
||||||
msgstr ""
|
msgstr "<p>Es posible que uno o más modelos 3D no se impriman correctamente debido al tamaño del modelo y la configuración del material:</p>\n"
|
||||||
|
"<p>{model_names}</p>\n"
|
||||||
|
"<p>Obtenga más información sobre cómo garantizar la mejor calidad y fiabilidad de impresión posible.</p>\n"
|
||||||
|
"<p><a href=\"https://ultimaker.com/3D-model-assistant\">Ver guía de impresión de calidad</a></p>"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65
|
#: /home/ruben/Projects/Cura/plugins/Doodle3D-cura-plugin/Doodle3D/D3DCloudPrintOutputDevicePlugin.py:65
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -160,12 +163,12 @@ msgstr "Archivo X3G"
|
||||||
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/GPX-prefix/src/GPX/slicerplugins/cura15.06/X3gWriter/__init__.py:16
|
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/GPX-prefix/src/GPX/slicerplugins/cura15.06/X3gWriter/__init__.py:16
|
||||||
msgctxt "X3g Writer Plugin Description"
|
msgctxt "X3g Writer Plugin Description"
|
||||||
msgid "Writes X3g to files"
|
msgid "Writes X3g to files"
|
||||||
msgstr ""
|
msgstr "Escribe X3g en archivos"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/GPX-prefix/src/GPX/slicerplugins/cura15.06/X3gWriter/__init__.py:21
|
#: /home/ruben/Projects/Cura/plugins/X3GWriter/build/GPX-prefix/src/GPX/slicerplugins/cura15.06/X3gWriter/__init__.py:21
|
||||||
msgctxt "X3g Writer File Description"
|
msgctxt "X3g Writer File Description"
|
||||||
msgid "X3g File"
|
msgid "X3g File"
|
||||||
msgstr ""
|
msgstr "Archivo X3g"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/GCodeGzWriter/__init__.py:17
|
#: /home/ruben/Projects/Cura/plugins/GCodeGzWriter/__init__.py:17
|
||||||
#: /home/ruben/Projects/Cura/plugins/GCodeGzReader/__init__.py:17
|
#: /home/ruben/Projects/Cura/plugins/GCodeGzReader/__init__.py:17
|
||||||
|
@ -570,12 +573,12 @@ msgstr "Recopilando datos"
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "More info"
|
msgid "More info"
|
||||||
msgstr ""
|
msgstr "Más información"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
|
||||||
msgctxt "@action:tooltip"
|
msgctxt "@action:tooltip"
|
||||||
msgid "See more information on what data Cura sends."
|
msgid "See more information on what data Cura sends."
|
||||||
msgstr ""
|
msgstr "Obtenga más información sobre qué datos envía Cura."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:52
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -602,9 +605,8 @@ msgctxt "@info:status"
|
||||||
msgid ""
|
msgid ""
|
||||||
"Could not export using \"{}\" quality!\n"
|
"Could not export using \"{}\" quality!\n"
|
||||||
"Felt back to \"{}\"."
|
"Felt back to \"{}\"."
|
||||||
msgstr ""
|
msgstr "No ha podido exportarse con la calidad \"{}\"\n"
|
||||||
"No ha podido exportarse con la calidad \"{}\"\n"
|
"Retroceder a \"{}\"."
|
||||||
"Retroceder a \"{}»."
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14
|
#: /home/ruben/Projects/Cura/plugins/ImageReader/__init__.py:14
|
||||||
msgctxt "@item:inlistbox"
|
msgctxt "@item:inlistbox"
|
||||||
|
@ -1014,22 +1016,22 @@ msgstr "Volumen de impresión"
|
||||||
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:99
|
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:99
|
||||||
msgctxt "@info:backup_failed"
|
msgctxt "@info:backup_failed"
|
||||||
msgid "Could not create archive from user data directory: {}"
|
msgid "Could not create archive from user data directory: {}"
|
||||||
msgstr ""
|
msgstr "No se ha podido crear el archivo desde el directorio de datos de usuario: {}"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:104
|
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:104
|
||||||
msgctxt "@info:title"
|
msgctxt "@info:title"
|
||||||
msgid "Backup"
|
msgid "Backup"
|
||||||
msgstr ""
|
msgstr "Copia de seguridad"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:116
|
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:116
|
||||||
msgctxt "@info:backup_failed"
|
msgctxt "@info:backup_failed"
|
||||||
msgid "Tried to restore a Cura backup without having proper data or meta data."
|
msgid "Tried to restore a Cura backup without having proper data or meta data."
|
||||||
msgstr ""
|
msgstr "Se ha intentado restaurar una copia de seguridad de Cura sin tener los datos o metadatos adecuados."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:126
|
#: /home/ruben/Projects/Cura/cura/Backups/Backup.py:126
|
||||||
msgctxt "@info:backup_failed"
|
msgctxt "@info:backup_failed"
|
||||||
msgid "Tried to restore a Cura backup that does not match your current version."
|
msgid "Tried to restore a Cura backup that does not match your current version."
|
||||||
msgstr ""
|
msgstr "Se ha intentado restaurar una copia de seguridad de Cura que no coincide con la versión actual."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:27
|
#: /home/ruben/Projects/Cura/cura/MultiplyObjectsJob.py:27
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
|
@ -1120,7 +1122,7 @@ msgid ""
|
||||||
" "
|
" "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"<p><b>Se ha producido un error grave en Cura. Envíenos este informe de errores para que podamos solucionar el problema.</p></b>\n"
|
"<p><b>Se ha producido un error grave en Cura. Envíenos este informe de errores para que podamos solucionar el problema.</p></b>\n"
|
||||||
" <p>Utilice el botón «Enviar informe» para publicar automáticamente el informe de errores en nuestros servidores.</p>\n"
|
" <p>Utilice el botón \"Enviar informe\" para publicar automáticamente el informe de errores en nuestros servidores.</p>\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:177
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:177
|
||||||
|
@ -1435,7 +1437,7 @@ msgstr "Instalado"
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxErrorPage.qml:16
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxErrorPage.qml:16
|
||||||
msgctxt "@info"
|
msgctxt "@info"
|
||||||
msgid "Could not connect to the Cura Package database. Please check your connection."
|
msgid "Could not connect to the Cura Package database. Please check your connection."
|
||||||
msgstr ""
|
msgstr "No se ha podido conectar con la base de datos del Paquete Cura. Compruebe la conexión."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:35
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledPage.qml:35
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:26
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:26
|
||||||
|
@ -1446,17 +1448,17 @@ msgstr "Complementos"
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:79
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:79
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Version"
|
msgid "Version"
|
||||||
msgstr ""
|
msgstr "Versión"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:85
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:85
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Last updated"
|
msgid "Last updated"
|
||||||
msgstr ""
|
msgstr "Última actualización"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:91
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:91
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Author"
|
msgid "Author"
|
||||||
msgstr ""
|
msgstr "Autor"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:109
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailPage.qml:109
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:269
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:269
|
||||||
|
@ -1474,53 +1476,53 @@ msgstr "Actualizar"
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:31
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:31
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Updating"
|
msgid "Updating"
|
||||||
msgstr ""
|
msgstr "Actualizando"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml:46
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDetailTileActions.qml:46
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:32
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:32
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Updated"
|
msgid "Updated"
|
||||||
msgstr ""
|
msgstr "Actualizado"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/Toolbox.qml:13
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/Toolbox.qml:13
|
||||||
msgctxt "@title"
|
msgctxt "@title"
|
||||||
msgid "Toolbox"
|
msgid "Toolbox"
|
||||||
msgstr ""
|
msgstr "Cuadro de herramientas"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxBackColumn.qml:25
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxBackColumn.qml:25
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr ""
|
msgstr "Atrás"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:17
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:17
|
||||||
msgctxt "@info"
|
msgctxt "@info"
|
||||||
msgid "You will need to restart Cura before changes in packages have effect."
|
msgid "You will need to restart Cura before changes in packages have effect."
|
||||||
msgstr ""
|
msgstr "Tendrá que reiniciar Cura para que los cambios de los paquetes surtan efecto."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:32
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxFooter.qml:32
|
||||||
msgctxt "@info:button"
|
msgctxt "@info:button"
|
||||||
msgid "Quit Cura"
|
msgid "Quit Cura"
|
||||||
msgstr ""
|
msgstr "Salir de Cura"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:54
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxHeader.qml:54
|
||||||
msgctxt "@title:tab"
|
msgctxt "@title:tab"
|
||||||
msgid "Installed"
|
msgid "Installed"
|
||||||
msgstr ""
|
msgstr "Instalado"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:19
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:19
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Will install upon restarting"
|
msgid "Will install upon restarting"
|
||||||
msgstr ""
|
msgstr "Se instalará después de reiniciar"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:51
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:51
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Downgrade"
|
msgid "Downgrade"
|
||||||
msgstr ""
|
msgstr "Degradar"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:51
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxInstalledTileActions.qml:51
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Uninstall"
|
msgid "Uninstall"
|
||||||
msgstr ""
|
msgstr "Desinstalar"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml:16
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml:16
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
|
@ -1533,10 +1535,7 @@ msgid ""
|
||||||
"This plugin contains a license.\n"
|
"This plugin contains a license.\n"
|
||||||
"You need to accept this license to install this plugin.\n"
|
"You need to accept this license to install this plugin.\n"
|
||||||
"Do you agree with the terms below?"
|
"Do you agree with the terms below?"
|
||||||
msgstr ""
|
msgstr "Este complemento incluye una licencia.\nDebe aceptar dicha licencia para instalar el complemento.\n¿Acepta las condiciones que aparecen a continuación?"
|
||||||
"Este complemento incluye una licencia.\n"
|
|
||||||
"Debe aceptar dicha licencia para instalar el complemento.\n"
|
|
||||||
"¿Acepta las condiciones que aparecen a continuación?"
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml:54
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLicenseDialog.qml:54
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -1551,22 +1550,22 @@ msgstr "Rechazar"
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:17
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxDownloadsShowcase.qml:17
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Featured"
|
msgid "Featured"
|
||||||
msgstr ""
|
msgstr "Destacado"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:20
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxCompatibilityChart.qml:20
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Compatibility"
|
msgid "Compatibility"
|
||||||
msgstr ""
|
msgstr "Compatibilidad"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml:16
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxLoadingPage.qml:16
|
||||||
msgctxt "@info"
|
msgctxt "@info"
|
||||||
msgid "Fetching packages..."
|
msgid "Fetching packages..."
|
||||||
msgstr ""
|
msgstr "Buscando paquetes..."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:87
|
#: /home/ruben/Projects/Cura/plugins/Toolbox/resources/qml/ToolboxAuthorPage.qml:87
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Contact"
|
msgid "Contact"
|
||||||
msgstr ""
|
msgstr "Contacto"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.qml:22
|
#: /home/ruben/Projects/Cura/plugins/ModelChecker/ModelChecker.qml:22
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
|
@ -1651,10 +1650,7 @@ msgid ""
|
||||||
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
|
"To print directly to your printer over the network, please make sure your printer is connected to the network using a network cable or by connecting your printer to your WIFI network. If you don't connect Cura with your printer, you can still use a USB drive to transfer g-code files to your printer.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Select your printer from the list below:"
|
"Select your printer from the list below:"
|
||||||
msgstr ""
|
msgstr "Para imprimir directamente en la impresora a través de la red, asegúrese de que esta está conectada a la red utilizando un cable de red o conéctela a la red wifi. Si no conecta Cura con la impresora, también puede utilizar una unidad USB para transferir archivos GCode a la impresora.\n\nSeleccione la impresora de la siguiente lista:"
|
||||||
"Para imprimir directamente en la impresora a través de la red, asegúrese de que esta está conectada a la red utilizando un cable de red o conéctela a la red wifi. Si no conecta Cura con la impresora, también puede utilizar una unidad USB para transferir archivos GCode a la impresora.\n"
|
|
||||||
"\n"
|
|
||||||
"Seleccione la impresora de la siguiente lista:"
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:96
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:42
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:42
|
||||||
|
@ -2015,22 +2011,22 @@ msgstr "Cambia las secuencias de comandos de posprocesamiento."
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:16
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:16
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
msgid "More information on anonymous data collection"
|
msgid "More information on anonymous data collection"
|
||||||
msgstr ""
|
msgstr "Más información sobre la recopilación de datos anónimos"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:66
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:66
|
||||||
msgctxt "@text:window"
|
msgctxt "@text:window"
|
||||||
msgid "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent."
|
msgid "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent."
|
||||||
msgstr ""
|
msgstr "Cura envía datos anónimos a Ultimaker para mejorar la calidad de impresión y la experiencia de usuario. A continuación, hay un ejemplo de todos los datos que se han enviado."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:101
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:101
|
||||||
msgctxt "@text:window"
|
msgctxt "@text:window"
|
||||||
msgid "I don't want to send these data"
|
msgid "I don't want to send these data"
|
||||||
msgstr ""
|
msgstr "No quiero enviar estos datos"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:111
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/MoreInfoWindow.qml:111
|
||||||
msgctxt "@text:window"
|
msgctxt "@text:window"
|
||||||
msgid "Allow sending these data to Ultimaker and help us improve Cura"
|
msgid "Allow sending these data to Ultimaker and help us improve Cura"
|
||||||
msgstr ""
|
msgstr "Permita enviar estos datos a Ultimaker y ayúdenos a mejorar Cura"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:19
|
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:19
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
|
@ -2544,9 +2540,7 @@ msgctxt "@text:window"
|
||||||
msgid ""
|
msgid ""
|
||||||
"You have customized some profile settings.\n"
|
"You have customized some profile settings.\n"
|
||||||
"Would you like to keep or discard those settings?"
|
"Would you like to keep or discard those settings?"
|
||||||
msgstr ""
|
msgstr "Ha personalizado parte de los ajustes del perfil.\n¿Desea descartar los cambios o guardarlos?"
|
||||||
"Ha personalizado parte de los ajustes del perfil.\n"
|
|
||||||
"¿Desea descartar los cambios o guardarlos?"
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:110
|
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:110
|
||||||
msgctxt "@title:column"
|
msgctxt "@title:column"
|
||||||
|
@ -2609,7 +2603,7 @@ msgstr "Confirmar cambio de diámetro"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:95
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:95
|
||||||
msgctxt "@label (%1 is a number)"
|
msgctxt "@label (%1 is a number)"
|
||||||
msgid "The new filament diameter is set to %1 mm, which is not compatible with the current extruder. Do you wish to continue?"
|
msgid "The new filament diameter is set to %1 mm, which is not compatible with the current extruder. Do you wish to continue?"
|
||||||
msgstr ""
|
msgstr "El nuevo diámetro del filamento está ajustado en %1 mm y no es compatible con el extrusor actual. ¿Desea continuar?"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:128
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:128
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -2880,12 +2874,12 @@ msgstr "Escalar modelos demasiado pequeños"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:507
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:507
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
msgid "Should models be selected after they are loaded?"
|
msgid "Should models be selected after they are loaded?"
|
||||||
msgstr ""
|
msgstr "¿Se deberían seleccionar los modelos después de haberse cargado?"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:512
|
||||||
msgctxt "@option:check"
|
msgctxt "@option:check"
|
||||||
msgid "Select models when loaded"
|
msgid "Select models when loaded"
|
||||||
msgstr ""
|
msgstr "Seleccionar modelos al abrirlos"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:522
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
|
@ -2970,7 +2964,7 @@ msgstr "Enviar información (anónima) de impresión"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:708
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:708
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "More information"
|
msgid "More information"
|
||||||
msgstr ""
|
msgstr "Más información"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:726
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:726
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3246,9 +3240,7 @@ msgctxt "@info:credit"
|
||||||
msgid ""
|
msgid ""
|
||||||
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
|
"Cura is developed by Ultimaker B.V. in cooperation with the community.\n"
|
||||||
"Cura proudly uses the following open source projects:"
|
"Cura proudly uses the following open source projects:"
|
||||||
msgstr ""
|
msgstr "Ultimaker B.V. ha desarrollado Cura en cooperación con la comunidad.\nCura se enorgullece de utilizar los siguientes proyectos de código abierto:"
|
||||||
"Ultimaker B.V. ha desarrollado Cura en cooperación con la comunidad.\n"
|
|
||||||
"Cura se enorgullece de utilizar los siguientes proyectos de código abierto:"
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
|
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:118
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3361,10 +3353,7 @@ msgid ""
|
||||||
"Some setting/override values are different from the values stored in the profile.\n"
|
"Some setting/override values are different from the values stored in the profile.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Click to open the profile manager."
|
"Click to open the profile manager."
|
||||||
msgstr ""
|
msgstr "Algunos valores de los ajustes o sobrescrituras son distintos a los valores almacenados en el perfil.\n\nHaga clic para abrir el administrador de perfiles."
|
||||||
"Algunos valores de los ajustes o sobrescrituras son distintos a los valores almacenados en el perfil.\n"
|
|
||||||
"\n"
|
|
||||||
"Haga clic para abrir el administrador de perfiles."
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:199
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:199
|
||||||
msgctxt "@label:textbox"
|
msgctxt "@label:textbox"
|
||||||
|
@ -3405,12 +3394,12 @@ msgstr "Configurar visibilidad de los ajustes..."
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:621
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:621
|
||||||
msgctxt "@action:inmenu"
|
msgctxt "@action:inmenu"
|
||||||
msgid "Collapse All"
|
msgid "Collapse All"
|
||||||
msgstr ""
|
msgstr "Contraer todo"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:626
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingView.qml:626
|
||||||
msgctxt "@action:inmenu"
|
msgctxt "@action:inmenu"
|
||||||
msgid "Expand All"
|
msgid "Expand All"
|
||||||
msgstr ""
|
msgstr "Ampliar todo"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:249
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingCategory.qml:249
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3418,10 +3407,7 @@ msgid ""
|
||||||
"Some hidden settings use values different from their normal calculated value.\n"
|
"Some hidden settings use values different from their normal calculated value.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Click to make these settings visible."
|
"Click to make these settings visible."
|
||||||
msgstr ""
|
msgstr "Algunos ajustes ocultos utilizan valores diferentes de los valores normales calculados.\n\nHaga clic para mostrar estos ajustes."
|
||||||
"Algunos ajustes ocultos utilizan valores diferentes de los valores normales calculados.\n"
|
|
||||||
"\n"
|
|
||||||
"Haga clic para mostrar estos ajustes."
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:61
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:61
|
||||||
msgctxt "@label Header for list of settings."
|
msgctxt "@label Header for list of settings."
|
||||||
|
@ -3449,10 +3435,7 @@ msgid ""
|
||||||
"This setting has a value that is different from the profile.\n"
|
"This setting has a value that is different from the profile.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Click to restore the value of the profile."
|
"Click to restore the value of the profile."
|
||||||
msgstr ""
|
msgstr "Este ajuste tiene un valor distinto del perfil.\n\nHaga clic para restaurar el valor del perfil."
|
||||||
"Este ajuste tiene un valor distinto del perfil.\n"
|
|
||||||
"\n"
|
|
||||||
"Haga clic para restaurar el valor del perfil."
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:286
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:286
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3460,10 +3443,7 @@ msgid ""
|
||||||
"This setting is normally calculated, but it currently has an absolute value set.\n"
|
"This setting is normally calculated, but it currently has an absolute value set.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Click to restore the calculated value."
|
"Click to restore the calculated value."
|
||||||
msgstr ""
|
msgstr "Este ajuste se calcula normalmente pero actualmente tiene un valor absoluto establecido.\n\nHaga clic para restaurar el valor calculado."
|
||||||
"Este ajuste se calcula normalmente pero actualmente tiene un valor absoluto establecido.\n"
|
|
||||||
"\n"
|
|
||||||
"Haga clic para restaurar el valor calculado."
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/ManualPrinterControl.qml:129
|
#: /home/ruben/Projects/Cura/resources/qml/PrinterOutput/ManualPrinterControl.qml:129
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3607,7 +3587,7 @@ msgstr "&Placa de impresión"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:13
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:13
|
||||||
msgctxt "@action:inmenu"
|
msgctxt "@action:inmenu"
|
||||||
msgid "Visible Settings"
|
msgid "Visible Settings"
|
||||||
msgstr "Ajustes visibles:"
|
msgstr "Ajustes visibles"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:43
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/SettingVisibilityPresetsMenu.qml:43
|
||||||
msgctxt "@action:inmenu"
|
msgctxt "@action:inmenu"
|
||||||
|
@ -3651,12 +3631,12 @@ msgstr "Extrusor"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/ConfigurationMenu/SyncButton.qml:16
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/ConfigurationMenu/SyncButton.qml:16
|
||||||
msgctxt "@label:extruder label"
|
msgctxt "@label:extruder label"
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
msgstr ""
|
msgstr "Sí"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/ConfigurationMenu/SyncButton.qml:16
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/ConfigurationMenu/SyncButton.qml:16
|
||||||
msgctxt "@label:extruder label"
|
msgctxt "@label:extruder label"
|
||||||
msgid "No"
|
msgid "No"
|
||||||
msgstr ""
|
msgstr "No"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/RecentFilesMenu.qml:13
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/RecentFilesMenu.qml:13
|
||||||
msgctxt "@title:menu menubar:file"
|
msgctxt "@title:menu menubar:file"
|
||||||
|
@ -3673,9 +3653,7 @@ msgctxt "@label:listbox"
|
||||||
msgid ""
|
msgid ""
|
||||||
"Print Setup disabled\n"
|
"Print Setup disabled\n"
|
||||||
"G-code files cannot be modified"
|
"G-code files cannot be modified"
|
||||||
msgstr ""
|
msgstr "Ajustes de impresión deshabilitados\nNo se pueden modificar los archivos GCode"
|
||||||
"Ajustes de impresión deshabilitados\n"
|
|
||||||
"No se pueden modificar los archivos GCode"
|
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:341
|
#: /home/ruben/Projects/Cura/resources/qml/PrepareSidebar.qml:341
|
||||||
msgctxt "@label Hours and minutes"
|
msgctxt "@label Hours and minutes"
|
||||||
|
@ -3954,7 +3932,7 @@ msgstr "Mostrar carpeta de configuración"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:433
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:433
|
||||||
msgctxt "@action:menu"
|
msgctxt "@action:menu"
|
||||||
msgid "Browse packages..."
|
msgid "Browse packages..."
|
||||||
msgstr ""
|
msgstr "Examinar paquetes..."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:440
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:440
|
||||||
msgctxt "@action:inmenu menubar:view"
|
msgctxt "@action:inmenu menubar:view"
|
||||||
|
@ -4112,7 +4090,7 @@ msgstr "E&xtensiones"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:274
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:274
|
||||||
msgctxt "@title:menu menubar:toplevel"
|
msgctxt "@title:menu menubar:toplevel"
|
||||||
msgid "&Toolbox"
|
msgid "&Toolbox"
|
||||||
msgstr ""
|
msgstr "&Cuadro de herramientas"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:281
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:281
|
||||||
msgctxt "@title:menu menubar:toplevel"
|
msgctxt "@title:menu menubar:toplevel"
|
||||||
|
@ -4127,7 +4105,7 @@ msgstr "A&yuda"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:335
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:335
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "This package will be installed after restarting."
|
msgid "This package will be installed after restarting."
|
||||||
msgstr ""
|
msgstr "Este paquete se instalará después de reiniciar."
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:364
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:364
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -4152,7 +4130,7 @@ msgstr "¿Está seguro de que desea iniciar un nuevo proyecto? Esto borrará la
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:814
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:814
|
||||||
msgctxt "@window:title"
|
msgctxt "@window:title"
|
||||||
msgid "Install Package"
|
msgid "Install Package"
|
||||||
msgstr ""
|
msgstr "Instalar paquete"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:821
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:821
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
|
@ -4319,7 +4297,7 @@ msgstr "Registro del motor"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:70
|
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:70
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Printer type"
|
msgid "Printer type"
|
||||||
msgstr "Tipo de impresora:"
|
msgstr "Tipo de impresora"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
|
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -4360,7 +4338,7 @@ msgstr "Organizar placa de impresión actual"
|
||||||
#: MachineSettingsAction/plugin.json
|
#: MachineSettingsAction/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc.)."
|
msgid "Provides a way to change machine settings (such as build volume, nozzle size, etc.)."
|
||||||
msgstr ""
|
msgstr "Permite cambiar los ajustes de la máquina (como el volumen de impresión, el tamaño de la tobera, etc.)."
|
||||||
|
|
||||||
#: MachineSettingsAction/plugin.json
|
#: MachineSettingsAction/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
|
@ -4370,12 +4348,12 @@ msgstr "Acción Ajustes de la máquina"
|
||||||
#: Toolbox/plugin.json
|
#: Toolbox/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Find, manage and install new Cura packages."
|
msgid "Find, manage and install new Cura packages."
|
||||||
msgstr ""
|
msgstr "Buscar, administrar e instalar nuevos paquetes de Cura."
|
||||||
|
|
||||||
#: Toolbox/plugin.json
|
#: Toolbox/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "Toolbox"
|
msgid "Toolbox"
|
||||||
msgstr ""
|
msgstr "Cuadro de herramientas"
|
||||||
|
|
||||||
#: XRayView/plugin.json
|
#: XRayView/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
@ -4470,7 +4448,7 @@ msgstr "Impresión USB"
|
||||||
#: UserAgreement/plugin.json
|
#: UserAgreement/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Ask the user once if he/she agrees with our license."
|
msgid "Ask the user once if he/she agrees with our license."
|
||||||
msgstr ""
|
msgstr "Preguntar al usuario una vez si acepta la licencia"
|
||||||
|
|
||||||
#: UserAgreement/plugin.json
|
#: UserAgreement/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
|
@ -4480,12 +4458,12 @@ msgstr "UserAgreement"
|
||||||
#: X3GWriter/plugin.json
|
#: X3GWriter/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Allows saving the resulting slice as an X3G file, to support printers that read this format (Malyan, Makerbot and other Sailfish-based printers)."
|
msgid "Allows saving the resulting slice as an X3G file, to support printers that read this format (Malyan, Makerbot and other Sailfish-based printers)."
|
||||||
msgstr ""
|
msgstr "Permite guardar el segmento resultante como un archivo X3G para dar compatibilidad a impresoras que leen este formato (Malyan, Makerbot y otras impresoras basadas en Sailfish)."
|
||||||
|
|
||||||
#: X3GWriter/plugin.json
|
#: X3GWriter/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "X3GWriter"
|
msgid "X3GWriter"
|
||||||
msgstr ""
|
msgstr "X3GWriter"
|
||||||
|
|
||||||
#: GCodeGzWriter/plugin.json
|
#: GCodeGzWriter/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
@ -4540,7 +4518,7 @@ msgstr "Complemento de dispositivo de salida de unidad extraíble"
|
||||||
#: UM3NetworkPrinting/plugin.json
|
#: UM3NetworkPrinting/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Manages network connections to Ultimaker 3 printers."
|
msgid "Manages network connections to Ultimaker 3 printers."
|
||||||
msgstr ""
|
msgstr "Gestiona las conexiones de red a las impresoras Ultimaker 3."
|
||||||
|
|
||||||
#: UM3NetworkPrinting/plugin.json
|
#: UM3NetworkPrinting/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
|
@ -4670,12 +4648,12 @@ msgstr "Actualización de la versión 3.2 a la 3.3"
|
||||||
#: VersionUpgrade/VersionUpgrade33to34/plugin.json
|
#: VersionUpgrade/VersionUpgrade33to34/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Upgrades configurations from Cura 3.3 to Cura 3.4."
|
msgid "Upgrades configurations from Cura 3.3 to Cura 3.4."
|
||||||
msgstr ""
|
msgstr "Actualiza la configuración de Cura 3.3 a Cura 3.4."
|
||||||
|
|
||||||
#: VersionUpgrade/VersionUpgrade33to34/plugin.json
|
#: VersionUpgrade/VersionUpgrade33to34/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "Version Upgrade 3.3 to 3.4"
|
msgid "Version Upgrade 3.3 to 3.4"
|
||||||
msgstr ""
|
msgstr "Actualización de la versión 3.3 a la 3.4"
|
||||||
|
|
||||||
#: VersionUpgrade/VersionUpgrade25to26/plugin.json
|
#: VersionUpgrade/VersionUpgrade25to26/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
@ -4820,7 +4798,7 @@ msgstr "Escritor de 3MF"
|
||||||
#: UltimakerMachineActions/plugin.json
|
#: UltimakerMachineActions/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.)."
|
msgid "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.)."
|
||||||
msgstr ""
|
msgstr "Proporciona las acciones de la máquina de las máquinas Ultimaker (como un asistente para la nivelación de la plataforma, la selección de actualizaciones, etc.)."
|
||||||
|
|
||||||
#: UltimakerMachineActions/plugin.json
|
#: UltimakerMachineActions/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
|
|
|
@ -79,7 +79,7 @@ msgstr "Coordenada Y del desplazamiento de la tobera."
|
||||||
#: fdmextruder.def.json
|
#: fdmextruder.def.json
|
||||||
msgctxt "machine_extruder_start_code label"
|
msgctxt "machine_extruder_start_code label"
|
||||||
msgid "Extruder Start G-Code"
|
msgid "Extruder Start G-Code"
|
||||||
msgstr "Gcode inicial del extrusor"
|
msgstr "GCode inicial del extrusor"
|
||||||
|
|
||||||
#: fdmextruder.def.json
|
#: fdmextruder.def.json
|
||||||
msgctxt "machine_extruder_start_code description"
|
msgctxt "machine_extruder_start_code description"
|
||||||
|
@ -119,7 +119,7 @@ msgstr "Coordenada Y de la posición de inicio cuando se enciende el extrusor."
|
||||||
#: fdmextruder.def.json
|
#: fdmextruder.def.json
|
||||||
msgctxt "machine_extruder_end_code label"
|
msgctxt "machine_extruder_end_code label"
|
||||||
msgid "Extruder End G-Code"
|
msgid "Extruder End G-Code"
|
||||||
msgstr "Gcode final del extrusor"
|
msgstr "GCode final del extrusor"
|
||||||
|
|
||||||
#: fdmextruder.def.json
|
#: fdmextruder.def.json
|
||||||
msgctxt "machine_extruder_end_code description"
|
msgctxt "machine_extruder_end_code description"
|
||||||
|
|
|
@ -57,9 +57,7 @@ msgctxt "machine_start_gcode description"
|
||||||
msgid ""
|
msgid ""
|
||||||
"G-code commands to be executed at the very start - separated by \n"
|
"G-code commands to be executed at the very start - separated by \n"
|
||||||
"."
|
"."
|
||||||
msgstr ""
|
msgstr "Los comandos de GCode que se ejecutarán justo al inicio separados por - \n."
|
||||||
"Los comandos de GCode que se ejecutarán justo al inicio separados por - \n"
|
|
||||||
"."
|
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "machine_end_gcode label"
|
msgctxt "machine_end_gcode label"
|
||||||
|
@ -71,9 +69,7 @@ msgctxt "machine_end_gcode description"
|
||||||
msgid ""
|
msgid ""
|
||||||
"G-code commands to be executed at the very end - separated by \n"
|
"G-code commands to be executed at the very end - separated by \n"
|
||||||
"."
|
"."
|
||||||
msgstr ""
|
msgstr "Los comandos de GCode que se ejecutarán justo al final separados por -\n."
|
||||||
"Los comandos de GCode que se ejecutarán justo al final separados por -\n"
|
|
||||||
"."
|
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "material_guid label"
|
msgctxt "material_guid label"
|
||||||
|
@ -433,7 +429,7 @@ msgstr "Silueta 2D del cabezal de impresión (incluidas las tapas del ventilador
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "gantry_height label"
|
msgctxt "gantry_height label"
|
||||||
msgid "Gantry height"
|
msgid "Gantry height"
|
||||||
msgstr "Altura del caballete"
|
msgstr "Altura del puente"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "gantry_height description"
|
msgctxt "gantry_height description"
|
||||||
|
@ -1088,7 +1084,7 @@ msgstr "Optimizar el orden de impresión de paredes"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "optimize_wall_printing_order description"
|
msgctxt "optimize_wall_printing_order description"
|
||||||
msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization. First layer is not optimized when choosing brim as build plate adhesion type."
|
msgid "Optimize the order in which walls are printed so as to reduce the number of retractions and the distance travelled. Most parts will benefit from this being enabled but some may actually take longer so please compare the print time estimates with and without optimization. First layer is not optimized when choosing brim as build plate adhesion type."
|
||||||
msgstr ""
|
msgstr "Optimizar el orden en el que se imprimen las paredes a fin de reducir el número de retracciones y la distancia recorrida. La mayoría de los componentes se beneficiarán si este ajuste está habilitado pero, en algunos casos, se puede tardar más, por lo que deben compararse las previsiones de tiempo de impresión con y sin optimización. La primera capa no está optimizada al elegir el borde como el tipo de adhesión de la placa de impresión."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "outer_inset_first label"
|
msgctxt "outer_inset_first label"
|
||||||
|
@ -1678,22 +1674,22 @@ msgstr "No genere áreas con un relleno inferior a este (utilice forro)."
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_support_enabled label"
|
msgctxt "infill_support_enabled label"
|
||||||
msgid "Infill Support"
|
msgid "Infill Support"
|
||||||
msgstr ""
|
msgstr "Soporte de relleno"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_support_enabled description"
|
msgctxt "infill_support_enabled description"
|
||||||
msgid "Print infill structures only where tops of the model should be supported. Enabling this reduces print time and material usage, but leads to ununiform object strength."
|
msgid "Print infill structures only where tops of the model should be supported. Enabling this reduces print time and material usage, but leads to ununiform object strength."
|
||||||
msgstr ""
|
msgstr "Imprimir estructuras de relleno solo cuando se deban soportar las partes superiores del modelo. Habilitar esto reduce el tiempo de impresión y el uso de material, pero ocasiona que la resistencia del objeto no sea uniforme."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_support_angle label"
|
msgctxt "infill_support_angle label"
|
||||||
msgid "Infill Overhang Angle"
|
msgid "Infill Overhang Angle"
|
||||||
msgstr ""
|
msgstr "Ángulo de voladizo de relleno"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_support_angle description"
|
msgctxt "infill_support_angle description"
|
||||||
msgid "The minimum angle of internal overhangs for which infill is added. At a value of 0° objects are totally filled with infill, 90° will not provide any infill."
|
msgid "The minimum angle of internal overhangs for which infill is added. At a value of 0° objects are totally filled with infill, 90° will not provide any infill."
|
||||||
msgstr ""
|
msgstr "El ángulo mínimo de los voladizos internos para los que se agrega relleno. A partir de un valor de 0 º todos los objetos estarán totalmente rellenos, a 90 º no se proporcionará ningún relleno."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "skin_preshrink label"
|
msgctxt "skin_preshrink label"
|
||||||
|
@ -2038,12 +2034,12 @@ msgstr "Ventana en la que se aplica el recuento máximo de retracciones. Este va
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "limit_support_retractions label"
|
msgctxt "limit_support_retractions label"
|
||||||
msgid "Limit Support Retractions"
|
msgid "Limit Support Retractions"
|
||||||
msgstr ""
|
msgstr "Limitar las retracciones de soporte"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "limit_support_retractions description"
|
msgctxt "limit_support_retractions description"
|
||||||
msgid "Omit retraction when moving from support to support in a straight line. Enabling this setting saves print time, but can lead to excesive stringing within the support structure."
|
msgid "Omit retraction when moving from support to support in a straight line. Enabling this setting saves print time, but can lead to excesive stringing within the support structure."
|
||||||
msgstr ""
|
msgstr "Omitir la retracción al moverse de soporte a soporte en línea recta. Habilitar este ajuste ahorra tiempo de impresión pero puede ocasionar un encordado excesivo en la estructura de soporte."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "material_standby_temperature label"
|
msgctxt "material_standby_temperature label"
|
||||||
|
@ -2738,17 +2734,17 @@ msgstr "Todo"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "retraction_combing option noskin"
|
msgctxt "retraction_combing option noskin"
|
||||||
msgid "Not in Skin"
|
msgid "Not in Skin"
|
||||||
msgstr ""
|
msgstr "No en el forro"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "retraction_combing_max_distance label"
|
msgctxt "retraction_combing_max_distance label"
|
||||||
msgid "Max Comb Distance With No Retract"
|
msgid "Max Comb Distance With No Retract"
|
||||||
msgstr ""
|
msgstr "Distancia de peinada máxima sin retracción"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "retraction_combing_max_distance description"
|
msgctxt "retraction_combing_max_distance description"
|
||||||
msgid "When non-zero, combing travel moves that are longer than this distance will use retraction."
|
msgid "When non-zero, combing travel moves that are longer than this distance will use retraction."
|
||||||
msgstr ""
|
msgstr "Si no es cero, los movimientos de desplazamiento de peinada que sean superiores a esta distancia utilizarán retracción."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "travel_retract_before_outer_wall label"
|
msgctxt "travel_retract_before_outer_wall label"
|
||||||
|
@ -2773,12 +2769,12 @@ msgstr "La tobera evita las partes ya impresas al desplazarse. Esta opción solo
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "travel_avoid_supports label"
|
msgctxt "travel_avoid_supports label"
|
||||||
msgid "Avoid Supports When Traveling"
|
msgid "Avoid Supports When Traveling"
|
||||||
msgstr ""
|
msgstr "Evitar soportes al desplazarse"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "travel_avoid_supports description"
|
msgctxt "travel_avoid_supports description"
|
||||||
msgid "The nozzle avoids already printed supports when traveling. This option is only available when combing is enabled."
|
msgid "The nozzle avoids already printed supports when traveling. This option is only available when combing is enabled."
|
||||||
msgstr ""
|
msgstr "La tobera evita los soportes ya impresos al desplazarse. Esta opción solo está disponible cuando se ha activado la opción de peinada."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "travel_avoid_distance label"
|
msgctxt "travel_avoid_distance label"
|
||||||
|
@ -3078,7 +3074,7 @@ msgstr "Tocando la placa de impresión"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_type option everywhere"
|
msgctxt "support_type option everywhere"
|
||||||
msgid "Everywhere"
|
msgid "Everywhere"
|
||||||
msgstr "En todas partes"
|
msgstr "En todos sitios"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_angle label"
|
msgctxt "support_angle label"
|
||||||
|
@ -3138,12 +3134,12 @@ msgstr "Cruz"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_wall_count label"
|
msgctxt "support_wall_count label"
|
||||||
msgid "Support Wall Line Count"
|
msgid "Support Wall Line Count"
|
||||||
msgstr ""
|
msgstr "Recuento de líneas de pared del soporte"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_wall_count description"
|
msgctxt "support_wall_count description"
|
||||||
msgid "The number of walls with which to surround support infill. Adding a wall can make support print more reliably and can support overhangs better, but increases print time and material used."
|
msgid "The number of walls with which to surround support infill. Adding a wall can make support print more reliably and can support overhangs better, but increases print time and material used."
|
||||||
msgstr ""
|
msgstr "El número de paredes con las que el soporte rodea el relleno. Añadir una pared puede hacer que la impresión de soporte sea más fiable y pueda soportar mejor los voladizos pero aumenta el tiempo de impresión y el material utilizado."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "zig_zaggify_support label"
|
msgctxt "zig_zaggify_support label"
|
||||||
|
@ -3715,9 +3711,7 @@ msgctxt "skirt_gap description"
|
||||||
msgid ""
|
msgid ""
|
||||||
"The horizontal distance between the skirt and the first layer of the print.\n"
|
"The horizontal distance between the skirt and the first layer of the print.\n"
|
||||||
"This is the minimum distance. Multiple skirt lines will extend outwards from this distance."
|
"This is the minimum distance. Multiple skirt lines will extend outwards from this distance."
|
||||||
msgstr ""
|
msgstr "La distancia horizontal entre la falda y la primera capa de la impresión.\nSe trata de la distancia mínima. Múltiples líneas de falda se extenderán hacia el exterior a partir de esta distancia."
|
||||||
"La distancia horizontal entre la falda y la primera capa de la impresión.\n"
|
|
||||||
"Se trata de la distancia mínima. Múltiples líneas de falda se extenderán hacia el exterior a partir de esta distancia."
|
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "skirt_brim_minimal_length label"
|
msgctxt "skirt_brim_minimal_length label"
|
||||||
|
@ -4677,12 +4671,12 @@ msgstr "El tamaño mínimo de un segmento de línea tras la segmentación. Si se
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "meshfix_maximum_travel_resolution label"
|
msgctxt "meshfix_maximum_travel_resolution label"
|
||||||
msgid "Maximum Travel Resolution"
|
msgid "Maximum Travel Resolution"
|
||||||
msgstr ""
|
msgstr "Resolución de desplazamiento máximo"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "meshfix_maximum_travel_resolution description"
|
msgctxt "meshfix_maximum_travel_resolution description"
|
||||||
msgid "The minimum size of a travel line segment after slicing. If you increase this, the travel moves will have less smooth corners. This may allow the printer to keep up with the speed it has to process g-code, but it may cause model avoidance to become less accurate."
|
msgid "The minimum size of a travel line segment after slicing. If you increase this, the travel moves will have less smooth corners. This may allow the printer to keep up with the speed it has to process g-code, but it may cause model avoidance to become less accurate."
|
||||||
msgstr ""
|
msgstr "El tamaño mínimo de un segmento de línea de desplazamiento tras la segmentación. Si se aumenta, los movimientos de desplazamiento tendrán esquinas menos suavizadas. Esto puede le permite a la impresora mantener la velocidad que necesita para procesar GCode pero puede ocasionar que evitar el modelo sea menos preciso."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_skip_some_zags label"
|
msgctxt "support_skip_some_zags label"
|
||||||
|
@ -4847,22 +4841,22 @@ msgstr "Tamaño de las bolsas en cruces del patrón de cruz 3D en las alturas en
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "cross_infill_density_image label"
|
msgctxt "cross_infill_density_image label"
|
||||||
msgid "Cross Infill Density Image"
|
msgid "Cross Infill Density Image"
|
||||||
msgstr ""
|
msgstr "Imagen de densidad de relleno cruzada"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "cross_infill_density_image description"
|
msgctxt "cross_infill_density_image description"
|
||||||
msgid "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the infill of the print."
|
msgid "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the infill of the print."
|
||||||
msgstr ""
|
msgstr "La ubicación del archivo de una imagen de la que los valores de brillo determinan la densidad mínima en la ubicación correspondiente en el relleno de la impresión."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "cross_support_density_image label"
|
msgctxt "cross_support_density_image label"
|
||||||
msgid "Cross Fill Density Image for Support"
|
msgid "Cross Fill Density Image for Support"
|
||||||
msgstr ""
|
msgstr "Imagen de densidad de relleno cruzada para soporte"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "cross_support_density_image description"
|
msgctxt "cross_support_density_image description"
|
||||||
msgid "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the support."
|
msgid "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the support."
|
||||||
msgstr ""
|
msgstr "La ubicación del archivo de una imagen de la que los valores de brillo determinan la densidad mínima en la ubicación correspondiente del soporte."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "spaghetti_infill_enabled label"
|
msgctxt "spaghetti_infill_enabled label"
|
||||||
|
@ -5174,9 +5168,7 @@ msgctxt "wireframe_up_half_speed description"
|
||||||
msgid ""
|
msgid ""
|
||||||
"Distance of an upward move which is extruded with half speed.\n"
|
"Distance of an upward move which is extruded with half speed.\n"
|
||||||
"This can cause better adhesion to previous layers, while not heating the material in those layers too much. Only applies to Wire Printing."
|
"This can cause better adhesion to previous layers, while not heating the material in those layers too much. Only applies to Wire Printing."
|
||||||
msgstr ""
|
msgstr "Distancia de un movimiento ascendente que se extrude a media velocidad.\nEsto puede causar una mejor adherencia a las capas anteriores, aunque no calienta demasiado el material en esas capas. Solo se aplica a la impresión de alambre."
|
||||||
"Distancia de un movimiento ascendente que se extrude a media velocidad.\n"
|
|
||||||
"Esto puede causar una mejor adherencia a las capas anteriores, aunque no calienta demasiado el material en esas capas. Solo se aplica a la impresión de alambre."
|
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "wireframe_top_jump label"
|
msgctxt "wireframe_top_jump label"
|
||||||
|
@ -5301,7 +5293,7 @@ msgstr "Variación máxima de las capas de adaptación"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_variation description"
|
msgctxt "adaptive_layer_height_variation description"
|
||||||
msgid "The maximum allowed height different from the base layer height."
|
msgid "The maximum allowed height different from the base layer height."
|
||||||
msgstr ""
|
msgstr "La diferencia de altura máxima permitida en comparación con la altura de la capa base."
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_variation_step label"
|
msgctxt "adaptive_layer_height_variation_step label"
|
||||||
|
@ -5366,7 +5358,7 @@ msgstr "Ancho máximo permitido de la cámara de aire por debajo de una línea d
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "bridge_wall_coast label"
|
msgctxt "bridge_wall_coast label"
|
||||||
msgid "Bridge Wall Coasting"
|
msgid "Bridge Wall Coasting"
|
||||||
msgstr "Deslizamiento por la pared del puente"
|
msgstr "Depósito por inercia de la pared del puente"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "bridge_wall_coast description"
|
msgctxt "bridge_wall_coast description"
|
||||||
|
@ -5536,7 +5528,7 @@ msgstr "Ajustes que únicamente se utilizan si CuraEngine no se ejecuta desde la
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "center_object label"
|
msgctxt "center_object label"
|
||||||
msgid "Center Object"
|
msgid "Center Object"
|
||||||
msgstr ""
|
msgstr "Centrar objeto"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "center_object description"
|
msgctxt "center_object description"
|
||||||
|
@ -5546,7 +5538,7 @@ msgstr "Centrar o no el objeto en el centro de la plataforma de impresión (0, 0
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_x label"
|
msgctxt "mesh_position_x label"
|
||||||
msgid "Mesh Position X"
|
msgid "Mesh Position X"
|
||||||
msgstr ""
|
msgstr "Posición X en la malla"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_x description"
|
msgctxt "mesh_position_x description"
|
||||||
|
@ -5556,7 +5548,7 @@ msgstr "Desplazamiento aplicado al objeto en la dirección x."
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_y label"
|
msgctxt "mesh_position_y label"
|
||||||
msgid "Mesh Position Y"
|
msgid "Mesh Position Y"
|
||||||
msgstr ""
|
msgstr "Posición Y en la malla"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_y description"
|
msgctxt "mesh_position_y description"
|
||||||
|
@ -5566,7 +5558,7 @@ msgstr "Desplazamiento aplicado al objeto en la dirección y."
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_z label"
|
msgctxt "mesh_position_z label"
|
||||||
msgid "Mesh Position Z"
|
msgid "Mesh Position Z"
|
||||||
msgstr ""
|
msgstr "Posición Z en la malla"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "mesh_position_z description"
|
msgctxt "mesh_position_z description"
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue