1# Copyright (c) 2018 Ultimaker B.V.
2# Cura is released under the terms of the LGPLv3 or higher.
3
4from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
5from typing import List
6
7MYPY = False
8if MYPY:
9    from cura.PrinterOutput.Models.ExtruderConfigurationModel import ExtruderConfigurationModel
10
11
12class PrinterConfigurationModel(QObject):
13
14    configurationChanged = pyqtSignal()
15
16    def __init__(self) -> None:
17        super().__init__()
18        self._printer_type = ""
19        self._extruder_configurations = []     # type: List[ExtruderConfigurationModel]
20        self._buildplate_configuration = ""
21
22    def setPrinterType(self, printer_type: str) -> None:
23        self._printer_type = printer_type
24
25    @pyqtProperty(str, fset = setPrinterType, notify = configurationChanged)
26    def printerType(self) -> str:
27        return self._printer_type
28
29    def setExtruderConfigurations(self, extruder_configurations: List["ExtruderConfigurationModel"]) -> None:
30        if self._extruder_configurations != extruder_configurations:
31            self._extruder_configurations = extruder_configurations
32
33            for extruder_configuration in self._extruder_configurations:
34                extruder_configuration.extruderConfigurationChanged.connect(self.configurationChanged)
35
36            self.configurationChanged.emit()
37
38    @pyqtProperty("QVariantList", fset = setExtruderConfigurations, notify = configurationChanged)
39    def extruderConfigurations(self):
40        return self._extruder_configurations
41
42    def setBuildplateConfiguration(self, buildplate_configuration: str) -> None:
43        if self._buildplate_configuration != buildplate_configuration:
44            self._buildplate_configuration = buildplate_configuration
45            self.configurationChanged.emit()
46
47    @pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
48    def buildplateConfiguration(self) -> str:
49        return self._buildplate_configuration
50
51    def isValid(self) -> bool:
52        """This method is intended to indicate whether the configuration is valid or not.
53
54        The method checks if the mandatory fields are or not set
55        """
56        if not self._extruder_configurations:
57            return False
58        for configuration in self._extruder_configurations:
59            if configuration is None:
60                return False
61        return self._printer_type != ""
62
63    def hasAnyMaterialLoaded(self) -> bool:
64        if not self.isValid():
65            return False
66        for configuration in self._extruder_configurations:
67            if configuration.activeMaterial and configuration.activeMaterial.type != "empty":
68                return True
69        return False
70
71    def __str__(self):
72        message_chunks = []
73        message_chunks.append("Printer type: " + self._printer_type)
74        message_chunks.append("Extruders: [")
75        for configuration in self._extruder_configurations:
76            message_chunks.append("   " + str(configuration))
77        message_chunks.append("]")
78        if self._buildplate_configuration is not None:
79            message_chunks.append("Buildplate: " + self._buildplate_configuration)
80
81        return "\n".join(message_chunks)
82
83    def __eq__(self, other):
84        if not isinstance(other, PrinterConfigurationModel):
85            return False
86
87        if self.printerType != other.printerType:
88            return False
89
90        if self.buildplateConfiguration != other.buildplateConfiguration:
91            return False
92
93        if len(self.extruderConfigurations) != len(other.extruderConfigurations):
94            return False
95
96        for self_extruder, other_extruder in zip(sorted(self._extruder_configurations, key=lambda x: x.position), sorted(other.extruderConfigurations, key=lambda x: x.position)):
97            if self_extruder != other_extruder:
98                return False
99
100        return True
101
102    def __hash__(self):
103        """The hash function is used to compare and create unique sets. The configuration is unique if the configuration
104
105        of the extruders is unique (the order of the extruders matters), and the type and buildplate is the same.
106        """
107        extruder_hash = hash(0)
108        first_extruder = None
109        for configuration in self._extruder_configurations:
110            extruder_hash ^= hash(configuration)
111            if configuration.position == 0:
112                first_extruder = configuration
113        # To ensure the correct order of the extruders, we add an "and" operation using the first extruder hash value
114        if first_extruder:
115            extruder_hash &= hash(first_extruder)
116
117        return hash(self._printer_type) ^ extruder_hash ^ hash(self._buildplate_configuration)
118