mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-10 23:35:07 -06:00
CURA-5048 solved merge conflict
This commit is contained in:
commit
1d4b2eac4b
48 changed files with 1665 additions and 325 deletions
|
@ -60,18 +60,20 @@ from cura.Machines.Models.BuildPlateModel import BuildPlateModel
|
|||
from cura.Machines.Models.NozzleModel import NozzleModel
|
||||
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
||||
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
|
||||
|
||||
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
||||
|
||||
from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
|
||||
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
|
||||
from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
|
||||
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
||||
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
|
||||
from cura.Machines.Models.MachineManagementModel import MachineManagementModel
|
||||
|
||||
from cura.Machines.MachineErrorChecker import MachineErrorChecker
|
||||
|
||||
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
|
||||
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
|
||||
|
||||
from cura.Machines.VariantManager import VariantManager
|
||||
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
||||
|
||||
from . import PlatformPhysics
|
||||
from . import BuildVolume
|
||||
|
@ -88,7 +90,6 @@ from cura.Settings.ExtruderManager import ExtruderManager
|
|||
from cura.Settings.UserChangesModel import UserChangesModel
|
||||
from cura.Settings.ExtrudersModel import ExtrudersModel
|
||||
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
||||
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
|
||||
from cura.Settings.ContainerManager import ContainerManager
|
||||
|
||||
from cura.ObjectsModel import ObjectsModel
|
||||
|
@ -142,12 +143,6 @@ class CuraApplication(QtApplication):
|
|||
|
||||
Q_ENUMS(ResourceTypes)
|
||||
|
||||
# FIXME: This signal belongs to the MachineManager, but the CuraEngineBackend plugin requires on it.
|
||||
# Because plugins are initialized before the ContainerRegistry, putting this signal in MachineManager
|
||||
# will make it initialized before ContainerRegistry does, and it won't find the active machine, thus
|
||||
# Cura will always show the Add Machine Dialog upon start.
|
||||
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# this list of dir names will be used by UM to detect an old cura directory
|
||||
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
|
||||
|
@ -224,12 +219,14 @@ class CuraApplication(QtApplication):
|
|||
self._machine_manager = None # This is initialized on demand.
|
||||
self._extruder_manager = None
|
||||
self._material_manager = None
|
||||
self._quality_manager = None
|
||||
self._object_manager = None
|
||||
self._build_plate_model = None
|
||||
self._multi_build_plate_model = None
|
||||
self._setting_inheritance_manager = None
|
||||
self._simple_mode_settings_manager = None
|
||||
self._cura_scene_controller = None
|
||||
self._machine_error_checker = None
|
||||
|
||||
self._additional_components = {} # Components to add to certain areas in the interface
|
||||
|
||||
|
@ -743,19 +740,28 @@ class CuraApplication(QtApplication):
|
|||
self.preRun()
|
||||
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
|
||||
Logger.log("i", "Initializing variant manager")
|
||||
self._variant_manager = VariantManager(container_registry)
|
||||
self._variant_manager.initialize()
|
||||
|
||||
Logger.log("i", "Initializing material manager")
|
||||
from cura.Machines.MaterialManager import MaterialManager
|
||||
self._material_manager = MaterialManager(container_registry, parent = self)
|
||||
self._material_manager.initialize()
|
||||
|
||||
Logger.log("i", "Initializing quality manager")
|
||||
from cura.Machines.QualityManager import QualityManager
|
||||
self._quality_manager = QualityManager(container_registry, parent = self)
|
||||
self._quality_manager.initialize()
|
||||
|
||||
Logger.log("i", "Initializing machine manager")
|
||||
self._machine_manager = MachineManager(self)
|
||||
|
||||
Logger.log("i", "Initializing machine error checker")
|
||||
self._machine_error_checker = MachineErrorChecker(self)
|
||||
self._machine_error_checker.initialize()
|
||||
|
||||
# Check if we should run as single instance or not
|
||||
self._setUpSingleInstanceServer()
|
||||
|
||||
|
@ -781,8 +787,11 @@ class CuraApplication(QtApplication):
|
|||
self._openFile(file_name)
|
||||
|
||||
self.started = True
|
||||
self.initializationFinished.emit()
|
||||
self.exec_()
|
||||
|
||||
initializationFinished = pyqtSignal()
|
||||
|
||||
## Run Cura without GUI elements and interaction (server mode).
|
||||
def runWithoutGUI(self):
|
||||
self._use_gui = False
|
||||
|
@ -847,6 +856,9 @@ class CuraApplication(QtApplication):
|
|||
def hasGui(self):
|
||||
return self._use_gui
|
||||
|
||||
def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
|
||||
return self._machine_error_checker
|
||||
|
||||
def getMachineManager(self, *args) -> MachineManager:
|
||||
if self._machine_manager is None:
|
||||
self._machine_manager = MachineManager(self)
|
||||
|
@ -961,6 +973,7 @@ class CuraApplication(QtApplication):
|
|||
qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
|
||||
qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
|
||||
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
|
||||
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
|
||||
|
||||
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
|
||||
|
|
181
cura/Machines/MachineErrorChecker.py
Normal file
181
cura/Machines/MachineErrorChecker.py
Normal file
|
@ -0,0 +1,181 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import time
|
||||
|
||||
from collections import deque
|
||||
|
||||
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.SettingDefinition import SettingDefinition
|
||||
from UM.Settings.Validator import ValidatorState
|
||||
|
||||
|
||||
#
|
||||
# This class performs setting error checks for the currently active machine.
|
||||
#
|
||||
# The whole error checking process is pretty heavy which can take ~0.5 secs, so it can cause GUI to lag.
|
||||
# The idea here is to split the whole error check into small tasks, each of which only checks a single setting key
|
||||
# in a stack. According to my profiling results, the maximal runtime for such a sub-task is <0.03 secs, which should
|
||||
# be good enough. Moreover, if any changes happened to the machine, we can cancel the check in progress without wait
|
||||
# for it to finish the complete work.
|
||||
#
|
||||
class MachineErrorChecker(QObject):
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._global_stack = None
|
||||
|
||||
self._has_errors = True # Result of the error check, indicating whether there are errors in the stack
|
||||
self._error_keys = set() # A set of settings keys that have errors
|
||||
self._error_keys_in_progress = set() # The variable that stores the results of the currently in progress check
|
||||
|
||||
self._stacks_and_keys_to_check = None # a FIFO queue of tuples (stack, key) to check for errors
|
||||
|
||||
self._need_to_check = False # Whether we need to schedule a new check or not. This flag is set when a new
|
||||
# error check needs to take place while there is already one running at the moment.
|
||||
self._check_in_progress = False # Whether there is an error check running in progress at the moment.
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
|
||||
self._start_time = 0 # measure checking time
|
||||
|
||||
# This timer delays the starting of error check so we can react less frequently if the user is frequently
|
||||
# changing settings.
|
||||
self._error_check_timer = QTimer(self)
|
||||
self._error_check_timer.setInterval(100)
|
||||
self._error_check_timer.setSingleShot(True)
|
||||
|
||||
def initialize(self):
|
||||
self._error_check_timer.timeout.connect(self._rescheduleCheck)
|
||||
|
||||
# Reconnect all signals when the active machine gets changed.
|
||||
self._machine_manager.globalContainerChanged.connect(self._onMachineChanged)
|
||||
|
||||
# Whenever the machine settings get changed, we schedule an error check.
|
||||
self._machine_manager.globalContainerChanged.connect(self.startErrorCheck)
|
||||
self._machine_manager.globalValueChanged.connect(self.startErrorCheck)
|
||||
|
||||
self._onMachineChanged()
|
||||
|
||||
def _onMachineChanged(self):
|
||||
if self._global_stack:
|
||||
self._global_stack.propertyChanged.disconnect(self.startErrorCheck)
|
||||
self._global_stack.containersChanged.disconnect(self.startErrorCheck)
|
||||
|
||||
for extruder in self._global_stack.extruders.values():
|
||||
extruder.propertyChanged.disconnect(self.startErrorCheck)
|
||||
extruder.containersChanged.disconnect(self.startErrorCheck)
|
||||
|
||||
self._global_stack = self._machine_manager.activeMachine
|
||||
|
||||
if self._global_stack:
|
||||
self._global_stack.propertyChanged.connect(self.startErrorCheck)
|
||||
self._global_stack.containersChanged.connect(self.startErrorCheck)
|
||||
|
||||
for extruder in self._global_stack.extruders.values():
|
||||
extruder.propertyChanged.connect(self.startErrorCheck)
|
||||
extruder.containersChanged.connect(self.startErrorCheck)
|
||||
|
||||
hasErrorUpdated = pyqtSignal()
|
||||
needToWaitForResultChanged = pyqtSignal()
|
||||
errorCheckFinished = pyqtSignal()
|
||||
|
||||
@pyqtProperty(bool, notify = hasErrorUpdated)
|
||||
def hasError(self) -> bool:
|
||||
return self._has_errors
|
||||
|
||||
@pyqtProperty(bool, notify = needToWaitForResultChanged)
|
||||
def needToWaitForResult(self) -> bool:
|
||||
return self._need_to_check or self._check_in_progress
|
||||
|
||||
# Starts the error check timer to schedule a new error check.
|
||||
def startErrorCheck(self, *args):
|
||||
if not self._check_in_progress:
|
||||
self._need_to_check = True
|
||||
self.needToWaitForResultChanged.emit()
|
||||
self._error_check_timer.start()
|
||||
|
||||
# This function is called by the timer to reschedule a new error check.
|
||||
# If there is no check in progress, it will start a new one. If there is any, it sets the "_need_to_check" flag
|
||||
# to notify the current check to stop and start a new one.
|
||||
def _rescheduleCheck(self):
|
||||
if self._check_in_progress and not self._need_to_check:
|
||||
self._need_to_check = True
|
||||
self.needToWaitForResultChanged.emit()
|
||||
return
|
||||
|
||||
self._error_keys_in_progress = set()
|
||||
self._need_to_check = False
|
||||
self.needToWaitForResultChanged.emit()
|
||||
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
if global_stack is None:
|
||||
Logger.log("i", "No active machine, nothing to check.")
|
||||
return
|
||||
|
||||
# Populate the (stack, key) tuples to check
|
||||
self._stacks_and_keys_to_check = deque()
|
||||
for stack in [global_stack] + list(global_stack.extruders.values()):
|
||||
for key in stack.getAllKeys():
|
||||
self._stacks_and_keys_to_check.append((stack, key))
|
||||
|
||||
self._application.callLater(self._checkStack)
|
||||
self._start_time = time.time()
|
||||
Logger.log("d", "New error check scheduled.")
|
||||
|
||||
def _checkStack(self):
|
||||
if self._need_to_check:
|
||||
Logger.log("d", "Need to check for errors again. Discard the current progress and reschedule a check.")
|
||||
self._check_in_progress = False
|
||||
self._application.callLater(self.startErrorCheck)
|
||||
return
|
||||
|
||||
self._check_in_progress = True
|
||||
|
||||
# If there is nothing to check any more, it means there is no error.
|
||||
if not self._stacks_and_keys_to_check:
|
||||
# Finish
|
||||
self._setResult(False)
|
||||
return
|
||||
|
||||
# Get the next stack and key to check
|
||||
stack, key = self._stacks_and_keys_to_check.popleft()
|
||||
|
||||
enabled = stack.getProperty(key, "enabled")
|
||||
if not enabled:
|
||||
self._application.callLater(self._checkStack)
|
||||
return
|
||||
|
||||
validation_state = stack.getProperty(key, "validationState")
|
||||
if validation_state is None:
|
||||
# Setting is not validated. This can happen if there is only a setting definition.
|
||||
# We do need to validate it, because a setting definitions value can be set by a function, which could
|
||||
# be an invalid setting.
|
||||
definition = stack.getSettingDefinition(key)
|
||||
validator_type = SettingDefinition.getValidatorForType(definition.type)
|
||||
if validator_type:
|
||||
validator = validator_type(key)
|
||||
validation_state = validator(stack)
|
||||
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
|
||||
# Finish
|
||||
self._setResult(True)
|
||||
return
|
||||
|
||||
# Schedule the check for the next key
|
||||
self._application.callLater(self._checkStack)
|
||||
|
||||
def _setResult(self, result: bool):
|
||||
if result != self._has_errors:
|
||||
self._has_errors = result
|
||||
self.hasErrorUpdated.emit()
|
||||
self._machine_manager.stacksValidationChanged.emit()
|
||||
self._need_to_check = False
|
||||
self._check_in_progress = False
|
||||
self.needToWaitForResultChanged.emit()
|
||||
self.errorCheckFinished.emit()
|
||||
Logger.log("i", "Error check finished, result = %s, time = %0.1fs", result, time.time() - self._start_time)
|
|
@ -16,10 +16,11 @@ from cura.Machines.MaterialNode import MaterialNode #For type checking.
|
|||
# so "generic_abs_ultimaker3", "generic_abs_ultimaker3_AA_0.4", etc.
|
||||
#
|
||||
class MaterialGroup:
|
||||
__slots__ = ("name", "root_material_node", "derived_material_node_list")
|
||||
__slots__ = ("name", "is_read_only", "root_material_node", "derived_material_node_list")
|
||||
|
||||
def __init__(self, name: str, root_material_node: MaterialNode):
|
||||
self.name = name
|
||||
self.is_read_only = False
|
||||
self.root_material_node = root_material_node
|
||||
self.derived_material_node_list = [] #type: List[MaterialNode]
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ class MaterialManager(QObject):
|
|||
root_material_id = material_metadata.get("base_file")
|
||||
if root_material_id not in self._material_group_map:
|
||||
self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
|
||||
self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)
|
||||
group = self._material_group_map[root_material_id]
|
||||
|
||||
#Store this material in the group of the appropriate root material.
|
||||
|
@ -331,6 +332,35 @@ class MaterialManager(QObject):
|
|||
|
||||
return material_node
|
||||
|
||||
#
|
||||
# Gets MaterialNode for the given extruder and machine with the given material type.
|
||||
# Returns None if:
|
||||
# 1. the given machine doesn't have materials;
|
||||
# 2. cannot find any material InstanceContainers with the given settings.
|
||||
#
|
||||
def getMaterialNodeByType(self, global_stack: "GlobalStack", extruder_variant_name: str, material_guid: str) -> Optional["MaterialNode"]:
|
||||
node = None
|
||||
machine_definition = global_stack.definition
|
||||
if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
|
||||
material_diameter = machine_definition.getProperty("material_diameter", "value")
|
||||
if isinstance(material_diameter, SettingFunction):
|
||||
material_diameter = material_diameter(global_stack)
|
||||
|
||||
# Look at the guid to material dictionary
|
||||
root_material_id = None
|
||||
for material_group in self._guid_material_groups_map[material_guid]:
|
||||
if material_group.is_read_only:
|
||||
root_material_id = material_group.root_material_node.metadata["id"]
|
||||
break
|
||||
|
||||
if not root_material_id:
|
||||
Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)
|
||||
return None
|
||||
|
||||
node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
|
||||
material_diameter, root_material_id)
|
||||
return node
|
||||
|
||||
#
|
||||
# Used by QualityManager. Built-in quality profiles may be based on generic material IDs such as "generic_pla".
|
||||
# For materials such as ultimaker_pla_orange, no quality profiles may be found, so we should fall back to use
|
||||
|
|
|
@ -53,8 +53,8 @@ class BrandMaterialsModel(ListModel):
|
|||
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
||||
self._material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
|
||||
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||
self._material_manager.materialsUpdated.connect(self._update)
|
||||
self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
|
||||
self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
|
||||
self._update()
|
||||
|
||||
def setExtruderPosition(self, position: int):
|
||||
|
|
|
@ -15,8 +15,8 @@ class GenericMaterialsModel(BaseMaterialsModel):
|
|||
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
||||
self._material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||
|
||||
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||
self._material_manager.materialsUpdated.connect(self._update)
|
||||
self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
|
||||
self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
|
|
79
cura/Machines/Models/MachineManagementModel.py
Normal file
79
cura/Machines/Models/MachineManagementModel.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt, pyqtSignal
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
#
|
||||
# This the QML model for the quality management page.
|
||||
#
|
||||
class MachineManagementModel(ListModel):
|
||||
NameRole = Qt.UserRole + 1
|
||||
IdRole = Qt.UserRole + 2
|
||||
MetaDataRole = Qt.UserRole + 3
|
||||
GroupRole = Qt.UserRole + 4
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.IdRole, "id")
|
||||
self.addRoleName(self.MetaDataRole, "metadata")
|
||||
self.addRoleName(self.GroupRole, "group")
|
||||
self._local_container_stacks = []
|
||||
self._network_container_stacks = []
|
||||
|
||||
# Listen to changes
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
|
||||
ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
|
||||
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
|
||||
self._filter_dict = {}
|
||||
self._update()
|
||||
|
||||
## Handler for container added/removed events from registry
|
||||
def _onContainerChanged(self, container):
|
||||
# We only need to update when the added / removed container is a stack.
|
||||
if isinstance(container, ContainerStack):
|
||||
self._update()
|
||||
|
||||
## Private convenience function to reset & repopulate the model.
|
||||
def _update(self):
|
||||
items = []
|
||||
|
||||
# Get first the network enabled printers
|
||||
network_filter_printers = {"type": "machine", "um_network_key": "*", "hidden": "False"}
|
||||
self._network_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**network_filter_printers)
|
||||
self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("connect_group_name"))
|
||||
|
||||
for container in self._network_container_stacks:
|
||||
metadata = container.getMetaData().copy()
|
||||
if container.getBottom():
|
||||
metadata["definition_name"] = container.getBottom().getName()
|
||||
|
||||
items.append({"name": metadata["connect_group_name"],
|
||||
"id": container.getId(),
|
||||
"metadata": metadata,
|
||||
"group": catalog.i18nc("@info:title", "Network enabled printers")})
|
||||
|
||||
# Get now the local printes
|
||||
local_filter_printers = {"type": "machine", "um_network_key": None}
|
||||
self._local_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**local_filter_printers)
|
||||
self._local_container_stacks.sort(key = lambda i: i.getName())
|
||||
|
||||
for container in self._local_container_stacks:
|
||||
metadata = container.getMetaData().copy()
|
||||
if container.getBottom():
|
||||
metadata["definition_name"] = container.getBottom().getName()
|
||||
|
||||
items.append({"name": container.getName(),
|
||||
"id": container.getId(),
|
||||
"metadata": metadata,
|
||||
"group": catalog.i18nc("@info:title", "Local printers")})
|
||||
|
||||
self.setItems(items)
|
|
@ -25,7 +25,7 @@ ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
|
|||
|
||||
|
||||
#
|
||||
# VariantManager is THE place to look for a specific variant. It maintains a variant lookup table with the following
|
||||
# VariantManager is THE place to look for a specific variant. It maintains two variant lookup tables with the following
|
||||
# structure:
|
||||
#
|
||||
# [machine_definition_id] -> [variant_type] -> [variant_name] -> ContainerNode(metadata / container)
|
||||
|
@ -35,6 +35,9 @@ ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
|
|||
# -> "BB 0.8"
|
||||
# -> ...
|
||||
#
|
||||
# [machine_definition_id] -> [machine_buildplate_type] -> ContainerNode(metadata / container)
|
||||
# Example: "ultimaker3" -> "glass" (this is different from the variant name) -> ContainerNode
|
||||
#
|
||||
# Note that the "container" field is not loaded in the beginning because it would defeat the purpose of lazy-loading.
|
||||
# A container is loaded when getVariant() is called to load a variant InstanceContainer.
|
||||
#
|
||||
|
@ -44,6 +47,7 @@ class VariantManager:
|
|||
self._container_registry = container_registry # type: ContainerRegistry
|
||||
|
||||
self._machine_to_variant_dict_map = dict() # <machine_type> -> <variant_dict>
|
||||
self._machine_to_buildplate_dict_map = dict()
|
||||
|
||||
self._exclude_variant_id_list = ["empty_variant"]
|
||||
|
||||
|
@ -53,6 +57,7 @@ class VariantManager:
|
|||
#
|
||||
def initialize(self):
|
||||
self._machine_to_variant_dict_map = OrderedDict()
|
||||
self._machine_to_buildplate_dict_map = OrderedDict()
|
||||
|
||||
# Cache all variants from the container registry to a variant map for better searching and organization.
|
||||
variant_metadata_list = self._container_registry.findContainersMetadata(type = "variant")
|
||||
|
@ -78,6 +83,22 @@ class VariantManager:
|
|||
|
||||
variant_dict[variant_name] = ContainerNode(metadata = variant_metadata)
|
||||
|
||||
# If the variant is a buildplate then fill also the buildplate map
|
||||
if variant_type == VariantType.BUILD_PLATE:
|
||||
if variant_definition not in self._machine_to_buildplate_dict_map:
|
||||
self._machine_to_buildplate_dict_map[variant_definition] = OrderedDict()
|
||||
|
||||
variant_container = self._container_registry.findContainers(type = "variant", id = variant_metadata["id"])
|
||||
if not variant_container:
|
||||
# ERROR: not variant container. This should never happen
|
||||
raise RuntimeError("Not variant found [%s], type [%s] for machine [%s]" %
|
||||
(variant_name, variant_type, variant_definition))
|
||||
buildplate_type = variant_container[0].getProperty("machine_buildplate_type", "value")
|
||||
if buildplate_type not in self._machine_to_buildplate_dict_map[variant_definition]:
|
||||
self._machine_to_variant_dict_map[variant_definition][buildplate_type] = dict()
|
||||
|
||||
self._machine_to_buildplate_dict_map[variant_definition][buildplate_type] = variant_dict[variant_name]
|
||||
|
||||
#
|
||||
# Gets the variant InstanceContainer with the given information.
|
||||
# Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
|
||||
|
@ -117,3 +138,8 @@ class VariantManager:
|
|||
if preferred_variant_name:
|
||||
node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
|
||||
return node
|
||||
|
||||
def getBuildplateVariantNode(self, machine_definition_id: str, buildplate_type: str) -> Optional["ContainerNode"]:
|
||||
if machine_definition_id in self._machine_to_buildplate_dict_map:
|
||||
return self._machine_to_buildplate_dict_map[machine_definition_id].get(buildplate_type)
|
||||
return None
|
||||
|
|
|
@ -40,6 +40,8 @@ class PlatformPhysics:
|
|||
Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
|
||||
|
||||
def _onSceneChanged(self, source):
|
||||
if not source.getMeshData():
|
||||
return
|
||||
self._change_timer.start()
|
||||
|
||||
def _onChangeTimerFinished(self):
|
||||
|
|
81
cura/PrinterOutput/ConfigurationModel.py
Normal file
81
cura/PrinterOutput/ConfigurationModel.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
||||
from typing import List
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
|
||||
|
||||
class ConfigurationModel(QObject):
|
||||
|
||||
configurationChanged = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._printer_type = None
|
||||
self._extruder_configurations = [] # type: List[ExtruderConfigurationModel]
|
||||
self._buildplate_configuration = None
|
||||
|
||||
def setPrinterType(self, printer_type):
|
||||
self._printer_type = printer_type
|
||||
|
||||
@pyqtProperty(str, fset = setPrinterType, notify = configurationChanged)
|
||||
def printerType(self):
|
||||
return self._printer_type
|
||||
|
||||
def setExtruderConfigurations(self, extruder_configurations):
|
||||
self._extruder_configurations = extruder_configurations
|
||||
|
||||
@pyqtProperty("QVariantList", fset = setExtruderConfigurations, notify = configurationChanged)
|
||||
def extruderConfigurations(self):
|
||||
return self._extruder_configurations
|
||||
|
||||
def setBuildplateConfiguration(self, buildplate_configuration):
|
||||
self._buildplate_configuration = buildplate_configuration
|
||||
|
||||
@pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
|
||||
def buildplateConfiguration(self):
|
||||
return self._buildplate_configuration
|
||||
|
||||
## This method is intended to indicate whether the configuration is valid or not.
|
||||
# The method checks if the mandatory fields are or not set
|
||||
def isValid(self):
|
||||
if not self._extruder_configurations:
|
||||
return False
|
||||
for configuration in self._extruder_configurations:
|
||||
if configuration is None:
|
||||
return False
|
||||
return self._printer_type is not None
|
||||
|
||||
def __str__(self):
|
||||
message_chunks = []
|
||||
message_chunks.append("Printer type: " + self._printer_type)
|
||||
message_chunks.append("Extruders: [")
|
||||
for configuration in self._extruder_configurations:
|
||||
message_chunks.append(" " + str(configuration))
|
||||
message_chunks.append("]")
|
||||
if self._buildplate_configuration is not None:
|
||||
message_chunks.append("Buildplate: " + self._buildplate_configuration)
|
||||
|
||||
return "\n".join(message_chunks)
|
||||
|
||||
def __eq__(self, other):
|
||||
return hash(self) == hash(other)
|
||||
|
||||
## The hash function is used to compare and create unique sets. The configuration is unique if the configuration
|
||||
# of the extruders is unique (the order of the extruders matters), and the type and buildplate is the same.
|
||||
def __hash__(self):
|
||||
extruder_hash = hash(0)
|
||||
first_extruder = None
|
||||
for configuration in self._extruder_configurations:
|
||||
extruder_hash ^= hash(configuration)
|
||||
if configuration.position == 0:
|
||||
first_extruder = configuration
|
||||
# To ensure the correct order of the extruders, we add an "and" operation using the first extruder hash value
|
||||
if first_extruder:
|
||||
extruder_hash &= hash(first_extruder)
|
||||
|
||||
return hash(self._printer_type) ^ extruder_hash ^ hash(self._buildplate_configuration)
|
59
cura/PrinterOutput/ExtruderConfigurationModel.py
Normal file
59
cura/PrinterOutput/ExtruderConfigurationModel.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
||||
|
||||
|
||||
class ExtruderConfigurationModel(QObject):
|
||||
|
||||
extruderConfigurationChanged = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._position = -1
|
||||
self._material = None
|
||||
self._hotend_id = None
|
||||
|
||||
def setPosition(self, position):
|
||||
self._position = position
|
||||
|
||||
@pyqtProperty(int, fset = setPosition, notify = extruderConfigurationChanged)
|
||||
def position(self):
|
||||
return self._position
|
||||
|
||||
def setMaterial(self, material):
|
||||
self._material = material
|
||||
|
||||
@pyqtProperty(QObject, fset = setMaterial, notify = extruderConfigurationChanged)
|
||||
def material(self):
|
||||
return self._material
|
||||
|
||||
def setHotendID(self, hotend_id):
|
||||
self._hotend_id = hotend_id
|
||||
|
||||
@pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)
|
||||
def hotendID(self):
|
||||
return self._hotend_id
|
||||
|
||||
## This method is intended to indicate whether the configuration is valid or not.
|
||||
# The method checks if the mandatory fields are or not set
|
||||
# At this moment is always valid since we allow to have empty material and variants.
|
||||
def isValid(self):
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
message_chunks = []
|
||||
message_chunks.append("Position: " + str(self._position))
|
||||
message_chunks.append("-")
|
||||
message_chunks.append("Material: " + self.material.type if self.material else "empty")
|
||||
message_chunks.append("-")
|
||||
message_chunks.append("HotendID: " + self.hotendID if self.hotendID else "empty")
|
||||
return " ".join(message_chunks)
|
||||
|
||||
def __eq__(self, other):
|
||||
return hash(self) == hash(other)
|
||||
|
||||
# Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
|
||||
# unique within a set
|
||||
def __hash__(self):
|
||||
return hash(self._position) ^ (hash(self._material.guid) if self._material is not None else hash(0)) ^ hash(self._hotend_id)
|
|
@ -1,8 +1,8 @@
|
|||
# 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, pyqtSlot
|
||||
from UM.Logger import Logger
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
||||
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
@ -17,14 +17,18 @@ class ExtruderOutputModel(QObject):
|
|||
targetHotendTemperatureChanged = pyqtSignal()
|
||||
hotendTemperatureChanged = pyqtSignal()
|
||||
activeMaterialChanged = pyqtSignal()
|
||||
extruderConfigurationChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, printer: "PrinterOutputModel", parent=None):
|
||||
def __init__(self, printer: "PrinterOutputModel", position, parent=None):
|
||||
super().__init__(parent)
|
||||
self._printer = printer
|
||||
self._position = position
|
||||
self._target_hotend_temperature = 0
|
||||
self._hotend_temperature = 0
|
||||
self._hotend_id = ""
|
||||
self._active_material = None # type: Optional[MaterialOutputModel]
|
||||
self._extruder_configuration = ExtruderConfigurationModel()
|
||||
self._extruder_configuration.position = self._position
|
||||
|
||||
@pyqtProperty(QObject, notify = activeMaterialChanged)
|
||||
def activeMaterial(self) -> "MaterialOutputModel":
|
||||
|
@ -33,7 +37,9 @@ class ExtruderOutputModel(QObject):
|
|||
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
|
||||
if self._active_material != material:
|
||||
self._active_material = material
|
||||
self._extruder_configuration.material = self._active_material
|
||||
self.activeMaterialChanged.emit()
|
||||
self.extruderConfigurationChanged.emit()
|
||||
|
||||
## Update the hotend temperature. This only changes it locally.
|
||||
def updateHotendTemperature(self, temperature: float):
|
||||
|
@ -56,7 +62,7 @@ class ExtruderOutputModel(QObject):
|
|||
def targetHotendTemperature(self) -> float:
|
||||
return self._target_hotend_temperature
|
||||
|
||||
@pyqtProperty(float, notify=hotendTemperatureChanged)
|
||||
@pyqtProperty(float, notify = hotendTemperatureChanged)
|
||||
def hotendTemperature(self) -> float:
|
||||
return self._hotend_temperature
|
||||
|
||||
|
@ -67,4 +73,12 @@ class ExtruderOutputModel(QObject):
|
|||
def updateHotendID(self, id: str):
|
||||
if self._hotend_id != id:
|
||||
self._hotend_id = id
|
||||
self._extruder_configuration.hotendID = self._hotend_id
|
||||
self.hotendIDChanged.emit()
|
||||
self.extruderConfigurationChanged.emit()
|
||||
|
||||
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
|
||||
def extruderConfiguration(self):
|
||||
if self._extruder_configuration.isValid():
|
||||
return self._extruder_configuration
|
||||
return None
|
|
@ -55,6 +55,14 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
|
||||
|
||||
printer_type = self._properties.get(b"machine", b"").decode("utf-8")
|
||||
if printer_type.startswith("9511"):
|
||||
self._printer_type = "ultimaker3_extended"
|
||||
elif printer_type.startswith("9066"):
|
||||
self._printer_type = "ultimaker3"
|
||||
else:
|
||||
self._printer_type = "unknown"
|
||||
|
||||
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs) -> None:
|
||||
raise NotImplementedError("requestWrite needs to be implemented")
|
||||
|
||||
|
@ -301,6 +309,10 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
def firmwareVersion(self) -> str:
|
||||
return self._properties.get(b"firmware_version", b"").decode("utf-8")
|
||||
|
||||
@pyqtProperty(str, constant=True)
|
||||
def printerType(self) -> str:
|
||||
return self._printer_type
|
||||
|
||||
## IPadress of this printer
|
||||
@pyqtProperty(str, constant=True)
|
||||
def ipAddress(self) -> str:
|
||||
|
|
|
@ -6,7 +6,7 @@ from UM.Logger import Logger
|
|||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.ExtruderOuputModel import ExtruderOuputModel
|
||||
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
|
||||
|
||||
|
@ -18,7 +18,7 @@ class PrinterOutputController:
|
|||
self.can_control_manually = True
|
||||
self._output_device = output_device
|
||||
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOuputModel", temperature: int):
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: int):
|
||||
Logger.log("w", "Set target hotend temperature not implemented in controller")
|
||||
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
# 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, pyqtSlot
|
||||
from UM.Logger import Logger
|
||||
from typing import Optional, List
|
||||
from typing import Optional
|
||||
from UM.Math.Vector import Vector
|
||||
from cura.PrinterOutput.ExtruderOuputModel import ExtruderOutputModel
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
|
@ -22,8 +22,10 @@ class PrinterOutputModel(QObject):
|
|||
nameChanged = pyqtSignal()
|
||||
headPositionChanged = pyqtSignal()
|
||||
keyChanged = pyqtSignal()
|
||||
typeChanged = pyqtSignal()
|
||||
printerTypeChanged = pyqtSignal()
|
||||
buildplateChanged = pyqtSignal()
|
||||
cameraChanged = pyqtSignal()
|
||||
configurationChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = ""):
|
||||
super().__init__(parent)
|
||||
|
@ -32,13 +34,18 @@ class PrinterOutputModel(QObject):
|
|||
self._name = ""
|
||||
self._key = "" # Unique identifier
|
||||
self._controller = output_controller
|
||||
self._extruders = [ExtruderOutputModel(printer=self) for i in range(number_of_extruders)]
|
||||
self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
|
||||
self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
|
||||
self._head_position = Vector(0, 0, 0)
|
||||
self._active_print_job = None # type: Optional[PrintJobOutputModel]
|
||||
self._firmware_version = firmware_version
|
||||
self._printer_state = "unknown"
|
||||
self._is_preheating = False
|
||||
self._type = ""
|
||||
self._printer_type = ""
|
||||
self._buildplate_name = None
|
||||
# Update the printer configuration every time any of the extruders changes its configuration
|
||||
for extruder in self._extruders:
|
||||
extruder.extruderConfigurationChanged.connect(self._updateExtruderConfiguration)
|
||||
|
||||
self._camera = None
|
||||
|
||||
|
@ -64,14 +71,27 @@ class PrinterOutputModel(QObject):
|
|||
def camera(self):
|
||||
return self._camera
|
||||
|
||||
@pyqtProperty(str, notify = typeChanged)
|
||||
@pyqtProperty(str, notify = printerTypeChanged)
|
||||
def type(self):
|
||||
return self._type
|
||||
return self._printer_type
|
||||
|
||||
def updateType(self, type):
|
||||
if self._type != type:
|
||||
self._type = type
|
||||
self.typeChanged.emit()
|
||||
def updateType(self, printer_type):
|
||||
if self._printer_type != printer_type:
|
||||
self._printer_type = printer_type
|
||||
self._printer_configuration.printerType = self._printer_type
|
||||
self.printerTypeChanged.emit()
|
||||
self.configurationChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify = buildplateChanged)
|
||||
def buildplate(self):
|
||||
return self._buildplate_name
|
||||
|
||||
def updateBuildplateName(self, buildplate_name):
|
||||
if self._buildplate_name != buildplate_name:
|
||||
self._buildplate_name = buildplate_name
|
||||
self._printer_configuration.buildplateConfiguration = self._buildplate_name
|
||||
self.buildplateChanged.emit()
|
||||
self.configurationChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify=keyChanged)
|
||||
def key(self):
|
||||
|
@ -238,3 +258,14 @@ class PrinterOutputModel(QObject):
|
|||
if self._controller:
|
||||
return self._controller.can_control_manually
|
||||
return False
|
||||
|
||||
# Returns the configuration (material, variant and buildplate) of the current printer
|
||||
@pyqtProperty(QObject, notify = configurationChanged)
|
||||
def printerConfiguration(self):
|
||||
if self._printer_configuration.isValid():
|
||||
return self._printer_configuration
|
||||
return None
|
||||
|
||||
def _updateExtruderConfiguration(self):
|
||||
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders]
|
||||
self.configurationChanged.emit()
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.OutputDevice.OutputDevice import OutputDevice
|
||||
from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal
|
||||
from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal, QVariant
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Signal import signalemitter
|
||||
from UM.Application import Application
|
||||
|
@ -17,6 +16,7 @@ from typing import List, Optional
|
|||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
@ -44,10 +44,14 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
# Signal to indicate that the info text about the connection has changed.
|
||||
connectionTextChanged = pyqtSignal()
|
||||
|
||||
# Signal to indicate that the configuration of one of the printers has changed.
|
||||
uniqueConfigurationsChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, device_id, parent = 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
|
||||
|
@ -69,6 +73,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
self._address = ""
|
||||
self._connection_text = ""
|
||||
self.printersChanged.connect(self._onPrintersChanged)
|
||||
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations)
|
||||
|
||||
@pyqtProperty(str, notify = connectionTextChanged)
|
||||
def address(self):
|
||||
|
@ -175,6 +181,23 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
self.acceptsCommandsChanged.emit()
|
||||
|
||||
# Returns the unique configurations of the printers within this output device
|
||||
@pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged)
|
||||
def uniqueConfigurations(self):
|
||||
return self._unique_configurations
|
||||
|
||||
def _updateUniqueConfigurations(self):
|
||||
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):
|
||||
for printer in self._printers:
|
||||
printer.configurationChanged.connect(self._updateUniqueConfigurations)
|
||||
|
||||
# At this point there may be non-updated configurations
|
||||
self._updateUniqueConfigurations()
|
||||
|
||||
|
||||
## The current processing state of the backend.
|
||||
class ConnectionState(IntEnum):
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import collections
|
||||
import time
|
||||
#Type hinting.
|
||||
from typing import Union, List, Dict, TYPE_CHECKING, Optional
|
||||
from typing import List, Dict, TYPE_CHECKING, Optional
|
||||
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Signal import Signal
|
||||
|
@ -20,13 +20,15 @@ from UM.Logger import Logger
|
|||
from UM.Message import Message
|
||||
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Signal import postponeSignals, CompressTechnique
|
||||
|
||||
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||
|
||||
from cura.Machines.VariantManager import VariantType
|
||||
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.ExtruderManager import ExtruderManager
|
||||
|
||||
from .CuraStackBuilder import CuraStackBuilder
|
||||
|
@ -48,7 +50,6 @@ class MachineManager(QObject):
|
|||
self._global_container_stack = None # type: GlobalStack
|
||||
|
||||
self._current_root_material_id = {}
|
||||
self._current_root_material_name = {}
|
||||
self._current_quality_group = None
|
||||
self._current_quality_changes_group = None
|
||||
|
||||
|
@ -56,11 +57,6 @@ class MachineManager(QObject):
|
|||
|
||||
self.machine_extruder_material_update_dict = collections.defaultdict(list)
|
||||
|
||||
self._error_check_timer = QTimer()
|
||||
self._error_check_timer.setInterval(250)
|
||||
self._error_check_timer.setSingleShot(True)
|
||||
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
|
||||
|
||||
self._instance_container_timer = QTimer()
|
||||
self._instance_container_timer.setInterval(250)
|
||||
self._instance_container_timer.setSingleShot(True)
|
||||
|
@ -109,6 +105,12 @@ class MachineManager(QObject):
|
|||
# There might already be some output devices by the time the signal is connected
|
||||
self._onOutputDevicesChanged()
|
||||
|
||||
self._current_printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
|
||||
self.activeMaterialChanged.connect(self._onCurrentConfigurationChanged)
|
||||
self.activeVariantChanged.connect(self._onCurrentConfigurationChanged)
|
||||
# Force to compute the current configuration
|
||||
self._onCurrentConfigurationChanged()
|
||||
|
||||
self._application.callLater(self.setInitialActiveMachine)
|
||||
|
||||
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
|
||||
|
@ -119,12 +121,16 @@ class MachineManager(QObject):
|
|||
if containers:
|
||||
containers[0].nameChanged.connect(self._onMaterialNameChanged)
|
||||
|
||||
self._material_manager = self._application._material_manager
|
||||
self._material_manager = self._application.getMaterialManager()
|
||||
self._variant_manager = self._application.getVariantManager()
|
||||
self._quality_manager = self._application.getQualityManager()
|
||||
|
||||
# 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.
|
||||
self._material_manager.materialsUpdated.connect(self.rootMaterialChanged)
|
||||
# When the materials get updated, it can be that an activated material's diameter gets changed. In that case,
|
||||
# a material update should be triggered to make sure that the machine still has compatible materials activated.
|
||||
self._material_manager.materialsUpdated.connect(self._updateUponMaterialMetadataChange)
|
||||
self.rootMaterialChanged.connect(self._onRootMaterialChanged)
|
||||
|
||||
activeQualityGroupChanged = pyqtSignal()
|
||||
|
@ -145,6 +151,7 @@ class MachineManager(QObject):
|
|||
blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
|
||||
|
||||
outputDevicesChanged = pyqtSignal()
|
||||
currentConfigurationChanged = pyqtSignal() # Emitted every time the current configurations of the machine changes
|
||||
|
||||
rootMaterialChanged = pyqtSignal()
|
||||
|
||||
|
@ -164,6 +171,39 @@ class MachineManager(QObject):
|
|||
|
||||
self.outputDevicesChanged.emit()
|
||||
|
||||
@pyqtProperty(QObject, notify = currentConfigurationChanged)
|
||||
def currentConfiguration(self):
|
||||
return self._current_printer_configuration
|
||||
|
||||
def _onCurrentConfigurationChanged(self) -> None:
|
||||
if not self._global_container_stack:
|
||||
return
|
||||
|
||||
# Create the configuration model with the current data in Cura
|
||||
self._current_printer_configuration.printerType = self._global_container_stack.definition.getName()
|
||||
self._current_printer_configuration.extruderConfigurations = []
|
||||
for extruder in self._global_container_stack.extruders.values():
|
||||
extruder_configuration = ExtruderConfigurationModel()
|
||||
# For compare just the GUID is needed at this moment
|
||||
mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != self._empty_material_container else None
|
||||
mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != self._empty_material_container else None
|
||||
mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != self._empty_material_container else None
|
||||
mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != self._empty_material_container else None
|
||||
mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != self._empty_material_container else None
|
||||
material_model = MaterialOutputModel(mat_guid, mat_type, mat_color, mat_brand, mat_name)
|
||||
|
||||
extruder_configuration.position = int(extruder.getMetaDataEntry("position"))
|
||||
extruder_configuration.material = material_model
|
||||
extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != self._empty_variant_container else None
|
||||
self._current_printer_configuration.extruderConfigurations.append(extruder_configuration)
|
||||
|
||||
self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != self._empty_variant_container else None
|
||||
self.currentConfigurationChanged.emit()
|
||||
|
||||
@pyqtSlot(QObject, result = bool)
|
||||
def matchesConfiguration(self, configuration: ConfigurationModel) -> bool:
|
||||
return self._current_printer_configuration == configuration
|
||||
|
||||
@pyqtProperty("QVariantList", notify = outputDevicesChanged)
|
||||
def printerOutputDevices(self):
|
||||
return self._printer_output_devices
|
||||
|
@ -228,15 +268,6 @@ class MachineManager(QObject):
|
|||
del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
|
||||
|
||||
self.activeQualityGroupChanged.emit()
|
||||
self._error_check_timer.start()
|
||||
|
||||
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
|
||||
def _updateStacksHaveErrors(self) -> None:
|
||||
old_stacks_have_errors = self._stacks_have_errors
|
||||
self._stacks_have_errors = self._checkStacksHaveErrors()
|
||||
if old_stacks_have_errors != self._stacks_have_errors:
|
||||
self.stacksValidationChanged.emit()
|
||||
Application.getInstance().stacksValidationFinished.emit()
|
||||
|
||||
def _onActiveExtruderStackChanged(self) -> None:
|
||||
self.blurSettings.emit() # Ensure no-one has focus.
|
||||
|
@ -256,8 +287,6 @@ class MachineManager(QObject):
|
|||
|
||||
self.rootMaterialChanged.emit()
|
||||
|
||||
self._error_check_timer.start()
|
||||
|
||||
def _onInstanceContainersChanged(self, container) -> None:
|
||||
self._instance_container_timer.start()
|
||||
|
||||
|
@ -266,9 +295,6 @@ class MachineManager(QObject):
|
|||
# Notify UI items, such as the "changed" star in profile pull down menu.
|
||||
self.activeStackValueChanged.emit()
|
||||
|
||||
elif property_name == "validationState":
|
||||
self._error_check_timer.start()
|
||||
|
||||
## Given a global_stack, make sure that it's all valid by searching for this quality group and applying it again
|
||||
def _initMachineState(self, global_stack):
|
||||
material_dict = {}
|
||||
|
@ -312,9 +338,13 @@ class MachineManager(QObject):
|
|||
|
||||
self.__emitChangedSignals()
|
||||
|
||||
## Given a definition id, return the machine with this id.
|
||||
# Optional: add a list of keys and values to filter the list of machines with the given definition id
|
||||
# \param definition_id \type{str} definition id that needs to look for
|
||||
# \param metadata_filter \type{dict} list of metadata keys and values used for filtering
|
||||
@staticmethod
|
||||
def getMachine(definition_id: str) -> Optional["GlobalStack"]:
|
||||
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
|
||||
def getMachine(definition_id: str, metadata_filter: Dict[str, str] = None) -> Optional["GlobalStack"]:
|
||||
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||
for machine in machines:
|
||||
if machine.definition.getId() == definition_id:
|
||||
return machine
|
||||
|
@ -419,6 +449,12 @@ class MachineManager(QObject):
|
|||
def stacksHaveErrors(self) -> bool:
|
||||
return bool(self._stacks_have_errors)
|
||||
|
||||
@pyqtProperty(str, notify = globalContainerChanged)
|
||||
def activeMachineDefinitionName(self) -> str:
|
||||
if self._global_container_stack:
|
||||
return self._global_container_stack.definition.getName()
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify = globalContainerChanged)
|
||||
def activeMachineName(self) -> str:
|
||||
if self._global_container_stack:
|
||||
|
@ -431,6 +467,18 @@ class MachineManager(QObject):
|
|||
return self._global_container_stack.getId()
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify = globalContainerChanged)
|
||||
def activeMachineNetworkKey(self) -> str:
|
||||
if self._global_container_stack:
|
||||
return self._global_container_stack.getMetaDataEntry("um_network_key")
|
||||
return ""
|
||||
|
||||
@pyqtProperty(str, notify = globalContainerChanged)
|
||||
def activeMachineNetworkGroupName(self) -> str:
|
||||
if self._global_container_stack:
|
||||
return self._global_container_stack.getMetaDataEntry("connect_group_name")
|
||||
return ""
|
||||
|
||||
@pyqtProperty(QObject, notify = globalContainerChanged)
|
||||
def activeMachine(self) -> Optional["GlobalStack"]:
|
||||
return self._global_container_stack
|
||||
|
@ -833,10 +881,11 @@ class MachineManager(QObject):
|
|||
## This will fire the propertiesChanged for all settings so they will be updated in the front-end
|
||||
@pyqtSlot()
|
||||
def forceUpdateAllSettings(self):
|
||||
property_names = ["value", "resolve"]
|
||||
for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
|
||||
for setting_key in container.getAllKeys():
|
||||
container.propertiesChanged.emit(setting_key, property_names)
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
property_names = ["value", "resolve"]
|
||||
for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
|
||||
for setting_key in container.getAllKeys():
|
||||
container.propertiesChanged.emit(setting_key, property_names)
|
||||
|
||||
@pyqtSlot(int, bool)
|
||||
def setExtruderEnabled(self, position: int, enabled) -> None:
|
||||
|
@ -881,28 +930,18 @@ class MachineManager(QObject):
|
|||
return []
|
||||
return sorted(list(self._global_container_stack.extruders.keys()))
|
||||
|
||||
## Update _current_root_material_id and _current_root_material_name when
|
||||
# the current root material was changed.
|
||||
## Update _current_root_material_id when the current root material was changed.
|
||||
def _onRootMaterialChanged(self):
|
||||
self._current_root_material_id = {}
|
||||
|
||||
if self._global_container_stack:
|
||||
for position in self._global_container_stack.extruders:
|
||||
self._current_root_material_id[position] = self._global_container_stack.extruders[position].material.getMetaDataEntry("base_file")
|
||||
self._current_root_material_name = {}
|
||||
for position in self._global_container_stack.extruders:
|
||||
if position not in self._current_root_material_name:
|
||||
material = self._global_container_stack.extruders[position].material
|
||||
self._current_root_material_name[position] = material.getName()
|
||||
|
||||
@pyqtProperty("QVariant", notify = rootMaterialChanged)
|
||||
def currentRootMaterialId(self):
|
||||
return self._current_root_material_id
|
||||
|
||||
@pyqtProperty("QVariant", notify = rootMaterialChanged)
|
||||
def currentRootMaterialName(self):
|
||||
return self._current_root_material_name
|
||||
|
||||
## Return the variant names in the extruder stack(s).
|
||||
## For the variant in the global stack, use activeVariantBuildplateName
|
||||
@pyqtProperty("QVariant", notify = activeVariantChanged)
|
||||
|
@ -999,15 +1038,12 @@ class MachineManager(QObject):
|
|||
if container_node:
|
||||
self._global_container_stack.extruders[position].material = container_node.getContainer()
|
||||
root_material_id = container_node.metadata["base_file"]
|
||||
root_material_name = container_node.getContainer().getName()
|
||||
else:
|
||||
self._global_container_stack.extruders[position].material = self._empty_material_container
|
||||
root_material_id = None
|
||||
root_material_name = None
|
||||
# The _current_root_material_id is used in the MaterialMenu to see which material is selected
|
||||
if root_material_id != self._current_root_material_id[position]:
|
||||
self._current_root_material_id[position] = root_material_id
|
||||
self._current_root_material_name[position] = root_material_name
|
||||
self.rootMaterialChanged.emit()
|
||||
|
||||
def activeMaterialsCompatible(self):
|
||||
|
@ -1021,7 +1057,7 @@ class MachineManager(QObject):
|
|||
return True
|
||||
|
||||
## Update current quality type and machine after setting material
|
||||
def _updateQualityWithMaterial(self):
|
||||
def _updateQualityWithMaterial(self, *args):
|
||||
Logger.log("i", "Updating quality/quality_changes due to material change")
|
||||
current_quality_type = None
|
||||
if self._current_quality_group:
|
||||
|
@ -1066,9 +1102,15 @@ class MachineManager(QObject):
|
|||
extruder = self._global_container_stack.extruders[position]
|
||||
|
||||
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
|
||||
current_variant_name = extruder.variant.getMetaDataEntry("name")
|
||||
current_variant_name = None
|
||||
if extruder.variant.getId() != self._empty_variant_container.getId():
|
||||
current_variant_name = extruder.variant.getMetaDataEntry("name")
|
||||
|
||||
material_diameter = self._global_container_stack.getProperty("material_diameter", "value")
|
||||
from UM.Settings.Interfaces import PropertyEvaluationContext
|
||||
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
||||
context = PropertyEvaluationContext(extruder)
|
||||
context.context["evaluate_from_container_index"] = _ContainerIndexes.DefinitionChanges
|
||||
material_diameter = self._global_container_stack.getProperty("material_diameter", "value", context)
|
||||
candidate_materials = self._material_manager.getAvailableMaterials(
|
||||
self._global_container_stack.definition.getId(),
|
||||
current_variant_name,
|
||||
|
@ -1083,6 +1125,74 @@ class MachineManager(QObject):
|
|||
self._setMaterial(position, 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)
|
||||
|
||||
## 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):
|
||||
# Don't switch if the user tries to change to the same type of printer
|
||||
if 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()
|
||||
# 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
|
||||
if not new_machine:
|
||||
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
|
||||
new_machine.addMetaDataEntry("um_network_key", self.activeMachineNetworkKey)
|
||||
new_machine.addMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName)
|
||||
new_machine.addMetaDataEntry("hidden", False)
|
||||
else:
|
||||
Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey)
|
||||
new_machine.setMetaDataEntry("hidden", False)
|
||||
|
||||
# Set the current printer instance to hidden (the metadata entry must exist)
|
||||
self._global_container_stack.setMetaDataEntry("hidden", True)
|
||||
|
||||
self.setActiveMachine(new_machine.getId())
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def applyRemoteConfiguration(self, configuration: ConfigurationModel):
|
||||
self.blurSettings.emit()
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self.switchPrinterType(configuration.printerType)
|
||||
for extruder_configuration in configuration.extruderConfigurations:
|
||||
position = str(extruder_configuration.position)
|
||||
variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(), extruder_configuration.hotendID)
|
||||
material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack, extruder_configuration.hotendID,extruder_configuration.material.guid)
|
||||
if variant_container_node:
|
||||
self._setVariantNode(position, variant_container_node)
|
||||
else:
|
||||
self._global_container_stack.extruders[position].variant = self._empty_variant_container
|
||||
|
||||
if material_container_node:
|
||||
self._setMaterial(position, material_container_node)
|
||||
else:
|
||||
self._global_container_stack.extruders[position].material = self._empty_material_container
|
||||
self._updateMaterialWithVariant(position)
|
||||
|
||||
if configuration.buildplateConfiguration is not None:
|
||||
global_variant_container_node = self._variant_manager.getBuildplateVariantNode(self._global_container_stack.definition.getId(), configuration.buildplateConfiguration)
|
||||
if global_variant_container_node:
|
||||
self._setGlobalVariant(global_variant_container_node)
|
||||
else:
|
||||
self._global_container_stack.variant = self._empty_variant_container
|
||||
else:
|
||||
self._global_container_stack.variant = self._empty_variant_container
|
||||
self._updateQualityWithMaterial()
|
||||
|
||||
## 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):
|
||||
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
|
||||
for machine in machines:
|
||||
if machine.getMetaDataEntry(key) == value:
|
||||
machine.setMetaDataEntry(key, new_value)
|
||||
|
||||
@pyqtSlot("QVariant")
|
||||
def setGlobalVariant(self, container_node):
|
||||
self.blurSettings.emit()
|
||||
|
@ -1144,3 +1254,8 @@ class MachineManager(QObject):
|
|||
elif self._current_quality_group:
|
||||
name = self._current_quality_group.name
|
||||
return name
|
||||
|
||||
def _updateUponMaterialMetadataChange(self):
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
self._updateMaterialWithVariant(None)
|
||||
self._updateQualityWithMaterial()
|
||||
|
|
|
@ -66,7 +66,7 @@ class Snapshot:
|
|||
size = max(bbox.width, bbox.height, bbox.depth * 0.5)
|
||||
|
||||
# Looking from this direction (x, y, z) in OGL coordinates
|
||||
looking_from_offset = Vector(1, 1, 2)
|
||||
looking_from_offset = Vector(1, 1, -2)
|
||||
if size > 0:
|
||||
# determine the watch distance depending on the size
|
||||
looking_from_offset = looking_from_offset * size * 1.3
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue