mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-08 14:34:01 -06:00
Merge remote-tracking branch 'origin/master' into feature_send_material_profiles
This commit is contained in:
commit
8f7370db6c
703 changed files with 272749 additions and 117038 deletions
|
@ -1,5 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Tuple, Optional
|
||||
|
||||
from cura.Backups.BackupsManager import BackupsManager
|
||||
|
||||
|
||||
|
@ -17,7 +19,7 @@ class Backups:
|
|||
## Create a new back-up using the BackupsManager.
|
||||
# \return Tuple containing a ZIP file with the back-up data and a dict
|
||||
# with metadata about the back-up.
|
||||
def createBackup(self) -> (bytes, dict):
|
||||
def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]:
|
||||
return self.manager.createBackup()
|
||||
|
||||
## Restore a back-up using the BackupsManager.
|
||||
|
|
|
@ -20,14 +20,14 @@ from typing import List
|
|||
|
||||
## Do arrangements on multiple build plates (aka builtiplexer)
|
||||
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._y = y
|
||||
self._fixed_nodes = fixed_nodes
|
||||
self._count = 0
|
||||
self._first_empty = None
|
||||
self._has_empty = False
|
||||
self._arrange = []
|
||||
self._arrange = [] # type: List[Arrange]
|
||||
|
||||
def _update_first_empty(self):
|
||||
for i, a in enumerate(self._arrange):
|
||||
|
@ -48,16 +48,17 @@ class ArrangeArray:
|
|||
return self._count
|
||||
|
||||
def get(self, index):
|
||||
print(self._arrange)
|
||||
return self._arrange[index]
|
||||
|
||||
def getFirstEmpty(self):
|
||||
if not self._is_empty:
|
||||
if not self._has_empty:
|
||||
self.add()
|
||||
return self._arrange[self._first_empty]
|
||||
|
||||
|
||||
class ArrangeObjectsAllBuildPlatesJob(Job):
|
||||
def __init__(self, nodes: List[SceneNode], min_offset = 8):
|
||||
def __init__(self, nodes: List[SceneNode], min_offset = 8) -> None:
|
||||
super().__init__()
|
||||
self._nodes = nodes
|
||||
self._min_offset = min_offset
|
||||
|
|
|
@ -20,7 +20,7 @@ from typing import List
|
|||
|
||||
|
||||
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__()
|
||||
self._nodes = nodes
|
||||
self._fixed_nodes = fixed_nodes
|
||||
|
|
|
@ -28,12 +28,12 @@ class Backup:
|
|||
# Re-use translation catalog.
|
||||
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.meta_data = meta_data # type: Optional[dict]
|
||||
|
||||
## 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()
|
||||
version_data_dir = Resources.getDataStoragePath()
|
||||
|
||||
|
@ -54,6 +54,8 @@ class Backup:
|
|||
# Create an empty buffer and write the archive to it.
|
||||
buffer = io.BytesIO()
|
||||
archive = self._makeArchive(buffer, version_data_dir)
|
||||
if archive is None:
|
||||
return
|
||||
files = archive.namelist()
|
||||
|
||||
# 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.
|
||||
# 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 cura.Backups.Backup import Backup
|
||||
|
@ -18,7 +18,7 @@ class BackupsManager:
|
|||
## Get a back-up of the current configuration.
|
||||
# \return A tuple containing a ZipFile (the actual back-up) and a dict
|
||||
# containing some metadata (like version).
|
||||
def createBackup(self) -> (Optional[bytes], Optional[dict]):
|
||||
def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]:
|
||||
self._disableAutoSave()
|
||||
backup = Backup()
|
||||
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.
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from typing import List, TYPE_CHECKING
|
||||
|
||||
from UM.Event import CallFunctionEvent
|
||||
from UM.Application import Application
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Scene.Selection import Selection
|
||||
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.TranslateOperation import TranslateOperation
|
||||
|
||||
import cura.CuraApplication
|
||||
from cura.Operations.SetParentOperation import SetParentOperation
|
||||
from cura.MultiplyObjectsJob import MultiplyObjectsJob
|
||||
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
|
||||
|
@ -23,28 +24,30 @@ from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOper
|
|||
|
||||
from UM.Logger import Logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
class CuraActions(QObject):
|
||||
def __init__(self, parent = None):
|
||||
def __init__(self, parent: QObject = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
@pyqtSlot()
|
||||
def openDocumentation(self):
|
||||
def openDocumentation(self) -> None:
|
||||
# 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.
|
||||
# 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")], {})
|
||||
Application.getInstance().functionEvent(event)
|
||||
cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
|
||||
|
||||
@pyqtSlot()
|
||||
def openBugReportPage(self):
|
||||
def openBugReportPage(self) -> None:
|
||||
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
|
||||
@pyqtSlot()
|
||||
def homeCamera(self) -> None:
|
||||
scene = Application.getInstance().getController().getScene()
|
||||
scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene()
|
||||
camera = scene.getActiveCamera()
|
||||
camera.setPosition(Vector(-80, 250, 700))
|
||||
camera.setPerspective(True)
|
||||
|
@ -72,17 +75,17 @@ class CuraActions(QObject):
|
|||
# \param count The number of times to multiply the selection.
|
||||
@pyqtSlot(int)
|
||||
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.start()
|
||||
|
||||
## Delete all selected objects.
|
||||
@pyqtSlot()
|
||||
def deleteSelection(self) -> None:
|
||||
if not Application.getInstance().getController().getToolsEnabled():
|
||||
if not cura.CuraApplication.CuraApplication.getInstance().getController().getToolsEnabled():
|
||||
return
|
||||
|
||||
removed_group_nodes = []
|
||||
removed_group_nodes = [] #type: List[SceneNode]
|
||||
op = GroupedOperation()
|
||||
nodes = Selection.getAllSelectedObjects()
|
||||
for node in nodes:
|
||||
|
@ -96,7 +99,7 @@ class CuraActions(QObject):
|
|||
op.addOperation(RemoveSceneNodeOperation(group_node))
|
||||
|
||||
# Reset the print information
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
cura.CuraApplication.CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
|
||||
op.push()
|
||||
|
||||
|
@ -111,7 +114,7 @@ class CuraActions(QObject):
|
|||
for node in Selection.getAllSelectedObjects():
|
||||
# If the node is a group, apply the active extruder to all children of the group.
|
||||
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:
|
||||
continue
|
||||
|
||||
|
@ -143,7 +146,7 @@ class CuraActions(QObject):
|
|||
Logger.log("d", "Setting build plate number... %d" % build_plate_nr)
|
||||
operation = GroupedOperation()
|
||||
|
||||
root = Application.getInstance().getController().getScene().getRoot()
|
||||
root = cura.CuraApplication.CuraApplication.getInstance().getController().getScene().getRoot()
|
||||
|
||||
nodes_to_change = []
|
||||
for node in Selection.getAllSelectedObjects():
|
||||
|
@ -151,7 +154,7 @@ class CuraActions(QObject):
|
|||
while parent_node.getParent() != root:
|
||||
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)
|
||||
|
||||
if not nodes_to_change:
|
||||
|
@ -164,5 +167,5 @@ class CuraActions(QObject):
|
|||
|
||||
Selection.clear()
|
||||
|
||||
def _openUrl(self, url):
|
||||
def _openUrl(self, url: QUrl) -> None:
|
||||
QDesktopServices.openUrl(url)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
@ -14,7 +13,8 @@ from PyQt5.QtGui import QColor, QIcon
|
|||
from PyQt5.QtWidgets import QMessageBox
|
||||
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.Camera import Camera
|
||||
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.Logger import Logger
|
||||
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.GroupDecorator import GroupDecorator
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
|
@ -109,21 +111,19 @@ from UM.FlameProfiler import pyqtSlot
|
|||
|
||||
numpy.seterr(all = "ignore")
|
||||
|
||||
MYPY = False
|
||||
if not MYPY:
|
||||
try:
|
||||
from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode
|
||||
except ImportError:
|
||||
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
|
||||
CuraBuildType = ""
|
||||
CuraDebugMode = False
|
||||
try:
|
||||
from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode
|
||||
except ImportError:
|
||||
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
|
||||
CuraBuildType = ""
|
||||
CuraDebugMode = False
|
||||
|
||||
|
||||
class CuraApplication(QtApplication):
|
||||
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
||||
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
|
||||
# changes of the settings.
|
||||
SettingVersion = 4
|
||||
SettingVersion = 5
|
||||
|
||||
Created = False
|
||||
|
||||
|
@ -669,6 +669,7 @@ class CuraApplication(QtApplication):
|
|||
self._plugins_loaded = True
|
||||
|
||||
def run(self):
|
||||
super().run()
|
||||
container_registry = self._container_registry
|
||||
|
||||
Logger.log("i", "Initializing variant manager")
|
||||
|
@ -1719,7 +1720,7 @@ class CuraApplication(QtApplication):
|
|||
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.
|
||||
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:
|
||||
while(node.getParent() and node.getParent().callDecoration("isGroup")):
|
||||
node = node.getParent()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# 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
|
||||
|
||||
|
@ -9,6 +9,9 @@ from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
|||
from UM.Logger import Logger
|
||||
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.
|
||||
|
@ -23,10 +26,16 @@ from UM.Settings.InstanceContainer import InstanceContainer
|
|||
class ContainerNode:
|
||||
__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.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"]:
|
||||
return self.children_map.get(child_key)
|
||||
|
@ -50,4 +59,4 @@ class ContainerNode:
|
|||
return self.container
|
||||
|
||||
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:
|
||||
__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.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]
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
from collections import defaultdict, OrderedDict
|
||||
import copy
|
||||
import uuid
|
||||
from typing import Dict
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
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.
|
||||
#
|
||||
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
|
||||
rounded_diameter = str(round(diameter))
|
||||
if rounded_diameter not in self._diameter_machine_variant_material_map:
|
||||
|
@ -288,7 +289,7 @@ class MaterialManager(QObject):
|
|||
# 3. generic material (for fdmprinter)
|
||||
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:
|
||||
if node is not None:
|
||||
# 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
|
||||
for node in nodes_to_remove:
|
||||
self._container_registry.removeContainer(node.metadata["id"])
|
||||
self._container_registry.removeContainer(node.getMetaDataEntry("id", ""))
|
||||
|
||||
#
|
||||
# Methods for GUI
|
||||
|
@ -445,22 +446,27 @@ class MaterialManager(QObject):
|
|||
#
|
||||
@pyqtSlot("QVariant", 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):
|
||||
Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
|
||||
return
|
||||
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
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.
|
||||
#
|
||||
@pyqtSlot("QVariant")
|
||||
def removeMaterial(self, material_node: "MaterialNode"):
|
||||
root_material_id = material_node.metadata["base_file"]
|
||||
self.removeMaterialByRootId(root_material_id)
|
||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
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.
|
||||
|
@ -539,6 +545,10 @@ class MaterialManager(QObject):
|
|||
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
|
||||
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.
|
||||
new_id = self._container_registry.uniqueName("custom_material")
|
||||
new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, Dict
|
||||
|
||||
from .ContainerNode import ContainerNode
|
||||
|
||||
|
@ -15,7 +14,6 @@ from .ContainerNode import ContainerNode
|
|||
class MaterialNode(ContainerNode):
|
||||
__slots__ = ("material_map", "children_map")
|
||||
|
||||
def __init__(self, metadata: Optional[dict] = None):
|
||||
def __init__(self, metadata: Optional[dict] = None) -> None:
|
||||
super().__init__(metadata = metadata)
|
||||
self.material_map = {} # material_root_id -> material_node
|
||||
self.children_map = {} # mapping for the child nodes
|
||||
self.material_map = {} # type: Dict[str, MaterialNode] # material_root_id -> material_node
|
||||
|
|
|
@ -83,7 +83,7 @@ class QualityProfilesDropDownMenuModel(ListModel):
|
|||
|
||||
self.setItems(item_list)
|
||||
|
||||
def _fetchLayerHeight(self, quality_group: "QualityGroup"):
|
||||
def _fetchLayerHeight(self, quality_group: "QualityGroup") -> float:
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
if not self._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")
|
||||
|
||||
# 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()
|
||||
|
||||
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")
|
||||
else:
|
||||
# Look for layer_height in the GlobalStack from material -> definition
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
|
||||
from .QualityGroup import QualityGroup
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Machines.QualityNode import QualityNode
|
||||
|
||||
|
||||
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)
|
||||
self._container_registry = Application.getInstance().getContainerRegistry()
|
||||
|
||||
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.
|
||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(node.metadata["id"])
|
||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(node.getMetaDataEntry("id"))
|
||||
return
|
||||
|
||||
if extruder_position is None: #Then we're a global quality changes profile.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# 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 cura.Machines.ContainerNode import ContainerNode
|
||||
|
||||
#
|
||||
# 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):
|
||||
|
||||
def __init__(self, name: str, quality_type: str, parent = None):
|
||||
def __init__(self, name: str, quality_type: str, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.name = name
|
||||
self.node_for_global = None # type: Optional["QualityGroup"]
|
||||
self.nodes_for_extruders = {} # type: Dict[int, "QualityGroup"]
|
||||
self.node_for_global = None # type: Optional[ContainerNode]
|
||||
self.nodes_for_extruders = {} # type: Dict[int, ContainerNode]
|
||||
self.quality_type = quality_type
|
||||
self.is_available = False
|
||||
|
||||
|
@ -33,15 +33,17 @@ class QualityGroup(QObject):
|
|||
def getName(self) -> str:
|
||||
return self.name
|
||||
|
||||
def getAllKeys(self) -> set:
|
||||
result = set()
|
||||
def getAllKeys(self) -> Set[str]:
|
||||
result = set() #type: Set[str]
|
||||
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
|
||||
if node is None:
|
||||
continue
|
||||
result.update(node.getContainer().getAllKeys())
|
||||
container = node.getContainer()
|
||||
if container:
|
||||
result.update(container.getAllKeys())
|
||||
return result
|
||||
|
||||
def getAllNodes(self) -> List["QualityGroup"]:
|
||||
def getAllNodes(self) -> List[ContainerNode]:
|
||||
result = []
|
||||
if self.node_for_global is not None:
|
||||
result.append(self.node_for_global)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# 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
|
||||
|
||||
|
@ -90,7 +90,7 @@ class QualityManager(QObject):
|
|||
|
||||
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()
|
||||
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:
|
||||
# For global qualities, save data in the machine node
|
||||
|
@ -102,7 +102,7 @@ class QualityManager(QObject):
|
|||
# too.
|
||||
if variant_name not in machine_node.children_map:
|
||||
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 only variant_name is specified but material is not, add the quality/quality_changes metadata
|
||||
|
@ -114,7 +114,7 @@ class QualityManager(QObject):
|
|||
# material node.
|
||||
if root_material_id not in variant_node.children_map:
|
||||
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)
|
||||
|
||||
|
@ -123,7 +123,7 @@ class QualityManager(QObject):
|
|||
if root_material_id is not None:
|
||||
if root_material_id not in machine_node.children_map:
|
||||
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)
|
||||
|
||||
|
@ -351,7 +351,7 @@ class QualityManager(QObject):
|
|||
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"):
|
||||
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
|
||||
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.
|
||||
|
@ -365,7 +365,9 @@ class QualityManager(QObject):
|
|||
|
||||
new_name = self._container_registry.uniqueName(new_name)
|
||||
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
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# 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 .QualityChangesGroup import QualityChangesGroup
|
||||
|
@ -12,9 +12,9 @@ from .QualityChangesGroup import QualityChangesGroup
|
|||
#
|
||||
class QualityNode(ContainerNode):
|
||||
|
||||
def __init__(self, metadata: Optional[dict] = None):
|
||||
def __init__(self, metadata: Optional[dict] = None) -> None:
|
||||
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):
|
||||
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:
|
||||
quality_type_node.children_map[name] = QualityChangesGroup(name, quality_type)
|
||||
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.
|
||||
# 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.Resources import Resources
|
||||
|
||||
|
@ -10,19 +13,21 @@ from UM.View.RenderBatch import RenderBatch
|
|||
|
||||
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.
|
||||
# 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
|
||||
class PickingPass(RenderPass):
|
||||
def __init__(self, width: int, height: int):
|
||||
def __init__(self, width: int, height: int) -> None:
|
||||
super().__init__("picking", width, height)
|
||||
|
||||
self._renderer = Application.getInstance().getRenderer()
|
||||
self._renderer = QtApplication.getInstance().getRenderer()
|
||||
|
||||
self._shader = None
|
||||
self._scene = Application.getInstance().getController().getScene()
|
||||
self._shader = None #type: Optional[ShaderProgram]
|
||||
self._scene = QtApplication.getInstance().getController().getScene()
|
||||
|
||||
def render(self) -> None:
|
||||
if not self._shader:
|
||||
|
@ -37,7 +42,7 @@ class PickingPass(RenderPass):
|
|||
batch = RenderBatch(self._shader)
|
||||
|
||||
# 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():
|
||||
batch.addItem(node.getWorldTransformation(), node.getMeshData())
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Resources import Resources
|
||||
|
||||
|
@ -10,7 +13,8 @@ from UM.View.RenderBatch import RenderBatch
|
|||
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
|
||||
from typing import Optional
|
||||
if TYPE_CHECKING:
|
||||
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||
|
||||
MYPY = False
|
||||
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.
|
||||
class PreviewPass(RenderPass):
|
||||
def __init__(self, width: int, height: int):
|
||||
def __init__(self, width: int, height: int) -> None:
|
||||
super().__init__("preview", width, height, 0)
|
||||
|
||||
self._camera = None # type: Optional[Camera]
|
||||
|
||||
self._renderer = Application.getInstance().getRenderer()
|
||||
|
||||
self._shader = None
|
||||
self._non_printing_shader = None
|
||||
self._support_mesh_shader = None
|
||||
self._shader = None #type: Optional[ShaderProgram]
|
||||
self._non_printing_shader = None #type: Optional[ShaderProgram]
|
||||
self._support_mesh_shader = None #type: Optional[ShaderProgram]
|
||||
self._scene = Application.getInstance().getController().getScene()
|
||||
|
||||
# Set the camera to be used by this render pass
|
||||
|
@ -53,20 +57,23 @@ class PreviewPass(RenderPass):
|
|||
def render(self) -> None:
|
||||
if not self._shader:
|
||||
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "overhang.shader"))
|
||||
self._shader.setUniformValue("u_overhangAngle", 1.0)
|
||||
self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0])
|
||||
self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0])
|
||||
self._shader.setUniformValue("u_shininess", 20.0)
|
||||
if self._shader:
|
||||
self._shader.setUniformValue("u_overhangAngle", 1.0)
|
||||
self._shader.setUniformValue("u_ambientColor", [0.1, 0.1, 0.1, 1.0])
|
||||
self._shader.setUniformValue("u_specularColor", [0.6, 0.6, 0.6, 1.0])
|
||||
self._shader.setUniformValue("u_shininess", 20.0)
|
||||
|
||||
if not self._non_printing_shader:
|
||||
self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
|
||||
self._non_printing_shader.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 0.5])
|
||||
self._non_printing_shader.setUniformValue("u_opacity", 0.6)
|
||||
if self._non_printing_shader:
|
||||
self._non_printing_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "transparent_object.shader"))
|
||||
self._non_printing_shader.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 0.5])
|
||||
self._non_printing_shader.setUniformValue("u_opacity", 0.6)
|
||||
|
||||
if not self._support_mesh_shader:
|
||||
self._support_mesh_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
|
||||
self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
|
||||
self._support_mesh_shader.setUniformValue("u_width", 5.0)
|
||||
if self._support_mesh_shader:
|
||||
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.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_support_mesh = RenderBatch(self._support_mesh_shader)
|
||||
|
||||
# Fill up the batch with objects that can be sliced. `
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
# Fill up the batch with objects that can be sliced.
|
||||
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():
|
||||
per_mesh_stack = node.callDecoration("getStack")
|
||||
if node.callDecoration("isNonThumbnailVisibleMesh"):
|
||||
|
|
|
@ -4,10 +4,9 @@
|
|||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
||||
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
|
||||
from typing import Optional
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
if TYPE_CHECKING:
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||
|
||||
|
@ -20,12 +19,12 @@ class ExtruderOutputModel(QObject):
|
|||
extruderConfigurationChanged = pyqtSignal()
|
||||
isPreheatingChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, printer: "PrinterOutputModel", position, parent=None):
|
||||
def __init__(self, printer: "PrinterOutputModel", position, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self._printer = printer
|
||||
self._position = position
|
||||
self._target_hotend_temperature = 0
|
||||
self._hotend_temperature = 0
|
||||
self._target_hotend_temperature = 0 # type: float
|
||||
self._hotend_temperature = 0 # type: float
|
||||
self._hotend_id = ""
|
||||
self._active_material = None # type: Optional[MaterialOutputModel]
|
||||
self._extruder_configuration = ExtruderConfigurationModel()
|
||||
|
@ -47,7 +46,7 @@ class ExtruderOutputModel(QObject):
|
|||
return False
|
||||
|
||||
@pyqtProperty(QObject, notify = activeMaterialChanged)
|
||||
def activeMaterial(self) -> "MaterialOutputModel":
|
||||
def activeMaterial(self) -> Optional["MaterialOutputModel"]:
|
||||
return self._active_material
|
||||
|
||||
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from PyQt5.QtCore import QTimer
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
if TYPE_CHECKING:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||
|
||||
|
||||
class GenericOutputController(PrinterOutputController):
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# 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.Scene.SceneNode import SceneNode #For typing.
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
||||
|
||||
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, pyqtSignal, QUrl, QCoreApplication
|
||||
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
|
||||
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 typing import List
|
||||
|
||||
import os # To get the username
|
||||
import gzip
|
||||
|
@ -27,20 +28,20 @@ class AuthState(IntEnum):
|
|||
class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
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)
|
||||
self._manager = None # type: QNetworkAccessManager
|
||||
self._last_manager_create_time = None # type: float
|
||||
self._manager = None # type: Optional[QNetworkAccessManager]
|
||||
self._last_manager_create_time = None # type: Optional[float]
|
||||
self._recreate_network_manager_time = 30
|
||||
self._timeout_time = 10 # After how many seconds of no response should a timeout occur?
|
||||
|
||||
self._last_response_time = None # type: float
|
||||
self._last_request_time = None # type: float
|
||||
self._last_response_time = None # type: Optional[float]
|
||||
self._last_request_time = None # type: Optional[float]
|
||||
|
||||
self._api_prefix = ""
|
||||
self._address = address
|
||||
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._authentication_state = AuthState.NotAuthenticated
|
||||
|
@ -67,16 +68,16 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._printer_type = value
|
||||
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")
|
||||
|
||||
def setAuthenticationState(self, authentication_state) -> None:
|
||||
def setAuthenticationState(self, authentication_state: AuthState) -> None:
|
||||
if self._authentication_state != authentication_state:
|
||||
self._authentication_state = authentication_state
|
||||
self.authenticationStateChanged.emit()
|
||||
|
||||
@pyqtProperty(int, notify=authenticationStateChanged)
|
||||
def authenticationState(self) -> int:
|
||||
@pyqtProperty(int, notify = authenticationStateChanged)
|
||||
def authenticationState(self) -> AuthState:
|
||||
return self._authentication_state
|
||||
|
||||
def _compressDataAndNotifyQt(self, data_to_append: str) -> bytes:
|
||||
|
@ -121,7 +122,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._compressing_gcode = False
|
||||
return b"".join(file_data_bytes_list)
|
||||
|
||||
def _update(self) -> bool:
|
||||
def _update(self) -> None:
|
||||
if self._last_response_time:
|
||||
time_since_last_response = time() - self._last_response_time
|
||||
else:
|
||||
|
@ -144,16 +145,16 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
if time_since_last_response > self._recreate_network_manager_time:
|
||||
if self._last_manager_create_time is None:
|
||||
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()
|
||||
assert(self._manager is not None)
|
||||
elif self._connection_state == ConnectionState.closed:
|
||||
# Go out of timeout.
|
||||
self.setConnectionState(self._connection_state_before_timeout)
|
||||
self._connection_state_before_timeout = None
|
||||
if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here
|
||||
self.setConnectionState(self._connection_state_before_timeout)
|
||||
self._connection_state_before_timeout = None
|
||||
|
||||
return True
|
||||
|
||||
def _createEmptyRequest(self, target, content_type: Optional[str] = "application/json") -> QNetworkRequest:
|
||||
def _createEmptyRequest(self, target: str, content_type: Optional[str] = "application/json") -> QNetworkRequest:
|
||||
url = QUrl("http://" + self._address + self._api_prefix + target)
|
||||
request = QNetworkRequest(url)
|
||||
if content_type is not None:
|
||||
|
@ -161,7 +162,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
|
||||
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()
|
||||
|
||||
if not content_header.startswith("form-data;"):
|
||||
|
@ -187,40 +188,34 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
if reply in self._kept_alive_multiparts:
|
||||
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:
|
||||
self._createNetworkManager()
|
||||
request = self._createEmptyRequest(target)
|
||||
self._last_request_time = time()
|
||||
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:
|
||||
self._createNetworkManager()
|
||||
request = self._createEmptyRequest(target)
|
||||
self._last_request_time = time()
|
||||
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:
|
||||
self._createNetworkManager()
|
||||
request = self._createEmptyRequest(target)
|
||||
self._last_request_time = time()
|
||||
reply = self._manager.post(request, data)
|
||||
if onProgress is not None:
|
||||
reply.uploadProgress.connect(onProgress)
|
||||
self._registerOnFinishedCallback(reply, onFinished)
|
||||
if on_progress is not None:
|
||||
reply.uploadProgress.connect(on_progress)
|
||||
self._registerOnFinishedCallback(reply, on_finished)
|
||||
|
||||
def postFormWithParts(self, target: str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
|
||||
|
||||
## Send an API call to the printer via HTTP POST.
|
||||
# \param target The target URL on the printer.
|
||||
# \param parts The parts of a form. One must be provided for each form
|
||||
# element in the POST call. Create form parts using _createFormPart.
|
||||
# \param onFinished A function to call when the call has been completed.
|
||||
# \param onProgress A function to call when progress has been made. Use
|
||||
# this to update a progress bar.
|
||||
def postFormWithParts(self, target: str, parts: List[QHttpPart], onFinished: Optional[Callable[[Any, QNetworkReply], None]] = None, onProgress: Callable = None) -> None:
|
||||
if self._manager is None:
|
||||
self._createNetworkManager()
|
||||
request = self._createEmptyRequest(target, content_type=None)
|
||||
|
@ -234,20 +229,20 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
self._kept_alive_multiparts[reply] = multi_post_part
|
||||
|
||||
if onProgress is not None:
|
||||
reply.uploadProgress.connect(onProgress)
|
||||
self._registerOnFinishedCallback(reply, onFinished)
|
||||
if on_progress is not None:
|
||||
reply.uploadProgress.connect(on_progress)
|
||||
self._registerOnFinishedCallback(reply, on_finished)
|
||||
|
||||
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.setHeader(QNetworkRequest.ContentDispositionHeader, header_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()))
|
||||
|
||||
def _createNetworkManager(self) -> None:
|
||||
|
@ -262,11 +257,11 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
|
||||
|
||||
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:
|
||||
if onFinished is not None:
|
||||
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = onFinished
|
||||
def _registerOnFinishedCallback(self, reply: QNetworkReply, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||
if on_finished is not None:
|
||||
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = on_finished
|
||||
|
||||
def __handleOnFinished(self, reply: QNetworkReply) -> None:
|
||||
# Due to garbage collection, we need to cache certain bits of post operations.
|
||||
|
@ -303,30 +298,30 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
## Get the unique key of this machine
|
||||
# \return key String containing the key of the machine.
|
||||
@pyqtProperty(str, constant=True)
|
||||
@pyqtProperty(str, constant = True)
|
||||
def key(self) -> str:
|
||||
return self._id
|
||||
|
||||
## The IP address of the printer.
|
||||
@pyqtProperty(str, constant=True)
|
||||
@pyqtProperty(str, constant = True)
|
||||
def address(self) -> str:
|
||||
return self._properties.get(b"address", b"").decode("utf-8")
|
||||
|
||||
## Name of the printer (as returned from the ZeroConf properties)
|
||||
@pyqtProperty(str, constant=True)
|
||||
@pyqtProperty(str, constant = True)
|
||||
def name(self) -> str:
|
||||
return self._properties.get(b"name", b"").decode("utf-8")
|
||||
|
||||
## Firmware version (as returned from the ZeroConf properties)
|
||||
@pyqtProperty(str, constant=True)
|
||||
@pyqtProperty(str, constant = True)
|
||||
def firmwareVersion(self) -> str:
|
||||
return self._properties.get(b"firmware_version", b"").decode("utf-8")
|
||||
|
||||
@pyqtProperty(str, constant=True)
|
||||
@pyqtProperty(str, constant = True)
|
||||
def printerType(self) -> str:
|
||||
return self._printer_type
|
||||
|
||||
## IPadress of this printer
|
||||
@pyqtProperty(str, constant=True)
|
||||
## IP adress of this printer
|
||||
@pyqtProperty(str, constant = True)
|
||||
def ipAddress(self) -> str:
|
||||
return self._address
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
||||
from typing import Optional
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
|
||||
|
@ -18,7 +18,7 @@ class PrintJobOutputModel(QObject):
|
|||
assignedPrinterChanged = 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)
|
||||
self._output_controller = output_controller
|
||||
self._state = ""
|
||||
|
|
|
@ -27,7 +27,7 @@ class PrinterOutputModel(QObject):
|
|||
cameraChanged = 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)
|
||||
self._bed_temperature = -1 # Use -1 for no heated bed.
|
||||
self._target_bed_temperature = 0
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Decorators import deprecated
|
||||
from UM.i18n import i18nCatalog
|
||||
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 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.Application import Application
|
||||
from UM.Qt.QtApplication import QtApplication
|
||||
|
||||
from enum import IntEnum # For the connection state tracking.
|
||||
from typing import List, Optional
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
|
@ -20,6 +23,16 @@ if MYPY:
|
|||
|
||||
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.
|
||||
#
|
||||
# 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.
|
||||
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)
|
||||
|
||||
self._printers = [] # type: List[PrinterOutputModel]
|
||||
self._unique_configurations = [] # type: List[ConfigurationModel]
|
||||
|
||||
self._monitor_view_qml_path = ""
|
||||
self._monitor_component = None
|
||||
self._monitor_item = None
|
||||
self._monitor_view_qml_path = "" #type: str
|
||||
self._monitor_component = None #type: Optional[QObject]
|
||||
self._monitor_item = None #type: Optional[QObject]
|
||||
|
||||
self._control_view_qml_path = ""
|
||||
self._control_component = None
|
||||
self._control_item = None
|
||||
self._control_view_qml_path = "" #type: str
|
||||
self._control_component = None #type: Optional[QObject]
|
||||
self._control_item = None #type: Optional[QObject]
|
||||
|
||||
self._qml_context = None
|
||||
self._accepts_commands = False
|
||||
self._accepts_commands = False #type: bool
|
||||
|
||||
self._update_timer = QTimer()
|
||||
self._update_timer = QTimer() #type: QTimer
|
||||
self._update_timer.setInterval(2000) # TODO; Add preference for update interval
|
||||
self._update_timer.setSingleShot(False)
|
||||
self._update_timer.timeout.connect(self._update)
|
||||
|
||||
self._connection_state = ConnectionState.closed
|
||||
self._connection_state = ConnectionState.closed #type: ConnectionState
|
||||
|
||||
self._firmware_name = None
|
||||
self._address = ""
|
||||
self._connection_text = ""
|
||||
self._firmware_name = None #type: Optional[str]
|
||||
self._address = "" #type: str
|
||||
self._connection_text = "" #type: str
|
||||
self.printersChanged.connect(self._onPrintersChanged)
|
||||
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations)
|
||||
QtApplication.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations)
|
||||
|
||||
@pyqtProperty(str, notify = connectionTextChanged)
|
||||
def address(self):
|
||||
def address(self) -> str:
|
||||
return self._address
|
||||
|
||||
def setConnectionText(self, connection_text):
|
||||
|
@ -87,36 +99,36 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
self.connectionTextChanged.emit()
|
||||
|
||||
@pyqtProperty(str, constant=True)
|
||||
def connectionText(self):
|
||||
def connectionText(self) -> str:
|
||||
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'")
|
||||
callback(QMessageBox.Yes)
|
||||
|
||||
def isConnected(self):
|
||||
def isConnected(self) -> bool:
|
||||
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:
|
||||
self._connection_state = connection_state
|
||||
self.connectionStateChanged.emit(self._id)
|
||||
|
||||
@pyqtProperty(str, notify = connectionStateChanged)
|
||||
def connectionState(self):
|
||||
def connectionState(self) -> ConnectionState:
|
||||
return self._connection_state
|
||||
|
||||
def _update(self):
|
||||
def _update(self) -> None:
|
||||
pass
|
||||
|
||||
def _getPrinterByKey(self, key) -> Optional["PrinterOutputModel"]:
|
||||
def _getPrinterByKey(self, key: str) -> Optional["PrinterOutputModel"]:
|
||||
for printer in self._printers:
|
||||
if printer.key == key:
|
||||
return printer
|
||||
|
||||
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")
|
||||
|
||||
@pyqtProperty(QObject, notify = printersChanged)
|
||||
|
@ -126,11 +138,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
return None
|
||||
|
||||
@pyqtProperty("QVariantList", notify = printersChanged)
|
||||
def printers(self):
|
||||
def printers(self) -> List["PrinterOutputModel"]:
|
||||
return self._printers
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
def monitorItem(self):
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def monitorItem(self) -> QObject:
|
||||
# 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
|
||||
# create the item (and fail) every time.
|
||||
|
@ -138,49 +150,49 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
self._createMonitorViewFromQML()
|
||||
return self._monitor_item
|
||||
|
||||
@pyqtProperty(QObject, constant=True)
|
||||
def controlItem(self):
|
||||
@pyqtProperty(QObject, constant = True)
|
||||
def controlItem(self) -> QObject:
|
||||
if not self._control_component:
|
||||
self._createControlViewFromQML()
|
||||
return self._control_item
|
||||
|
||||
def _createControlViewFromQML(self):
|
||||
def _createControlViewFromQML(self) -> None:
|
||||
if not self._control_view_qml_path:
|
||||
return
|
||||
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:
|
||||
return
|
||||
|
||||
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
|
||||
def connect(self):
|
||||
def connect(self) -> None:
|
||||
self.setConnectionState(ConnectionState.connecting)
|
||||
self._update_timer.start()
|
||||
|
||||
## Attempt to close the connection
|
||||
def close(self):
|
||||
def close(self) -> None:
|
||||
self._update_timer.stop()
|
||||
self.setConnectionState(ConnectionState.closed)
|
||||
|
||||
## Ensure that close gets called when object is destroyed
|
||||
def __del__(self):
|
||||
def __del__(self) -> None:
|
||||
self.close()
|
||||
|
||||
@pyqtProperty(bool, notify=acceptsCommandsChanged)
|
||||
def acceptsCommands(self):
|
||||
@pyqtProperty(bool, notify = acceptsCommandsChanged)
|
||||
def acceptsCommands(self) -> bool:
|
||||
return self._accepts_commands
|
||||
|
||||
@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)
|
||||
|
||||
## 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:
|
||||
self._accepts_commands = accepts_commands
|
||||
|
||||
|
@ -188,15 +200,15 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
# Returns the unique configurations of the printers within this output device
|
||||
@pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged)
|
||||
def uniqueConfigurations(self):
|
||||
def uniqueConfigurations(self) -> List["ConfigurationModel"]:
|
||||
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.sort(key = lambda k: k.printerType)
|
||||
self.uniqueConfigurationsChanged.emit()
|
||||
|
||||
def _onPrintersChanged(self):
|
||||
def _onPrintersChanged(self) -> None:
|
||||
for printer in self._printers:
|
||||
printer.configurationChanged.connect(self._updateUniqueConfigurations)
|
||||
|
||||
|
@ -205,21 +217,12 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
## Set the device firmware name
|
||||
#
|
||||
# \param name \type{str} The name of the firmware.
|
||||
def _setFirmwareName(self, name):
|
||||
# \param name The name of the firmware.
|
||||
def _setFirmwareName(self, name: str) -> None:
|
||||
self._firmware_name = name
|
||||
|
||||
## Get the name of device firmware
|
||||
#
|
||||
# This name can be used to define device type
|
||||
def getFirmwareName(self):
|
||||
return self._firmware_name
|
||||
|
||||
|
||||
## The current processing state of the backend.
|
||||
class ConnectionState(IntEnum):
|
||||
closed = 0
|
||||
connecting = 1
|
||||
connected = 2
|
||||
busy = 3
|
||||
error = 4
|
||||
def getFirmwareName(self) -> Optional[str]:
|
||||
return self._firmware_name
|
|
@ -16,7 +16,7 @@ from UM.Signal import Signal
|
|||
class CuraSceneController(QObject):
|
||||
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__()
|
||||
|
||||
self._objects_model = objects_model
|
||||
|
|
|
@ -1,40 +1,47 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from copy import deepcopy
|
||||
from typing import List, Optional
|
||||
from typing import cast, Dict, List, Optional
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Math.Polygon import Polygon #For typing.
|
||||
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
|
||||
# Note that many other nodes can just be UM SceneNode objects.
|
||||
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)
|
||||
if not no_setting_override:
|
||||
self.addDecorator(SettingOverrideDecorator()) # now we always have a getActiveExtruderPosition, unless explicitly disabled
|
||||
self._outside_buildarea = False
|
||||
|
||||
def setOutsideBuildArea(self, new_value):
|
||||
def setOutsideBuildArea(self, new_value: bool) -> None:
|
||||
self._outside_buildarea = new_value
|
||||
|
||||
def isOutsideBuildArea(self):
|
||||
def isOutsideBuildArea(self) -> bool:
|
||||
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
|
||||
|
||||
def isVisible(self):
|
||||
return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
def isVisible(self) -> bool:
|
||||
return super().isVisible() and self.callDecoration("getBuildPlateNumber") == cura.CuraApplication.CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
|
||||
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
|
||||
# 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()
|
||||
if global_container_stack is None:
|
||||
return None
|
||||
|
||||
per_mesh_stack = self.callDecoration("getStack")
|
||||
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
|
||||
def collidesWithBbox(self, check_bbox):
|
||||
def collidesWithBbox(self, check_bbox: AxisAlignedBox) -> bool:
|
||||
bbox = self.getBoundingBox()
|
||||
|
||||
# Mark the node as outside the build volume if the bounding box test fails.
|
||||
if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
||||
return True
|
||||
if bbox is not None:
|
||||
# Mark the node as outside the build volume if the bounding box test fails.
|
||||
if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
## 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")
|
||||
if convex_hull:
|
||||
if not convex_hull.isValid():
|
||||
|
@ -104,8 +111,7 @@ class CuraSceneNode(SceneNode):
|
|||
return False
|
||||
|
||||
## Override of SceneNode._calculateAABB to exclude non-printing-meshes from bounding box
|
||||
def _calculateAABB(self):
|
||||
aabb = None
|
||||
def _calculateAABB(self) -> None:
|
||||
if self._mesh_data:
|
||||
aabb = self._mesh_data.getExtents(self.getWorldTransformation())
|
||||
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
|
||||
|
||||
## 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.setTransformation(self.getLocalTransformation())
|
||||
copy.setMeshData(self._mesh_data)
|
||||
copy.setVisible(deepcopy(self._visible, memo))
|
||||
copy._selectable = deepcopy(self._selectable, memo)
|
||||
copy._name = deepcopy(self._name, memo)
|
||||
copy.setVisible(cast(bool, deepcopy(self._visible, memo)))
|
||||
copy._selectable = cast(bool, deepcopy(self._selectable, memo))
|
||||
copy._name = cast(str, deepcopy(self._name, memo))
|
||||
for decorator in self._decorators:
|
||||
copy.addDecorator(deepcopy(decorator, memo))
|
||||
copy.addDecorator(cast(SceneNodeDecorator, deepcopy(decorator, memo)))
|
||||
|
||||
for child in self._children:
|
||||
copy.addChild(deepcopy(child, memo))
|
||||
copy.addChild(cast(SceneNode, deepcopy(child, memo)))
|
||||
self.calculateBoundingBoxMesh()
|
||||
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]
|
||||
self._container_registry.exportQualityProfile(container_list, path, file_type)
|
||||
|
||||
__instance = None
|
||||
__instance = None # type: ContainerManager
|
||||
|
||||
@classmethod
|
||||
def getInstance(cls, *args, **kwargs) -> "ContainerManager":
|
||||
|
|
|
@ -5,7 +5,7 @@ import os
|
|||
import re
|
||||
import configparser
|
||||
|
||||
from typing import Optional
|
||||
from typing import cast, Optional
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
|
@ -26,7 +26,7 @@ from UM.Resources import Resources
|
|||
from . import ExtruderStack
|
||||
from . import GlobalStack
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
import cura.CuraApplication
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
from cura.ReaderWriters.ProfileReader import NoProfileException
|
||||
|
||||
|
@ -57,7 +57,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
|
||||
if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()):
|
||||
# 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))
|
||||
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))
|
||||
|
@ -260,7 +260,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
profile_id = ContainerRegistry.getInstance().uniqueName(global_stack.getId() + "_extruder_" + str(idx + 1))
|
||||
profile = InstanceContainer(profile_id)
|
||||
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("definition", expected_machine_definition)
|
||||
profile.addMetaDataEntry("quality_type", quality_type)
|
||||
|
@ -356,13 +356,15 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
||||
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return None
|
||||
definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition)
|
||||
profile.setDefinition(definition_id)
|
||||
|
||||
# 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
|
||||
# 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)
|
||||
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)
|
||||
|
@ -465,7 +467,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
def addExtruderStackForSingleExtrusionMachine(self, machine, extruder_id, new_global_quality_changes = None, create_new_ids = True):
|
||||
new_extruder_id = extruder_id
|
||||
|
||||
application = CuraApplication.getInstance()
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
|
||||
extruder_definitions = self.findDefinitionContainers(id = new_extruder_id)
|
||||
if not extruder_definitions:
|
||||
|
@ -485,7 +487,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
definition_changes_name = definition_changes_id
|
||||
definition_changes = InstanceContainer(definition_changes_id, parent = application)
|
||||
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("definition", extruder_definition.getId())
|
||||
|
||||
|
@ -514,7 +516,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
user_container.setName(user_container_name)
|
||||
user_container.addMetaDataEntry("type", "user")
|
||||
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.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.setName(container_name)
|
||||
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("quality_type", machine_quality_changes.getMetaDataEntry("quality_type"))
|
||||
extruder_quality_changes_container.setDefinition(machine_quality_changes.getDefinition().getId())
|
||||
|
@ -675,7 +677,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
return extruder_stack
|
||||
|
||||
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
|
||||
|
||||
|
@ -731,3 +733,9 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
extruder_stack.setNextStack(machines[0])
|
||||
else:
|
||||
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.
|
||||
|
||||
import os.path
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from typing import Any, cast, List, Optional, Union
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Decorators import override
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
|
||||
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
|
||||
# will raise an exception.
|
||||
class CuraContainerStack(ContainerStack):
|
||||
def __init__(self, container_id: str):
|
||||
def __init__(self, container_id: str) -> None:
|
||||
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 = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
|
||||
self._empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0]
|
||||
self._empty_variant = self._container_registry.findInstanceContainers(id = "empty_variant")[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] #type: InstanceContainer
|
||||
self._empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] #type: InstanceContainer
|
||||
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.Quality] = self._empty_quality
|
||||
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.
|
||||
@pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged)
|
||||
def userChanges(self) -> InstanceContainer:
|
||||
return self._containers[_ContainerIndexes.UserChanges]
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.UserChanges])
|
||||
|
||||
## 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.
|
||||
@pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged)
|
||||
def qualityChanges(self) -> InstanceContainer:
|
||||
return self._containers[_ContainerIndexes.QualityChanges]
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.QualityChanges])
|
||||
|
||||
## Set the quality container.
|
||||
#
|
||||
# \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)
|
||||
|
||||
## 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.
|
||||
@pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged)
|
||||
def quality(self) -> InstanceContainer:
|
||||
return self._containers[_ContainerIndexes.Quality]
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.Quality])
|
||||
|
||||
## Set the material container.
|
||||
#
|
||||
# \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)
|
||||
|
||||
## 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.
|
||||
@pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged)
|
||||
def material(self) -> InstanceContainer:
|
||||
return self._containers[_ContainerIndexes.Material]
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.Material])
|
||||
|
||||
## 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.
|
||||
@pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged)
|
||||
def variant(self) -> InstanceContainer:
|
||||
return self._containers[_ContainerIndexes.Variant]
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.Variant])
|
||||
|
||||
## 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.
|
||||
@pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged)
|
||||
def definitionChanges(self) -> InstanceContainer:
|
||||
return self._containers[_ContainerIndexes.DefinitionChanges]
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.DefinitionChanges])
|
||||
|
||||
## 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.
|
||||
@pyqtProperty(QObject, fset = setDefinition, notify = pyqtContainersChanged)
|
||||
def definition(self) -> DefinitionContainer:
|
||||
return self._containers[_ContainerIndexes.Definition]
|
||||
return cast(DefinitionContainer, self._containers[_ContainerIndexes.Definition])
|
||||
|
||||
@override(ContainerStack)
|
||||
def getBottom(self) -> "DefinitionContainer":
|
||||
|
@ -189,13 +186,9 @@ class CuraContainerStack(ContainerStack):
|
|||
# \param key The key of the setting to set.
|
||||
# \param property_name The name of the property to set.
|
||||
# \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, new_value: Any, target_container: str = "user") -> None:
|
||||
container_index = _ContainerIndexes.TypeIndexMap.get(target_container, -1)
|
||||
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))
|
||||
def setProperty(self, key: str, property_name: str, property_value: Any, container: "ContainerInterface" = None, set_from_cache: bool = False) -> None:
|
||||
container_index = _ContainerIndexes.UserChanges
|
||||
self._containers[container_index].setProperty(key, property_name, property_value, container, set_from_cache)
|
||||
|
||||
## Overridden from ContainerStack
|
||||
#
|
||||
|
@ -310,15 +303,15 @@ class CuraContainerStack(ContainerStack):
|
|||
#
|
||||
# \return The ID of the definition container to use when searching for instance containers.
|
||||
@classmethod
|
||||
def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainer) -> str:
|
||||
def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainerInterface) -> str:
|
||||
quality_definition = machine_definition.getMetaDataEntry("quality_definition")
|
||||
if not quality_definition:
|
||||
return machine_definition.id
|
||||
return machine_definition.id #type: ignore
|
||||
|
||||
definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition)
|
||||
if not definitions:
|
||||
Logger.log("w", "Unable to find parent definition {parent} for machine {machine}", parent = quality_definition, machine = machine_definition.id)
|
||||
return 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 #type: ignore
|
||||
|
||||
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.
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
|
||||
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.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
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.PropertyEvaluationContext import PropertyEvaluationContext
|
||||
|
||||
from typing import Optional, List, TYPE_CHECKING, Union
|
||||
from typing import Optional, List, TYPE_CHECKING, Union, Dict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
|
@ -36,14 +36,13 @@ class ExtruderManager(QObject):
|
|||
|
||||
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._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
|
||||
self._selected_object_extruders = []
|
||||
self._addCurrentMachineExtruders()
|
||||
|
||||
#Application.getInstance().globalContainerStackChanged.connect(self._globalContainerStackChanged)
|
||||
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
||||
|
||||
## 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.
|
||||
@pyqtProperty(str, notify = activeExtruderChanged)
|
||||
def activeExtruderStackId(self) -> Optional[str]:
|
||||
if not Application.getInstance().getGlobalContainerStack():
|
||||
if not self._application.getGlobalContainerStack():
|
||||
return None # No active machine, so no active extruder.
|
||||
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.
|
||||
return None
|
||||
|
||||
## Return extruder count according to extruder trains.
|
||||
@pyqtProperty(int, notify = extrudersChanged)
|
||||
def extruderCount(self):
|
||||
if not Application.getInstance().getGlobalContainerStack():
|
||||
if not self._application.getGlobalContainerStack():
|
||||
return 0 # No active machine, so no extruders.
|
||||
try:
|
||||
return len(self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()])
|
||||
return len(self._extruder_trains[self._application.getGlobalContainerStack().getId()])
|
||||
except KeyError:
|
||||
return 0
|
||||
|
||||
## Gets a dict with the extruder stack ids with the extruder number as the key.
|
||||
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
||||
def extruderIds(self):
|
||||
def extruderIds(self) -> Dict[str, str]:
|
||||
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:
|
||||
for position in self._extruder_trains[global_stack_id]:
|
||||
extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId()
|
||||
if global_stack_id in self._extruder_trains:
|
||||
for position in self._extruder_trains[global_stack_id]:
|
||||
extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId()
|
||||
|
||||
return extruder_stack_ids
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def getQualityChangesIdByExtruderStackId(self, extruder_stack_id: str) -> str:
|
||||
for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]:
|
||||
extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position]
|
||||
if extruder.getId() == extruder_stack_id:
|
||||
return extruder.qualityChanges.getId()
|
||||
global_container_stack = self._application.getGlobalContainerStack()
|
||||
if global_container_stack is not None:
|
||||
for position in self._extruder_trains[global_container_stack.getId()]:
|
||||
extruder = self._extruder_trains[global_container_stack.getId()][position]
|
||||
if extruder.getId() == extruder_stack_id:
|
||||
return extruder.qualityChanges.getId()
|
||||
return ""
|
||||
|
||||
## Changes the active extruder by index.
|
||||
#
|
||||
|
@ -132,7 +136,7 @@ class ExtruderManager(QObject):
|
|||
selected_nodes = []
|
||||
for node in Selection.getAllSelectedObjects():
|
||||
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"):
|
||||
continue
|
||||
|
||||
|
@ -141,7 +145,7 @@ class ExtruderManager(QObject):
|
|||
selected_nodes.append(node)
|
||||
|
||||
# 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())
|
||||
for node in selected_nodes:
|
||||
extruder = node.callDecoration("getActiveExtruder")
|
||||
|
@ -164,7 +168,7 @@ class ExtruderManager(QObject):
|
|||
|
||||
@pyqtSlot(result = QObject)
|
||||
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.getId() in self._extruder_trains:
|
||||
|
@ -175,7 +179,7 @@ class ExtruderManager(QObject):
|
|||
|
||||
## Get an extruder stack by index
|
||||
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.getId() in self._extruder_trains:
|
||||
if str(index) in self._extruder_trains[global_container_stack.getId()]:
|
||||
|
@ -186,7 +190,9 @@ class ExtruderManager(QObject):
|
|||
def getExtruderStacks(self) -> List["ExtruderStack"]:
|
||||
result = []
|
||||
for i in range(self.extruderCount):
|
||||
result.append(self.getExtruderStack(i))
|
||||
stack = self.getExtruderStack(i)
|
||||
if stack:
|
||||
result.append(stack)
|
||||
return result
|
||||
|
||||
def registerExtruder(self, extruder_train, machine_id):
|
||||
|
@ -252,14 +258,14 @@ class ExtruderManager(QObject):
|
|||
support_bottom_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 len(self.extruderIds) == 0:
|
||||
return []
|
||||
|
||||
# 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:
|
||||
extruder_stack_id = mesh.callDecoration("getActiveExtruder")
|
||||
if not extruder_stack_id:
|
||||
|
@ -301,10 +307,10 @@ class ExtruderManager(QObject):
|
|||
|
||||
# The platform adhesion extruder. Not used if using none.
|
||||
if global_stack.getProperty("adhesion_type", "value") != "none":
|
||||
extruder_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value"))
|
||||
if extruder_nr == "-1":
|
||||
extruder_nr = Application.getInstance().getMachineManager().defaultExtruderPosition
|
||||
used_extruder_stack_ids.add(self.extruderIds[extruder_nr])
|
||||
extruder_str_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value"))
|
||||
if extruder_str_nr == "-1":
|
||||
extruder_str_nr = self._application.getMachineManager().defaultExtruderPosition
|
||||
used_extruder_stack_ids.add(self.extruderIds[extruder_str_nr])
|
||||
|
||||
try:
|
||||
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.
|
||||
# \return \type{List[ContainerStack]}
|
||||
def getActiveGlobalAndExtruderStacks(self) -> Optional[List[Union["ExtruderStack", "GlobalStack"]]]:
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = self._application.getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return None
|
||||
|
||||
|
@ -347,7 +353,7 @@ class ExtruderManager(QObject):
|
|||
#
|
||||
# \return \type{List[ContainerStack]} a list of
|
||||
def getActiveExtruderStacks(self) -> List["ExtruderStack"]:
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = self._application.getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return []
|
||||
|
||||
|
@ -471,7 +477,7 @@ class ExtruderManager(QObject):
|
|||
# If no extruder has the value, the list will contain the global value.
|
||||
@staticmethod
|
||||
def getExtruderValues(key):
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
|
||||
result = []
|
||||
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.
|
||||
@staticmethod
|
||||
def getDefaultExtruderValues(key):
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
context = PropertyEvaluationContext(global_stack)
|
||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||
context.context["override_operators"] = {
|
||||
|
@ -539,7 +545,7 @@ class ExtruderManager(QObject):
|
|||
## Return the default extruder position from the machine manager
|
||||
@staticmethod
|
||||
def getDefaultExtruderPosition() -> str:
|
||||
return Application.getInstance().getMachineManager().defaultExtruderPosition
|
||||
return cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition
|
||||
|
||||
## Get all extruder values for a certain setting.
|
||||
#
|
||||
|
@ -564,7 +570,7 @@ class ExtruderManager(QObject):
|
|||
@staticmethod
|
||||
def getExtruderValue(extruder_index, key):
|
||||
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)
|
||||
|
||||
if extruder:
|
||||
|
@ -573,7 +579,7 @@ class ExtruderManager(QObject):
|
|||
value = value(extruder)
|
||||
else:
|
||||
# Just a value from global.
|
||||
value = Application.getInstance().getGlobalContainerStack().getProperty(key, "value")
|
||||
value = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().getProperty(key, "value")
|
||||
|
||||
return value
|
||||
|
||||
|
@ -602,7 +608,7 @@ class ExtruderManager(QObject):
|
|||
if isinstance(value, SettingFunction):
|
||||
value = value(extruder, context = context)
|
||||
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
|
||||
|
||||
|
@ -615,7 +621,7 @@ class ExtruderManager(QObject):
|
|||
# \return The effective value
|
||||
@staticmethod
|
||||
def getResolveOrValue(key):
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
resolved_value = global_stack.getProperty(key, "value")
|
||||
|
||||
return resolved_value
|
||||
|
@ -629,7 +635,7 @@ class ExtruderManager(QObject):
|
|||
# \return The effective value
|
||||
@staticmethod
|
||||
def getDefaultResolveOrValue(key):
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
context = PropertyEvaluationContext(global_stack)
|
||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||
context.context["override_operators"] = {
|
||||
|
@ -642,7 +648,7 @@ class ExtruderManager(QObject):
|
|||
|
||||
return resolved_value
|
||||
|
||||
__instance = None
|
||||
__instance = None # type: ExtruderManager
|
||||
|
||||
@classmethod
|
||||
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.
|
||||
|
||||
from typing import Any, TYPE_CHECKING, Optional
|
||||
from typing import Any, Dict, TYPE_CHECKING, Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Decorators import override
|
||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
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.Util import parseBool
|
||||
|
||||
import cura.CuraApplication
|
||||
|
||||
from . import Exceptions
|
||||
from .CuraContainerStack import CuraContainerStack, _ContainerIndexes
|
||||
from .ExtruderManager import ExtruderManager
|
||||
|
@ -25,7 +26,7 @@ if TYPE_CHECKING:
|
|||
#
|
||||
#
|
||||
class ExtruderStack(CuraContainerStack):
|
||||
def __init__(self, container_id: str):
|
||||
def __init__(self, container_id: str) -> None:
|
||||
super().__init__(container_id)
|
||||
|
||||
self.addMetaDataEntry("type", "extruder_train") # For backward compatibility
|
||||
|
@ -50,14 +51,14 @@ class ExtruderStack(CuraContainerStack):
|
|||
def getNextStack(self) -> Optional["GlobalStack"]:
|
||||
return super().getNextStack()
|
||||
|
||||
def setEnabled(self, enabled):
|
||||
def setEnabled(self, enabled: bool) -> None:
|
||||
if "enabled" not in self._metadata:
|
||||
self.addMetaDataEntry("enabled", "True")
|
||||
self.setMetaDataEntry("enabled", str(enabled))
|
||||
self.enabledChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify = enabledChanged)
|
||||
def isEnabled(self):
|
||||
def isEnabled(self) -> bool:
|
||||
return parseBool(self.getMetaDataEntry("enabled", "True"))
|
||||
|
||||
@classmethod
|
||||
|
@ -113,7 +114,7 @@ class ExtruderStack(CuraContainerStack):
|
|||
limit_to_extruder = super().getProperty(key, "limit_to_extruder", context)
|
||||
if limit_to_extruder is not None:
|
||||
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)
|
||||
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:
|
||||
|
@ -142,7 +143,7 @@ class ExtruderStack(CuraContainerStack):
|
|||
if stacks:
|
||||
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,
|
||||
# we do not always get properly informed that we should re-evaluate the setting. So make sure to indicate
|
||||
# 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.
|
||||
|
||||
from collections import defaultdict
|
||||
import threading
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from typing import Any, Dict, Optional, Set, TYPE_CHECKING
|
||||
from PyQt5.QtCore import pyqtProperty
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Decorators import override
|
||||
|
||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.SettingInstance import InstanceState
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import PropertyEvaluationContext
|
||||
from UM.Logger import Logger
|
||||
import cura.CuraApplication
|
||||
|
||||
from . import Exceptions
|
||||
from .CuraContainerStack import CuraContainerStack
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
|
||||
## Represents the Global or Machine stack and its related containers.
|
||||
#
|
||||
class GlobalStack(CuraContainerStack):
|
||||
def __init__(self, container_id: str):
|
||||
def __init__(self, container_id: str) -> None:
|
||||
super().__init__(container_id)
|
||||
|
||||
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
|
||||
# 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.
|
||||
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.
|
||||
#
|
||||
|
@ -94,6 +95,7 @@ class GlobalStack(CuraContainerStack):
|
|||
context.pushContainer(self)
|
||||
|
||||
# Handle the "resolve" property.
|
||||
#TODO: Why the hell does this involve threading?
|
||||
if self._shouldResolve(key, property_name, context):
|
||||
current_thread = threading.current_thread()
|
||||
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)
|
||||
if limit_to_extruder is not None:
|
||||
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)
|
||||
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):
|
||||
|
@ -155,7 +157,7 @@ class GlobalStack(CuraContainerStack):
|
|||
|
||||
## Perform some sanity checks on the global stack
|
||||
# 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()
|
||||
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.
|
||||
|
||||
import collections
|
||||
import time
|
||||
#Type hinting.
|
||||
from typing import List, Dict, TYPE_CHECKING, Optional
|
||||
from typing import Any, Callable, List, Dict, TYPE_CHECKING, Optional
|
||||
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
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 UM.FlameProfiler import pyqtSlot
|
||||
from UM import Util
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Message import Message
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
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.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
|
||||
|
@ -40,29 +41,31 @@ catalog = i18nCatalog("cura")
|
|||
if TYPE_CHECKING:
|
||||
from cura.Settings.CuraContainerStack import CuraContainerStack
|
||||
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):
|
||||
|
||||
def __init__(self, parent = None):
|
||||
def __init__(self, parent: QObject = None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self._active_container_stack = None # type: Optional[ExtruderManager]
|
||||
self._global_container_stack = None # type: Optional[GlobalStack]
|
||||
|
||||
self._current_root_material_id = {} # type: Dict[str, str]
|
||||
self._current_quality_group = None
|
||||
self._current_quality_changes_group = None
|
||||
self._current_quality_group = None # type: Optional[QualityGroup]
|
||||
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.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.setSingleShot(True)
|
||||
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.getContainerRegistry().containerLoadComplete.connect(self._onContainersChanged)
|
||||
|
||||
|
@ -74,14 +77,14 @@ class MachineManager(QObject):
|
|||
self.globalContainerChanged.connect(self.activeQualityChangesGroupChanged)
|
||||
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_definition_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0]
|
||||
self._empty_variant_container = ContainerRegistry.getInstance().findContainers(id = "empty_variant")[0]
|
||||
self._empty_material_container = ContainerRegistry.getInstance().findContainers(id = "empty_material")[0]
|
||||
self._empty_quality_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
|
||||
self._empty_quality_changes_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0]
|
||||
self._empty_container = CuraContainerRegistry.getInstance().getEmptyInstanceContainer() #type: InstanceContainer
|
||||
self._empty_definition_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0] #type: InstanceContainer
|
||||
self._empty_variant_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_variant")[0] #type: InstanceContainer
|
||||
self._empty_material_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_material")[0] #type: InstanceContainer
|
||||
self._empty_quality_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] #type: InstanceContainer
|
||||
self._empty_quality_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0] #type: InstanceContainer
|
||||
|
||||
self._onGlobalContainerChanged()
|
||||
|
||||
|
@ -99,8 +102,6 @@ class MachineManager(QObject):
|
|||
|
||||
self._application.getPreferences().addPreference("cura/active_machine", "")
|
||||
|
||||
self._global_event_keys = set()
|
||||
|
||||
self._printer_output_devices = [] # type: List[PrinterOutputDevice]
|
||||
self._application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
||||
# 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",
|
||||
"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:
|
||||
containers[0].nameChanged.connect(self._onMaterialNameChanged)
|
||||
|
||||
self._material_manager = self._application.getMaterialManager()
|
||||
self._variant_manager = self._application.getVariantManager()
|
||||
self._quality_manager = self._application.getQualityManager()
|
||||
self._material_manager = self._application.getMaterialManager() #type: MaterialManager
|
||||
self._variant_manager = self._application.getVariantManager() #type: VariantManager
|
||||
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
|
||||
# be reflected on the GUI. This signal emission makes sure that it happens.
|
||||
|
@ -164,7 +165,7 @@ class MachineManager(QObject):
|
|||
|
||||
def setInitialActiveMachine(self) -> None:
|
||||
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.
|
||||
self.setActiveMachine(active_machine_id)
|
||||
|
||||
|
@ -215,7 +216,10 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtProperty(int, constant=True)
|
||||
def totalNumberOfSettings(self) -> int:
|
||||
return len(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0].getAllKeys())
|
||||
general_definition_containers = CuraContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")
|
||||
if not general_definition_containers:
|
||||
return 0
|
||||
return len(general_definition_containers[0].getAllKeys())
|
||||
|
||||
def _onGlobalContainerChanged(self) -> None:
|
||||
if self._global_container_stack:
|
||||
|
@ -355,7 +359,7 @@ class MachineManager(QObject):
|
|||
def setActiveMachine(self, stack_id: str) -> None:
|
||||
self.blurSettings.emit() # Ensure no-one has focus.
|
||||
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
container_registry = CuraContainerRegistry.getInstance()
|
||||
|
||||
containers = container_registry.findContainerStacks(id = stack_id)
|
||||
if not containers:
|
||||
|
@ -381,7 +385,7 @@ class MachineManager(QObject):
|
|||
# \param metadata_filter \type{dict} list of metadata keys and values used for filtering
|
||||
@staticmethod
|
||||
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:
|
||||
if machine.definition.getId() == definition_id:
|
||||
return machine
|
||||
|
@ -625,11 +629,13 @@ class MachineManager(QObject):
|
|||
## Check if a container is read_only
|
||||
@pyqtSlot(str, result = 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.
|
||||
@pyqtSlot(str)
|
||||
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")
|
||||
extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
|
||||
|
||||
|
@ -641,6 +647,8 @@ class MachineManager(QObject):
|
|||
## Copy the value of all manually changed settings of the current extruder to all other extruders.
|
||||
@pyqtSlot()
|
||||
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())
|
||||
for extruder_stack in extruder_stacks:
|
||||
if extruder_stack != self._active_container_stack:
|
||||
|
@ -704,7 +712,7 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtSlot(str, str)
|
||||
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)
|
||||
if machine_stack:
|
||||
new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].definition.getName())
|
||||
|
@ -718,23 +726,23 @@ class MachineManager(QObject):
|
|||
|
||||
# activate a new machine before removing a machine because this is safer
|
||||
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]
|
||||
if other_machine_stacks:
|
||||
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
|
||||
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:
|
||||
ContainerRegistry.getInstance().removeContainer(container["id"])
|
||||
ContainerRegistry.getInstance().removeContainer(machine_id)
|
||||
CuraContainerRegistry.getInstance().removeContainer(container["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 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:
|
||||
# This reuses the method and remove all printers recursively
|
||||
self.removeMachine(hidden_containers[0].getId())
|
||||
|
@ -802,14 +810,17 @@ class MachineManager(QObject):
|
|||
|
||||
## Get the Definition ID of a machine (specified by ID)
|
||||
# \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)
|
||||
def getDefinitionByMachineId(self, machine_id: str) -> str:
|
||||
containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
||||
def getDefinitionByMachineId(self, machine_id: str) -> Optional[str]:
|
||||
containers = CuraContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
||||
if containers:
|
||||
return containers[0].definition.getId()
|
||||
return None
|
||||
|
||||
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")
|
||||
result = [] # type: List[str]
|
||||
for setting_instance in container.findInstances():
|
||||
|
@ -834,6 +845,8 @@ class MachineManager(QObject):
|
|||
|
||||
## Update extruder number to a valid value when the number of extruders are changed, or when an extruder is changed
|
||||
def correctExtruderSettings(self) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
for setting_key in self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.userChanges):
|
||||
self._global_container_stack.userChanges.removeInstance(setting_key)
|
||||
add_user_changes = self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.qualityChanges)
|
||||
|
@ -851,6 +864,8 @@ class MachineManager(QObject):
|
|||
## Set the amount of extruders on the active machine (global stack)
|
||||
# \param extruder_count int the number of extruders to set
|
||||
def setActiveMachineExtruderCount(self, extruder_count: int) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
extruder_manager = self._application.getExtruderManager()
|
||||
|
||||
definition_changes_container = self._global_container_stack.definitionChanges
|
||||
|
@ -869,7 +884,7 @@ class MachineManager(QObject):
|
|||
|
||||
# Check to see if any objects are set to print with an extruder that will no longer exist
|
||||
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():
|
||||
extruder_nr = node.callDecoration("getActiveExtruderPosition")
|
||||
|
||||
|
@ -884,7 +899,7 @@ class MachineManager(QObject):
|
|||
global_user_container = self._global_container_stack.userChanges
|
||||
|
||||
# Make sure extruder_stacks exists
|
||||
extruder_stacks = []
|
||||
extruder_stacks = [] #type: List[ExtruderStack]
|
||||
|
||||
if previous_extruder_count == 1:
|
||||
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
|
@ -912,6 +927,8 @@ class MachineManager(QObject):
|
|||
return extruder
|
||||
|
||||
def updateDefaultExtruder(self) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
extruder_items = sorted(self._global_container_stack.extruders.items())
|
||||
old_position = self._default_extruder_position
|
||||
new_default_position = "0"
|
||||
|
@ -924,6 +941,8 @@ class MachineManager(QObject):
|
|||
self.extruderChanged.emit()
|
||||
|
||||
def updateNumberExtrudersEnabled(self) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
definition_changes_container = self._global_container_stack.definitionChanges
|
||||
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
||||
extruder_count = 0
|
||||
|
@ -936,6 +955,8 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtProperty(int, notify = numberExtrudersEnabledChanged)
|
||||
def numberExtrudersEnabled(self) -> int:
|
||||
if self._global_container_stack is None:
|
||||
return 1
|
||||
return self._global_container_stack.definitionChanges.getProperty("extruders_enabled_count", "value")
|
||||
|
||||
@pyqtProperty(str, notify = extruderChanged)
|
||||
|
@ -945,6 +966,8 @@ class MachineManager(QObject):
|
|||
## This will fire the propertiesChanged for all settings so they will be updated in the front-end
|
||||
@pyqtSlot()
|
||||
def forceUpdateAllSettings(self) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
property_names = ["value", "resolve", "validationState"]
|
||||
for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
|
||||
|
@ -954,8 +977,9 @@ class MachineManager(QObject):
|
|||
@pyqtSlot(int, bool)
|
||||
def setExtruderEnabled(self, position: int, enabled: bool) -> None:
|
||||
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)
|
||||
return
|
||||
|
||||
extruder.setEnabled(enabled)
|
||||
self.updateDefaultExtruder()
|
||||
|
@ -991,6 +1015,8 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtSlot(str, str, str)
|
||||
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():
|
||||
container = extruder.userChanges
|
||||
container.setProperty(setting_name, property_name, property_value)
|
||||
|
@ -999,6 +1025,8 @@ class MachineManager(QObject):
|
|||
# \param setting_name The ID of the setting to reset.
|
||||
@pyqtSlot(str)
|
||||
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():
|
||||
container = extruder.userChanges
|
||||
container.removeInstance(setting_name)
|
||||
|
@ -1041,6 +1069,8 @@ class MachineManager(QObject):
|
|||
# for all stacks in the currently active machine.
|
||||
#
|
||||
def _setEmptyQuality(self) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
self._current_quality_group = None
|
||||
self._current_quality_changes_group = None
|
||||
self._global_container_stack.quality = self._empty_quality_container
|
||||
|
@ -1052,12 +1082,14 @@ class MachineManager(QObject):
|
|||
self.activeQualityGroupChanged.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:
|
||||
self._setEmptyQuality()
|
||||
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
|
||||
for node in quality_group.nodes_for_extruders.values():
|
||||
if node.getContainer() is None:
|
||||
|
@ -1081,14 +1113,15 @@ class MachineManager(QObject):
|
|||
self.activeQualityGroupChanged.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())
|
||||
containers = [n.getContainer() for n in nodes if n is not None]
|
||||
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"
|
||||
|
||||
def _setQualityChangesGroup(self, quality_changes_group):
|
||||
def _setQualityChangesGroup(self, quality_changes_group: QualityChangesGroup) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return #Can't change that.
|
||||
quality_type = quality_changes_group.quality_type
|
||||
|
@ -1132,21 +1165,25 @@ class MachineManager(QObject):
|
|||
self.activeQualityGroupChanged.emit()
|
||||
self.activeQualityChangesGroupChanged.emit()
|
||||
|
||||
def _setVariantNode(self, position, container_node):
|
||||
if container_node.getContainer() is None:
|
||||
def _setVariantNode(self, position: str, container_node: ContainerNode) -> None:
|
||||
if container_node.getContainer() is None or self._global_container_stack is None:
|
||||
return
|
||||
self._global_container_stack.extruders[position].variant = container_node.getContainer()
|
||||
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()
|
||||
if not self._global_container_stack.variant:
|
||||
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():
|
||||
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:
|
||||
self._global_container_stack.extruders[position].material = self._empty_material_container
|
||||
root_material_id = None
|
||||
|
@ -1155,18 +1192,19 @@ class MachineManager(QObject):
|
|||
self._current_root_material_id[position] = root_material_id
|
||||
self.rootMaterialChanged.emit()
|
||||
|
||||
def activeMaterialsCompatible(self):
|
||||
def activeMaterialsCompatible(self) -> bool:
|
||||
# check material - variant compatibility
|
||||
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
|
||||
for position, extruder in self._global_container_stack.extruders.items():
|
||||
if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"):
|
||||
return False
|
||||
if not extruder.material.getMetaDataEntry("compatible"):
|
||||
return False
|
||||
if self._global_container_stack is not None:
|
||||
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
|
||||
for position, extruder in self._global_container_stack.extruders.items():
|
||||
if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"):
|
||||
return False
|
||||
if not extruder.material.getMetaDataEntry("compatible"):
|
||||
return False
|
||||
return True
|
||||
|
||||
## 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:
|
||||
return
|
||||
Logger.log("i", "Updating quality/quality_changes due to material change")
|
||||
|
@ -1205,7 +1243,7 @@ class MachineManager(QObject):
|
|||
current_quality_type, quality_type)
|
||||
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:
|
||||
return
|
||||
if position is None:
|
||||
|
@ -1213,8 +1251,8 @@ class MachineManager(QObject):
|
|||
else:
|
||||
position_list = [position]
|
||||
|
||||
for position in position_list:
|
||||
extruder = self._global_container_stack.extruders[position]
|
||||
for position_item in position_list:
|
||||
extruder = self._global_container_stack.extruders[position_item]
|
||||
|
||||
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
|
||||
current_variant_name = None
|
||||
|
@ -1232,28 +1270,28 @@ class MachineManager(QObject):
|
|||
material_diameter)
|
||||
|
||||
if not candidate_materials:
|
||||
self._setMaterial(position, container_node = None)
|
||||
self._setMaterial(position_item, container_node = None)
|
||||
continue
|
||||
|
||||
if current_material_base_name in candidate_materials:
|
||||
new_material = candidate_materials[current_material_base_name]
|
||||
self._setMaterial(position, new_material)
|
||||
self._setMaterial(position_item, new_material)
|
||||
continue
|
||||
|
||||
# The current material is not available, find the preferred one
|
||||
material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, current_variant_name)
|
||||
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
|
||||
# instance with the same network key.
|
||||
@pyqtSlot(str)
|
||||
def switchPrinterType(self, machine_name: str) -> None:
|
||||
# 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
|
||||
# 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
|
||||
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
|
||||
|
@ -1275,6 +1313,8 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtSlot(QObject)
|
||||
def applyRemoteConfiguration(self, configuration: ConfigurationModel) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
self.blurSettings.emit()
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self.switchPrinterType(configuration.printerType)
|
||||
|
@ -1309,7 +1349,7 @@ class MachineManager(QObject):
|
|||
|
||||
## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value'
|
||||
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:
|
||||
if machine.getMetaDataEntry(key) == value:
|
||||
machine.setMetaDataEntry(key, new_value)
|
||||
|
@ -1322,18 +1362,18 @@ class MachineManager(QObject):
|
|||
# Check if the connect_group_name is correct. If not, update all the containers connected to the same printer
|
||||
if self.activeMachineNetworkGroupName != group_name:
|
||||
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:
|
||||
container.setMetaDataEntry("connect_group_name", group_name)
|
||||
|
||||
## This method checks if there is an instance connected to the given network_key
|
||||
def existNetworkInstances(self, network_key: str) -> bool:
|
||||
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)
|
||||
|
||||
@pyqtSlot("QVariant")
|
||||
def setGlobalVariant(self, container_node):
|
||||
def setGlobalVariant(self, container_node: ContainerNode) -> None:
|
||||
self.blurSettings.emit()
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self._setGlobalVariant(container_node)
|
||||
|
@ -1341,7 +1381,9 @@ class MachineManager(QObject):
|
|||
self._updateQualityWithMaterial()
|
||||
|
||||
@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
|
||||
position = str(position)
|
||||
extruder_stack = self._global_container_stack.extruders[position]
|
||||
|
@ -1364,12 +1406,14 @@ class MachineManager(QObject):
|
|||
|
||||
@pyqtSlot(str, str)
|
||||
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
|
||||
variant_node = self._variant_manager.getVariantNode(machine_definition_id, variant_name)
|
||||
self.setVariant(position, variant_node)
|
||||
|
||||
@pyqtSlot(str, "QVariant")
|
||||
def setVariant(self, position: str, container_node):
|
||||
def setVariant(self, position: str, container_node: ContainerNode) -> None:
|
||||
position = str(position)
|
||||
self.blurSettings.emit()
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
|
@ -1391,7 +1435,7 @@ class MachineManager(QObject):
|
|||
self.setQualityGroup(quality_group)
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def setQualityGroup(self, quality_group, no_dialog = False):
|
||||
def setQualityGroup(self, quality_group: QualityGroup, no_dialog: bool = False) -> None:
|
||||
self.blurSettings.emit()
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self._setQualityGroup(quality_group)
|
||||
|
@ -1401,11 +1445,11 @@ class MachineManager(QObject):
|
|||
self._application.discardOrKeepProfileChanges()
|
||||
|
||||
@pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged)
|
||||
def activeQualityGroup(self):
|
||||
def activeQualityGroup(self) -> Optional[QualityGroup]:
|
||||
return self._current_quality_group
|
||||
|
||||
@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()
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self._setQualityChangesGroup(quality_changes_group)
|
||||
|
@ -1415,18 +1459,20 @@ class MachineManager(QObject):
|
|||
self._application.discardOrKeepProfileChanges()
|
||||
|
||||
@pyqtSlot()
|
||||
def resetToUseDefaultQuality(self):
|
||||
def resetToUseDefaultQuality(self) -> None:
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self._setQualityGroup(self._current_quality_group)
|
||||
for stack in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
|
||||
stack.userChanges.clear()
|
||||
|
||||
@pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
|
||||
def activeQualityChangesGroup(self):
|
||||
def activeQualityChangesGroup(self) -> Optional[QualityChangesGroup]:
|
||||
return self._current_quality_changes_group
|
||||
|
||||
@pyqtProperty(str, notify = activeQualityGroupChanged)
|
||||
def activeQualityOrQualityChangesName(self):
|
||||
def activeQualityOrQualityChangesName(self) -> str:
|
||||
name = self._empty_quality_container.getName()
|
||||
if self._current_quality_changes_group:
|
||||
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 UM.Application import Application
|
||||
|
@ -9,7 +12,6 @@ from .CuraContainerStack import CuraContainerStack
|
|||
|
||||
|
||||
class PerObjectContainerStack(CuraContainerStack):
|
||||
|
||||
@override(CuraContainerStack)
|
||||
def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any:
|
||||
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.
|
||||
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)
|
||||
context.popContainer()
|
||||
return result
|
||||
|
@ -53,13 +55,13 @@ class PerObjectContainerStack(CuraContainerStack):
|
|||
return result
|
||||
|
||||
@override(CuraContainerStack)
|
||||
def setNextStack(self, stack: CuraContainerStack):
|
||||
def setNextStack(self, stack: CuraContainerStack) -> None:
|
||||
super().setNextStack(stack)
|
||||
|
||||
# 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
|
||||
if instance.state != InstanceState.Default:
|
||||
if self.getContainer(0).getProperty(key, "state") != InstanceState.Default:
|
||||
continue
|
||||
|
||||
self._collectPropertyChanges(key, "value")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
|
||||
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
|
||||
# actually do anything, as only the 'leaf' settings are used by the engine.
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.Interfaces import ContainerInterface
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Settings.SettingInstance import InstanceState
|
||||
|
||||
|
@ -157,7 +159,7 @@ class SettingInheritanceManager(QObject):
|
|||
stack = self._active_container_stack
|
||||
if not stack: #No active container stack yet!
|
||||
return False
|
||||
containers = []
|
||||
containers = [] # type: List[ContainerInterface]
|
||||
|
||||
## Check if the setting has a user state. If not, it is never overwritten.
|
||||
has_user_state = stack.getProperty(key, "state") == InstanceState.User
|
||||
|
|
|
@ -39,12 +39,12 @@ class SimpleModeSettingsManager(QObject):
|
|||
global_stack = self._machine_manager.activeMachine
|
||||
|
||||
# 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
|
||||
if global_stack.extruders:
|
||||
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)
|
||||
for skip_key in self.__ignored_custom_setting_keys:
|
||||
|
@ -70,12 +70,12 @@ class SimpleModeSettingsManager(QObject):
|
|||
global_stack = self._machine_manager.activeMachine
|
||||
|
||||
# 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
|
||||
if global_stack.extruders:
|
||||
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)
|
||||
has_quality_changes = len(quality_changes_keys) > 0
|
||||
|
|
|
@ -7,12 +7,12 @@ from typing import List, Optional
|
|||
|
||||
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
|
||||
|
||||
from UM.Qt.QtApplication import QtApplication #For typing.
|
||||
from UM.Logger import Logger
|
||||
|
||||
|
||||
class SingleInstance:
|
||||
|
||||
def __init__(self, application, files_to_open: Optional[List[str]]):
|
||||
def __init__(self, application: QtApplication, files_to_open: Optional[List[str]]) -> None:
|
||||
self._application = application
|
||||
self._files_to_open = files_to_open
|
||||
|
||||
|
@ -61,17 +61,22 @@ class SingleInstance:
|
|||
|
||||
def startServer(self) -> None:
|
||||
self._single_instance_server = QLocalServer()
|
||||
self._single_instance_server.newConnection.connect(self._onClientConnected)
|
||||
self._single_instance_server.listen("ultimaker-cura")
|
||||
if self._single_instance_server:
|
||||
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")
|
||||
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:
|
||||
connection.readyRead.connect(lambda c = connection: self.__readCommands(c))
|
||||
|
||||
def __readCommands(self, connection):
|
||||
def __readCommands(self, connection: QLocalSocket) -> None:
|
||||
line = connection.readLine()
|
||||
while len(line) != 0: # There is also a .canReadLine()
|
||||
try:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue