mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-19 23:19:13 +08:00
commit
b00502d10f
@ -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.
|
||||||
|
@ -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,14 +111,12 @@ from UM.FlameProfiler import pyqtSlot
|
|||||||
|
|
||||||
numpy.seterr(all = "ignore")
|
numpy.seterr(all = "ignore")
|
||||||
|
|
||||||
MYPY = False
|
try:
|
||||||
if not MYPY:
|
from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode
|
||||||
try:
|
except ImportError:
|
||||||
from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode
|
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
|
||||||
except ImportError:
|
CuraBuildType = ""
|
||||||
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
|
CuraDebugMode = False
|
||||||
CuraBuildType = ""
|
|
||||||
CuraDebugMode = False
|
|
||||||
|
|
||||||
|
|
||||||
class CuraApplication(QtApplication):
|
class CuraApplication(QtApplication):
|
||||||
@ -1720,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.
|
||||||
@ -434,7 +435,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
|
||||||
@ -445,22 +446,27 @@ 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")
|
||||||
self.removeMaterialByRootId(root_material_id)
|
if root_material_id is not None:
|
||||||
|
self.removeMaterialByRootId(root_material_id)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Creates a duplicate of a material, which has the same GUID and base_file metadata.
|
# Creates a duplicate of a material, which has the same GUID and base_file metadata.
|
||||||
@ -539,6 +545,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))
|
||||||
|
@ -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,20 +57,23 @@ 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"))
|
||||||
self._shader.setUniformValue("u_overhangAngle", 1.0)
|
if self._shader:
|
||||||
self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0])
|
self._shader.setUniformValue("u_overhangAngle", 1.0)
|
||||||
self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0])
|
self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0])
|
||||||
self._shader.setUniformValue("u_shininess", 20.0)
|
self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0])
|
||||||
|
self._shader.setUniformValue("u_shininess", 20.0)
|
||||||
|
|
||||||
if not self._non_printing_shader:
|
if not self._non_printing_shader:
|
||||||
self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
|
if self._non_printing_shader:
|
||||||
self._non_printing_shader.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 0.5])
|
self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
|
||||||
self._non_printing_shader.setUniformValue("u_opacity", 0.6)
|
self._non_printing_shader.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 0.5])
|
||||||
|
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"))
|
||||||
self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
|
if self._support_mesh_shader:
|
||||||
self._support_mesh_shader.setUniformValue("u_width", 5.0)
|
self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
|
||||||
|
self._support_mesh_shader.setUniformValue("u_width", 5.0)
|
||||||
|
|
||||||
self._gl.glClearColor(0.0, 0.0, 0.0, 0.0)
|
self._gl.glClearColor(0.0, 0.0, 0.0, 0.0)
|
||||||
self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)
|
self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)
|
||||||
@ -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.
|
||||||
self.setConnectionState(self._connection_state_before_timeout)
|
if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here
|
||||||
self._connection_state_before_timeout = None
|
self.setConnectionState(self._connection_state_before_timeout)
|
||||||
|
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.
|
||||||
@ -296,30 +297,30 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||||||
|
|
||||||
## Get the unique key of this machine
|
## Get the unique key of this machine
|
||||||
# \return key String containing the key of the machine.
|
# \return key String containing the key of the machine.
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant = True)
|
||||||
def key(self) -> str:
|
def key(self) -> str:
|
||||||
return self._id
|
return self._id
|
||||||
|
|
||||||
## The IP address of the printer.
|
## The IP address of the printer.
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant = True)
|
||||||
def address(self) -> str:
|
def address(self) -> str:
|
||||||
return self._properties.get(b"address", b"").decode("utf-8")
|
return self._properties.get(b"address", b"").decode("utf-8")
|
||||||
|
|
||||||
## Name of the printer (as returned from the ZeroConf properties)
|
## Name of the printer (as returned from the ZeroConf properties)
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant = True)
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return self._properties.get(b"name", b"").decode("utf-8")
|
return self._properties.get(b"name", b"").decode("utf-8")
|
||||||
|
|
||||||
## Firmware version (as returned from the ZeroConf properties)
|
## Firmware version (as returned from the ZeroConf properties)
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant = True)
|
||||||
def firmwareVersion(self) -> str:
|
def firmwareVersion(self) -> str:
|
||||||
return self._properties.get(b"firmware_version", b"").decode("utf-8")
|
return self._properties.get(b"firmware_version", b"").decode("utf-8")
|
||||||
|
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant = True)
|
||||||
def printerType(self) -> str:
|
def printerType(self) -> str:
|
||||||
return self._printer_type
|
return self._printer_type
|
||||||
|
|
||||||
## IPadress of this printer
|
## IP adress of this printer
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant = True)
|
||||||
def ipAddress(self) -> str:
|
def ipAddress(self) -> str:
|
||||||
return self._address
|
return self._address
|
||||||
|
@ -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.
|
||||||
@ -138,49 +150,49 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||||||
self._createMonitorViewFromQML()
|
self._createMonitorViewFromQML()
|
||||||
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,17 +86,17 @@ 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
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@ -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,42 +59,47 @@ 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]:
|
||||||
extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId()
|
extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId()
|
||||||
|
|
||||||
return extruder_stack_ids
|
return extruder_stack_ids
|
||||||
|
|
||||||
@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:
|
||||||
if extruder.getId() == extruder_stack_id:
|
for position in self._extruder_trains[global_container_stack.getId()]:
|
||||||
return extruder.qualityChanges.getId()
|
extruder = self._extruder_trains[global_container_stack.getId()][position]
|
||||||
|
if extruder.getId() == extruder_stack_id:
|
||||||
|
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 []
|
||||||
|
|
||||||
@ -471,7 +477,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()):
|
||||||
@ -506,7 +512,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"] = {
|
||||||
@ -539,7 +545,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.
|
||||||
#
|
#
|
||||||
@ -564,7 +570,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:
|
||||||
@ -573,7 +579,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
|
||||||
|
|
||||||
@ -602,7 +608,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
|
||||||
|
|
||||||
@ -615,7 +621,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
|
||||||
@ -629,7 +635,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"] = {
|
||||||
@ -642,7 +648,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)
|
||||||
|
|
||||||
@ -74,14 +77,14 @@ class MachineManager(QObject):
|
|||||||
self.globalContainerChanged.connect(self.activeQualityChangesGroupChanged)
|
self.globalContainerChanged.connect(self.activeQualityChangesGroupChanged)
|
||||||
self.globalContainerChanged.connect(self.activeQualityGroupChanged)
|
self.globalContainerChanged.connect(self.activeQualityGroupChanged)
|
||||||
|
|
||||||
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,7 @@ 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())
|
return len(CuraContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0].getAllKeys())
|
||||||
|
|
||||||
def _onGlobalContainerChanged(self) -> None:
|
def _onGlobalContainerChanged(self) -> None:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
@ -355,7 +356,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 +382,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 +626,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 +644,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 +709,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 +723,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 +807,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 +842,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 +861,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 +881,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 +896,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 +924,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 +938,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 +952,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 +963,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 +974,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 +1012,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 +1022,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 +1066,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 +1079,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 +1110,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:
|
||||||
container.setMetaDataEntry("quality_type", "not_supported")
|
if container:
|
||||||
|
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 +1162,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,18 +1189,19 @@ 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 Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
|
if self._global_container_stack is not None:
|
||||||
for position, extruder in self._global_container_stack.extruders.items():
|
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
|
||||||
if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"):
|
for position, extruder in self._global_container_stack.extruders.items():
|
||||||
return False
|
if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"):
|
||||||
if not extruder.material.getMetaDataEntry("compatible"):
|
return False
|
||||||
return False
|
if not extruder.material.getMetaDataEntry("compatible"):
|
||||||
|
return False
|
||||||
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 +1240,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 +1248,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 +1267,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, current_variant_name)
|
material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, 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 +1310,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 +1346,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 +1359,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 +1378,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 +1403,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 +1432,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 +1442,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 +1456,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
|
||||||
|
@ -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()
|
||||||
self._single_instance_server.newConnection.connect(self._onClientConnected)
|
if self._single_instance_server:
|
||||||
self._single_instance_server.listen("ultimaker-cura")
|
self._single_instance_server.newConnection.connect(self._onClientConnected)
|
||||||
|
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 = self._single_instance_server.nextPendingConnection()
|
connection = None #type: Optional[QLocalSocket]
|
||||||
|
if self._single_instance_server:
|
||||||
|
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:
|
||||||
|
@ -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":
|
||||||
|
@ -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,7 +383,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
else:
|
else:
|
||||||
self.backendStateChange.emit(BackendState.NotStarted)
|
self.backendStateChange.emit(BackendState.NotStarted)
|
||||||
|
|
||||||
if job.getResult() == StartSliceJob.StartJobResult.ObjectsWithDisabledExtruder:
|
if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()),
|
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"))
|
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||||
self._error_message.show()
|
self._error_message.show()
|
||||||
@ -387,8 +391,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||||||
self.backendError.emit(job)
|
self.backendError.emit(job)
|
||||||
return
|
return
|
||||||
|
|
||||||
if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice:
|
if job.getResult() == StartJobResult.NothingToSlice:
|
||||||
if Application.getInstance().platformActivity:
|
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()
|
||||||
@ -411,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
|
||||||
@ -437,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")
|
||||||
@ -451,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
|
||||||
|
|
||||||
@ -506,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)
|
||||||
@ -521,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)
|
||||||
@ -545,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()
|
||||||
@ -554,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
|
||||||
@ -566,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)
|
||||||
@ -580,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
|
||||||
@ -600,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
|
||||||
|
|
||||||
@ -619,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
|
||||||
@ -641,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,
|
||||||
@ -677,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)
|
||||||
@ -688,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,
|
||||||
@ -705,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()
|
||||||
@ -716,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
|
||||||
@ -729,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
|
||||||
@ -737,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
|
||||||
@ -766,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)
|
||||||
@ -783,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.
|
||||||
@ -794,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()
|
||||||
@ -821,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
|
||||||
@ -35,19 +39,19 @@ class StartJobResult(IntEnum):
|
|||||||
ObjectsWithDisabledExtruder = 8
|
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])
|
||||||
@ -74,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
|
||||||
|
|
||||||
@ -105,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
|
||||||
|
|
||||||
@ -141,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
|
||||||
|
|
||||||
@ -151,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
|
||||||
@ -159,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.
|
||||||
@ -185,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
|
||||||
@ -212,12 +216,12 @@ 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
|
has_model_with_disabled_extruders = False
|
||||||
associated_disabled_extruders = set()
|
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:
|
||||||
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
||||||
@ -283,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.
|
||||||
@ -297,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")
|
||||||
@ -310,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
|
||||||
|
|
||||||
@ -319,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 = {
|
||||||
@ -343,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"))
|
||||||
|
|
||||||
@ -370,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
|
||||||
@ -384,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)
|
||||||
@ -405,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.
|
||||||
@ -415,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.
|
||||||
@ -426,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():
|
||||||
@ -455,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
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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)}
|
||||||
|
@ -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,7 +428,8 @@ 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)
|
||||||
self._network_manager.get(request)
|
if self._network_manager:
|
||||||
|
self._network_manager.get(request)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def startDownload(self, url: str) -> None:
|
def startDownload(self, url: str) -> None:
|
||||||
@ -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:
|
||||||
self._progress_message.setProgress(0)
|
if self._progress_message is not None:
|
||||||
self._progress_message.hide()
|
self._progress_message.setProgress(0)
|
||||||
|
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.")
|
||||||
self._progress_message.hide()
|
if self._progress_message is not None:
|
||||||
|
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
|
||||||
@ -288,9 +292,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
self._latest_reply_handler.disconnect()
|
self._latest_reply_handler.disconnect()
|
||||||
self._latest_reply_handler = None
|
self._latest_reply_handler = None
|
||||||
|
|
||||||
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:
|
||||||
@ -302,21 +306,21 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
Logger.log("d", "Opening printer control panel...")
|
Logger.log("d", "Opening printer control panel...")
|
||||||
QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers"))
|
QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers"))
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", notify=printJobsChanged)
|
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||||
def printJobs(self)-> List[PrintJobOutputModel] :
|
def printJobs(self)-> List[PrintJobOutputModel]:
|
||||||
return self._print_jobs
|
return self._print_jobs
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", notify=printJobsChanged)
|
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||||
def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
|
def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
|
||||||
return [print_job for print_job in self._print_jobs if print_job.state == "queued"]
|
return [print_job for print_job in self._print_jobs if print_job.state == "queued"]
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", notify=printJobsChanged)
|
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||||
def activePrintJobs(self) -> List[PrintJobOutputModel]:
|
def activePrintJobs(self) -> List[PrintJobOutputModel]:
|
||||||
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,20 +328,20 @@ 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)
|
||||||
def formatDuration(self, seconds: int) -> str:
|
def formatDuration(self, seconds: int) -> str:
|
||||||
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
|
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
|
||||||
|
|
||||||
@pyqtSlot(int, result=str)
|
@pyqtSlot(int, result = str)
|
||||||
def getTimeCompleted(self, time_remaining: int) -> str:
|
def getTimeCompleted(self, time_remaining: int) -> str:
|
||||||
current_time = time()
|
current_time = time()
|
||||||
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
|
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
|
||||||
return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute)
|
return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute)
|
||||||
|
|
||||||
@pyqtSlot(int, result=str)
|
@pyqtSlot(int, result = str)
|
||||||
def getDateCompleted(self, time_remaining: int) -> str:
|
def getDateCompleted(self, time_remaining: int) -> str:
|
||||||
current_time = time()
|
current_time = time()
|
||||||
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
|
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
|
||||||
@ -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"]
|
||||||
@ -479,7 +482,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||||||
printer.updateKey(data["uuid"])
|
printer.updateKey(data["uuid"])
|
||||||
printer.updateType(data["machine_variant"])
|
printer.updateType(data["machine_variant"])
|
||||||
|
|
||||||
# Do not store the buildplate information that comes from connect if the current printer has not buildplate information
|
# Do not store the build plate information that comes from connect if the current printer has not build plate information
|
||||||
if "build_plate" in data and machine_definition.getMetaDataEntry("has_variant_buildplates", False):
|
if "build_plate" in data and machine_definition.getMetaDataEntry("has_variant_buildplates", False):
|
||||||
printer.updateBuildplateName(data["build_plate"]["type"])
|
printer.updateBuildplateName(data["build_plate"]["type"])
|
||||||
if not data["enabled"]:
|
if not data["enabled"]:
|
||||||
@ -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]
|
||||||
|
@ -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:
|
||||||
base_metadata["name"] = cls._profile_name(material.text, color.text)
|
if material is not None and color is not None:
|
||||||
|
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,7 +861,8 @@ 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":
|
||||||
machine_compatibility = cls._parseCompatibleValue(entry.text)
|
if entry.text is not None:
|
||||||
|
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):
|
||||||
machine_id_list = product_id_map.get(identifier.get("product"), [])
|
machine_id_list = product_id_map.get(identifier.get("product"), [])
|
||||||
@ -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,10 +918,11 @@ 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 key == "hardware compatible":
|
if entry.text is not None:
|
||||||
buildplate_compatibility = cls._parseCompatibleValue(entry.text)
|
if key == "hardware compatible":
|
||||||
elif key == "hardware recommended":
|
buildplate_compatibility = cls._parseCompatibleValue(entry.text)
|
||||||
buildplate_recommended = cls._parseCompatibleValue(entry.text)
|
elif key == "hardware recommended":
|
||||||
|
buildplate_recommended = cls._parseCompatibleValue(entry.text)
|
||||||
|
|
||||||
buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility
|
buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility
|
||||||
buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended
|
buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended
|
||||||
@ -929,7 +936,8 @@ 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":
|
||||||
hotend_compatibility = cls._parseCompatibleValue(entry.text)
|
if entry.text is not None:
|
||||||
|
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(" ", "_")
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ def main():
|
|||||||
|
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
print("------------- Checking module {mod}".format(**locals()))
|
print("------------- Checking module {mod}".format(**locals()))
|
||||||
result = subprocess.run([sys.executable, mypyModule, "-p", mod])
|
result = subprocess.run([sys.executable, mypyModule, "-p", mod, "--ignore-missing-imports"])
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
print("\nModule {mod} failed checking. :(".format(**locals()))
|
print("\nModule {mod} failed checking. :(".format(**locals()))
|
||||||
return 1
|
return 1
|
||||||
|
@ -340,26 +340,4 @@ def test_setPropertyUser(key, property, value, extruder_stack):
|
|||||||
|
|
||||||
extruder_stack.setProperty(key, property, value) #The actual test.
|
extruder_stack.setProperty(key, property, value) #The actual test.
|
||||||
|
|
||||||
extruder_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call.
|
extruder_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call.
|
||||||
|
|
||||||
## Tests setting properties on specific containers on the global stack.
|
|
||||||
@pytest.mark.parametrize("target_container, stack_variable", [
|
|
||||||
("user", "userChanges"),
|
|
||||||
("quality_changes", "qualityChanges"),
|
|
||||||
("quality", "quality"),
|
|
||||||
("material", "material"),
|
|
||||||
("variant", "variant")
|
|
||||||
])
|
|
||||||
def test_setPropertyOtherContainers(target_container, stack_variable, extruder_stack):
|
|
||||||
#Other parameters that don't need to be varied.
|
|
||||||
key = "layer_height"
|
|
||||||
property = "value"
|
|
||||||
value = 0.1337
|
|
||||||
#A mock container in the right spot.
|
|
||||||
container = unittest.mock.MagicMock()
|
|
||||||
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = target_container)
|
|
||||||
setattr(extruder_stack, stack_variable, container) #For instance, set global_stack.qualityChanges = container.
|
|
||||||
|
|
||||||
extruder_stack.setProperty(key, property, value, target_container = target_container) #The actual test.
|
|
||||||
|
|
||||||
getattr(extruder_stack, stack_variable).setProperty.assert_called_once_with(key, property, value) #Make sure that the proper container gets a setProperty call.
|
|
@ -481,27 +481,4 @@ def test_setPropertyUser(key, property, value, global_stack):
|
|||||||
|
|
||||||
global_stack.setProperty(key, property, value) #The actual test.
|
global_stack.setProperty(key, property, value) #The actual test.
|
||||||
|
|
||||||
global_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call.
|
global_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call.
|
||||||
|
|
||||||
## Tests setting properties on specific containers on the global stack.
|
|
||||||
@pytest.mark.parametrize("target_container, stack_variable", [
|
|
||||||
("user", "userChanges"),
|
|
||||||
("quality_changes", "qualityChanges"),
|
|
||||||
("quality", "quality"),
|
|
||||||
("material", "material"),
|
|
||||||
("variant", "variant"),
|
|
||||||
("definition_changes", "definitionChanges")
|
|
||||||
])
|
|
||||||
def test_setPropertyOtherContainers(target_container, stack_variable, global_stack):
|
|
||||||
#Other parameters that don't need to be varied.
|
|
||||||
key = "layer_height"
|
|
||||||
property = "value"
|
|
||||||
value = 0.1337
|
|
||||||
#A mock container in the right spot.
|
|
||||||
container = unittest.mock.MagicMock()
|
|
||||||
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = target_container)
|
|
||||||
setattr(global_stack, stack_variable, container) #For instance, set global_stack.qualityChanges = container.
|
|
||||||
|
|
||||||
global_stack.setProperty(key, property, value, target_container = target_container) #The actual test.
|
|
||||||
|
|
||||||
getattr(global_stack, stack_variable).setProperty.assert_called_once_with(key, property, value) #Make sure that the proper container gets a setProperty call.
|
|
Loading…
x
Reference in New Issue
Block a user