Merge branch 'master' into refactor_singleton_settingsbase
1
.gitignore
vendored
|
@ -26,6 +26,7 @@ LC_MESSAGES
|
|||
*.lprof
|
||||
*~
|
||||
*.qm
|
||||
.directory
|
||||
.idea
|
||||
cura.desktop
|
||||
|
||||
|
|
13
Jenkinsfile
vendored
|
@ -12,6 +12,19 @@ parallel_nodes(['linux && cura', 'windows && cura']) {
|
|||
|
||||
// If any error occurs during building, we want to catch it and continue with the "finale" stage.
|
||||
catchError {
|
||||
stage('Pre Checks') {
|
||||
if (isUnix()) {
|
||||
try {
|
||||
sh """
|
||||
echo 'Check for duplicate shortcut keys in all translation files.'
|
||||
${env.CURA_ENVIRONMENT_PATH}/master/bin/python3 scripts/check_shortcut_keys.py
|
||||
"""
|
||||
} catch(e) {
|
||||
currentBuild.result = "UNSTABLE"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Building and testing should happen in a subdirectory.
|
||||
dir('build') {
|
||||
// Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
|
||||
|
|
|
@ -528,7 +528,7 @@ class BuildVolume(SceneNode):
|
|||
def _onStackChanged(self):
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||
extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
||||
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
for extruder in extruders:
|
||||
extruder.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||
|
||||
|
@ -536,7 +536,7 @@ class BuildVolume(SceneNode):
|
|||
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged)
|
||||
extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
||||
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
for extruder in extruders:
|
||||
extruder.propertyChanged.connect(self._onSettingPropertyChanged)
|
||||
|
||||
|
|
|
@ -17,7 +17,13 @@ class CameraImageProvider(QQuickImageProvider):
|
|||
if image.isNull():
|
||||
image = QImage()
|
||||
|
||||
return image, QSize(15, 15)
|
||||
except AttributeError:
|
||||
try:
|
||||
image = output_device.activeCamera.getImage()
|
||||
|
||||
return image, QSize(15, 15)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return QImage(), QSize(15, 15)
|
||||
|
|
|
@ -93,6 +93,7 @@ from . import CuraActions
|
|||
from cura.Scene import ZOffsetDecorator
|
||||
from . import CuraSplashScreen
|
||||
from . import CameraImageProvider
|
||||
from . import PrintJobPreviewImageProvider
|
||||
from . import MachineActionManager
|
||||
|
||||
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
|
||||
|
@ -113,6 +114,9 @@ from UM.FlameProfiler import pyqtSlot
|
|||
|
||||
if TYPE_CHECKING:
|
||||
from plugins.SliceInfoPlugin.SliceInfo import SliceInfo
|
||||
from cura.Machines.MaterialManager import MaterialManager
|
||||
from cura.Machines.QualityManager import QualityManager
|
||||
from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer
|
||||
|
||||
|
||||
numpy.seterr(all = "ignore")
|
||||
|
@ -174,12 +178,12 @@ class CuraApplication(QtApplication):
|
|||
|
||||
self._machine_action_manager = None
|
||||
|
||||
self.empty_container = None
|
||||
self.empty_definition_changes_container = None
|
||||
self.empty_variant_container = None
|
||||
self.empty_material_container = None
|
||||
self.empty_quality_container = None
|
||||
self.empty_quality_changes_container = None
|
||||
self.empty_container = None # type: EmptyInstanceContainer
|
||||
self.empty_definition_changes_container = None # type: EmptyInstanceContainer
|
||||
self.empty_variant_container = None # type: EmptyInstanceContainer
|
||||
self.empty_material_container = None # type: EmptyInstanceContainer
|
||||
self.empty_quality_container = None # type: EmptyInstanceContainer
|
||||
self.empty_quality_changes_container = None # type: EmptyInstanceContainer
|
||||
|
||||
self._variant_manager = None
|
||||
self._material_manager = None
|
||||
|
@ -367,7 +371,7 @@ class CuraApplication(QtApplication):
|
|||
# Add empty variant, material and quality containers.
|
||||
# Since they are empty, they should never be serialized and instead just programmatically created.
|
||||
# We need them to simplify the switching between materials.
|
||||
self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container
|
||||
self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container # type: EmptyInstanceContainer
|
||||
|
||||
self._container_registry.addContainer(
|
||||
cura.Settings.cura_empty_instance_containers.empty_definition_changes_container)
|
||||
|
@ -428,6 +432,7 @@ class CuraApplication(QtApplication):
|
|||
# Readers & Writers:
|
||||
"GCodeWriter",
|
||||
"STLReader",
|
||||
"3MFWriter",
|
||||
|
||||
# Tools:
|
||||
"CameraTool",
|
||||
|
@ -502,6 +507,7 @@ class CuraApplication(QtApplication):
|
|||
|
||||
def _onEngineCreated(self):
|
||||
self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||
self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
|
||||
|
||||
@pyqtProperty(bool)
|
||||
def needToShowUserAgreement(self):
|
||||
|
@ -804,20 +810,20 @@ class CuraApplication(QtApplication):
|
|||
self._machine_manager = MachineManager(self)
|
||||
return self._machine_manager
|
||||
|
||||
def getExtruderManager(self, *args):
|
||||
def getExtruderManager(self, *args) -> ExtruderManager:
|
||||
if self._extruder_manager is None:
|
||||
self._extruder_manager = ExtruderManager()
|
||||
return self._extruder_manager
|
||||
|
||||
def getVariantManager(self, *args):
|
||||
def getVariantManager(self, *args) -> VariantManager:
|
||||
return self._variant_manager
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getMaterialManager(self, *args):
|
||||
def getMaterialManager(self, *args) -> "MaterialManager":
|
||||
return self._material_manager
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getQualityManager(self, *args):
|
||||
def getQualityManager(self, *args) -> "QualityManager":
|
||||
return self._quality_manager
|
||||
|
||||
def getObjectsModel(self, *args):
|
||||
|
@ -826,23 +832,23 @@ class CuraApplication(QtApplication):
|
|||
return self._object_manager
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getMultiBuildPlateModel(self, *args):
|
||||
def getMultiBuildPlateModel(self, *args) -> MultiBuildPlateModel:
|
||||
if self._multi_build_plate_model is None:
|
||||
self._multi_build_plate_model = MultiBuildPlateModel(self)
|
||||
return self._multi_build_plate_model
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getBuildPlateModel(self, *args):
|
||||
def getBuildPlateModel(self, *args) -> BuildPlateModel:
|
||||
if self._build_plate_model is None:
|
||||
self._build_plate_model = BuildPlateModel(self)
|
||||
return self._build_plate_model
|
||||
|
||||
def getCuraSceneController(self, *args):
|
||||
def getCuraSceneController(self, *args) -> CuraSceneController:
|
||||
if self._cura_scene_controller is None:
|
||||
self._cura_scene_controller = CuraSceneController.createCuraSceneController()
|
||||
return self._cura_scene_controller
|
||||
|
||||
def getSettingInheritanceManager(self, *args):
|
||||
def getSettingInheritanceManager(self, *args) -> SettingInheritanceManager:
|
||||
if self._setting_inheritance_manager is None:
|
||||
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
|
||||
return self._setting_inheritance_manager
|
||||
|
|
|
@ -24,29 +24,34 @@ if TYPE_CHECKING:
|
|||
# This is used in Variant, Material, and Quality Managers.
|
||||
#
|
||||
class ContainerNode:
|
||||
__slots__ = ("metadata", "container", "children_map")
|
||||
__slots__ = ("_metadata", "container", "children_map")
|
||||
|
||||
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||
self.metadata = metadata
|
||||
self._metadata = metadata
|
||||
self.container = None
|
||||
self.children_map = OrderedDict() #type: OrderedDict[str, Union[QualityGroup, ContainerNode]]
|
||||
self.children_map = OrderedDict() # type: ignore # This is because it's children are supposed to override it.
|
||||
|
||||
## Get an entry value from the metadata
|
||||
def getMetaDataEntry(self, entry: str, default: Any = None) -> Any:
|
||||
if self.metadata is None:
|
||||
if self._metadata is None:
|
||||
return default
|
||||
return self.metadata.get(entry, default)
|
||||
return self._metadata.get(entry, default)
|
||||
|
||||
def getMetadata(self) -> Dict[str, Any]:
|
||||
if self._metadata is None:
|
||||
return {}
|
||||
return self._metadata
|
||||
|
||||
def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
|
||||
return self.children_map.get(child_key)
|
||||
|
||||
def getContainer(self) -> Optional["InstanceContainer"]:
|
||||
if self.metadata is None:
|
||||
if self._metadata is None:
|
||||
Logger.log("e", "Cannot get container for a ContainerNode without metadata.")
|
||||
return None
|
||||
|
||||
if self.container is None:
|
||||
container_id = self.metadata["id"]
|
||||
container_id = self._metadata["id"]
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
|
||||
if not container_list:
|
||||
|
|
|
@ -50,7 +50,7 @@ class MachineErrorChecker(QObject):
|
|||
self._error_check_timer.setInterval(100)
|
||||
self._error_check_timer.setSingleShot(True)
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
self._error_check_timer.timeout.connect(self._rescheduleCheck)
|
||||
|
||||
# Reconnect all signals when the active machine gets changed.
|
||||
|
@ -62,7 +62,7 @@ class MachineErrorChecker(QObject):
|
|||
|
||||
self._onMachineChanged()
|
||||
|
||||
def _onMachineChanged(self):
|
||||
def _onMachineChanged(self) -> None:
|
||||
if self._global_stack:
|
||||
self._global_stack.propertyChanged.disconnect(self.startErrorCheck)
|
||||
self._global_stack.containersChanged.disconnect(self.startErrorCheck)
|
||||
|
@ -94,7 +94,7 @@ class MachineErrorChecker(QObject):
|
|||
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):
|
||||
def startErrorCheck(self, *args) -> None:
|
||||
if not self._check_in_progress:
|
||||
self._need_to_check = True
|
||||
self.needToWaitForResultChanged.emit()
|
||||
|
@ -103,7 +103,7 @@ class MachineErrorChecker(QObject):
|
|||
# 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):
|
||||
def _rescheduleCheck(self) -> None:
|
||||
if self._check_in_progress and not self._need_to_check:
|
||||
self._need_to_check = True
|
||||
self.needToWaitForResultChanged.emit()
|
||||
|
@ -128,7 +128,7 @@ class MachineErrorChecker(QObject):
|
|||
self._start_time = time.time()
|
||||
Logger.log("d", "New error check scheduled.")
|
||||
|
||||
def _checkStack(self):
|
||||
def _checkStack(self) -> None:
|
||||
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
|
||||
|
@ -169,7 +169,7 @@ class MachineErrorChecker(QObject):
|
|||
# Schedule the check for the next key
|
||||
self._application.callLater(self._checkStack)
|
||||
|
||||
def _setResult(self, result: bool):
|
||||
def _setResult(self, result: bool) -> None:
|
||||
if result != self._has_errors:
|
||||
self._has_errors = result
|
||||
self.hasErrorUpdated.emit()
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
from collections import defaultdict, OrderedDict
|
||||
import copy
|
||||
import uuid
|
||||
import json
|
||||
from typing import Dict, Optional, TYPE_CHECKING
|
||||
from typing import Dict, Optional, TYPE_CHECKING, Any, Set, List, cast, Tuple
|
||||
|
||||
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
|
||||
|
||||
|
@ -47,18 +46,27 @@ class MaterialManager(QObject):
|
|||
self._application = Application.getInstance()
|
||||
self._container_registry = container_registry # type: ContainerRegistry
|
||||
|
||||
self._fallback_materials_map = dict() # material_type -> generic material metadata
|
||||
self._material_group_map = dict() # root_material_id -> MaterialGroup
|
||||
self._diameter_machine_nozzle_buildplate_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode)
|
||||
# Material_type -> generic material metadata
|
||||
self._fallback_materials_map = dict() # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
# Root_material_id -> MaterialGroup
|
||||
self._material_group_map = dict() # type: Dict[str, MaterialGroup]
|
||||
|
||||
# Approximate diameter str
|
||||
self._diameter_machine_nozzle_buildplate_material_map = dict() # type: Dict[str, Dict[str, MaterialNode]]
|
||||
|
||||
# We're using these two maps to convert between the specific diameter material id and the generic material id
|
||||
# because the generic material ids are used in qualities and definitions, while the specific diameter material is meant
|
||||
# i.e. generic_pla -> generic_pla_175
|
||||
self._material_diameter_map = defaultdict(dict) # root_material_id -> approximate diameter str -> root_material_id for that diameter
|
||||
self._diameter_material_map = dict() # material id including diameter (generic_pla_175) -> material root id (generic_pla)
|
||||
# root_material_id -> approximate diameter str -> root_material_id for that diameter
|
||||
self._material_diameter_map = defaultdict(dict) # type: Dict[str, Dict[str, str]]
|
||||
|
||||
# Material id including diameter (generic_pla_175) -> material root id (generic_pla)
|
||||
self._diameter_material_map = dict() # type: Dict[str, str]
|
||||
|
||||
# This is used in Legacy UM3 send material function and the material management page.
|
||||
self._guid_material_groups_map = defaultdict(list) # GUID -> a list of material_groups
|
||||
# GUID -> a list of material_groups
|
||||
self._guid_material_groups_map = defaultdict(list) # type: Dict[str, List[MaterialGroup]]
|
||||
|
||||
# The machine definition ID for the non-machine-specific materials.
|
||||
# This is used as the last fallback option if the given machine-specific material(s) cannot be found.
|
||||
|
@ -77,15 +85,15 @@ class MaterialManager(QObject):
|
|||
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
|
||||
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
|
||||
|
||||
self._favorites = set()
|
||||
self._favorites = set() # type: Set[str]
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
# Find all materials and put them in a matrix for quick search.
|
||||
material_metadatas = {metadata["id"]: metadata for metadata in
|
||||
self._container_registry.findContainersMetadata(type = "material") if
|
||||
metadata.get("GUID")}
|
||||
metadata.get("GUID")} # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
self._material_group_map = dict()
|
||||
self._material_group_map = dict() # type: Dict[str, MaterialGroup]
|
||||
|
||||
# Map #1
|
||||
# root_material_id -> MaterialGroup
|
||||
|
@ -94,7 +102,7 @@ class MaterialManager(QObject):
|
|||
if material_id == "empty_material":
|
||||
continue
|
||||
|
||||
root_material_id = material_metadata.get("base_file")
|
||||
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)
|
||||
|
@ -110,26 +118,26 @@ class MaterialManager(QObject):
|
|||
|
||||
# Map #1.5
|
||||
# GUID -> material group list
|
||||
self._guid_material_groups_map = defaultdict(list)
|
||||
self._guid_material_groups_map = defaultdict(list) # type: Dict[str, List[MaterialGroup]]
|
||||
for root_material_id, material_group in self._material_group_map.items():
|
||||
guid = material_group.root_material_node.metadata["GUID"]
|
||||
guid = material_group.root_material_node.getMetaDataEntry("GUID", "")
|
||||
self._guid_material_groups_map[guid].append(material_group)
|
||||
|
||||
# Map #2
|
||||
# Lookup table for material type -> fallback material metadata, only for read-only materials
|
||||
grouped_by_type_dict = dict()
|
||||
grouped_by_type_dict = dict() # type: Dict[str, Any]
|
||||
material_types_without_fallback = set()
|
||||
for root_material_id, material_node in self._material_group_map.items():
|
||||
material_type = material_node.root_material_node.metadata["material"]
|
||||
material_type = material_node.root_material_node.getMetaDataEntry("material", "")
|
||||
if material_type not in grouped_by_type_dict:
|
||||
grouped_by_type_dict[material_type] = {"generic": None,
|
||||
"others": []}
|
||||
material_types_without_fallback.add(material_type)
|
||||
brand = material_node.root_material_node.metadata["brand"]
|
||||
brand = material_node.root_material_node.getMetaDataEntry("brand", "")
|
||||
if brand.lower() == "generic":
|
||||
to_add = True
|
||||
if material_type in grouped_by_type_dict:
|
||||
diameter = material_node.root_material_node.metadata.get("approximate_diameter")
|
||||
diameter = material_node.root_material_node.getMetaDataEntry("approximate_diameter", "")
|
||||
if diameter != self._default_approximate_diameter_for_quality_search:
|
||||
to_add = False # don't add if it's not the default diameter
|
||||
|
||||
|
@ -138,7 +146,7 @@ class MaterialManager(QObject):
|
|||
# - if it's in the list, it means that is a new material without fallback
|
||||
# - if it is not, then it is a custom material with a fallback material (parent)
|
||||
if material_type in material_types_without_fallback:
|
||||
grouped_by_type_dict[material_type] = material_node.root_material_node.metadata
|
||||
grouped_by_type_dict[material_type] = material_node.root_material_node._metadata
|
||||
material_types_without_fallback.remove(material_type)
|
||||
|
||||
# Remove the materials that have no fallback materials
|
||||
|
@ -155,15 +163,15 @@ class MaterialManager(QObject):
|
|||
self._diameter_material_map = dict()
|
||||
|
||||
# Group the material IDs by the same name, material, brand, and color but with different diameters.
|
||||
material_group_dict = dict()
|
||||
material_group_dict = dict() # type: Dict[Tuple[Any], Dict[str, str]]
|
||||
keys_to_fetch = ("name", "material", "brand", "color")
|
||||
for root_material_id, machine_node in self._material_group_map.items():
|
||||
root_material_metadata = machine_node.root_material_node.metadata
|
||||
root_material_metadata = machine_node.root_material_node._metadata
|
||||
|
||||
key_data = []
|
||||
key_data_list = [] # type: List[Any]
|
||||
for key in keys_to_fetch:
|
||||
key_data.append(root_material_metadata.get(key))
|
||||
key_data = tuple(key_data)
|
||||
key_data_list.append(machine_node.root_material_node.getMetaDataEntry(key))
|
||||
key_data = cast(Tuple[Any], tuple(key_data_list)) # type: Tuple[Any]
|
||||
|
||||
# If the key_data doesn't exist, it doesn't matter if the material is read only...
|
||||
if key_data not in material_group_dict:
|
||||
|
@ -172,8 +180,8 @@ class MaterialManager(QObject):
|
|||
# ...but if key_data exists, we just overwrite it if the material is read only, otherwise we skip it
|
||||
if not machine_node.is_read_only:
|
||||
continue
|
||||
approximate_diameter = root_material_metadata.get("approximate_diameter")
|
||||
material_group_dict[key_data][approximate_diameter] = root_material_metadata["id"]
|
||||
approximate_diameter = machine_node.root_material_node.getMetaDataEntry("approximate_diameter", "")
|
||||
material_group_dict[key_data][approximate_diameter] = machine_node.root_material_node.getMetaDataEntry("id", "")
|
||||
|
||||
# Map [root_material_id][diameter] -> root_material_id for this diameter
|
||||
for data_dict in material_group_dict.values():
|
||||
|
@ -192,7 +200,7 @@ class MaterialManager(QObject):
|
|||
|
||||
# Map #4
|
||||
# "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer
|
||||
self._diameter_machine_nozzle_buildplate_material_map = dict()
|
||||
self._diameter_machine_nozzle_buildplate_material_map = dict() # type: Dict[str, Dict[str, MaterialNode]]
|
||||
for material_metadata in material_metadatas.values():
|
||||
self.__addMaterialMetadataIntoLookupTree(material_metadata)
|
||||
|
||||
|
@ -203,7 +211,7 @@ class MaterialManager(QObject):
|
|||
self._favorites.add(item)
|
||||
self.favoritesUpdated.emit()
|
||||
|
||||
def __addMaterialMetadataIntoLookupTree(self, material_metadata: dict) -> None:
|
||||
def __addMaterialMetadataIntoLookupTree(self, material_metadata: Dict[str, Any]) -> None:
|
||||
material_id = material_metadata["id"]
|
||||
|
||||
# We don't store empty material in the lookup tables
|
||||
|
@ -290,7 +298,7 @@ class MaterialManager(QObject):
|
|||
return self._material_diameter_map.get(root_material_id, {}).get(approximate_diameter, root_material_id)
|
||||
|
||||
def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
|
||||
return self._diameter_material_map.get(root_material_id)
|
||||
return self._diameter_material_map.get(root_material_id, "")
|
||||
|
||||
def getMaterialGroupListByGUID(self, guid: str) -> Optional[list]:
|
||||
return self._guid_material_groups_map.get(guid)
|
||||
|
@ -351,7 +359,7 @@ class MaterialManager(QObject):
|
|||
# A convenience function to get available materials for the given machine with the extruder position.
|
||||
#
|
||||
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
|
||||
extruder_stack: "ExtruderStack") -> Optional[dict]:
|
||||
extruder_stack: "ExtruderStack") -> Optional[Dict[str, MaterialNode]]:
|
||||
buildplate_name = machine.getBuildplateName()
|
||||
nozzle_name = None
|
||||
if extruder_stack.variant.getId() != "empty_variant":
|
||||
|
@ -368,7 +376,7 @@ class MaterialManager(QObject):
|
|||
# 2. cannot find any material InstanceContainers with the given settings.
|
||||
#
|
||||
def getMaterialNode(self, machine_definition_id: str, nozzle_name: Optional[str],
|
||||
buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
|
||||
buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["MaterialNode"]:
|
||||
# round the diameter to get the approximate diameter
|
||||
rounded_diameter = str(round(diameter))
|
||||
if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
|
||||
|
@ -377,7 +385,7 @@ class MaterialManager(QObject):
|
|||
return None
|
||||
|
||||
# If there are nozzle materials, get the nozzle-specific material
|
||||
machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter]
|
||||
machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter] # type: Dict[str, MaterialNode]
|
||||
machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
|
||||
nozzle_node = None
|
||||
buildplate_node = None
|
||||
|
@ -426,7 +434,7 @@ class MaterialManager(QObject):
|
|||
# Look at the guid to material dictionary
|
||||
root_material_id = None
|
||||
for material_group in self._guid_material_groups_map[material_guid]:
|
||||
root_material_id = material_group.root_material_node.metadata["id"]
|
||||
root_material_id = cast(str, material_group.root_material_node.getMetaDataEntry("id", ""))
|
||||
break
|
||||
|
||||
if not root_material_id:
|
||||
|
@ -502,7 +510,7 @@ class MaterialManager(QObject):
|
|||
# Sets the new name for the given material.
|
||||
#
|
||||
@pyqtSlot("QVariant", str)
|
||||
def setMaterialName(self, material_node: "MaterialNode", name: str):
|
||||
def setMaterialName(self, material_node: "MaterialNode", name: str) -> None:
|
||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
if root_material_id is None:
|
||||
return
|
||||
|
@ -520,7 +528,7 @@ class MaterialManager(QObject):
|
|||
# Removes the given material.
|
||||
#
|
||||
@pyqtSlot("QVariant")
|
||||
def removeMaterial(self, material_node: "MaterialNode"):
|
||||
def removeMaterial(self, material_node: "MaterialNode") -> None:
|
||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
if root_material_id is not None:
|
||||
self.removeMaterialByRootId(root_material_id)
|
||||
|
@ -530,8 +538,8 @@ class MaterialManager(QObject):
|
|||
# Returns the root material ID of the duplicated material if successful.
|
||||
#
|
||||
@pyqtSlot("QVariant", result = str)
|
||||
def duplicateMaterial(self, material_node, new_base_id = None, new_metadata = None) -> Optional[str]:
|
||||
root_material_id = material_node.metadata["base_file"]
|
||||
def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
|
||||
root_material_id = cast(str, material_node.getMetaDataEntry("base_file", ""))
|
||||
|
||||
material_group = self.getMaterialGroup(root_material_id)
|
||||
if not material_group:
|
||||
|
@ -586,7 +594,7 @@ class MaterialManager(QObject):
|
|||
|
||||
#
|
||||
# Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID.
|
||||
#
|
||||
# Returns the ID of the newly created material.
|
||||
@pyqtSlot(result = str)
|
||||
def createMaterial(self) -> str:
|
||||
from UM.i18n import i18nCatalog
|
||||
|
@ -619,7 +627,7 @@ class MaterialManager(QObject):
|
|||
return new_id
|
||||
|
||||
@pyqtSlot(str)
|
||||
def addFavorite(self, root_material_id: str):
|
||||
def addFavorite(self, root_material_id: str) -> None:
|
||||
self._favorites.add(root_material_id)
|
||||
self.favoritesUpdated.emit()
|
||||
|
||||
|
@ -628,7 +636,7 @@ class MaterialManager(QObject):
|
|||
self._application.saveSettings()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def removeFavorite(self, root_material_id: str):
|
||||
def removeFavorite(self, root_material_id: str) -> None:
|
||||
self._favorites.remove(root_material_id)
|
||||
self.favoritesUpdated.emit()
|
||||
|
||||
|
|
|
@ -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, Dict
|
||||
|
||||
from typing import Optional, Dict, Any
|
||||
from collections import OrderedDict
|
||||
from .ContainerNode import ContainerNode
|
||||
|
||||
|
||||
|
@ -14,6 +14,12 @@ from .ContainerNode import ContainerNode
|
|||
class MaterialNode(ContainerNode):
|
||||
__slots__ = ("material_map", "children_map")
|
||||
|
||||
def __init__(self, metadata: Optional[dict] = None) -> None:
|
||||
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||
super().__init__(metadata = metadata)
|
||||
self.material_map = {} # type: Dict[str, MaterialNode] # material_root_id -> material_node
|
||||
|
||||
# We overide this as we want to indicate that MaterialNodes can only contain other material nodes.
|
||||
self.children_map = OrderedDict() # type: OrderedDict[str, "MaterialNode"]
|
||||
|
||||
def getChildNode(self, child_key: str) -> Optional["MaterialNode"]:
|
||||
return self.children_map.get(child_key)
|
|
@ -113,7 +113,7 @@ class BaseMaterialsModel(ListModel):
|
|||
## This is another convenience function which is shared by all material
|
||||
# models so it's put here to avoid having so much duplicated code.
|
||||
def _createMaterialItem(self, root_material_id, container_node):
|
||||
metadata = container_node.metadata
|
||||
metadata = container_node.getMetadata()
|
||||
item = {
|
||||
"root_material_id": root_material_id,
|
||||
"id": metadata["id"],
|
||||
|
@ -124,7 +124,7 @@ class BaseMaterialsModel(ListModel):
|
|||
"description": metadata["description"],
|
||||
"material": metadata["material"],
|
||||
"color_name": metadata["color_name"],
|
||||
"color_code": metadata["color_code"],
|
||||
"color_code": metadata.get("color_code", ""),
|
||||
"density": metadata.get("properties", {}).get("density", ""),
|
||||
"diameter": metadata.get("properties", {}).get("diameter", ""),
|
||||
"approximate_diameter": metadata["approximate_diameter"],
|
||||
|
|
|
@ -23,7 +23,7 @@ class FavoriteMaterialsModel(BaseMaterialsModel):
|
|||
item_list = []
|
||||
|
||||
for root_material_id, container_node in self._available_materials.items():
|
||||
metadata = container_node.metadata
|
||||
metadata = container_node.getMetadata()
|
||||
|
||||
# Do not include the materials from a to-be-removed package
|
||||
if bool(metadata.get("removed", False)):
|
||||
|
|
|
@ -23,7 +23,7 @@ class GenericMaterialsModel(BaseMaterialsModel):
|
|||
item_list = []
|
||||
|
||||
for root_material_id, container_node in self._available_materials.items():
|
||||
metadata = container_node.metadata
|
||||
metadata = container_node.getMetadata()
|
||||
|
||||
# Do not include the materials from a to-be-removed package
|
||||
if bool(metadata.get("removed", False)):
|
||||
|
|
|
@ -41,21 +41,19 @@ class MaterialBrandsModel(BaseMaterialsModel):
|
|||
|
||||
# Part 1: Generate the entire tree of brands -> material types -> spcific materials
|
||||
for root_material_id, container_node in self._available_materials.items():
|
||||
metadata = container_node.metadata
|
||||
|
||||
# Do not include the materials from a to-be-removed package
|
||||
if bool(metadata.get("removed", False)):
|
||||
if bool(container_node.getMetaDataEntry("removed", False)):
|
||||
continue
|
||||
|
||||
# Add brands we haven't seen yet to the dict, skipping generics
|
||||
brand = metadata["brand"]
|
||||
brand = container_node.getMetaDataEntry("brand", "")
|
||||
if brand.lower() == "generic":
|
||||
continue
|
||||
if brand not in brand_group_dict:
|
||||
brand_group_dict[brand] = {}
|
||||
|
||||
# Add material types we haven't seen yet to the dict
|
||||
material_type = metadata["material"]
|
||||
material_type = container_node.getMetaDataEntry("material", "")
|
||||
if material_type not in brand_group_dict[brand]:
|
||||
brand_group_dict[brand][material_type] = []
|
||||
|
||||
|
|
|
@ -6,10 +6,10 @@ from PyQt5.QtCore import Qt
|
|||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
|
||||
from cura.Machines.QualityManager import QualityGroup
|
||||
|
||||
|
||||
#
|
||||
# QML Model for all built-in quality profiles. This model is used for the drop-down quality menu.
|
||||
#
|
||||
|
@ -106,4 +106,8 @@ class QualityProfilesDropDownMenuModel(ListModel):
|
|||
container = global_stack.definition
|
||||
if container and container.hasProperty("layer_height", "value"):
|
||||
layer_height = container.getProperty("layer_height", "value")
|
||||
|
||||
if isinstance(layer_height, SettingFunction):
|
||||
layer_height = layer_height(global_stack)
|
||||
|
||||
return float(layer_height)
|
||||
|
|
|
@ -17,7 +17,7 @@ class QualityChangesGroup(QualityGroup):
|
|||
super().__init__(name, quality_type, parent)
|
||||
self._container_registry = Application.getInstance().getContainerRegistry()
|
||||
|
||||
def addNode(self, node: "QualityNode"):
|
||||
def addNode(self, node: "QualityNode") -> None:
|
||||
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.
|
||||
|
|
|
@ -6,6 +6,7 @@ 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.
|
||||
# Some concrete examples are Quality and QualityChanges: when we select quality type "normal", this quality type
|
||||
|
|
|
@ -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, cast
|
||||
from typing import TYPE_CHECKING, Optional, cast, Dict, List
|
||||
|
||||
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
|
||||
|
||||
|
@ -20,6 +20,8 @@ if TYPE_CHECKING:
|
|||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from .QualityChangesGroup import QualityChangesGroup
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
|
||||
#
|
||||
|
@ -36,17 +38,20 @@ class QualityManager(QObject):
|
|||
|
||||
qualitiesUpdated = pyqtSignal()
|
||||
|
||||
def __init__(self, container_registry, parent = None):
|
||||
def __init__(self, container_registry: "ContainerRegistry", parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._application = Application.getInstance()
|
||||
self._application = Application.getInstance() # type: CuraApplication
|
||||
self._material_manager = self._application.getMaterialManager()
|
||||
self._container_registry = container_registry
|
||||
|
||||
self._empty_quality_container = self._application.empty_quality_container
|
||||
self._empty_quality_changes_container = self._application.empty_quality_changes_container
|
||||
|
||||
self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # for quality lookup
|
||||
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
|
||||
# For quality lookup
|
||||
self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # type: Dict[str, QualityNode]
|
||||
|
||||
# For quality_changes lookup
|
||||
self._machine_quality_type_to_quality_changes_dict = {} # type: Dict[str, QualityNode]
|
||||
|
||||
self._default_machine_definition_id = "fdmprinter"
|
||||
|
||||
|
@ -62,7 +67,7 @@ class QualityManager(QObject):
|
|||
self._update_timer.setSingleShot(True)
|
||||
self._update_timer.timeout.connect(self._updateMaps)
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
# Initialize the lookup tree for quality profiles with following structure:
|
||||
# <machine> -> <nozzle> -> <buildplate> -> <material>
|
||||
# <machine> -> <material>
|
||||
|
@ -133,13 +138,13 @@ class QualityManager(QObject):
|
|||
Logger.log("d", "Lookup tables updated.")
|
||||
self.qualitiesUpdated.emit()
|
||||
|
||||
def _updateMaps(self):
|
||||
def _updateMaps(self) -> None:
|
||||
self.initialize()
|
||||
|
||||
def _onContainerMetadataChanged(self, container):
|
||||
def _onContainerMetadataChanged(self, container: InstanceContainer) -> None:
|
||||
self._onContainerChanged(container)
|
||||
|
||||
def _onContainerChanged(self, container):
|
||||
def _onContainerChanged(self, container: InstanceContainer) -> None:
|
||||
container_type = container.getMetaDataEntry("type")
|
||||
if container_type not in ("quality", "quality_changes"):
|
||||
return
|
||||
|
@ -148,7 +153,7 @@ class QualityManager(QObject):
|
|||
self._update_timer.start()
|
||||
|
||||
# Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
|
||||
def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list):
|
||||
def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list) -> None:
|
||||
used_extruders = set()
|
||||
for i in range(machine.getProperty("machine_extruder_count", "value")):
|
||||
if str(i) in machine.extruders and machine.extruders[str(i)].isEnabled:
|
||||
|
@ -196,32 +201,42 @@ class QualityManager(QObject):
|
|||
# Whether a QualityGroup is available can be unknown via the field QualityGroup.is_available.
|
||||
# For more details, see QualityGroup.
|
||||
#
|
||||
def getQualityGroups(self, machine: "GlobalStack") -> dict:
|
||||
def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
|
||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||
|
||||
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
|
||||
has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False))
|
||||
has_machine_specific_qualities = machine.getHasMachineQuality()
|
||||
|
||||
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||
# (1) the machine-specific node
|
||||
# (2) the generic node
|
||||
machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
|
||||
# Check if this machine has specific quality profiles for its extruders, if so, when looking up extruder
|
||||
# qualities, we should not fall back to use the global qualities.
|
||||
has_extruder_specific_qualities = False
|
||||
if machine_node:
|
||||
if machine_node.children_map:
|
||||
has_extruder_specific_qualities = True
|
||||
|
||||
default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
|
||||
nodes_to_check = [machine_node, default_machine_node]
|
||||
|
||||
nodes_to_check = [] # type: List[QualityNode]
|
||||
if machine_node is not None:
|
||||
nodes_to_check.append(machine_node)
|
||||
if default_machine_node is not None:
|
||||
nodes_to_check.append(default_machine_node)
|
||||
|
||||
# Iterate over all quality_types in the machine node
|
||||
quality_group_dict = {}
|
||||
for node in nodes_to_check:
|
||||
if node and node.quality_type_map:
|
||||
# Only include global qualities
|
||||
if has_variant_materials:
|
||||
quality_node = list(node.quality_type_map.values())[0]
|
||||
is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
|
||||
is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False))
|
||||
if not is_global_quality:
|
||||
continue
|
||||
|
||||
for quality_type, quality_node in node.quality_type_map.items():
|
||||
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
|
||||
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
|
||||
quality_group.node_for_global = quality_node
|
||||
quality_group_dict[quality_type] = quality_group
|
||||
break
|
||||
|
@ -268,13 +283,16 @@ class QualityManager(QObject):
|
|||
# Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
|
||||
# the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
|
||||
# qualities from there.
|
||||
node_info_list_0 = [nozzle_name, buildplate_name, root_material_id]
|
||||
node_info_list_0 = [nozzle_name, buildplate_name, root_material_id] # type: List[Optional[str]]
|
||||
nodes_to_check = []
|
||||
|
||||
# This function tries to recursively find the deepest (the most specific) branch and add those nodes to
|
||||
# the search list in the order described above. So, by iterating over that search node list, we first look
|
||||
# in the more specific branches and then the less specific (generic) ones.
|
||||
def addNodesToCheck(node, nodes_to_check_list, node_info_list, node_info_idx):
|
||||
def addNodesToCheck(node: Optional[QualityNode], nodes_to_check_list: List[QualityNode], node_info_list, node_info_idx: int) -> None:
|
||||
if node is None:
|
||||
return
|
||||
|
||||
if node_info_idx < len(node_info_list):
|
||||
node_name = node_info_list[node_info_idx]
|
||||
if node_name is not None:
|
||||
|
@ -295,35 +313,42 @@ class QualityManager(QObject):
|
|||
|
||||
# The last fall back will be the global qualities (either from the machine-specific node or the generic
|
||||
# node), but we only use one. For details see the overview comments above.
|
||||
if machine_node.quality_type_map:
|
||||
|
||||
if machine_node is not None and machine_node.quality_type_map:
|
||||
nodes_to_check += [machine_node]
|
||||
else:
|
||||
elif default_machine_node is not None:
|
||||
nodes_to_check += [default_machine_node]
|
||||
|
||||
for node in nodes_to_check:
|
||||
for node_idx, node in enumerate(nodes_to_check):
|
||||
if node and node.quality_type_map:
|
||||
if has_variant_materials:
|
||||
if has_extruder_specific_qualities:
|
||||
# Only include variant qualities; skip non global qualities
|
||||
quality_node = list(node.quality_type_map.values())[0]
|
||||
is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
|
||||
is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False))
|
||||
if is_global_quality:
|
||||
continue
|
||||
|
||||
for quality_type, quality_node in node.quality_type_map.items():
|
||||
if quality_type not in quality_group_dict:
|
||||
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
|
||||
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
|
||||
quality_group_dict[quality_type] = quality_group
|
||||
|
||||
quality_group = quality_group_dict[quality_type]
|
||||
if position not in quality_group.nodes_for_extruders:
|
||||
quality_group.nodes_for_extruders[position] = quality_node
|
||||
|
||||
# If the machine has its own specific qualities, for extruders, it should skip the global qualities
|
||||
# and use the material/variant specific qualities.
|
||||
if has_extruder_specific_qualities:
|
||||
if node_idx == len(nodes_to_check) - 1:
|
||||
break
|
||||
|
||||
# Update availabilities for each quality group
|
||||
self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
|
||||
|
||||
return quality_group_dict
|
||||
|
||||
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict:
|
||||
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
|
||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||
|
||||
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||
|
@ -339,7 +364,7 @@ class QualityManager(QObject):
|
|||
for node in nodes_to_check:
|
||||
if node and node.quality_type_map:
|
||||
for quality_type, quality_node in node.quality_type_map.items():
|
||||
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
|
||||
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
|
||||
quality_group.node_for_global = quality_node
|
||||
quality_group_dict[quality_type] = quality_group
|
||||
break
|
||||
|
@ -361,10 +386,21 @@ class QualityManager(QObject):
|
|||
# Remove the given quality changes group.
|
||||
#
|
||||
@pyqtSlot(QObject)
|
||||
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"):
|
||||
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
|
||||
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
|
||||
removed_quality_changes_ids = set()
|
||||
for node in quality_changes_group.getAllNodes():
|
||||
self._container_registry.removeContainer(node.getMetaDataEntry("id"))
|
||||
container_id = node.getMetaDataEntry("id")
|
||||
self._container_registry.removeContainer(container_id)
|
||||
removed_quality_changes_ids.add(container_id)
|
||||
|
||||
# Reset all machines that have activated this quality changes to empty.
|
||||
for global_stack in self._container_registry.findContainerStacks(type = "machine"):
|
||||
if global_stack.qualityChanges.getId() in removed_quality_changes_ids:
|
||||
global_stack.qualityChanges = self._empty_quality_changes_container
|
||||
for extruder_stack in self._container_registry.findContainerStacks(type = "extruder_train"):
|
||||
if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids:
|
||||
extruder_stack.qualityChanges = self._empty_quality_changes_container
|
||||
|
||||
#
|
||||
# Rename a set of quality changes containers. Returns the new name.
|
||||
|
@ -393,7 +429,7 @@ class QualityManager(QObject):
|
|||
# Duplicates the given quality.
|
||||
#
|
||||
@pyqtSlot(str, "QVariantMap")
|
||||
def duplicateQualityChanges(self, quality_changes_name, quality_model_item):
|
||||
def duplicateQualityChanges(self, quality_changes_name: str, quality_model_item) -> None:
|
||||
global_stack = self._application.getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
Logger.log("i", "No active global stack, cannot duplicate quality changes.")
|
||||
|
@ -421,7 +457,7 @@ class QualityManager(QObject):
|
|||
# the user containers in each stack. These then replace the quality_changes containers in the
|
||||
# stack and clear the user settings.
|
||||
@pyqtSlot(str)
|
||||
def createQualityChanges(self, base_name):
|
||||
def createQualityChanges(self, base_name: str) -> None:
|
||||
machine_manager = Application.getInstance().getMachineManager()
|
||||
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
|
|
@ -16,6 +16,9 @@ class QualityNode(ContainerNode):
|
|||
super().__init__(metadata = metadata)
|
||||
self.quality_type_map = {} # type: Dict[str, QualityNode] # quality_type -> QualityNode for InstanceContainer
|
||||
|
||||
def getChildNode(self, child_key: str) -> Optional["QualityNode"]:
|
||||
return self.children_map.get(child_key)
|
||||
|
||||
def addQualityMetadata(self, quality_type: str, metadata: dict):
|
||||
if quality_type not in self.quality_type_map:
|
||||
self.quality_type_map[quality_type] = QualityNode(metadata)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import Optional, TYPE_CHECKING, Dict
|
||||
|
||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||
from UM.Logger import Logger
|
||||
|
@ -36,11 +36,11 @@ if TYPE_CHECKING:
|
|||
#
|
||||
class VariantManager:
|
||||
|
||||
def __init__(self, container_registry):
|
||||
self._container_registry = container_registry # type: ContainerRegistry
|
||||
def __init__(self, container_registry: ContainerRegistry) -> None:
|
||||
self._container_registry = container_registry
|
||||
|
||||
self._machine_to_variant_dict_map = dict() # <machine_type> -> <variant_dict>
|
||||
self._machine_to_buildplate_dict_map = dict()
|
||||
self._machine_to_variant_dict_map = dict() # type: Dict[str, Dict["VariantType", Dict[str, ContainerNode]]]
|
||||
self._machine_to_buildplate_dict_map = dict() # type: Dict[str, Dict[str, ContainerNode]]
|
||||
|
||||
self._exclude_variant_id_list = ["empty_variant"]
|
||||
|
||||
|
@ -48,7 +48,7 @@ class VariantManager:
|
|||
# Initializes the VariantManager including:
|
||||
# - initializing the variant lookup table based on the metadata in ContainerRegistry.
|
||||
#
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
self._machine_to_variant_dict_map = OrderedDict()
|
||||
self._machine_to_buildplate_dict_map = OrderedDict()
|
||||
|
||||
|
@ -106,10 +106,10 @@ class VariantManager:
|
|||
variant_node = variant_dict[variant_name]
|
||||
break
|
||||
return variant_node
|
||||
|
||||
return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name)
|
||||
|
||||
def getVariantNodes(self, machine: "GlobalStack",
|
||||
variant_type: Optional["VariantType"] = None) -> dict:
|
||||
def getVariantNodes(self, machine: "GlobalStack", variant_type: "VariantType") -> Dict[str, ContainerNode]:
|
||||
machine_definition_id = machine.definition.getId()
|
||||
return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
|
||||
|
||||
|
|
27
cura/PrintJobPreviewImageProvider.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from PyQt5.QtGui import QImage
|
||||
from PyQt5.QtQuick import QQuickImageProvider
|
||||
from PyQt5.QtCore import QSize
|
||||
|
||||
from UM.Application import Application
|
||||
|
||||
|
||||
class PrintJobPreviewImageProvider(QQuickImageProvider):
|
||||
def __init__(self):
|
||||
super().__init__(QQuickImageProvider.Image)
|
||||
|
||||
## Request a new image.
|
||||
def requestImage(self, id: str, size: QSize) -> QImage:
|
||||
# The id will have an uuid and an increment separated by a slash. As we don't care about the value of the
|
||||
# increment, we need to strip that first.
|
||||
uuid = id[id.find("/") + 1:]
|
||||
for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
|
||||
if not hasattr(output_device, "printJobs"):
|
||||
continue
|
||||
|
||||
for print_job in output_device.printJobs:
|
||||
if print_job.key == uuid:
|
||||
if print_job.getPreviewImage():
|
||||
return print_job.getPreviewImage(), QSize(15, 15)
|
||||
else:
|
||||
return QImage(), QSize(15, 15)
|
||||
return QImage(), QSize(15,15)
|
|
@ -27,8 +27,14 @@ class ConfigurationModel(QObject):
|
|||
return self._printer_type
|
||||
|
||||
def setExtruderConfigurations(self, extruder_configurations):
|
||||
if self._extruder_configurations != extruder_configurations:
|
||||
self._extruder_configurations = extruder_configurations
|
||||
|
||||
for extruder_configuration in self._extruder_configurations:
|
||||
extruder_configuration.extruderConfigurationChanged.connect(self.configurationChanged)
|
||||
|
||||
self.configurationChanged.emit()
|
||||
|
||||
@pyqtProperty("QVariantList", fset = setExtruderConfigurations, notify = configurationChanged)
|
||||
def extruderConfigurations(self):
|
||||
return self._extruder_configurations
|
||||
|
|
|
@ -1,56 +1,67 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
||||
|
||||
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||
|
||||
|
||||
class ExtruderConfigurationModel(QObject):
|
||||
|
||||
extruderConfigurationChanged = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, position: int = -1) -> None:
|
||||
super().__init__()
|
||||
self._position = -1
|
||||
self._material = None
|
||||
self._hotend_id = None
|
||||
self._position = position # type: int
|
||||
self._material = None # type: Optional[MaterialOutputModel]
|
||||
self._hotend_id = None # type: Optional[str]
|
||||
|
||||
def setPosition(self, position):
|
||||
def setPosition(self, position: int) -> None:
|
||||
self._position = position
|
||||
|
||||
@pyqtProperty(int, fset = setPosition, notify = extruderConfigurationChanged)
|
||||
def position(self):
|
||||
def position(self) -> int:
|
||||
return self._position
|
||||
|
||||
def setMaterial(self, material):
|
||||
def setMaterial(self, material: Optional[MaterialOutputModel]) -> None:
|
||||
if self._hotend_id != material:
|
||||
self._material = material
|
||||
self.extruderConfigurationChanged.emit()
|
||||
|
||||
@pyqtProperty(QObject, fset = setMaterial, notify = extruderConfigurationChanged)
|
||||
def material(self):
|
||||
def activeMaterial(self) -> Optional[MaterialOutputModel]:
|
||||
return self._material
|
||||
|
||||
def setHotendID(self, hotend_id):
|
||||
@pyqtProperty(QObject, fset=setMaterial, notify=extruderConfigurationChanged)
|
||||
def material(self) -> Optional[MaterialOutputModel]:
|
||||
return self._material
|
||||
|
||||
def setHotendID(self, hotend_id: Optional[str]) -> None:
|
||||
if self._hotend_id != hotend_id:
|
||||
self._hotend_id = hotend_id
|
||||
self.extruderConfigurationChanged.emit()
|
||||
|
||||
@pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)
|
||||
def hotendID(self):
|
||||
def hotendID(self) -> Optional[str]:
|
||||
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):
|
||||
def isValid(self) -> bool:
|
||||
return True
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
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("Material: " + self.activeMaterial.type if self.activeMaterial else "empty")
|
||||
message_chunks.append("-")
|
||||
message_chunks.append("HotendID: " + self.hotendID if self.hotendID else "empty")
|
||||
return " ".join(message_chunks)
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other) -> bool:
|
||||
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
|
||||
|
|
|
@ -12,64 +12,61 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class ExtruderOutputModel(QObject):
|
||||
hotendIDChanged = pyqtSignal()
|
||||
targetHotendTemperatureChanged = pyqtSignal()
|
||||
hotendTemperatureChanged = pyqtSignal()
|
||||
activeMaterialChanged = pyqtSignal()
|
||||
|
||||
extruderConfigurationChanged = pyqtSignal()
|
||||
isPreheatingChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, printer: "PrinterOutputModel", position, parent=None) -> None:
|
||||
def __init__(self, printer: "PrinterOutputModel", position: int, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self._printer = printer
|
||||
self._printer = printer # type: PrinterOutputModel
|
||||
self._position = position
|
||||
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()
|
||||
self._extruder_configuration.position = self._position
|
||||
self._target_hotend_temperature = 0.0 # type: float
|
||||
self._hotend_temperature = 0.0 # type: float
|
||||
|
||||
self._is_preheating = False
|
||||
|
||||
def getPrinter(self):
|
||||
# The extruder output model wraps the configuration model. This way we can use the same config model for jobs
|
||||
# and extruders alike.
|
||||
self._extruder_configuration = ExtruderConfigurationModel()
|
||||
self._extruder_configuration.position = self._position
|
||||
self._extruder_configuration.extruderConfigurationChanged.connect(self.extruderConfigurationChanged)
|
||||
|
||||
def getPrinter(self) -> "PrinterOutputModel":
|
||||
return self._printer
|
||||
|
||||
def getPosition(self):
|
||||
def getPosition(self) -> int:
|
||||
return self._position
|
||||
|
||||
# Does the printer support pre-heating the bed at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canPreHeatHotends(self):
|
||||
def canPreHeatHotends(self) -> bool:
|
||||
if self._printer:
|
||||
return self._printer.canPreHeatHotends
|
||||
return False
|
||||
|
||||
@pyqtProperty(QObject, notify = activeMaterialChanged)
|
||||
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
|
||||
def activeMaterial(self) -> Optional["MaterialOutputModel"]:
|
||||
return self._active_material
|
||||
return self._extruder_configuration.activeMaterial
|
||||
|
||||
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()
|
||||
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]) -> None:
|
||||
self._extruder_configuration.setMaterial(material)
|
||||
|
||||
## Update the hotend temperature. This only changes it locally.
|
||||
def updateHotendTemperature(self, temperature: float):
|
||||
def updateHotendTemperature(self, temperature: float) -> None:
|
||||
if self._hotend_temperature != temperature:
|
||||
self._hotend_temperature = temperature
|
||||
self.hotendTemperatureChanged.emit()
|
||||
|
||||
def updateTargetHotendTemperature(self, temperature: float):
|
||||
def updateTargetHotendTemperature(self, temperature: float) -> None:
|
||||
if self._target_hotend_temperature != temperature:
|
||||
self._target_hotend_temperature = temperature
|
||||
self.targetHotendTemperatureChanged.emit()
|
||||
|
||||
## Set the target hotend temperature. This ensures that it's actually sent to the remote.
|
||||
@pyqtSlot(float)
|
||||
def setTargetHotendTemperature(self, temperature: float):
|
||||
def setTargetHotendTemperature(self, temperature: float) -> None:
|
||||
self._printer.getController().setTargetHotendTemperature(self._printer, self, temperature)
|
||||
self.updateTargetHotendTemperature(temperature)
|
||||
|
||||
|
@ -81,30 +78,26 @@ class ExtruderOutputModel(QObject):
|
|||
def hotendTemperature(self) -> float:
|
||||
return self._hotend_temperature
|
||||
|
||||
@pyqtProperty(str, notify = hotendIDChanged)
|
||||
@pyqtProperty(str, notify = extruderConfigurationChanged)
|
||||
def hotendID(self) -> str:
|
||||
return self._hotend_id
|
||||
return self._extruder_configuration.hotendID
|
||||
|
||||
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()
|
||||
def updateHotendID(self, hotend_id: str) -> None:
|
||||
self._extruder_configuration.setHotendID(hotend_id)
|
||||
|
||||
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
|
||||
def extruderConfiguration(self):
|
||||
def extruderConfiguration(self) -> Optional[ExtruderConfigurationModel]:
|
||||
if self._extruder_configuration.isValid():
|
||||
return self._extruder_configuration
|
||||
return None
|
||||
|
||||
def updateIsPreheating(self, pre_heating):
|
||||
def updateIsPreheating(self, pre_heating: bool) -> None:
|
||||
if self._is_preheating != pre_heating:
|
||||
self._is_preheating = pre_heating
|
||||
self.isPreheatingChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify=isPreheatingChanged)
|
||||
def isPreheating(self):
|
||||
def isPreheating(self) -> bool:
|
||||
return self._is_preheating
|
||||
|
||||
## Pre-heats the extruder before printer.
|
||||
|
@ -113,9 +106,9 @@ class ExtruderOutputModel(QObject):
|
|||
# Celsius.
|
||||
# \param duration How long the bed should stay warm, in seconds.
|
||||
@pyqtSlot(float, float)
|
||||
def preheatHotend(self, temperature, duration):
|
||||
def preheatHotend(self, temperature: float, duration: float) -> None:
|
||||
self._printer._controller.preheatHotend(self, temperature, duration)
|
||||
|
||||
@pyqtSlot()
|
||||
def cancelPreheatHotend(self):
|
||||
def cancelPreheatHotend(self) -> None:
|
||||
self._printer._controller.cancelPreheatHotend(self)
|
|
@ -188,40 +188,55 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
if reply in self._kept_alive_multiparts:
|
||||
del self._kept_alive_multiparts[reply]
|
||||
|
||||
def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||
def _validateManager(self) -> None:
|
||||
if self._manager is None:
|
||||
self._createNetworkManager()
|
||||
assert (self._manager is not None)
|
||||
|
||||
def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||
self._validateManager()
|
||||
request = self._createEmptyRequest(target)
|
||||
self._last_request_time = time()
|
||||
if self._manager is not None:
|
||||
reply = self._manager.put(request, data.encode())
|
||||
self._registerOnFinishedCallback(reply, on_finished)
|
||||
else:
|
||||
Logger.log("e", "Could not find manager.")
|
||||
|
||||
def delete(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||
self._validateManager()
|
||||
request = self._createEmptyRequest(target)
|
||||
self._last_request_time = time()
|
||||
if self._manager is not None:
|
||||
reply = self._manager.deleteResource(request)
|
||||
self._registerOnFinishedCallback(reply, on_finished)
|
||||
else:
|
||||
Logger.log("e", "Could not find manager.")
|
||||
|
||||
def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||
if self._manager is None:
|
||||
self._createNetworkManager()
|
||||
assert (self._manager is not None)
|
||||
self._validateManager()
|
||||
request = self._createEmptyRequest(target)
|
||||
self._last_request_time = time()
|
||||
if self._manager is not None:
|
||||
reply = self._manager.get(request)
|
||||
self._registerOnFinishedCallback(reply, on_finished)
|
||||
else:
|
||||
Logger.log("e", "Could not find manager.")
|
||||
|
||||
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()
|
||||
assert (self._manager is not None)
|
||||
self._validateManager()
|
||||
request = self._createEmptyRequest(target)
|
||||
self._last_request_time = time()
|
||||
if self._manager is not None:
|
||||
reply = self._manager.post(request, data)
|
||||
if on_progress is not None:
|
||||
reply.uploadProgress.connect(on_progress)
|
||||
self._registerOnFinishedCallback(reply, on_finished)
|
||||
else:
|
||||
Logger.log("e", "Could not find manager.")
|
||||
|
||||
def postFormWithParts(self, target: str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
|
||||
|
||||
if self._manager is None:
|
||||
self._createNetworkManager()
|
||||
assert (self._manager is not None)
|
||||
self._validateManager()
|
||||
request = self._createEmptyRequest(target, content_type=None)
|
||||
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
|
||||
for part in parts:
|
||||
|
@ -229,6 +244,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
self._last_request_time = time()
|
||||
|
||||
if self._manager is not None:
|
||||
reply = self._manager.post(request, multi_post_part)
|
||||
|
||||
self._kept_alive_multiparts[reply] = multi_post_part
|
||||
|
@ -238,6 +254,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._registerOnFinishedCallback(reply, on_finished)
|
||||
|
||||
return reply
|
||||
else:
|
||||
Logger.log("e", "Could not find manager.")
|
||||
|
||||
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()
|
||||
|
|
|
@ -2,11 +2,15 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import Optional, TYPE_CHECKING, List
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QImage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
|
||||
|
||||
class PrintJobOutputModel(QObject):
|
||||
|
@ -17,6 +21,9 @@ class PrintJobOutputModel(QObject):
|
|||
keyChanged = pyqtSignal()
|
||||
assignedPrinterChanged = pyqtSignal()
|
||||
ownerChanged = pyqtSignal()
|
||||
configurationChanged = pyqtSignal()
|
||||
previewImageChanged = pyqtSignal()
|
||||
compatibleMachineFamiliesChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
|
@ -29,6 +36,48 @@ class PrintJobOutputModel(QObject):
|
|||
self._assigned_printer = None # type: Optional[PrinterOutputModel]
|
||||
self._owner = "" # Who started/owns the print job?
|
||||
|
||||
self._configuration = None # type: Optional[ConfigurationModel]
|
||||
self._compatible_machine_families = [] # type: List[str]
|
||||
self._preview_image_id = 0
|
||||
|
||||
self._preview_image = None # type: Optional[QImage]
|
||||
|
||||
@pyqtProperty("QStringList", notify=compatibleMachineFamiliesChanged)
|
||||
def compatibleMachineFamilies(self):
|
||||
# Hack; Some versions of cluster will return a family more than once...
|
||||
return set(self._compatible_machine_families)
|
||||
|
||||
def setCompatibleMachineFamilies(self, compatible_machine_families: List[str]) -> None:
|
||||
if self._compatible_machine_families != compatible_machine_families:
|
||||
self._compatible_machine_families = compatible_machine_families
|
||||
self.compatibleMachineFamiliesChanged.emit()
|
||||
|
||||
@pyqtProperty(QUrl, notify=previewImageChanged)
|
||||
def previewImageUrl(self):
|
||||
self._preview_image_id += 1
|
||||
# There is an image provider that is called "camera". In order to ensure that the image qml object, that
|
||||
# requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
|
||||
# as new (instead of relying on cached version and thus forces an update.
|
||||
temp = "image://print_job_preview/" + str(self._preview_image_id) + "/" + self._key
|
||||
return QUrl(temp, QUrl.TolerantMode)
|
||||
|
||||
def getPreviewImage(self) -> Optional[QImage]:
|
||||
return self._preview_image
|
||||
|
||||
def updatePreviewImage(self, preview_image: Optional[QImage]) -> None:
|
||||
if self._preview_image != preview_image:
|
||||
self._preview_image = preview_image
|
||||
self.previewImageChanged.emit()
|
||||
|
||||
@pyqtProperty(QObject, notify=configurationChanged)
|
||||
def configuration(self) -> Optional["ConfigurationModel"]:
|
||||
return self._configuration
|
||||
|
||||
def updateConfiguration(self, configuration: Optional["ConfigurationModel"]) -> None:
|
||||
if self._configuration != configuration:
|
||||
self._configuration = configuration
|
||||
self.configurationChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify=ownerChanged)
|
||||
def owner(self):
|
||||
return self._owner
|
||||
|
|
|
@ -43,9 +43,9 @@ class PrinterOutputModel(QObject):
|
|||
self._is_preheating = False
|
||||
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._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
|
||||
self._extruders]
|
||||
|
||||
self._camera = None
|
||||
|
||||
|
@ -283,7 +283,3 @@ class PrinterOutputModel(QObject):
|
|||
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()
|
||||
|
|
|
@ -304,7 +304,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
if self._global_stack:
|
||||
self._global_stack.propertyChanged.disconnect(self._onSettingValueChanged)
|
||||
self._global_stack.containersChanged.disconnect(self._onChanged)
|
||||
extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_stack.getId())
|
||||
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
for extruder in extruders:
|
||||
extruder.propertyChanged.disconnect(self._onSettingValueChanged)
|
||||
|
||||
|
@ -314,7 +314,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
|||
self._global_stack.propertyChanged.connect(self._onSettingValueChanged)
|
||||
self._global_stack.containersChanged.connect(self._onChanged)
|
||||
|
||||
extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_stack.getId())
|
||||
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
for extruder in extruders:
|
||||
extruder.propertyChanged.connect(self._onSettingValueChanged)
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
import os
|
||||
import urllib.parse
|
||||
import uuid
|
||||
from typing import Any
|
||||
from typing import Dict, Union, Optional
|
||||
from typing import Dict, Union, Any, TYPE_CHECKING, List
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl, QVariant
|
||||
from PyQt5.QtCore import QObject, QUrl
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Logger import Logger
|
||||
|
@ -21,6 +21,18 @@ from UM.Settings.ContainerStack import ContainerStack
|
|||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerNode import ContainerNode
|
||||
from cura.Machines.MaterialNode import MaterialNode
|
||||
from cura.Machines.QualityChangesGroup import QualityChangesGroup
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from cura.Settings.MachineManager import MachineManager
|
||||
from cura.Machines.MaterialManager import MaterialManager
|
||||
from cura.Machines.QualityManager import QualityManager
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
|
@ -31,19 +43,19 @@ catalog = i18nCatalog("cura")
|
|||
# when a certain action happens. This can be done through this class.
|
||||
class ContainerManager(QObject):
|
||||
|
||||
def __init__(self, application):
|
||||
def __init__(self, application: "CuraApplication") -> None:
|
||||
if ContainerManager.__instance is not None:
|
||||
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
|
||||
ContainerManager.__instance = self
|
||||
|
||||
super().__init__(parent = application)
|
||||
|
||||
self._application = application
|
||||
self._plugin_registry = self._application.getPluginRegistry()
|
||||
self._container_registry = self._application.getContainerRegistry()
|
||||
self._machine_manager = self._application.getMachineManager()
|
||||
self._material_manager = self._application.getMaterialManager()
|
||||
self._quality_manager = self._application.getQualityManager()
|
||||
self._application = application # type: CuraApplication
|
||||
self._plugin_registry = self._application.getPluginRegistry() # type: PluginRegistry
|
||||
self._container_registry = self._application.getContainerRegistry() # type: ContainerRegistry
|
||||
self._machine_manager = self._application.getMachineManager() # type: MachineManager
|
||||
self._material_manager = self._application.getMaterialManager() # type: MaterialManager
|
||||
self._quality_manager = self._application.getQualityManager() # type: QualityManager
|
||||
self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
@pyqtSlot(str, str, result=str)
|
||||
|
@ -69,21 +81,23 @@ class ContainerManager(QObject):
|
|||
# by using "/" as a separator. For example, to change an entry "foo" in a
|
||||
# dictionary entry "bar", you can specify "bar/foo" as entry name.
|
||||
#
|
||||
# \param container_id \type{str} The ID of the container to change.
|
||||
# \param container_node \type{ContainerNode}
|
||||
# \param entry_name \type{str} The name of the metadata entry to change.
|
||||
# \param entry_value The new value of the entry.
|
||||
#
|
||||
# \return True if successful, False if not.
|
||||
# TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
|
||||
# Update: In order for QML to use objects and sub objects, those (sub) objects must all be QObject. Is that what we want?
|
||||
@pyqtSlot("QVariant", str, str)
|
||||
def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
|
||||
root_material_id = container_node.metadata["base_file"]
|
||||
def setContainerMetaDataEntry(self, container_node: "ContainerNode", entry_name: str, entry_value: str) -> bool:
|
||||
root_material_id = container_node.getMetaDataEntry("base_file", "")
|
||||
if self._container_registry.isReadOnly(root_material_id):
|
||||
Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
|
||||
return False
|
||||
|
||||
material_group = self._material_manager.getMaterialGroup(root_material_id)
|
||||
if material_group is None:
|
||||
Logger.log("w", "Unable to find material group for: %s.", root_material_id)
|
||||
return False
|
||||
|
||||
entries = entry_name.split("/")
|
||||
entry_name = entries.pop()
|
||||
|
@ -91,7 +105,7 @@ class ContainerManager(QObject):
|
|||
sub_item_changed = False
|
||||
if entries:
|
||||
root_name = entries.pop(0)
|
||||
root = material_group.root_material_node.metadata.get(root_name)
|
||||
root = material_group.root_material_node.getMetaDataEntry(root_name)
|
||||
|
||||
item = root
|
||||
for _ in range(len(entries)):
|
||||
|
@ -109,9 +123,10 @@ class ContainerManager(QObject):
|
|||
container.setMetaDataEntry(entry_name, entry_value)
|
||||
if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed.
|
||||
container.metaDataChanged.emit(container)
|
||||
return True
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def makeUniqueName(self, original_name):
|
||||
def makeUniqueName(self, original_name: str) -> str:
|
||||
return self._container_registry.uniqueName(original_name)
|
||||
|
||||
## Get a list of string that can be used as name filters for a Qt File Dialog
|
||||
|
@ -125,7 +140,7 @@ class ContainerManager(QObject):
|
|||
#
|
||||
# \return A string list with name filters.
|
||||
@pyqtSlot(str, result = "QStringList")
|
||||
def getContainerNameFilters(self, type_name):
|
||||
def getContainerNameFilters(self, type_name: str) -> List[str]:
|
||||
if not self._container_name_filters:
|
||||
self._updateContainerNameFilters()
|
||||
|
||||
|
@ -257,7 +272,7 @@ class ContainerManager(QObject):
|
|||
#
|
||||
# \return \type{bool} True if successful, False if not.
|
||||
@pyqtSlot(result = bool)
|
||||
def updateQualityChanges(self):
|
||||
def updateQualityChanges(self) -> bool:
|
||||
global_stack = self._machine_manager.activeMachine
|
||||
if not global_stack:
|
||||
return False
|
||||
|
@ -313,10 +328,10 @@ class ContainerManager(QObject):
|
|||
# \param material_id \type{str} the id of the material for which to get the linked materials.
|
||||
# \return \type{list} a list of names of materials with the same GUID
|
||||
@pyqtSlot("QVariant", bool, result = "QStringList")
|
||||
def getLinkedMaterials(self, material_node, exclude_self = False):
|
||||
guid = material_node.metadata["GUID"]
|
||||
def getLinkedMaterials(self, material_node: "MaterialNode", exclude_self: bool = False):
|
||||
guid = material_node.getMetaDataEntry("GUID", "")
|
||||
|
||||
self_root_material_id = material_node.metadata["base_file"]
|
||||
self_root_material_id = material_node.getMetaDataEntry("base_file")
|
||||
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
|
||||
|
||||
linked_material_names = []
|
||||
|
@ -324,15 +339,19 @@ class ContainerManager(QObject):
|
|||
for material_group in material_group_list:
|
||||
if exclude_self and material_group.name == self_root_material_id:
|
||||
continue
|
||||
linked_material_names.append(material_group.root_material_node.metadata["name"])
|
||||
linked_material_names.append(material_group.root_material_node.getMetaDataEntry("name", ""))
|
||||
return linked_material_names
|
||||
|
||||
## Unlink a material from all other materials by creating a new GUID
|
||||
# \param material_id \type{str} the id of the material to create a new GUID for.
|
||||
@pyqtSlot("QVariant")
|
||||
def unlinkMaterial(self, material_node):
|
||||
def unlinkMaterial(self, material_node: "MaterialNode") -> None:
|
||||
# Get the material group
|
||||
material_group = self._material_manager.getMaterialGroup(material_node.metadata["base_file"])
|
||||
material_group = self._material_manager.getMaterialGroup(material_node.getMetaDataEntry("base_file", ""))
|
||||
|
||||
if material_group is None:
|
||||
Logger.log("w", "Unable to find material group for %s", material_node)
|
||||
return
|
||||
|
||||
# Generate a new GUID
|
||||
new_guid = str(uuid.uuid4())
|
||||
|
@ -344,7 +363,7 @@ class ContainerManager(QObject):
|
|||
if container is not None:
|
||||
container.setMetaDataEntry("GUID", new_guid)
|
||||
|
||||
def _performMerge(self, merge_into, merge, clear_settings = True):
|
||||
def _performMerge(self, merge_into: InstanceContainer, merge: InstanceContainer, clear_settings: bool = True) -> None:
|
||||
if merge == merge_into:
|
||||
return
|
||||
|
||||
|
@ -400,7 +419,7 @@ class ContainerManager(QObject):
|
|||
|
||||
## Import single profile, file_url does not have to end with curaprofile
|
||||
@pyqtSlot(QUrl, result="QVariantMap")
|
||||
def importProfile(self, file_url):
|
||||
def importProfile(self, file_url: QUrl):
|
||||
if not file_url.isValid():
|
||||
return
|
||||
path = file_url.toLocalFile()
|
||||
|
@ -409,7 +428,7 @@ class ContainerManager(QObject):
|
|||
return self._container_registry.importProfile(path)
|
||||
|
||||
@pyqtSlot(QObject, QUrl, str)
|
||||
def exportQualityChangesGroup(self, quality_changes_group, file_url: QUrl, file_type: str):
|
||||
def exportQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", file_url: QUrl, file_type: str) -> None:
|
||||
if not file_url.isValid():
|
||||
return
|
||||
path = file_url.toLocalFile()
|
||||
|
|
|
@ -12,15 +12,15 @@ from UM.Scene.Selection import Selection
|
|||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
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, Dict
|
||||
from typing import Optional, TYPE_CHECKING, Dict, List, Any, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
|
||||
## Manages all existing extruder stacks.
|
||||
|
@ -38,9 +38,13 @@ class ExtruderManager(QObject):
|
|||
|
||||
self._application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
|
||||
self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
|
||||
# Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
|
||||
self._extruder_trains = {} # type: Dict[str, Dict[str, "ExtruderStack"]]
|
||||
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
|
||||
self._selected_object_extruders = []
|
||||
|
||||
# TODO; I have no idea why this is a union of ID's and extruder stacks. This needs to be fixed at some point.
|
||||
self._selected_object_extruders = [] # type: List[Union[str, "ExtruderStack"]]
|
||||
|
||||
self._addCurrentMachineExtruders()
|
||||
|
||||
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
||||
|
@ -68,7 +72,7 @@ class ExtruderManager(QObject):
|
|||
|
||||
## Return extruder count according to extruder trains.
|
||||
@pyqtProperty(int, notify = extrudersChanged)
|
||||
def extruderCount(self):
|
||||
def extruderCount(self) -> int:
|
||||
if not self._application.getGlobalContainerStack():
|
||||
return 0 # No active machine, so no extruders.
|
||||
try:
|
||||
|
@ -79,28 +83,14 @@ class ExtruderManager(QObject):
|
|||
## Gets a dict with the extruder stack ids with the extruder number as the key.
|
||||
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
||||
def extruderIds(self) -> Dict[str, str]:
|
||||
extruder_stack_ids = {}
|
||||
extruder_stack_ids = {} # type: Dict[str, str]
|
||||
|
||||
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()
|
||||
extruder_stack_ids = {position: extruder.id for position, extruder in global_container_stack.extruders.items()}
|
||||
|
||||
return extruder_stack_ids
|
||||
|
||||
@pyqtSlot(str, result = str)
|
||||
def getQualityChangesIdByExtruderStackId(self, extruder_stack_id: str) -> str:
|
||||
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.
|
||||
#
|
||||
# \param index The index of the new active extruder.
|
||||
|
@ -117,9 +107,9 @@ class ExtruderManager(QObject):
|
|||
#
|
||||
# \param index The index of the extruder whose name to get.
|
||||
@pyqtSlot(int, result = str)
|
||||
def getExtruderName(self, index):
|
||||
def getExtruderName(self, index: int) -> str:
|
||||
try:
|
||||
return list(self.getActiveExtruderStacks())[index].getName()
|
||||
return self.getActiveExtruderStacks()[index].getName()
|
||||
except IndexError:
|
||||
return ""
|
||||
|
||||
|
@ -128,12 +118,12 @@ class ExtruderManager(QObject):
|
|||
|
||||
## Provides a list of extruder IDs used by the current selected objects.
|
||||
@pyqtProperty("QVariantList", notify = selectedObjectExtrudersChanged)
|
||||
def selectedObjectExtruders(self) -> List[str]:
|
||||
def selectedObjectExtruders(self) -> List[Union[str, "ExtruderStack"]]:
|
||||
if not self._selected_object_extruders:
|
||||
object_extruders = set()
|
||||
|
||||
# First, build a list of the actual selected objects (including children of groups, excluding group nodes)
|
||||
selected_nodes = []
|
||||
selected_nodes = [] # type: List["SceneNode"]
|
||||
for node in Selection.getAllSelectedObjects():
|
||||
if node.callDecoration("isGroup"):
|
||||
for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||
|
@ -145,16 +135,15 @@ class ExtruderManager(QObject):
|
|||
selected_nodes.append(node)
|
||||
|
||||
# Then, figure out which nodes are used by those selected nodes.
|
||||
global_stack = self._application.getGlobalContainerStack()
|
||||
current_extruder_trains = self._extruder_trains.get(global_stack.getId())
|
||||
current_extruder_trains = self.getActiveExtruderStacks()
|
||||
for node in selected_nodes:
|
||||
extruder = node.callDecoration("getActiveExtruder")
|
||||
if extruder:
|
||||
object_extruders.add(extruder)
|
||||
elif current_extruder_trains:
|
||||
object_extruders.add(current_extruder_trains["0"].getId())
|
||||
object_extruders.add(current_extruder_trains[0].getId())
|
||||
|
||||
self._selected_object_extruders = list(object_extruders)
|
||||
self._selected_object_extruders = list(object_extruders) # type: List[Union[str, "ExtruderStack"]]
|
||||
|
||||
return self._selected_object_extruders
|
||||
|
||||
|
@ -163,19 +152,12 @@ class ExtruderManager(QObject):
|
|||
# This will trigger a recalculation of the extruders used for the
|
||||
# selection.
|
||||
def resetSelectedObjectExtruders(self) -> None:
|
||||
self._selected_object_extruders = []
|
||||
self._selected_object_extruders = [] # type: List[Union[str, "ExtruderStack"]]
|
||||
self.selectedObjectExtrudersChanged.emit()
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
|
||||
global_container_stack = self._application.getGlobalContainerStack()
|
||||
|
||||
if global_container_stack:
|
||||
if global_container_stack.getId() in self._extruder_trains:
|
||||
if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]:
|
||||
return self._extruder_trains[global_container_stack.getId()][str(self._active_extruder_index)]
|
||||
|
||||
return None
|
||||
return self.getExtruderStack(self._active_extruder_index)
|
||||
|
||||
## Get an extruder stack by index
|
||||
def getExtruderStack(self, index) -> Optional["ExtruderStack"]:
|
||||
|
@ -186,16 +168,7 @@ class ExtruderManager(QObject):
|
|||
return self._extruder_trains[global_container_stack.getId()][str(index)]
|
||||
return None
|
||||
|
||||
## Get all extruder stacks
|
||||
def getExtruderStacks(self) -> List["ExtruderStack"]:
|
||||
result = []
|
||||
for i in range(self.extruderCount):
|
||||
stack = self.getExtruderStack(i)
|
||||
if stack:
|
||||
result.append(stack)
|
||||
return result
|
||||
|
||||
def registerExtruder(self, extruder_train, machine_id):
|
||||
def registerExtruder(self, extruder_train: "ExtruderStack", machine_id: str) -> None:
|
||||
changed = False
|
||||
|
||||
if machine_id not in self._extruder_trains:
|
||||
|
@ -214,23 +187,20 @@ class ExtruderManager(QObject):
|
|||
if changed:
|
||||
self.extrudersChanged.emit(machine_id)
|
||||
|
||||
def getAllExtruderValues(self, setting_key):
|
||||
return self.getAllExtruderSettings(setting_key, "value")
|
||||
|
||||
## Gets a property of a setting for all extruders.
|
||||
#
|
||||
# \param setting_key \type{str} The setting to get the property of.
|
||||
# \param property \type{str} The property to get.
|
||||
# \return \type{List} the list of results
|
||||
def getAllExtruderSettings(self, setting_key: str, prop: str):
|
||||
def getAllExtruderSettings(self, setting_key: str, prop: str) -> List:
|
||||
result = []
|
||||
for index in self.extruderIds:
|
||||
extruder_stack_id = self.extruderIds[str(index)]
|
||||
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
||||
|
||||
for extruder_stack in self.getActiveExtruderStacks():
|
||||
result.append(extruder_stack.getProperty(setting_key, prop))
|
||||
|
||||
return result
|
||||
|
||||
def extruderValueWithDefault(self, value):
|
||||
def extruderValueWithDefault(self, value: str) -> str:
|
||||
machine_manager = self._application.getMachineManager()
|
||||
if value == "-1":
|
||||
return machine_manager.defaultExtruderPosition
|
||||
|
@ -321,7 +291,7 @@ class ExtruderManager(QObject):
|
|||
## Removes the container stack and user profile for the extruders for a specific machine.
|
||||
#
|
||||
# \param machine_id The machine to remove the extruders for.
|
||||
def removeMachineExtruders(self, machine_id: str):
|
||||
def removeMachineExtruders(self, machine_id: str) -> None:
|
||||
for extruder in self.getMachineExtruders(machine_id):
|
||||
ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId())
|
||||
ContainerRegistry.getInstance().removeContainer(extruder.getId())
|
||||
|
@ -331,24 +301,11 @@ class ExtruderManager(QObject):
|
|||
## Returns extruders for a specific machine.
|
||||
#
|
||||
# \param machine_id The machine to get the extruders of.
|
||||
def getMachineExtruders(self, machine_id: str):
|
||||
def getMachineExtruders(self, machine_id: str) -> List["ExtruderStack"]:
|
||||
if machine_id not in self._extruder_trains:
|
||||
return []
|
||||
return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
|
||||
|
||||
## Returns a list containing the global stack and active extruder stacks.
|
||||
#
|
||||
# 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 = self._application.getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return None
|
||||
|
||||
result = [global_stack]
|
||||
result.extend(self.getActiveExtruderStacks())
|
||||
return result
|
||||
|
||||
## Returns the list of active extruder stacks, taking into account the machine extruder count.
|
||||
#
|
||||
# \return \type{List[ContainerStack]} a list of
|
||||
|
@ -357,10 +314,7 @@ class ExtruderManager(QObject):
|
|||
if not global_stack:
|
||||
return []
|
||||
|
||||
result = []
|
||||
if global_stack.getId() in self._extruder_trains:
|
||||
for extruder in sorted(self._extruder_trains[global_stack.getId()]):
|
||||
result.append(self._extruder_trains[global_stack.getId()][extruder])
|
||||
result = list(global_stack.extruders.values())
|
||||
|
||||
machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value")
|
||||
|
||||
|
@ -406,7 +360,7 @@ class ExtruderManager(QObject):
|
|||
|
||||
# After 3.4, all single-extrusion machines have their own extruder definition files instead of reusing
|
||||
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.
|
||||
def _fixSingleExtrusionMachineExtruderDefinition(self, global_stack):
|
||||
def _fixSingleExtrusionMachineExtruderDefinition(self, global_stack: "GlobalStack") -> None:
|
||||
expected_extruder_definition_0_id = global_stack.getMetaDataEntry("machine_extruder_trains")["0"]
|
||||
extruder_stack_0 = global_stack.extruders["0"]
|
||||
if extruder_stack_0.definition.getId() != expected_extruder_definition_0_id:
|
||||
|
@ -425,11 +379,11 @@ class ExtruderManager(QObject):
|
|||
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
||||
# If no extruder has the value, the list will contain the global value.
|
||||
@staticmethod
|
||||
def getExtruderValues(key):
|
||||
def getExtruderValues(key: str) -> List[Any]:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
|
||||
result = []
|
||||
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
||||
for extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
if not extruder.isEnabled:
|
||||
continue
|
||||
# only include values from extruders that are "active" for the current machine instance
|
||||
|
@ -460,7 +414,7 @@ class ExtruderManager(QObject):
|
|||
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
||||
# If no extruder has the value, the list will contain the global value.
|
||||
@staticmethod
|
||||
def getDefaultExtruderValues(key):
|
||||
def getDefaultExtruderValues(key: str) -> List[Any]:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
context = PropertyEvaluationContext(global_stack)
|
||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||
|
@ -471,7 +425,7 @@ class ExtruderManager(QObject):
|
|||
}
|
||||
|
||||
result = []
|
||||
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
||||
for extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
# only include values from extruders that are "active" for the current machine instance
|
||||
if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context):
|
||||
continue
|
||||
|
@ -504,7 +458,7 @@ class ExtruderManager(QObject):
|
|||
#
|
||||
# \return String representing the extruder values
|
||||
@pyqtSlot(str, result="QVariant")
|
||||
def getInstanceExtruderValues(self, key):
|
||||
def getInstanceExtruderValues(self, key) -> List:
|
||||
return ExtruderManager.getExtruderValues(key)
|
||||
|
||||
## Get the value for a setting from a specific extruder.
|
||||
|
@ -517,7 +471,7 @@ class ExtruderManager(QObject):
|
|||
# \return The value of the setting for the specified extruder or for the
|
||||
# global stack if not found.
|
||||
@staticmethod
|
||||
def getExtruderValue(extruder_index, key):
|
||||
def getExtruderValue(extruder_index: int, key: str) -> Any:
|
||||
if extruder_index == -1:
|
||||
extruder_index = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition)
|
||||
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
||||
|
@ -542,7 +496,7 @@ class ExtruderManager(QObject):
|
|||
# \return The value of the setting for the specified extruder or for the
|
||||
# global stack if not found.
|
||||
@staticmethod
|
||||
def getDefaultExtruderValue(extruder_index, key):
|
||||
def getDefaultExtruderValue(extruder_index: int, key: str) -> Any:
|
||||
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
||||
context = PropertyEvaluationContext(extruder)
|
||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||
|
@ -569,7 +523,7 @@ class ExtruderManager(QObject):
|
|||
#
|
||||
# \return The effective value
|
||||
@staticmethod
|
||||
def getResolveOrValue(key):
|
||||
def getResolveOrValue(key: str) -> Any:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
resolved_value = global_stack.getProperty(key, "value")
|
||||
|
||||
|
@ -583,7 +537,7 @@ class ExtruderManager(QObject):
|
|||
#
|
||||
# \return The effective value
|
||||
@staticmethod
|
||||
def getDefaultResolveOrValue(key):
|
||||
def getDefaultResolveOrValue(key: str) -> Any:
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
context = PropertyEvaluationContext(global_stack)
|
||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||
|
|
|
@ -134,7 +134,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
# Link to new extruders
|
||||
self._active_machine_extruders = []
|
||||
extruder_manager = Application.getInstance().getExtruderManager()
|
||||
for extruder in extruder_manager.getExtruderStacks():
|
||||
for extruder in extruder_manager.getActiveExtruderStacks():
|
||||
if extruder is None: #This extruder wasn't loaded yet. This happens asynchronously while this model is constructed from QML.
|
||||
continue
|
||||
extruder.containersChanged.connect(self._onExtruderStackContainersChanged)
|
||||
|
@ -171,7 +171,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
|||
# get machine extruder count for verification
|
||||
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
|
||||
|
||||
for extruder in Application.getInstance().getExtruderManager().getMachineExtruders(global_container_stack.getId()):
|
||||
for extruder in Application.getInstance().getExtruderManager().getActiveExtruderStacks():
|
||||
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
||||
try:
|
||||
position = int(position)
|
||||
|
|
|
@ -13,6 +13,8 @@ from UM.Settings.SettingInstance import InstanceState
|
|||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import PropertyEvaluationContext
|
||||
from UM.Logger import Logger
|
||||
from UM.Util import parseBool
|
||||
|
||||
import cura.CuraApplication
|
||||
|
||||
from . import Exceptions
|
||||
|
@ -188,6 +190,15 @@ class GlobalStack(CuraContainerStack):
|
|||
def getHeadAndFansCoordinates(self):
|
||||
return self.getProperty("machine_head_with_fans_polygon", "value")
|
||||
|
||||
def getHasMaterials(self) -> bool:
|
||||
return parseBool(self.getMetaDataEntry("has_materials", False))
|
||||
|
||||
def getHasVariants(self) -> bool:
|
||||
return parseBool(self.getMetaDataEntry("has_variants", False))
|
||||
|
||||
def getHasMachineQuality(self) -> bool:
|
||||
return parseBool(self.getMetaDataEntry("has_machine_quality", False))
|
||||
|
||||
|
||||
## private:
|
||||
global_stack_mime = MimeType(
|
||||
|
|
|
@ -385,7 +385,9 @@ class MachineManager(QObject):
|
|||
# \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, metadata_filter: Dict[str, str] = None) -> Optional["GlobalStack"]:
|
||||
def getMachine(definition_id: str, metadata_filter: Optional[Dict[str, str]] = None) -> Optional["GlobalStack"]:
|
||||
if metadata_filter is None:
|
||||
metadata_filter = {}
|
||||
machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||
for machine in machines:
|
||||
if machine.definition.getId() == definition_id:
|
||||
|
@ -412,7 +414,7 @@ class MachineManager(QObject):
|
|||
|
||||
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
|
||||
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
||||
extruder_stacks = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
||||
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
count = 1 # we start with the global stack
|
||||
for stack in extruder_stacks:
|
||||
md = stack.getMetaData()
|
||||
|
@ -435,7 +437,7 @@ class MachineManager(QObject):
|
|||
if self._global_container_stack.getTop().findInstances():
|
||||
return True
|
||||
|
||||
stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
for stack in stacks:
|
||||
if stack.getTop().findInstances():
|
||||
return True
|
||||
|
@ -448,7 +450,7 @@ class MachineManager(QObject):
|
|||
return 0
|
||||
num_user_settings = 0
|
||||
num_user_settings += len(self._global_container_stack.getTop().findInstances())
|
||||
stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
for stack in stacks:
|
||||
num_user_settings += len(stack.getTop().findInstances())
|
||||
return num_user_settings
|
||||
|
@ -473,7 +475,7 @@ class MachineManager(QObject):
|
|||
stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
stacks = [stack]
|
||||
else:
|
||||
stacks = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
||||
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
|
||||
for stack in stacks:
|
||||
if stack is not None:
|
||||
|
@ -638,7 +640,7 @@ class MachineManager(QObject):
|
|||
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())]
|
||||
extruder_stacks = [stack for stack in ExtruderManager.getInstance().getActiveExtruderStacks()]
|
||||
|
||||
# check in which stack the value has to be replaced
|
||||
for extruder_stack in extruder_stacks:
|
||||
|
@ -890,7 +892,11 @@ class MachineManager(QObject):
|
|||
extruder_nr = node.callDecoration("getActiveExtruderPosition")
|
||||
|
||||
if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
|
||||
node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId())
|
||||
extruder = extruder_manager.getExtruderStack(extruder_count - 1)
|
||||
if extruder is not None:
|
||||
node.callDecoration("setActiveExtruder", extruder.getId())
|
||||
else:
|
||||
Logger.log("w", "Could not find extruder to set active.")
|
||||
|
||||
# Make sure one of the extruder stacks is active
|
||||
extruder_manager.setActiveExtruderIndex(0)
|
||||
|
|
|
@ -43,7 +43,9 @@ class UserChangesModel(ListModel):
|
|||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return
|
||||
stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
||||
|
||||
stacks = [global_stack]
|
||||
stacks.extend(global_stack.extruders.values())
|
||||
|
||||
# Check if the definition container has a translation file and ensure it's loaded.
|
||||
definition = global_stack.getBottom()
|
||||
|
|
|
@ -225,7 +225,7 @@ class ThreeMFReader(MeshReader):
|
|||
|
||||
except Exception:
|
||||
Logger.logException("e", "An exception occurred in 3mf reader.")
|
||||
return []
|
||||
return None
|
||||
|
||||
return result
|
||||
|
||||
|
|
|
@ -85,14 +85,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
name="application/x-curaproject+xml",
|
||||
comment="Cura Project File",
|
||||
suffixes=["curaproject.3mf"]
|
||||
)
|
||||
)
|
||||
|
||||
self._supported_extensions = [".3mf"]
|
||||
self._dialog = WorkspaceDialog()
|
||||
self._3mf_mesh_reader = None
|
||||
|
@ -726,8 +718,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
nodes = []
|
||||
|
||||
base_file_name = os.path.basename(file_name)
|
||||
if base_file_name.endswith(".curaproject.3mf"):
|
||||
base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")]
|
||||
self.setWorkspaceName(base_file_name)
|
||||
return nodes
|
||||
|
||||
|
@ -944,7 +934,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
root_material_id)
|
||||
|
||||
if material_node is not None and material_node.getContainer() is not None:
|
||||
extruder_stack.material = material_node.getContainer()
|
||||
extruder_stack.material = material_node.getContainer() # type: InstanceContainer
|
||||
|
||||
def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
|
||||
# Clear all first
|
||||
|
|
|
@ -18,11 +18,7 @@ catalog = i18nCatalog("cura")
|
|||
|
||||
|
||||
def getMetaData() -> Dict:
|
||||
# Workaround for osx not supporting double file extensions correctly.
|
||||
if Platform.isOSX():
|
||||
workspace_extension = "3mf"
|
||||
else:
|
||||
workspace_extension = "curaproject.3mf"
|
||||
|
||||
metaData = {}
|
||||
if "3MFReader.ThreeMFReader" in sys.modules:
|
||||
|
|
|
@ -15,11 +15,7 @@ from UM.Platform import Platform
|
|||
i18n_catalog = i18nCatalog("uranium")
|
||||
|
||||
def getMetaData():
|
||||
# Workarround for osx not supporting double file extensions correctly.
|
||||
if Platform.isOSX():
|
||||
workspace_extension = "3mf"
|
||||
else:
|
||||
workspace_extension = "curaproject.3mf"
|
||||
|
||||
metaData = {}
|
||||
|
||||
|
@ -36,7 +32,7 @@ def getMetaData():
|
|||
"output": [{
|
||||
"extension": workspace_extension,
|
||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
|
||||
"mime_type": "application/x-curaproject+xml",
|
||||
"mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
|
||||
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -342,7 +342,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
if not self._global_container_stack:
|
||||
Logger.log("w", "Global container stack not assigned to CuraEngineBackend!")
|
||||
return
|
||||
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||
error_keys = [] #type: List[str]
|
||||
for extruder in extruders:
|
||||
error_keys.extend(extruder.getErrorKeys())
|
||||
|
|
|
@ -178,7 +178,7 @@ class ProcessSlicedLayersJob(Job):
|
|||
# Find out colors per extruder
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
manager = ExtruderManager.getInstance()
|
||||
extruders = list(manager.getMachineExtruders(global_container_stack.getId()))
|
||||
extruders = manager.getActiveExtruderStacks()
|
||||
if extruders:
|
||||
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
|
||||
for extruder in extruders:
|
||||
|
|
|
@ -333,7 +333,7 @@ class StartSliceJob(Job):
|
|||
"-1": self._buildReplacementTokens(global_stack)
|
||||
}
|
||||
|
||||
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
||||
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
|
||||
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
|
||||
|
||||
|
|
|
@ -275,7 +275,7 @@ class FlavorParser:
|
|||
## For showing correct x, y offsets for each extruder
|
||||
def _extruderOffsets(self) -> Dict[int, List[float]]:
|
||||
result = {}
|
||||
for extruder in ExtruderManager.getInstance().getExtruderStacks():
|
||||
for extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
result[int(extruder.getMetaData().get("position", "0"))] = [
|
||||
extruder.getProperty("machine_nozzle_offset_x", "value"),
|
||||
extruder.getProperty("machine_nozzle_offset_y", "value")]
|
||||
|
|
|
@ -16,6 +16,8 @@ Cura.MachineAction
|
|||
property var extrudersModel: Cura.ExtrudersModel{}
|
||||
property int extruderTabsCount: 0
|
||||
|
||||
property var activeMachineId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.id : ""
|
||||
|
||||
Connections
|
||||
{
|
||||
target: base.extrudersModel
|
||||
|
@ -511,7 +513,7 @@ Cura.MachineAction
|
|||
}
|
||||
return "";
|
||||
}
|
||||
return Cura.MachineManager.activeMachineId;
|
||||
return base.activeMachineId
|
||||
}
|
||||
key: settingKey
|
||||
watchedProperties: [ "value", "description" ]
|
||||
|
@ -564,7 +566,7 @@ Cura.MachineAction
|
|||
}
|
||||
return "";
|
||||
}
|
||||
return Cura.MachineManager.activeMachineId;
|
||||
return base.activeMachineId
|
||||
}
|
||||
key: settingKey
|
||||
watchedProperties: [ "value", "description" ]
|
||||
|
@ -655,7 +657,7 @@ Cura.MachineAction
|
|||
}
|
||||
return "";
|
||||
}
|
||||
return Cura.MachineManager.activeMachineId;
|
||||
return base.activeMachineId
|
||||
}
|
||||
key: settingKey
|
||||
watchedProperties: [ "value", "options", "description" ]
|
||||
|
@ -754,7 +756,7 @@ Cura.MachineAction
|
|||
}
|
||||
return "";
|
||||
}
|
||||
return Cura.MachineManager.activeMachineId;
|
||||
return base.activeMachineId
|
||||
}
|
||||
key: settingKey
|
||||
watchedProperties: [ "value", "description" ]
|
||||
|
@ -879,7 +881,7 @@ Cura.MachineAction
|
|||
{
|
||||
id: machineExtruderCountProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStackId: base.activeMachineId
|
||||
key: "machine_extruder_count"
|
||||
watchedProperties: [ "value", "description" ]
|
||||
storeIndex: manager.containerIndex
|
||||
|
@ -889,7 +891,7 @@ Cura.MachineAction
|
|||
{
|
||||
id: machineHeadPolygonProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStackId: base.acthiveMachineId
|
||||
key: "machine_head_with_fans_polygon"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: manager.containerIndex
|
||||
|
|
|
@ -17,7 +17,6 @@ Item {
|
|||
|
||||
width: childrenRect.width;
|
||||
height: childrenRect.height;
|
||||
|
||||
property var all_categories_except_support: [ "machine_settings", "resolution", "shell", "infill", "material", "speed",
|
||||
"travel", "cooling", "platform_adhesion", "dual", "meshfix", "blackmagic", "experimental"]
|
||||
|
||||
|
@ -45,7 +44,7 @@ Item {
|
|||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: meshTypePropertyProvider
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStack: Cura.MachineManager.activeMachine
|
||||
watchedProperties: [ "enabled" ]
|
||||
}
|
||||
|
||||
|
@ -518,7 +517,7 @@ Item {
|
|||
{
|
||||
id: machineExtruderCount
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStack: Cura.MachineManager.activeMachine
|
||||
key: "machine_extruder_count"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 0
|
||||
|
@ -528,7 +527,7 @@ Item {
|
|||
{
|
||||
id: printSequencePropertyProvider
|
||||
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStack: Cura.MachineManager.activeMachine
|
||||
key: "print_sequence"
|
||||
watchedProperties: [ "value" ]
|
||||
storeIndex: 0
|
||||
|
|
|
@ -384,7 +384,7 @@ UM.Dialog
|
|||
UM.SettingPropertyProvider
|
||||
{
|
||||
id: inheritStackProvider
|
||||
containerStackId: Cura.MachineManager.activeMachineId
|
||||
containerStack: Cura.MachineManager.activeMachine
|
||||
key: model.key ? model.key : "None"
|
||||
watchedProperties: [ "limit_to_extruder" ]
|
||||
}
|
||||
|
|
|
@ -44,12 +44,11 @@ UM.PointingRectangle {
|
|||
id: valueLabel
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
|
||||
verticalCenter: parent.verticalCenter
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
width: maximumValue.toString().length * 12 * screenScaleFactor
|
||||
width: (maximumValue.toString().length + 1) * 10 * screenScaleFactor
|
||||
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
|
||||
horizontalAlignment: TextInput.AlignRight
|
||||
|
||||
|
|
|
@ -667,8 +667,11 @@ Item
|
|||
{
|
||||
target: UM.SimulationView
|
||||
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
||||
onCurrentLayerChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||
onCurrentLayerChanged:
|
||||
{
|
||||
playButton.pauseSimulation()
|
||||
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||
}
|
||||
}
|
||||
|
||||
// make sure the slider handlers show the correct value after switching views
|
||||
|
@ -723,6 +726,7 @@ Item
|
|||
UM.SimulationView.setSimulationRunning(true)
|
||||
iconSource = "./resources/simulation_pause.svg"
|
||||
simulationTimer.start()
|
||||
status = 1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -766,6 +770,7 @@ Item
|
|||
{
|
||||
UM.SimulationView.setCurrentLayer(currentLayer+1)
|
||||
UM.SimulationView.setCurrentPath(0)
|
||||
playButton.resumeSimulation()
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -82,9 +82,16 @@ Item
|
|||
}
|
||||
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
|
||||
width: childrenRect.width
|
||||
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Contact") + ":"
|
||||
text: catalog.i18nc("@label", "Website") + ":"
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Email") + ":"
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
}
|
||||
|
@ -100,18 +107,32 @@ Item
|
|||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
|
||||
|
||||
Label
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (details.website)
|
||||
{
|
||||
return "<a href=\"" + details.website + "\">" + details.website + "</a>"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (details.email)
|
||||
{
|
||||
return "<a href=\"mailto:"+details.email+"\">"+details.name+"</a>"
|
||||
}
|
||||
else
|
||||
{
|
||||
return "<a href=\""+details.website+"\">"+details.name+"</a>"
|
||||
return "<a href=\"mailto:" + details.email + "\">" + details.email + "</a>"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
|
|
|
@ -1,14 +1,25 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
|
||||
property var packageData
|
||||
property var technicalDataSheetUrl: {
|
||||
var link = undefined
|
||||
if ("Technical Data Sheet" in packageData.links)
|
||||
{
|
||||
link = packageData.links["Technical Data Sheet"]
|
||||
}
|
||||
return link
|
||||
}
|
||||
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
height: visible ? childrenRect.height : 0
|
||||
visible: packageData.type == "material" && packageData.has_configs
|
||||
|
@ -132,4 +143,25 @@ Item
|
|||
width: Math.floor(table.width * 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: technical_data_sheet
|
||||
anchors.top: table.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height / 2
|
||||
visible: base.technicalDataSheetUrl !== undefined
|
||||
text:
|
||||
{
|
||||
if (base.technicalDataSheetUrl !== undefined)
|
||||
{
|
||||
return "<a href='%1'>%2</a>".arg(base.technicalDataSheetUrl).arg("Technical Data Sheet")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
|
|
@ -32,7 +32,7 @@ Item
|
|||
width: UM.Theme.getSize("toolbox_thumbnail_medium").width
|
||||
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: details.icon_url || "../images/logobot.svg"
|
||||
source: details === null ? "" : (details.icon_url || "../images/logobot.svg")
|
||||
mipmap: true
|
||||
anchors
|
||||
{
|
||||
|
@ -55,7 +55,7 @@ Item
|
|||
rightMargin: UM.Theme.getSize("wide_margin").width
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
text: details.name || ""
|
||||
text: details === null ? "" : (details.name || "")
|
||||
font: UM.Theme.getFont("large")
|
||||
color: UM.Theme.getColor("text")
|
||||
wrapMode: Text.WordWrap
|
||||
|
@ -114,7 +114,7 @@ Item
|
|||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
text: details.version || catalog.i18nc("@label", "Unknown")
|
||||
text: details === null ? "" : (details.version || catalog.i18nc("@label", "Unknown"))
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
|
@ -122,6 +122,10 @@ Item
|
|||
{
|
||||
text:
|
||||
{
|
||||
if (details === null)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
var date = new Date(details.last_updated)
|
||||
return date.toLocaleString(UM.Preferences.getValue("general/language"))
|
||||
}
|
||||
|
@ -132,6 +136,10 @@ Item
|
|||
{
|
||||
text:
|
||||
{
|
||||
if (details === null)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
if (details.author_email)
|
||||
{
|
||||
return "<a href=\"mailto:" + details.author_email+"?Subject=Cura: " + details.name + "\">" + details.author_name + "</a>"
|
||||
|
@ -148,7 +156,7 @@ Item
|
|||
}
|
||||
Label
|
||||
{
|
||||
text: details.download_count || catalog.i18nc("@label", "Unknown")
|
||||
text: details === null ? "" : (details.download_count || catalog.i18nc("@label", "Unknown"))
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick 2.7
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import UM 1.1 as UM
|
||||
|
|
|
@ -9,7 +9,7 @@ import UM 1.1 as UM
|
|||
|
||||
Item
|
||||
{
|
||||
property int packageCount: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getTotalNumberOfPackagesByAuthor(model.id) : 1
|
||||
property int packageCount: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1
|
||||
property int installedPackages: (toolbox.viewCategory == "material" && model.type === undefined) ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
|
||||
height: childrenRect.height
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
|
||||
|
|
|
@ -12,7 +12,9 @@ ScrollView
|
|||
width: parent.width
|
||||
height: parent.height
|
||||
style: UM.Theme.styles.scrollview
|
||||
|
||||
flickableItem.flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
Column
|
||||
{
|
||||
width: base.width
|
||||
|
@ -30,7 +32,7 @@ ScrollView
|
|||
id: allPlugins
|
||||
width: parent.width
|
||||
heading: toolbox.viewCategory == "material" ? catalog.i18nc("@label", "Community Contributions") : catalog.i18nc("@label", "Community Plugins")
|
||||
model: toolbox.viewCategory == "material" ? toolbox.authorsModel : toolbox.packagesModel
|
||||
model: toolbox.viewCategory == "material" ? toolbox.materialsAvailableModel : toolbox.pluginsAvailableModel
|
||||
}
|
||||
|
||||
ToolboxDownloadsGrid
|
||||
|
|
|
@ -9,7 +9,7 @@ import UM 1.1 as UM
|
|||
|
||||
Rectangle
|
||||
{
|
||||
property int packageCount: toolbox.viewCategory == "material" ? toolbox.getTotalNumberOfPackagesByAuthor(model.id) : 1
|
||||
property int packageCount: toolbox.viewCategory == "material" ? toolbox.getTotalNumberOfMaterialPackagesByAuthor(model.id) : 1
|
||||
property int installedPackages: toolbox.viewCategory == "material" ? toolbox.getNumberOfInstalledPackagesByAuthor(model.id) : (toolbox.isInstalled(model.id) ? 1 : 0)
|
||||
id: tileBase
|
||||
width: UM.Theme.getSize("toolbox_thumbnail_large").width + (2 * UM.Theme.getSize("default_lining").width)
|
||||
|
|
|
@ -6,6 +6,7 @@ import QtQuick.Dialogs 1.1
|
|||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
ScrollView
|
||||
|
@ -16,6 +17,7 @@ ScrollView
|
|||
height: parent.height
|
||||
style: UM.Theme.styles.scrollview
|
||||
flickableItem.flickableDirection: Flickable.VerticalFlick
|
||||
|
||||
Column
|
||||
{
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
|
|
@ -33,6 +33,9 @@ class AuthorsModel(ListModel):
|
|||
|
||||
def _update(self):
|
||||
items = []
|
||||
if not self._metadata:
|
||||
self.setItems([])
|
||||
return
|
||||
|
||||
for author in self._metadata:
|
||||
items.append({
|
||||
|
|
|
@ -5,9 +5,13 @@ import re
|
|||
from typing import Dict
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtProperty
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
from .ConfigsModel import ConfigsModel
|
||||
|
||||
|
||||
## Model that holds cura packages. By setting the filter property the instances held by this model can be changed.
|
||||
class PackagesModel(ListModel):
|
||||
def __init__(self, parent = None):
|
||||
|
@ -34,6 +38,8 @@ class PackagesModel(ListModel):
|
|||
self.addRoleName(Qt.UserRole + 17, "supported_configs")
|
||||
self.addRoleName(Qt.UserRole + 18, "download_count")
|
||||
self.addRoleName(Qt.UserRole + 19, "tags")
|
||||
self.addRoleName(Qt.UserRole + 20, "links")
|
||||
self.addRoleName(Qt.UserRole + 21, "website")
|
||||
|
||||
# List of filters for queries. The result is the union of the each list of results.
|
||||
self._filter = {} # type: Dict[str, str]
|
||||
|
@ -45,10 +51,16 @@ class PackagesModel(ListModel):
|
|||
def _update(self):
|
||||
items = []
|
||||
|
||||
for package in self._metadata:
|
||||
if self._metadata is None:
|
||||
Logger.logException("w", "Failed to load packages for Toolbox")
|
||||
self.setItems(items)
|
||||
return
|
||||
|
||||
for package in self._metadata:
|
||||
has_configs = False
|
||||
configs_model = None
|
||||
|
||||
links_dict = {}
|
||||
if "data" in package:
|
||||
if "supported_configs" in package["data"]:
|
||||
if len(package["data"]["supported_configs"]) > 0:
|
||||
|
@ -56,10 +68,14 @@ class PackagesModel(ListModel):
|
|||
configs_model = ConfigsModel()
|
||||
configs_model.setConfigs(package["data"]["supported_configs"])
|
||||
|
||||
# Links is a list of dictionaries with "title" and "url". Convert this list into a dict so it's easier
|
||||
# to process.
|
||||
link_list = package['data']['links'] if 'links' in package['data'] else []
|
||||
links_dict = {d["title"]: d["url"] for d in link_list}
|
||||
|
||||
if "author_id" not in package["author"] or "display_name" not in package["author"]:
|
||||
package["author"]["author_id"] = ""
|
||||
package["author"]["display_name"] = ""
|
||||
# raise Exception("Detected a package with malformed author data.")
|
||||
|
||||
items.append({
|
||||
"id": package["package_id"],
|
||||
|
@ -80,17 +96,19 @@ class PackagesModel(ListModel):
|
|||
"has_configs": has_configs,
|
||||
"supported_configs": configs_model,
|
||||
"download_count": package["download_count"] if "download_count" in package else 0,
|
||||
"tags": package["tags"] if "tags" in package else []
|
||||
"tags": package["tags"] if "tags" in package else [],
|
||||
"links": links_dict,
|
||||
"website": package["website"] if "website" in package else None,
|
||||
})
|
||||
|
||||
# Filter on all the key-word arguments.
|
||||
for key, value in self._filter.items():
|
||||
if key is "tags":
|
||||
key_filter = lambda item, value = value: value in item["tags"]
|
||||
key_filter = lambda item, v = value: v in item["tags"]
|
||||
elif "*" in value:
|
||||
key_filter = lambda candidate, key = key, value = value: self._matchRegExp(candidate, key, value)
|
||||
key_filter = lambda candidate, k = key, v = value: self._matchRegExp(candidate, k, v)
|
||||
else:
|
||||
key_filter = lambda candidate, key = key, value = value: self._matchString(candidate, key, value)
|
||||
key_filter = lambda candidate, k = key, v = value: self._matchString(candidate, k, v)
|
||||
items = filter(key_filter, items)
|
||||
|
||||
# Execute all filters.
|
||||
|
|
|
@ -6,7 +6,7 @@ import json
|
|||
import os
|
||||
import tempfile
|
||||
import platform
|
||||
from typing import cast, List
|
||||
from typing import cast, List, TYPE_CHECKING, Tuple, Optional
|
||||
|
||||
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||
|
@ -20,9 +20,13 @@ from UM.Version import Version
|
|||
|
||||
import cura
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
from .AuthorsModel import AuthorsModel
|
||||
from .PackagesModel import PackagesModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
|
@ -83,7 +87,7 @@ class Toolbox(QObject, Extension):
|
|||
"plugins_available": PackagesModel(self),
|
||||
"plugins_installed": PackagesModel(self),
|
||||
"materials_showcase": AuthorsModel(self),
|
||||
"materials_available": PackagesModel(self),
|
||||
"materials_available": AuthorsModel(self),
|
||||
"materials_installed": PackagesModel(self),
|
||||
"materials_generic": PackagesModel(self)
|
||||
} # type: Dict[str, ListModel]
|
||||
|
@ -117,8 +121,6 @@ class Toolbox(QObject, Extension):
|
|||
|
||||
self._application.initializationFinished.connect(self._onAppInitialized)
|
||||
|
||||
|
||||
|
||||
# Signals:
|
||||
# --------------------------------------------------------------------------
|
||||
# Downloading changes
|
||||
|
@ -137,11 +139,11 @@ class Toolbox(QObject, Extension):
|
|||
showLicenseDialog = pyqtSignal()
|
||||
uninstallVariablesChanged = pyqtSignal()
|
||||
|
||||
def _resetUninstallVariables(self):
|
||||
self._package_id_to_uninstall = None
|
||||
def _resetUninstallVariables(self) -> None:
|
||||
self._package_id_to_uninstall = None # type: Optional[str]
|
||||
self._package_name_to_uninstall = ""
|
||||
self._package_used_materials = []
|
||||
self._package_used_qualities = []
|
||||
self._package_used_materials = [] # type: List[Tuple[GlobalStack, str, str]]
|
||||
self._package_used_qualities = [] # type: List[Tuple[GlobalStack, str, str]]
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getLicenseDialogPluginName(self) -> str:
|
||||
|
@ -229,10 +231,12 @@ class Toolbox(QObject, Extension):
|
|||
# Make remote requests:
|
||||
self._makeRequestByType("packages")
|
||||
self._makeRequestByType("authors")
|
||||
self._makeRequestByType("plugins_showcase")
|
||||
self._makeRequestByType("materials_showcase")
|
||||
self._makeRequestByType("materials_available")
|
||||
self._makeRequestByType("materials_generic")
|
||||
# TODO: Uncomment in the future when the tag-filtered api calls work in the cloud server
|
||||
# self._makeRequestByType("plugins_showcase")
|
||||
# self._makeRequestByType("plugins_available")
|
||||
# self._makeRequestByType("materials_showcase")
|
||||
# self._makeRequestByType("materials_available")
|
||||
# self._makeRequestByType("materials_generic")
|
||||
|
||||
# Gather installed packages:
|
||||
self._updateInstalledModels()
|
||||
|
@ -344,26 +348,26 @@ class Toolbox(QObject, Extension):
|
|||
self.uninstall(package_id)
|
||||
|
||||
@pyqtProperty(str, notify = uninstallVariablesChanged)
|
||||
def pluginToUninstall(self):
|
||||
def pluginToUninstall(self) -> str:
|
||||
return self._package_name_to_uninstall
|
||||
|
||||
@pyqtProperty(str, notify = uninstallVariablesChanged)
|
||||
def uninstallUsedMaterials(self):
|
||||
def uninstallUsedMaterials(self) -> str:
|
||||
return "\n".join(["%s (%s)" % (str(global_stack.getName()), material) for global_stack, extruder_nr, material in self._package_used_materials])
|
||||
|
||||
@pyqtProperty(str, notify = uninstallVariablesChanged)
|
||||
def uninstallUsedQualities(self):
|
||||
def uninstallUsedQualities(self) -> str:
|
||||
return "\n".join(["%s (%s)" % (str(global_stack.getName()), quality) for global_stack, extruder_nr, quality in self._package_used_qualities])
|
||||
|
||||
@pyqtSlot()
|
||||
def closeConfirmResetDialog(self):
|
||||
def closeConfirmResetDialog(self) -> None:
|
||||
if self._confirm_reset_dialog is not None:
|
||||
self._confirm_reset_dialog.close()
|
||||
|
||||
## Uses "uninstall variables" to reset qualities and materials, then uninstall
|
||||
# It's used as an action on Confirm reset on Uninstall
|
||||
@pyqtSlot()
|
||||
def resetMaterialsQualitiesAndUninstall(self):
|
||||
def resetMaterialsQualitiesAndUninstall(self) -> None:
|
||||
application = CuraApplication.getInstance()
|
||||
material_manager = application.getMaterialManager()
|
||||
quality_manager = application.getQualityManager()
|
||||
|
@ -376,8 +380,8 @@ class Toolbox(QObject, Extension):
|
|||
default_quality_group = quality_manager.getDefaultQualityType(global_stack)
|
||||
machine_manager.setQualityGroup(default_quality_group, global_stack = global_stack)
|
||||
|
||||
if self._package_id_to_uninstall is not None:
|
||||
self._markPackageMaterialsAsToBeUninstalled(self._package_id_to_uninstall)
|
||||
|
||||
self.uninstall(self._package_id_to_uninstall)
|
||||
self._resetUninstallVariables()
|
||||
self.closeConfirmResetDialog()
|
||||
|
@ -514,10 +518,12 @@ class Toolbox(QObject, Extension):
|
|||
count += 1
|
||||
return count
|
||||
|
||||
# This slot is only used to get the number of material packages by author, not any other type of packages.
|
||||
@pyqtSlot(str, result = int)
|
||||
def getTotalNumberOfPackagesByAuthor(self, author_id: str) -> int:
|
||||
def getTotalNumberOfMaterialPackagesByAuthor(self, author_id: str) -> int:
|
||||
count = 0
|
||||
for package in self._metadata["materials_available"]:
|
||||
for package in self._metadata["packages"]:
|
||||
if package["package_type"] == "material":
|
||||
if package["author"]["author_id"] == author_id:
|
||||
count += 1
|
||||
return count
|
||||
|
@ -606,8 +612,22 @@ class Toolbox(QObject, Extension):
|
|||
self.resetDownload()
|
||||
return
|
||||
|
||||
# HACK: These request are not handled independently at this moment, but together from the "packages" call
|
||||
do_not_handle = [
|
||||
"materials_available",
|
||||
"materials_showcase",
|
||||
"materials_generic",
|
||||
"plugins_available",
|
||||
"plugins_showcase",
|
||||
]
|
||||
|
||||
if reply.operation() == QNetworkAccessManager.GetOperation:
|
||||
for type, url in self._request_urls.items():
|
||||
|
||||
# HACK: Do nothing because we'll handle these from the "packages" call
|
||||
if type in do_not_handle:
|
||||
return
|
||||
|
||||
if reply.url() == url:
|
||||
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) == 200:
|
||||
try:
|
||||
|
@ -624,17 +644,6 @@ class Toolbox(QObject, Extension):
|
|||
Logger.log("e", "Could not find the %s model.", type)
|
||||
break
|
||||
|
||||
# HACK: Eventually get rid of the code from here...
|
||||
if type is "plugins_showcase" or type is "materials_showcase":
|
||||
self._metadata["plugins_showcase"] = json_data["data"]["plugin"]["packages"]
|
||||
self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"])
|
||||
self._metadata["materials_showcase"] = json_data["data"]["material"]["authors"]
|
||||
self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"])
|
||||
else:
|
||||
# ...until here.
|
||||
# This hack arises for multiple reasons but the main
|
||||
# one is because there are not separate API calls
|
||||
# for different kinds of showcases.
|
||||
self._metadata[type] = json_data["data"]
|
||||
self._models[type].setMetadata(self._metadata[type])
|
||||
|
||||
|
@ -642,6 +651,8 @@ class Toolbox(QObject, Extension):
|
|||
# TODO: Make multiple API calls in the future to handle this
|
||||
if type is "packages":
|
||||
self._models[type].setFilter({"type": "plugin"})
|
||||
self.buildMaterialsModels()
|
||||
self.buildPluginsModels()
|
||||
if type is "authors":
|
||||
self._models[type].setFilter({"package_types": "material"})
|
||||
if type is "materials_generic":
|
||||
|
@ -680,7 +691,7 @@ class Toolbox(QObject, Extension):
|
|||
self._temp_plugin_file.close()
|
||||
self._onDownloadComplete(file_path)
|
||||
|
||||
def _onDownloadComplete(self, file_path: str):
|
||||
def _onDownloadComplete(self, file_path: str) -> None:
|
||||
Logger.log("i", "Toolbox: Download complete.")
|
||||
package_info = self._package_manager.getPackageInfo(file_path)
|
||||
if not package_info:
|
||||
|
@ -739,9 +750,7 @@ class Toolbox(QObject, Extension):
|
|||
def viewPage(self) -> str:
|
||||
return self._view_page
|
||||
|
||||
|
||||
|
||||
# Expose Models:
|
||||
# Exposed Models:
|
||||
# --------------------------------------------------------------------------
|
||||
@pyqtProperty(QObject, notify = metadataChanged)
|
||||
def authorsModel(self) -> AuthorsModel:
|
||||
|
@ -755,6 +764,10 @@ class Toolbox(QObject, Extension):
|
|||
def pluginsShowcaseModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["plugins_showcase"])
|
||||
|
||||
@pyqtProperty(QObject, notify = metadataChanged)
|
||||
def pluginsAvailableModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["plugins_available"])
|
||||
|
||||
@pyqtProperty(QObject, notify = metadataChanged)
|
||||
def pluginsInstalledModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["plugins_installed"])
|
||||
|
@ -763,6 +776,10 @@ class Toolbox(QObject, Extension):
|
|||
def materialsShowcaseModel(self) -> AuthorsModel:
|
||||
return cast(AuthorsModel, self._models["materials_showcase"])
|
||||
|
||||
@pyqtProperty(QObject, notify = metadataChanged)
|
||||
def materialsAvailableModel(self) -> AuthorsModel:
|
||||
return cast(AuthorsModel, self._models["materials_available"])
|
||||
|
||||
@pyqtProperty(QObject, notify = metadataChanged)
|
||||
def materialsInstalledModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["materials_installed"])
|
||||
|
@ -771,8 +788,6 @@ class Toolbox(QObject, Extension):
|
|||
def materialsGenericModel(self) -> PackagesModel:
|
||||
return cast(PackagesModel, self._models["materials_generic"])
|
||||
|
||||
|
||||
|
||||
# Filter Models:
|
||||
# --------------------------------------------------------------------------
|
||||
@pyqtSlot(str, str, str)
|
||||
|
@ -798,3 +813,48 @@ class Toolbox(QObject, Extension):
|
|||
return
|
||||
self._models[model_type].setFilter({})
|
||||
self.filterChanged.emit()
|
||||
|
||||
# HACK(S):
|
||||
# --------------------------------------------------------------------------
|
||||
def buildMaterialsModels(self) -> None:
|
||||
self._metadata["materials_showcase"] = []
|
||||
self._metadata["materials_available"] = []
|
||||
|
||||
processed_authors = [] # type: List[str]
|
||||
|
||||
for item in self._metadata["packages"]:
|
||||
if item["package_type"] == "material":
|
||||
|
||||
author = item["author"]
|
||||
if author["author_id"] in processed_authors:
|
||||
continue
|
||||
|
||||
# Generic materials to be in the same section
|
||||
if "generic" in item["tags"]:
|
||||
self._metadata["materials_generic"].append(item)
|
||||
else:
|
||||
if "showcase" in item["tags"]:
|
||||
self._metadata["materials_showcase"].append(author)
|
||||
else:
|
||||
self._metadata["materials_available"].append(author)
|
||||
|
||||
processed_authors.append(author["author_id"])
|
||||
|
||||
self._models["materials_showcase"].setMetadata(self._metadata["materials_showcase"])
|
||||
self._models["materials_available"].setMetadata(self._metadata["materials_available"])
|
||||
self._models["materials_generic"].setMetadata(self._metadata["materials_generic"])
|
||||
|
||||
def buildPluginsModels(self) -> None:
|
||||
self._metadata["plugins_showcase"] = []
|
||||
self._metadata["plugins_available"] = []
|
||||
|
||||
for item in self._metadata["packages"]:
|
||||
if item["package_type"] == "plugin":
|
||||
|
||||
if "showcase" in item["tags"]:
|
||||
self._metadata["plugins_showcase"].append(item)
|
||||
else:
|
||||
self._metadata["plugins_available"].append(item)
|
||||
|
||||
self._models["plugins_showcase"].setMetadata(self._metadata["plugins_showcase"])
|
||||
self._models["plugins_available"].setMetadata(self._metadata["plugins_available"])
|
||||
|
|
|
@ -13,6 +13,7 @@ from UM.PluginRegistry import PluginRegistry #To get the g-code writer.
|
|||
from PyQt5.QtCore import QBuffer
|
||||
|
||||
from cura.Snapshot import Snapshot
|
||||
from cura.Utils.Threading import call_on_qt_thread
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
@ -29,6 +30,11 @@ class UFPWriter(MeshWriter):
|
|||
Logger.log("d", "Creating thumbnail image...")
|
||||
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
|
||||
|
||||
# This needs to be called on the main thread (Qt thread) because the serialization of material containers can
|
||||
# trigger loading other containers. Because those loaded containers are QtObjects, they must be created on the
|
||||
# Qt thread. The File read/write operations right now are executed on separated threads because they are scheduled
|
||||
# by the Job class.
|
||||
@call_on_qt_thread
|
||||
def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
|
||||
archive = VirtualFile()
|
||||
archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly)
|
||||
|
@ -60,5 +66,50 @@ class UFPWriter(MeshWriter):
|
|||
else:
|
||||
Logger.log("d", "Thumbnail not created, cannot save it")
|
||||
|
||||
# Store the material.
|
||||
application = Application.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
material_manager = application.getMaterialManager()
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
material_extension = "xml.fdm_material"
|
||||
material_mime_type = "application/x-ultimaker-material-profile"
|
||||
|
||||
try:
|
||||
archive.addContentType(extension = material_extension, mime_type = material_mime_type)
|
||||
except:
|
||||
Logger.log("w", "The material extension: %s was already added", material_extension)
|
||||
|
||||
added_materials = []
|
||||
for extruder_stack in global_stack.extruders.values():
|
||||
material = extruder_stack.material
|
||||
material_file_name = material.getMetaData()["base_file"] + ".xml.fdm_material"
|
||||
material_file_name = "/Materials/" + material_file_name
|
||||
|
||||
#Same material cannot be added
|
||||
if material_file_name in added_materials:
|
||||
continue
|
||||
|
||||
material_root_id = material.getMetaDataEntry("base_file")
|
||||
material_group = material_manager.getMaterialGroup(material_root_id)
|
||||
if material_group is None:
|
||||
Logger.log("e", "Cannot find material container with root id [%s]", material_root_id)
|
||||
return False
|
||||
|
||||
material_container = material_group.root_material_node.getContainer()
|
||||
try:
|
||||
serialized_material = material_container.serialize()
|
||||
except NotImplementedError:
|
||||
Logger.log("e", "Unable serialize material container with root id: %s", material_root_id)
|
||||
return False
|
||||
|
||||
material_file = archive.getStream(material_file_name)
|
||||
material_file.write(serialized_material.encode("UTF-8"))
|
||||
archive.addRelation(virtual_path = material_file_name,
|
||||
relation_type = "http://schemas.ultimaker.org/package/2018/relationships/material",
|
||||
origin = "/3D/model.gcode")
|
||||
|
||||
added_materials.append(material_file_name)
|
||||
|
||||
archive.close()
|
||||
return True
|
||||
|
|
|
@ -1,241 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Component
|
||||
{
|
||||
Rectangle
|
||||
{
|
||||
id: base
|
||||
property var manager: Cura.MachineManager.printerOutputDevices[0]
|
||||
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
|
||||
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
|
||||
|
||||
visible: manager != null
|
||||
anchors.fill: parent
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: activePrintersLabel
|
||||
font: UM.Theme.getFont("large")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right:parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
text: Cura.MachineManager.printerOutputDevices[0].name
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: printJobArea
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: lineColor
|
||||
anchors.top: activePrintersLabel.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin:UM.Theme.getSize("default_margin").width
|
||||
radius: cornerRadius
|
||||
height: childrenRect.height
|
||||
|
||||
Item
|
||||
{
|
||||
id: printJobTitleBar
|
||||
width: parent.width
|
||||
height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
id: printJobTitleLabel
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
text: catalog.i18nc("@title", "Print jobs")
|
||||
font: UM.Theme.getFont("default")
|
||||
opacity: 0.75
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
anchors.bottom: parent.bottom
|
||||
height: UM.Theme.getSize("default_lining").width
|
||||
color: lineColor
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
id: printJobColumn
|
||||
anchors.top: printJobTitleBar.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
opacity: 0.65
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Printing")
|
||||
font: UM.Theme.getFont("very_small")
|
||||
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: manager.activePrintJobs.length
|
||||
font: UM.Theme.getFont("small")
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
opacity: 0.65
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Queued")
|
||||
font: UM.Theme.getFont("very_small")
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: manager.queuedPrintJobs.length
|
||||
font: UM.Theme.getFont("small")
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenPanelButton
|
||||
{
|
||||
anchors.top: printJobColumn.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").height
|
||||
id: configButton
|
||||
onClicked: base.manager.openPrintJobControlPanel()
|
||||
text: catalog.i18nc("@action:button", "View print jobs")
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
// spacer
|
||||
anchors.top: configButton.bottom
|
||||
width: UM.Theme.getSize("default_margin").width
|
||||
height: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: printersArea
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: lineColor
|
||||
anchors.top: printJobArea.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin:UM.Theme.getSize("default_margin").width
|
||||
radius: cornerRadius
|
||||
height: childrenRect.height
|
||||
|
||||
Item
|
||||
{
|
||||
id: printersTitleBar
|
||||
width: parent.width
|
||||
height: printJobTitleLabel.height + 2 * UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
id: printersTitleLabel
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
text: catalog.i18nc("@label:title", "Printers")
|
||||
font: UM.Theme.getFont("default")
|
||||
opacity: 0.75
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
anchors.bottom: parent.bottom
|
||||
height: UM.Theme.getSize("default_lining").width
|
||||
color: lineColor
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
Column
|
||||
{
|
||||
id: printersColumn
|
||||
anchors.top: printersTitleBar.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Repeater
|
||||
{
|
||||
model: manager.connectedPrintersTypeCount
|
||||
Item
|
||||
{
|
||||
width: parent.width
|
||||
height: childrenRect.height
|
||||
opacity: 0.65
|
||||
Label
|
||||
{
|
||||
text: modelData.machine_type
|
||||
font: UM.Theme.getFont("very_small")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
text: modelData.count
|
||||
font: UM.Theme.getFont("small")
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
OpenPanelButton
|
||||
{
|
||||
anchors.top: printersColumn.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").height
|
||||
id: printerConfigButton
|
||||
onClicked: base.manager.openPrinterControlPanel()
|
||||
|
||||
text: catalog.i18nc("@action:button", "View printers")
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
// spacer
|
||||
anchors.top: printerConfigButton.bottom
|
||||
width: UM.Theme.getSize("default_margin").width
|
||||
height: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Component
|
||||
{
|
||||
Rectangle
|
||||
{
|
||||
id: monitorFrame
|
||||
width: maximumWidth
|
||||
height: maximumHeight
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight")
|
||||
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
|
||||
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: activePrintersLabel
|
||||
font: UM.Theme.getFont("large")
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: UM.Theme.getSize("default_margin").height * 2 // a bit more spacing to give it some breathing room
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
text: OutputDevice.printers.length == 0 ? catalog.i18nc("@label: arg 1 is group name", "%1 is not set up to host a group of connected Ultimaker 3 printers").arg(Cura.MachineManager.printerOutputDevices[0].name) : ""
|
||||
|
||||
visible: OutputDevice.printers.length == 0
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
width: Math.min(800 * screenScaleFactor, maximumWidth)
|
||||
height: children.height
|
||||
visible: OutputDevice.printers.length != 0
|
||||
|
||||
Label
|
||||
{
|
||||
id: addRemovePrintersLabel
|
||||
anchors.right: parent.right
|
||||
text: catalog.i18nc("@label link to connect manager", "Add/Remove printers")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("text")
|
||||
linkColor: UM.Theme.getColor("text_link")
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: addRemovePrintersLabel
|
||||
hoverEnabled: true
|
||||
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
|
||||
onEntered: addRemovePrintersLabel.font.underline = true
|
||||
onExited: addRemovePrintersLabel.font.underline = false
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: printerScrollView
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
anchors.top: activePrintersLabel.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_lining").width // To ensure border can be drawn.
|
||||
anchors.rightMargin: UM.Theme.getSize("default_lining").width
|
||||
anchors.right: parent.right
|
||||
|
||||
ListView
|
||||
{
|
||||
anchors.fill: parent
|
||||
spacing: -UM.Theme.getSize("default_lining").height
|
||||
|
||||
model: OutputDevice.printers
|
||||
|
||||
delegate: PrinterInfoBlock
|
||||
{
|
||||
printer: modelData
|
||||
width: Math.min(800 * screenScaleFactor, maximumWidth)
|
||||
height: 125 * screenScaleFactor
|
||||
|
||||
// Add a 1 pix margin, as the border is sometimes cut off otherwise.
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrinterVideoStream
|
||||
{
|
||||
visible: OutputDevice.activePrinter != null
|
||||
anchors.fill:parent
|
||||
}
|
||||
|
||||
onVisibleChanged:
|
||||
{
|
||||
if (!monitorFrame.visible)
|
||||
{
|
||||
// After switching the Tab ensure that active printer is Null, the video stream image
|
||||
// might be active
|
||||
OutputDevice.setActivePrinter(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Controls.Styles 1.1
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
Button {
|
||||
objectName: "openPanelSaveAreaButton"
|
||||
id: openPanelSaveAreaButton
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
|
||||
height: UM.Theme.getSize("save_button_save_to_button").height
|
||||
tooltip: catalog.i18nc("@info:tooltip", "Opens the print jobs page with your default web browser.")
|
||||
text: catalog.i18nc("@action:button", "View print jobs")
|
||||
|
||||
// FIXME: This button style is copied and duplicated from SaveButton.qml
|
||||
style: ButtonStyle {
|
||||
background: Rectangle
|
||||
{
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color:
|
||||
{
|
||||
if(!control.enabled)
|
||||
return UM.Theme.getColor("action_button_disabled_border");
|
||||
else if(control.pressed)
|
||||
return UM.Theme.getColor("print_button_ready_pressed_border");
|
||||
else if(control.hovered)
|
||||
return UM.Theme.getColor("print_button_ready_hovered_border");
|
||||
else
|
||||
return UM.Theme.getColor("print_button_ready_border");
|
||||
}
|
||||
color:
|
||||
{
|
||||
if(!control.enabled)
|
||||
return UM.Theme.getColor("action_button_disabled");
|
||||
else if(control.pressed)
|
||||
return UM.Theme.getColor("print_button_ready_pressed");
|
||||
else if(control.hovered)
|
||||
return UM.Theme.getColor("print_button_ready_hovered");
|
||||
else
|
||||
return UM.Theme.getColor("print_button_ready");
|
||||
}
|
||||
|
||||
Behavior on color { ColorAnimation { duration: 50; } }
|
||||
|
||||
implicitWidth: actualLabel.contentWidth + (UM.Theme.getSize("sidebar_margin").width * 2)
|
||||
|
||||
Label {
|
||||
id: actualLabel
|
||||
anchors.centerIn: parent
|
||||
color:
|
||||
{
|
||||
if(!control.enabled)
|
||||
return UM.Theme.getColor("action_button_disabled_text");
|
||||
else if(control.pressed)
|
||||
return UM.Theme.getColor("print_button_ready_text");
|
||||
else if(control.hovered)
|
||||
return UM.Theme.getColor("print_button_ready_text");
|
||||
else
|
||||
return UM.Theme.getColor("print_button_ready_text");
|
||||
}
|
||||
font: UM.Theme.getFont("action_button")
|
||||
text: control.text;
|
||||
}
|
||||
}
|
||||
label: Item { }
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
|
||||
|
||||
Item
|
||||
{
|
||||
id: extruderInfo
|
||||
property var printCoreConfiguration
|
||||
|
||||
width: Math.round(parent.width / 2)
|
||||
height: childrenRect.height
|
||||
Label
|
||||
{
|
||||
id: materialLabel
|
||||
text: printCoreConfiguration.activeMaterial != null ? printCoreConfiguration.activeMaterial.name : ""
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
font: UM.Theme.getFont("very_small")
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: printCoreLabel
|
||||
text: printCoreConfiguration.hotendID
|
||||
anchors.top: materialLabel.bottom
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
font: UM.Theme.getFont("very_small")
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
|
@ -1,431 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
Rectangle
|
||||
{
|
||||
function strPadLeft(string, pad, length)
|
||||
{
|
||||
return (new Array(length + 1).join(pad) + string).slice(-length);
|
||||
}
|
||||
|
||||
function getPrettyTime(time)
|
||||
{
|
||||
return OutputDevice.formatDuration(time)
|
||||
}
|
||||
|
||||
function formatPrintJobPercent(printJob)
|
||||
{
|
||||
if (printJob == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
if (printJob.timeTotal === 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
return Math.min(100, Math.round(printJob.timeElapsed / printJob.timeTotal * 100)) + "%";
|
||||
}
|
||||
|
||||
function printerStatusText(printer)
|
||||
{
|
||||
switch (printer.state)
|
||||
{
|
||||
case "pre_print":
|
||||
return catalog.i18nc("@label:status", "Preparing to print")
|
||||
case "printing":
|
||||
return catalog.i18nc("@label:status", "Printing");
|
||||
case "idle":
|
||||
return catalog.i18nc("@label:status", "Available");
|
||||
case "unreachable":
|
||||
return catalog.i18nc("@label:status", "Lost connection with the printer");
|
||||
case "maintenance":
|
||||
return catalog.i18nc("@label:status", "Unavailable");
|
||||
default:
|
||||
return catalog.i18nc("@label:status", "Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
id: printerDelegate
|
||||
|
||||
property var printer: null
|
||||
property var printJob: printer != null ? printer.activePrintJob: null
|
||||
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: mouse.containsMouse ? emphasisColor : lineColor
|
||||
z: mouse.containsMouse ? 1 : 0 // Push this item up a bit on mouse over to ensure that the highlighted bottom border is visible.
|
||||
|
||||
MouseArea
|
||||
{
|
||||
id: mouse
|
||||
anchors.fill:parent
|
||||
onClicked: OutputDevice.setActivePrinter(printer)
|
||||
hoverEnabled: true;
|
||||
|
||||
// Only clickable if no printer is selected
|
||||
enabled: OutputDevice.activePrinter == null && printer.state !== "unreachable"
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
|
||||
Rectangle
|
||||
{
|
||||
width: Math.round(parent.width / 3)
|
||||
height: parent.height
|
||||
|
||||
Label // Print job name
|
||||
{
|
||||
id: jobNameLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
text: printJob != null ? printJob.name : ""
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
elide: Text.ElideRight
|
||||
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: jobOwnerLabel
|
||||
anchors.top: jobNameLabel.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
text: printJob != null ? printJob.owner : ""
|
||||
opacity: 0.50
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: totalTimeLabel
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
text: printJob != null ? getPrettyTime(printJob.timeTotal) : ""
|
||||
opacity: 0.65
|
||||
font: UM.Theme.getFont("default")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
width: Math.round(parent.width / 3 * 2)
|
||||
height: parent.height
|
||||
|
||||
Label // Friendly machine name
|
||||
{
|
||||
id: printerNameLabel
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width - showCameraIcon.width)
|
||||
text: printer.name
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label // Machine variant
|
||||
{
|
||||
id: printerTypeLabel
|
||||
anchors.top: printerNameLabel.bottom
|
||||
width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
|
||||
text: printer.type
|
||||
anchors.left: parent.left
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("very_small")
|
||||
opacity: 0.50
|
||||
}
|
||||
|
||||
Rectangle // Camera icon
|
||||
{
|
||||
id: showCameraIcon
|
||||
width: 40 * screenScaleFactor
|
||||
height: width
|
||||
radius: width
|
||||
anchors.right: printProgressArea.left
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
color: emphasisColor
|
||||
opacity: printer != null && printer.state === "unreachable" ? 0.3 : 1
|
||||
|
||||
Image
|
||||
{
|
||||
width: parent.width
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: parent.rightMargin
|
||||
source: "camera-icon.svg"
|
||||
}
|
||||
}
|
||||
|
||||
Row // PrintCore config
|
||||
{
|
||||
id: extruderInfo
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
|
||||
height: childrenRect.height
|
||||
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: leftExtruderInfo
|
||||
width: Math.round((parent.width - extruderSeperator.width) / 2)
|
||||
printCoreConfiguration: printer.extruders[0]
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: extruderSeperator
|
||||
width: UM.Theme.getSize("default_lining").width
|
||||
height: parent.height
|
||||
color: lineColor
|
||||
}
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: rightExtruderInfo
|
||||
width: Math.round((parent.width - extruderSeperator.width) / 2)
|
||||
printCoreConfiguration: printer.extruders[1]
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle // Print progress
|
||||
{
|
||||
id: printProgressArea
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
height: showExtended ? parent.height: printProgressTitleBar.height
|
||||
width: Math.round(parent.width / 2 - UM.Theme.getSize("default_margin").width)
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: lineColor
|
||||
radius: cornerRadius
|
||||
property var showExtended: {
|
||||
if(printJob != null)
|
||||
{
|
||||
var extendStates = ["sent_to_printer", "wait_for_configuration", "printing", "pre_print", "post_print", "wait_cleanup", "queued"];
|
||||
return extendStates.indexOf(printJob.state) !== -1;
|
||||
}
|
||||
return printer.state == "disabled"
|
||||
}
|
||||
|
||||
Item // Status and Percent
|
||||
{
|
||||
id: printProgressTitleBar
|
||||
|
||||
property var showPercent: {
|
||||
return printJob != null && (["printing", "post_print", "pre_print", "sent_to_printer"].indexOf(printJob.state) !== -1);
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
//TODO: hardcoded value
|
||||
height: 40 * screenScaleFactor
|
||||
anchors.left: parent.left
|
||||
|
||||
Label
|
||||
{
|
||||
id: statusText
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.right: progressText.left
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (printer.state == "disabled")
|
||||
{
|
||||
return catalog.i18nc("@label:status", "Disabled");
|
||||
}
|
||||
|
||||
if (printer.state === "unreachable")
|
||||
{
|
||||
return printerStatusText(printer);
|
||||
}
|
||||
|
||||
if (printJob != null)
|
||||
{
|
||||
switch (printJob.state)
|
||||
{
|
||||
case "printing":
|
||||
case "post_print":
|
||||
return catalog.i18nc("@label:status", "Printing")
|
||||
case "wait_for_configuration":
|
||||
return catalog.i18nc("@label:status", "Reserved")
|
||||
case "wait_cleanup":
|
||||
case "wait_user_action":
|
||||
return catalog.i18nc("@label:status", "Finished")
|
||||
case "pre_print":
|
||||
case "sent_to_printer":
|
||||
return catalog.i18nc("@label", "Preparing to print")
|
||||
case "queued":
|
||||
return catalog.i18nc("@label:status", "Action required");
|
||||
case "pausing":
|
||||
case "paused":
|
||||
return catalog.i18nc("@label:status", "Paused");
|
||||
case "resuming":
|
||||
return catalog.i18nc("@label:status", "Resuming");
|
||||
case "aborted":
|
||||
return catalog.i18nc("@label:status", "Print aborted");
|
||||
default:
|
||||
// If print job has unknown status show printer.status
|
||||
return printerStatusText(printer);
|
||||
}
|
||||
}
|
||||
return printerStatusText(printer);
|
||||
}
|
||||
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("small")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: progressText
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.top: statusText.top
|
||||
|
||||
text: formatPrintJobPercent(printJob)
|
||||
visible: printProgressTitleBar.showPercent
|
||||
//TODO: Hardcoded value
|
||||
opacity: 0.65
|
||||
font: UM.Theme.getFont("very_small")
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
width: statusText.height
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.top: statusText.top
|
||||
|
||||
visible: !printProgressTitleBar.showPercent
|
||||
|
||||
source: {
|
||||
if (printer.state == "disabled")
|
||||
{
|
||||
return "blocked-icon.svg";
|
||||
}
|
||||
|
||||
if (printer.state === "unreachable")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (printJob != null)
|
||||
{
|
||||
if(printJob.state === "queued")
|
||||
{
|
||||
return "action-required-icon.svg";
|
||||
}
|
||||
else if (printJob.state === "wait_cleanup")
|
||||
{
|
||||
return "checkmark-icon.svg";
|
||||
}
|
||||
}
|
||||
return ""; // We're not going to show it, so it will not be resolved as a url.
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
//TODO: This will become a progress bar in the future
|
||||
width: parent.width
|
||||
height: UM.Theme.getSize("default_lining").height
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
visible: printProgressArea.showExtended
|
||||
color: lineColor
|
||||
}
|
||||
}
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
anchors.top: printProgressTitleBar.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
width: parent.width - 2 * UM.Theme.getSize("default_margin").width
|
||||
|
||||
visible: printProgressArea.showExtended
|
||||
|
||||
Label // Status detail
|
||||
{
|
||||
text:
|
||||
{
|
||||
if (printer.state == "disabled")
|
||||
{
|
||||
return catalog.i18nc("@label", "Not accepting print jobs");
|
||||
}
|
||||
|
||||
if (printer.state === "unreachable")
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if(printJob != null)
|
||||
{
|
||||
switch (printJob.state)
|
||||
{
|
||||
case "printing":
|
||||
case "post_print":
|
||||
return catalog.i18nc("@label", "Finishes at: ") + OutputDevice.getTimeCompleted(printJob.timeTotal - printJob.timeElapsed)
|
||||
case "wait_cleanup":
|
||||
return catalog.i18nc("@label", "Clear build plate")
|
||||
case "sent_to_printer":
|
||||
case "pre_print":
|
||||
return catalog.i18nc("@label", "Preparing to print")
|
||||
case "wait_for_configuration":
|
||||
return catalog.i18nc("@label", "Not accepting print jobs")
|
||||
case "queued":
|
||||
return catalog.i18nc("@label", "Waiting for configuration change");
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
|
||||
Label // Status 2nd row
|
||||
{
|
||||
text: {
|
||||
if(printJob != null)
|
||||
{
|
||||
if(printJob.state == "printing" || printJob.state == "post_print")
|
||||
{
|
||||
return OutputDevice.getDateCompleted(printJob.timeTotal - printJob.timeElapsed)
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: base
|
||||
width: 250 * screenScaleFactor
|
||||
height: 250 * screenScaleFactor
|
||||
signal clicked()
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill:parent
|
||||
onClicked: base.clicked()
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
// TODO: Actually add UM icon / picture
|
||||
width: 100 * screenScaleFactor
|
||||
height: 100 * screenScaleFactor
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: nameLabel
|
||||
anchors.bottom: ipLabel.top
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
text: modelData.friendly_name.toString()
|
||||
font: UM.Theme.getFont("large")
|
||||
elide: Text.ElideMiddle;
|
||||
height: UM.Theme.getSize("section").height;
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: ipLabel
|
||||
text: modelData.ip_address.toString()
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
font: UM.Theme.getFont("default")
|
||||
height:10 * screenScaleFactor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import DiscoverUM3Action
|
||||
from .src import DiscoverUM3Action
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
from . import UM3OutputDevicePlugin
|
||||
from .src import UM3OutputDevicePlugin
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
|
717
plugins/UM3NetworkPrinting/resources/qml/ClusterControlItem.qml
Normal file
|
@ -0,0 +1,717 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import QtQuick.Controls 2.0 as Controls2
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
Component
|
||||
{
|
||||
Rectangle
|
||||
{
|
||||
id: base
|
||||
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
|
||||
|
||||
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
|
||||
visible: OutputDevice != null
|
||||
anchors.fill: parent
|
||||
color: "white"
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: printingLabel
|
||||
font: UM.Theme.getFont("large")
|
||||
anchors
|
||||
{
|
||||
margins: 2 * UM.Theme.getSize("default_margin").width
|
||||
leftMargin: 4 * UM.Theme.getSize("default_margin").width
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
text: catalog.i18nc("@label", "Printing")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: managePrintersLabel
|
||||
anchors.rightMargin: 4 * UM.Theme.getSize("default_margin").width
|
||||
anchors.right: printerScrollView.right
|
||||
anchors.bottom: printingLabel.bottom
|
||||
text: catalog.i18nc("@label link to connect manager", "Manage printers")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("primary")
|
||||
linkColor: UM.Theme.getColor("primary")
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: managePrintersLabel
|
||||
hoverEnabled: true
|
||||
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
|
||||
onEntered: managePrintersLabel.font.underline = true
|
||||
onExited: managePrintersLabel.font.underline = false
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: printerScrollView
|
||||
anchors
|
||||
{
|
||||
top: printingLabel.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
bottom: parent.bottom
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
|
||||
style: UM.Theme.styles.scrollview
|
||||
|
||||
ListView
|
||||
{
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: 2 * UM.Theme.getSize("default_margin").width
|
||||
rightMargin: 2 * UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
spacing: UM.Theme.getSize("default_margin").height -10
|
||||
model: OutputDevice.printers
|
||||
|
||||
delegate: Item
|
||||
{
|
||||
width: parent.width
|
||||
height: base.height + 2 * base.shadowRadius // To ensure that the shadow doesn't get cut off.
|
||||
Rectangle
|
||||
{
|
||||
width: parent.width - 2 * shadowRadius
|
||||
height: childrenRect.height + UM.Theme.getSize("default_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: base
|
||||
property var shadowRadius: 5
|
||||
property var collapsed: true
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow
|
||||
{
|
||||
radius: base.shadowRadius
|
||||
verticalOffset: 2
|
||||
color: "#3F000000" // 25% shadow
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: printerInfo
|
||||
height: machineIcon.height
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onClicked: base.collapsed = !base.collapsed
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: machineIcon
|
||||
// Yeah, this is hardcoded now, but I can't think of a good way to fix this.
|
||||
// The UI is going to get another update soon, so it's probably not worth the effort...
|
||||
width: 58
|
||||
height: 58
|
||||
anchors.top: parent.top
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
anchors.left: parent.left
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
anchors.centerIn: parent
|
||||
source:
|
||||
{
|
||||
switch(modelData.type)
|
||||
{
|
||||
case "Ultimaker 3":
|
||||
return "../svg/UM3-icon.svg"
|
||||
case "Ultimaker 3 Extended":
|
||||
return "../svg/UM3x-icon.svg"
|
||||
case "Ultimaker S5":
|
||||
return "../svg/UMs5-icon.svg"
|
||||
}
|
||||
}
|
||||
width: sourceSize.width
|
||||
height: sourceSize.height
|
||||
|
||||
color:
|
||||
{
|
||||
if(modelData.state == "disabled")
|
||||
{
|
||||
return UM.Theme.getColor("setting_control_disabled")
|
||||
}
|
||||
|
||||
if(modelData.activePrintJob != undefined)
|
||||
{
|
||||
return UM.Theme.getColor("primary")
|
||||
}
|
||||
|
||||
return UM.Theme.getColor("setting_control_disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
Item
|
||||
{
|
||||
height: childrenRect.height
|
||||
anchors
|
||||
{
|
||||
right: collapseIcon.left
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
left: machineIcon.right
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
|
||||
verticalCenter: machineIcon.verticalCenter
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: machineNameLabel
|
||||
text: modelData.name
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: activeJobLabel
|
||||
text:
|
||||
{
|
||||
if (modelData.state == "disabled")
|
||||
{
|
||||
return catalog.i18nc("@label", "Not available")
|
||||
} else if (modelData.state == "unreachable")
|
||||
{
|
||||
return catalog.i18nc("@label", "Unreachable")
|
||||
}
|
||||
if (modelData.activePrintJob != null)
|
||||
{
|
||||
return modelData.activePrintJob.name
|
||||
}
|
||||
return catalog.i18nc("@label", "Available")
|
||||
}
|
||||
anchors.top: machineNameLabel.bottom
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default")
|
||||
opacity: 0.6
|
||||
}
|
||||
}
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: collapseIcon
|
||||
width: 15
|
||||
height: 15
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
source: base.collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
color: "black"
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: detailedInfo
|
||||
property var printJob: modelData.activePrintJob
|
||||
visible: height == childrenRect.height
|
||||
anchors.top: printerInfo.bottom
|
||||
width: parent.width
|
||||
height: !base.collapsed ? childrenRect.height : 0
|
||||
opacity: visible ? 1 : 0
|
||||
Behavior on height { NumberAnimation { duration: 100 } }
|
||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||
Rectangle
|
||||
{
|
||||
id: topSpacer
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
height: 2
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
top: parent.top
|
||||
topMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
PrinterFamilyPill
|
||||
{
|
||||
id: printerFamilyPill
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
anchors.top: topSpacer.bottom
|
||||
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||
text: modelData.type
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||
padding: 3
|
||||
}
|
||||
Row
|
||||
{
|
||||
id: extrudersInfo
|
||||
anchors.top: printerFamilyPill.bottom
|
||||
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2 * UM.Theme.getSize("default_margin").width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 2 * UM.Theme.getSize("default_margin").width
|
||||
height: childrenRect.height
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: leftExtruderInfo
|
||||
width: Math.round(parent.width / 2)
|
||||
printCoreConfiguration: modelData.printerConfiguration.extruderConfigurations[0]
|
||||
}
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: rightExtruderInfo
|
||||
width: Math.round(parent.width / 2)
|
||||
printCoreConfiguration: modelData.printerConfiguration.extruderConfigurations[1]
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: jobSpacer
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
height: 2
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
top: extrudersInfo.bottom
|
||||
topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: jobInfo
|
||||
property var showJobInfo: modelData.activePrintJob != null && modelData.activePrintJob.state != "queued"
|
||||
|
||||
anchors.top: jobSpacer.bottom
|
||||
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: UM.Theme.getSize("default_margin").width
|
||||
anchors.leftMargin: 2 * UM.Theme.getSize("default_margin").width
|
||||
height: showJobInfo ? childrenRect.height + 2 * UM.Theme.getSize("default_margin").height: 0
|
||||
visible: showJobInfo
|
||||
Label
|
||||
{
|
||||
id: printJobName
|
||||
text: modelData.activePrintJob != null ? modelData.activePrintJob.name : ""
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
anchors.left: parent.left
|
||||
anchors.right: contextButton.left
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: ownerName
|
||||
anchors.top: printJobName.bottom
|
||||
text: modelData.activePrintJob != null ? modelData.activePrintJob.owner : ""
|
||||
font: UM.Theme.getFont("default")
|
||||
opacity: 0.6
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
function switchPopupState()
|
||||
{
|
||||
if (popup.visible)
|
||||
{
|
||||
popup.close()
|
||||
}
|
||||
else
|
||||
{
|
||||
popup.open()
|
||||
}
|
||||
}
|
||||
|
||||
Controls2.Button
|
||||
{
|
||||
id: contextButton
|
||||
text: "\u22EE" //Unicode; Three stacked points.
|
||||
font.pixelSize: 25
|
||||
width: 35
|
||||
height: width
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
hoverEnabled: true
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: contextButton.down || contextButton.hovered ? 1 : 0
|
||||
width: contextButton.width
|
||||
height: contextButton.height
|
||||
radius: 0.5 * width
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
|
||||
onClicked: parent.switchPopupState()
|
||||
}
|
||||
|
||||
Controls2.Popup
|
||||
{
|
||||
// TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
|
||||
id: popup
|
||||
clip: true
|
||||
closePolicy: Controls2.Popup.CloseOnPressOutsideParent
|
||||
x: parent.width - width
|
||||
y: contextButton.height
|
||||
width: 160
|
||||
height: contentItem.height + 2 * padding
|
||||
visible: false
|
||||
|
||||
transformOrigin: Controls2.Popup.Top
|
||||
contentItem: Item
|
||||
{
|
||||
width: popup.width - 2 * popup.padding
|
||||
height: childrenRect.height + 15
|
||||
Controls2.Button
|
||||
{
|
||||
id: pauseButton
|
||||
text: modelData.activePrintJob != null && modelData.activePrintJob.state == "paused" ? catalog.i18nc("@label", "Resume") : catalog.i18nc("@label", "Pause")
|
||||
onClicked:
|
||||
{
|
||||
if(modelData.activePrintJob.state == "paused")
|
||||
{
|
||||
modelData.activePrintJob.setState("print")
|
||||
}
|
||||
else if(modelData.activePrintJob.state == "printing")
|
||||
{
|
||||
modelData.activePrintJob.setState("pause")
|
||||
}
|
||||
popup.close()
|
||||
}
|
||||
width: parent.width
|
||||
enabled: modelData.activePrintJob != null && ["paused", "printing"].indexOf(modelData.activePrintJob.state) >= 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
hoverEnabled: true
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: pauseButton.down || pauseButton.hovered ? 1 : 0
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
}
|
||||
|
||||
Controls2.Button
|
||||
{
|
||||
id: abortButton
|
||||
text: catalog.i18nc("@label", "Abort")
|
||||
onClicked:
|
||||
{
|
||||
modelData.activePrintJob.setState("abort")
|
||||
popup.close()
|
||||
}
|
||||
width: parent.width
|
||||
anchors.top: pauseButton.bottom
|
||||
hoverEnabled: true
|
||||
enabled: modelData.activePrintJob != null && ["paused", "printing", "pre_print"].indexOf(modelData.activePrintJob.state) >= 0
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: abortButton.down || abortButton.hovered ? 1 : 0
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Item
|
||||
{
|
||||
width: popup.width
|
||||
height: popup.height
|
||||
|
||||
DropShadow
|
||||
{
|
||||
anchors.fill: pointedRectangle
|
||||
radius: 5
|
||||
color: "#3F000000" // 25% shadow
|
||||
source: pointedRectangle
|
||||
transparentBorder: true
|
||||
verticalOffset: 2
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: pointedRectangle
|
||||
width: parent.width -10
|
||||
height: parent.height -10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: point
|
||||
height: 13
|
||||
width: 13
|
||||
color: UM.Theme.getColor("setting_control")
|
||||
transform: Rotation { angle: 45}
|
||||
anchors.right: bloop.right
|
||||
y: 1
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: bloop
|
||||
color: UM.Theme.getColor("setting_control")
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition
|
||||
{
|
||||
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
|
||||
NumberAnimation { property: "visible"; duration: 75; }
|
||||
}
|
||||
enter: Transition
|
||||
{
|
||||
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
|
||||
NumberAnimation { property: "visible"; duration: 75; }
|
||||
}
|
||||
|
||||
onClosed: visible = false
|
||||
onOpened: visible = true
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
id: printJobPreview
|
||||
source: modelData.activePrintJob != null ? modelData.activePrintJob.previewImageUrl : ""
|
||||
anchors.top: ownerName.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width / 2
|
||||
height: width
|
||||
opacity:
|
||||
{
|
||||
if(modelData.activePrintJob == null)
|
||||
{
|
||||
return 1.0
|
||||
}
|
||||
|
||||
switch(modelData.activePrintJob.state)
|
||||
{
|
||||
case "wait_cleanup":
|
||||
case "wait_user_action":
|
||||
case "paused":
|
||||
return 0.5
|
||||
default:
|
||||
return 1.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: statusImage
|
||||
anchors.centerIn: printJobPreview
|
||||
source:
|
||||
{
|
||||
if(modelData.activePrintJob == null)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
switch(modelData.activePrintJob.state)
|
||||
{
|
||||
case "paused":
|
||||
return "../svg/paused-icon.svg"
|
||||
case "wait_cleanup":
|
||||
if(modelData.activePrintJob.timeElapsed < modelData.activePrintJob.timeTotal)
|
||||
{
|
||||
return "../svg/aborted-icon.svg"
|
||||
}
|
||||
return "../svg/approved-icon.svg"
|
||||
case "wait_user_action":
|
||||
return "../svg/aborted-icon.svg"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
visible: source != ""
|
||||
width: 0.5 * printJobPreview.width
|
||||
height: 0.5 * printJobPreview.height
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
color: "black"
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: showCameraIcon
|
||||
width: 35 * screenScaleFactor
|
||||
height: width
|
||||
radius: 0.5 * width
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: printJobPreview.bottom
|
||||
color: UM.Theme.getColor("setting_control_border_highlight")
|
||||
Image
|
||||
{
|
||||
width: parent.width
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: parent.rightMargin
|
||||
source: "../svg/camera-icon.svg"
|
||||
}
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill:parent
|
||||
onClicked:
|
||||
{
|
||||
OutputDevice.setActiveCamera(modelData.camera)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProgressBar
|
||||
{
|
||||
property var progress:
|
||||
{
|
||||
if(modelData.activePrintJob == null)
|
||||
{
|
||||
return 0
|
||||
}
|
||||
var result = modelData.activePrintJob.timeElapsed / modelData.activePrintJob.timeTotal
|
||||
if(result > 1.0)
|
||||
{
|
||||
result = 1.0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
id: jobProgressBar
|
||||
width: parent.width
|
||||
value: progress
|
||||
anchors.top: detailedInfo.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
visible: modelData.activePrintJob != null && modelData.activePrintJob != undefined
|
||||
|
||||
style: ProgressBarStyle
|
||||
{
|
||||
property var progressText:
|
||||
{
|
||||
if(modelData.activePrintJob == null)
|
||||
{
|
||||
return ""
|
||||
}
|
||||
|
||||
switch(modelData.activePrintJob.state)
|
||||
{
|
||||
case "wait_cleanup":
|
||||
if(modelData.activePrintJob.timeTotal > modelData.activePrintJob.timeElapsed)
|
||||
{
|
||||
return catalog.i18nc("@label:status", "Aborted")
|
||||
}
|
||||
return catalog.i18nc("@label:status", "Finished")
|
||||
case "pre_print":
|
||||
case "sent_to_printer":
|
||||
return catalog.i18nc("@label:status", "Preparing")
|
||||
case "aborted":
|
||||
case "wait_user_action":
|
||||
return catalog.i18nc("@label:status", "Aborted")
|
||||
case "pausing":
|
||||
return catalog.i18nc("@label:status", "Pausing")
|
||||
case "paused":
|
||||
return catalog.i18nc("@label:status", "Paused")
|
||||
case "resuming":
|
||||
return catalog.i18nc("@label:status", "Resuming")
|
||||
case "queued":
|
||||
return catalog.i18nc("@label:status", "Action required")
|
||||
default:
|
||||
OutputDevice.formatDuration(modelData.activePrintJob.timeTotal - modelData.activePrintJob.timeElapsed)
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
implicitWidth: 100
|
||||
implicitHeight: visible ? 24 : 0
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
|
||||
progress: Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("primary")
|
||||
id: progressItem
|
||||
function getTextOffset()
|
||||
{
|
||||
if(progressItem.width + progressLabel.width < control.width)
|
||||
{
|
||||
return progressItem.width + UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
else
|
||||
{
|
||||
return progressItem.width - progressLabel.width - UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: progressLabel
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: getTextOffset()
|
||||
text: progressText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: progressItem.width + progressLabel.width < control.width ? "black" : "white"
|
||||
width: contentWidth
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
plugins/UM3NetworkPrinting/resources/qml/ClusterMonitorItem.qml
Normal file
|
@ -0,0 +1,108 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.3 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
Component
|
||||
{
|
||||
Rectangle
|
||||
{
|
||||
id: monitorFrame
|
||||
width: maximumWidth
|
||||
height: maximumHeight
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight")
|
||||
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
|
||||
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: manageQueueLabel
|
||||
anchors.rightMargin: 4 * UM.Theme.getSize("default_margin").width
|
||||
anchors.right: queuedPrintJobs.right
|
||||
anchors.bottom: queuedLabel.bottom
|
||||
text: catalog.i18nc("@label link to connect manager", "Manage queue")
|
||||
font: UM.Theme.getFont("default")
|
||||
color: UM.Theme.getColor("primary")
|
||||
linkColor: UM.Theme.getColor("primary")
|
||||
}
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: manageQueueLabel
|
||||
hoverEnabled: true
|
||||
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel()
|
||||
onEntered: manageQueueLabel.font.underline = true
|
||||
onExited: manageQueueLabel.font.underline = false
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: queuedLabel
|
||||
anchors.left: queuedPrintJobs.left
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
|
||||
anchors.leftMargin: 3 * UM.Theme.getSize("default_margin").width
|
||||
text: catalog.i18nc("@label", "Queued")
|
||||
font: UM.Theme.getFont("large")
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
|
||||
ScrollView
|
||||
{
|
||||
id: queuedPrintJobs
|
||||
|
||||
anchors
|
||||
{
|
||||
top: queuedLabel.bottom
|
||||
topMargin: UM.Theme.getSize("default_margin").height
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottomMargin: 0
|
||||
bottom: parent.bottom
|
||||
}
|
||||
style: UM.Theme.styles.scrollview
|
||||
width: Math.min(800 * screenScaleFactor, maximumWidth)
|
||||
ListView
|
||||
{
|
||||
anchors.fill: parent
|
||||
//anchors.margins: UM.Theme.getSize("default_margin").height
|
||||
spacing: UM.Theme.getSize("default_margin").height - 10 // 2x the shadow radius
|
||||
|
||||
model: OutputDevice.queuedPrintJobs
|
||||
|
||||
delegate: PrintJobInfoBlock
|
||||
{
|
||||
printJob: modelData
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.leftMargin: UM.Theme.getSize("default_margin").height
|
||||
height: 175 * screenScaleFactor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrinterVideoStream
|
||||
{
|
||||
visible: OutputDevice.activeCamera != null
|
||||
anchors.fill: parent
|
||||
camera: OutputDevice.activeCamera
|
||||
}
|
||||
|
||||
onVisibleChanged:
|
||||
{
|
||||
if (monitorFrame != null && !monitorFrame.visible)
|
||||
{
|
||||
OutputDevice.setActiveCamera(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -364,7 +364,6 @@ Cura.MachineAction
|
|||
{
|
||||
id: addressField
|
||||
width: parent.width
|
||||
maximumLength: 40
|
||||
validator: RegExpValidator
|
||||
{
|
||||
regExp: /[a-zA-Z0-9\.\-\_]*/
|
|
@ -0,0 +1,93 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import UM 1.2 as UM
|
||||
|
||||
|
||||
Item
|
||||
{
|
||||
id: extruderInfo
|
||||
property var printCoreConfiguration
|
||||
|
||||
width: Math.round(parent.width / 2)
|
||||
height: childrenRect.height
|
||||
|
||||
Item
|
||||
{
|
||||
id: extruderCircle
|
||||
width: 30
|
||||
height: 30
|
||||
|
||||
anchors.verticalCenter: printAndMaterialLabel.verticalCenter
|
||||
opacity:
|
||||
{
|
||||
if(printCoreConfiguration == null || printCoreConfiguration.activeMaterial == null || printCoreConfiguration.hotendID == null)
|
||||
{
|
||||
return 0.5
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors.fill: parent
|
||||
radius: Math.round(width / 2)
|
||||
border.width: 2
|
||||
border.color: "black"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
anchors.centerIn: parent
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
text: printCoreConfiguration.position + 1
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: printAndMaterialLabel
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
left: extruderCircle.right
|
||||
margins: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
height: childrenRect.height
|
||||
|
||||
Label
|
||||
{
|
||||
id: materialLabel
|
||||
text:
|
||||
{
|
||||
if(printCoreConfiguration != undefined && printCoreConfiguration.activeMaterial != undefined)
|
||||
{
|
||||
return printCoreConfiguration.activeMaterial.name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: printCoreLabel
|
||||
text:
|
||||
{
|
||||
if(printCoreConfiguration != undefined && printCoreConfiguration.hotendID != undefined)
|
||||
{
|
||||
return printCoreConfiguration.hotendID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
anchors.top: materialLabel.bottom
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
opacity: 0.6
|
||||
font: UM.Theme.getFont("default")
|
||||
}
|
||||
}
|
||||
}
|
378
plugins/UM3NetworkPrinting/resources/qml/PrintJobInfoBlock.qml
Normal file
|
@ -0,0 +1,378 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 2.0
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import UM 1.3 as UM
|
||||
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
property var printJob: null
|
||||
property var shadowRadius: 5
|
||||
function getPrettyTime(time)
|
||||
{
|
||||
return OutputDevice.formatDuration(time)
|
||||
}
|
||||
|
||||
UM.I18nCatalog
|
||||
{
|
||||
id: catalog
|
||||
name: "cura"
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: background
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
topMargin: 3
|
||||
left: parent.left
|
||||
leftMargin: base.shadowRadius
|
||||
rightMargin: base.shadowRadius
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
bottomMargin: base.shadowRadius
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow
|
||||
{
|
||||
radius: base.shadowRadius
|
||||
verticalOffset: 2
|
||||
color: "#3F000000" // 25% shadow
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
// Content on the left of the infobox
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.horizontalCenter
|
||||
margins: 2 * UM.Theme.getSize("default_margin").width
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: printJobName
|
||||
text: printJob.name
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: ownerName
|
||||
anchors.top: printJobName.bottom
|
||||
text: printJob.owner
|
||||
font: UM.Theme.getFont("default")
|
||||
opacity: 0.6
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
id: printJobPreview
|
||||
source: printJob.previewImageUrl
|
||||
anchors.top: ownerName.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: totalTimeLabel.bottom
|
||||
width: height
|
||||
opacity: printJob.state == "error" ? 0.5 : 1.0
|
||||
}
|
||||
|
||||
UM.RecolorImage
|
||||
{
|
||||
id: statusImage
|
||||
anchors.centerIn: printJobPreview
|
||||
source: printJob.state == "error" ? "../svg/aborted-icon.svg" : ""
|
||||
visible: source != ""
|
||||
width: 0.5 * printJobPreview.width
|
||||
height: 0.5 * printJobPreview.height
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
color: "black"
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: totalTimeLabel
|
||||
opacity: 0.6
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
font: UM.Theme.getFont("default")
|
||||
text: printJob != null ? getPrettyTime(printJob.timeTotal) : ""
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
// Content on the right side of the infobox.
|
||||
anchors
|
||||
{
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
left: parent.horizontalCenter
|
||||
right: parent.right
|
||||
margins: 2 * UM.Theme.getSize("default_margin").width
|
||||
leftMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: targetPrinterLabel
|
||||
elide: Text.ElideRight
|
||||
font: UM.Theme.getFont("default_bold")
|
||||
text:
|
||||
{
|
||||
if(printJob.assignedPrinter == null)
|
||||
{
|
||||
if(printJob.state == "error")
|
||||
{
|
||||
return catalog.i18nc("@label", "Waiting for: Unavailable printer")
|
||||
}
|
||||
return catalog.i18nc("@label", "Waiting for: First available")
|
||||
}
|
||||
else
|
||||
{
|
||||
return catalog.i18nc("@label", "Waiting for: ") + printJob.assignedPrinter.name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: contextButton.left
|
||||
rightMargin: UM.Theme.getSize("default_margin").width
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function switchPopupState()
|
||||
{
|
||||
popup.visible ? popup.close() : popup.open()
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: contextButton
|
||||
text: "\u22EE" //Unicode; Three stacked points.
|
||||
font.pixelSize: 25
|
||||
width: 35
|
||||
height: width
|
||||
anchors
|
||||
{
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
}
|
||||
hoverEnabled: true
|
||||
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: contextButton.down || contextButton.hovered ? 1 : 0
|
||||
width: contextButton.width
|
||||
height: contextButton.height
|
||||
radius: 0.5 * width
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
|
||||
onClicked: parent.switchPopupState()
|
||||
}
|
||||
|
||||
Popup
|
||||
{
|
||||
// TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
|
||||
id: popup
|
||||
clip: true
|
||||
closePolicy: Popup.CloseOnPressOutsideParent
|
||||
x: parent.width - width
|
||||
y: contextButton.height
|
||||
width: 160
|
||||
height: contentItem.height + 2 * padding
|
||||
visible: false
|
||||
|
||||
transformOrigin: Popup.Top
|
||||
contentItem: Item
|
||||
{
|
||||
width: popup.width - 2 * popup.padding
|
||||
height: childrenRect.height + 15
|
||||
Button
|
||||
{
|
||||
id: sendToTopButton
|
||||
text: catalog.i18nc("@label", "Move to top")
|
||||
onClicked:
|
||||
{
|
||||
OutputDevice.sendJobToTop(printJob.key)
|
||||
popup.close()
|
||||
}
|
||||
width: parent.width
|
||||
enabled: OutputDevice.queuedPrintJobs[0].key != printJob.key
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
hoverEnabled: true
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: sendToTopButton.down || sendToTopButton.hovered ? 1 : 0
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
id: deleteButton
|
||||
text: catalog.i18nc("@label", "Delete")
|
||||
onClicked:
|
||||
{
|
||||
OutputDevice.deleteJobFromQueue(printJob.key)
|
||||
popup.close()
|
||||
}
|
||||
width: parent.width
|
||||
anchors.top: sendToTopButton.bottom
|
||||
hoverEnabled: true
|
||||
background: Rectangle
|
||||
{
|
||||
opacity: deleteButton.down || deleteButton.hovered ? 1 : 0
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Item
|
||||
{
|
||||
width: popup.width
|
||||
height: popup.height
|
||||
|
||||
DropShadow
|
||||
{
|
||||
anchors.fill: pointedRectangle
|
||||
radius: 5
|
||||
color: "#3F000000" // 25% shadow
|
||||
source: pointedRectangle
|
||||
transparentBorder: true
|
||||
verticalOffset: 2
|
||||
}
|
||||
|
||||
Item
|
||||
{
|
||||
id: pointedRectangle
|
||||
width: parent.width -10
|
||||
height: parent.height -10
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: point
|
||||
height: 13
|
||||
width: 13
|
||||
color: UM.Theme.getColor("setting_control")
|
||||
transform: Rotation { angle: 45}
|
||||
anchors.right: bloop.right
|
||||
y: 1
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
id: bloop
|
||||
color: UM.Theme.getColor("setting_control")
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition
|
||||
{
|
||||
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
|
||||
NumberAnimation { property: "visible"; duration: 75; }
|
||||
}
|
||||
enter: Transition
|
||||
{
|
||||
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
|
||||
NumberAnimation { property: "visible"; duration: 75; }
|
||||
}
|
||||
|
||||
onClosed: visible = false
|
||||
onOpened: visible = true
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
id: printerFamilyPills
|
||||
spacing: 0.5 * UM.Theme.getSize("default_margin").width
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: extrudersInfo.top
|
||||
bottomMargin: UM.Theme.getSize("default_margin").height
|
||||
}
|
||||
height: childrenRect.height
|
||||
Repeater
|
||||
{
|
||||
model: printJob.compatibleMachineFamilies
|
||||
|
||||
delegate: PrinterFamilyPill
|
||||
{
|
||||
text: modelData
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
padding: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
// PrintCore && Material config
|
||||
Row
|
||||
{
|
||||
id: extrudersInfo
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: childrenRect.height
|
||||
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: leftExtruderInfo
|
||||
width: Math.round(parent.width / 2)
|
||||
printCoreConfiguration: printJob.configuration.extruderConfigurations[0]
|
||||
}
|
||||
|
||||
PrintCoreConfiguration
|
||||
{
|
||||
id: rightExtruderInfo
|
||||
width: Math.round(parent.width / 2)
|
||||
printCoreConfiguration: printJob.configuration.extruderConfigurations[1]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle
|
||||
{
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
width: 2
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: UM.Theme.getSize("default_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.4
|
||||
import UM 1.2 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
property alias color: background.color
|
||||
property alias text: familyNameLabel.text
|
||||
property var padding: 0
|
||||
implicitHeight: familyNameLabel.contentHeight + 2 * padding // Apply the padding to top and bottom.
|
||||
implicitWidth: familyNameLabel.contentWidth + implicitHeight // The extra height is added to ensure the radius doesn't cut something off.
|
||||
Rectangle
|
||||
{
|
||||
id: background
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
color: parent.color
|
||||
anchors.right: parent.right
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
radius: 0.5 * height
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: familyNameLabel
|
||||
anchors.centerIn: parent
|
||||
text: ""
|
||||
}
|
||||
}
|
|
@ -7,6 +7,8 @@ import UM 1.3 as UM
|
|||
|
||||
Item
|
||||
{
|
||||
property var camera: null
|
||||
|
||||
Rectangle
|
||||
{
|
||||
anchors.fill:parent
|
||||
|
@ -17,7 +19,7 @@ Item
|
|||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
onClicked: OutputDevice.setActivePrinter(null)
|
||||
onClicked: OutputDevice.setActiveCamera(null)
|
||||
z: 0
|
||||
}
|
||||
|
||||
|
@ -32,7 +34,7 @@ Item
|
|||
width: 20 * screenScaleFactor
|
||||
height: 20 * screenScaleFactor
|
||||
|
||||
onClicked: OutputDevice.setActivePrinter(null)
|
||||
onClicked: OutputDevice.setActiveCamera(null)
|
||||
|
||||
style: ButtonStyle
|
||||
{
|
||||
|
@ -65,23 +67,24 @@ Item
|
|||
{
|
||||
if(visible)
|
||||
{
|
||||
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
|
||||
if(camera != null)
|
||||
{
|
||||
OutputDevice.activePrinter.camera.start()
|
||||
camera.start()
|
||||
}
|
||||
} else
|
||||
{
|
||||
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
|
||||
if(camera != null)
|
||||
{
|
||||
OutputDevice.activePrinter.camera.stop()
|
||||
camera.stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
source:
|
||||
{
|
||||
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null && OutputDevice.activePrinter.camera.latestImage)
|
||||
if(camera != null && camera.latestImage != null)
|
||||
{
|
||||
return OutputDevice.activePrinter.camera.latestImage;
|
||||
return camera.latestImage;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
@ -92,7 +95,7 @@ Item
|
|||
anchors.fill: cameraImage
|
||||
onClicked:
|
||||
{
|
||||
OutputDevice.setActivePrinter(null)
|
||||
OutputDevice.setActiveCamera(null)
|
||||
}
|
||||
z: 1
|
||||
}
|
1
plugins/UM3NetworkPrinting/resources/svg/UM3-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 46.16 48"><defs><style>.cls-1{fill:#9a9a9a;}</style></defs><title>UM3-icon</title><g id="Symbols"><g id="system_overview_inactive" data-name="system overview inactive"><path id="Shape" class="cls-1" d="M18.4,12.2h9.26c.1,0,.1,0,.2-.2l1.73-4.27a.22.22,0,0,0-.2-.2H16.67a.22.22,0,0,0-.2.2L18.2,12C18.3,12.2,18.3,12.2,18.4,12.2Z"/><path id="Shape-2" data-name="Shape" class="cls-1" d="M38.33,35.08H7.72a.48.48,0,0,0-.5.51V37a.48.48,0,0,0,.5.51H38.44a.48.48,0,0,0,.5-.51V35.59A.64.64,0,0,0,38.33,35.08Z"/><path id="Shape-3" data-name="Shape" class="cls-1" d="M0,0V48H3.76a2.86,2.86,0,0,1,2.13-1H40.27a2.86,2.86,0,0,1,2.13,1h3.76V0ZM41.28,37a2.83,2.83,0,0,1-2.84,2.84H7.72A2.84,2.84,0,0,1,4.88,37V5.49a.65.65,0,0,1,.61-.61H40.67a.65.65,0,0,1,.61.61Z"/></g></g></svg>
|
After Width: | Height: | Size: 847 B |
1
plugins/UM3NetworkPrinting/resources/svg/UM3x-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 46.16 58"><defs><style>.cls-1{fill:#9a9a9a;}</style></defs><title>UM3x-icon</title><g id="Symbols"><g id="system_overview_inactive" data-name="system overview inactive"><path id="Shape" class="cls-1" d="M18.4,12.2h9.26c.1,0,.1,0,.2-.2l1.73-4.27a.22.22,0,0,0-.2-.2H16.67a.22.22,0,0,0-.2.2L18.2,12C18.3,12.2,18.3,12.2,18.4,12.2Z"/><path id="Shape-2" data-name="Shape" class="cls-1" d="M38.33,45.08H7.72a.48.48,0,0,0-.5.51V47a.48.48,0,0,0,.5.51H38.44a.48.48,0,0,0,.5-.51V45.59A.64.64,0,0,0,38.33,45.08Z"/><path id="Shape-3" data-name="Shape" class="cls-1" d="M0,0V58H3.76a2.86,2.86,0,0,1,2.13-1H40.27a2.86,2.86,0,0,1,2.13,1h3.76V0ZM41.28,35.32V47a2.83,2.83,0,0,1-2.84,2.84H7.72A2.84,2.84,0,0,1,4.88,47V5.49a.65.65,0,0,1,.61-.61H40.67a.65.65,0,0,1,.61.61Z"/></g></g></svg>
|
After Width: | Height: | Size: 854 B |
1
plugins/UM3NetworkPrinting/resources/svg/UMs5-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58"><defs><style>.cls-1{fill:none;}.cls-2{fill:#9a9a9a;}</style></defs><title>UMs5-icon</title><path class="cls-1" d="M33.83,12.33c-.1.2-.1.2-.2.2H24.37c-.1,0-.1,0-.2-.2L22.44,8.06a.22.22,0,0,1,.2-.2H35.36a.22.22,0,0,1,.2.2Z"/><path class="cls-2" d="M35.36,7.86H22.64a.22.22,0,0,0-.2.2l1.73,4.27c.1.2.1.2.2.2h9.26c.1,0,.1,0,.2-.2l1.73-4.27A.22.22,0,0,0,35.36,7.86Z"/><path class="cls-2" d="M0,0V58H3.75a2.85,2.85,0,0,1,2.12-1H52.13a2.85,2.85,0,0,1,2.12,1H58V0ZM37.5,53.82a1.5,1.5,0,0,1-1.5,1.5H22a1.5,1.5,0,0,1-1.5-1.5v-4a1.5,1.5,0,0,1,1.5-1.5H36a1.5,1.5,0,0,1,1.5,1.5Zm15.63-18.5V47a2.83,2.83,0,0,1-2.83,2.84H38.5v0a2.5,2.5,0,0,0-2.5-2.5H22a2.5,2.5,0,0,0-2.5,2.5v0H7.7A2.83,2.83,0,0,1,4.87,47V5.49a.65.65,0,0,1,.6-.61H52.53a.65.65,0,0,1,.6.61Z"/></svg>
|
After Width: | Height: | Size: 842 B |
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>aborted-icon</title><path d="M16,0A16,16,0,1,0,32,16,16,16,0,0,0,16,0Zm1.69,28.89a13,13,0,1,1,11.2-11.2A13,13,0,0,1,17.69,28.89Z"/><polygon points="20.6 9.28 16 13.88 11.4 9.28 9.28 11.4 13.88 16 9.28 20.6 11.4 22.72 16 18.12 20.6 22.72 22.72 20.6 18.12 16 22.72 11.4 20.6 9.28"/></svg>
|
After Width: | Height: | Size: 386 B |
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 844 B |
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>approved-icon</title><path d="M16,29A13,13,0,1,0,3,16,13,13,0,0,0,16,29ZM8,14.59l6,5.3L23.89,9l2.22,2L14.18,24.11,6,16.83Z" fill="none"/><path d="M16,32A16,16,0,1,0,0,16,16,16,0,0,0,16,32ZM16,3A13,13,0,1,1,3,16,13,13,0,0,1,16,3Z"/><polygon points="26.11 11.01 23.89 8.99 13.96 19.89 8 14.59 6 16.83 14.18 24.11 26.11 11.01"/></svg>
|
After Width: | Height: | Size: 431 B |
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 438 B |
Before Width: | Height: | Size: 194 B After Width: | Height: | Size: 194 B |
1
plugins/UM3NetworkPrinting/resources/svg/paused-icon.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>paused-icon</title><path d="M16,0A16,16,0,1,0,32,16,16,16,0,0,0,16,0Zm0,29A13,13,0,1,1,29,16,13,13,0,0,1,16,29Z"/><rect x="11.5" y="9" width="3" height="14"/><rect x="17.5" y="9" width="3" height="14"/></svg>
|
After Width: | Height: | Size: 308 B |
|
@ -0,0 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>warning-icon</title><path d="M18.09,1.31A2.35,2.35,0,0,0,16,0a2.31,2.31,0,0,0-2.09,1.31L.27,28.44A2.49,2.49,0,0,0,.11,30.3a2.38,2.38,0,0,0,1.16,1.42A2.33,2.33,0,0,0,2.36,32H29.64A2.4,2.4,0,0,0,32,29.57a2.55,2.55,0,0,0-.27-1.14ZM3.34,29,16,3.83,28.66,29Z"/><polygon points="13.94 25.19 13.94 25.19 13.94 25.19 13.94 25.19"/><polygon points="14.39 21.68 17.61 21.68 18.11 11.85 13.89 11.85 14.39 21.68"/><path d="M16.06,23.08a2.19,2.19,0,0,0-1.56,3.66,2.14,2.14,0,0,0,1.56.55,2.06,2.06,0,0,0,1.54-.55,2.1,2.1,0,0,0,.55-1.55,2.17,2.17,0,0,0-.53-1.55A2.05,2.05,0,0,0,16.06,23.08Z"/></svg>
|
After Width: | Height: | Size: 684 B |
|
@ -9,7 +9,7 @@ from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer as
|
|||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Mesh.MeshWriter import MeshWriter # For typing
|
||||
|
||||
from UM.Message import Message
|
||||
from UM.Qt.Duration import Duration, DurationFormat
|
||||
from UM.OutputDevice import OutputDeviceError # To show that something went wrong when writing.
|
||||
|
@ -17,6 +17,8 @@ from UM.Scene.SceneNode import SceneNode #For typing.
|
|||
from UM.Version import Version # To check against firmware versions for support.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
|
@ -27,12 +29,12 @@ from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
|
|||
from .SendMaterialJob import SendMaterialJob
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtGui import QDesktopServices, QImage
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
|
||||
|
||||
from time import time
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, List, Set
|
||||
from typing import Optional, Dict, List
|
||||
|
||||
import io # To create the correct buffers for sending data to the printer.
|
||||
import json
|
||||
|
@ -44,6 +46,7 @@ i18n_catalog = i18nCatalog("cura")
|
|||
class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||
printJobsChanged = pyqtSignal()
|
||||
activePrinterChanged = pyqtSignal()
|
||||
activeCameraChanged = pyqtSignal()
|
||||
|
||||
# This is a bit of a hack, as the notify can only use signals that are defined by the class that they are in.
|
||||
# Inheritance doesn't seem to work. Tying them together does work, but i'm open for better suggestions.
|
||||
|
@ -59,8 +62,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self._print_jobs = [] # type: List[PrintJobOutputModel]
|
||||
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
|
||||
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/ClusterMonitorItem.qml")
|
||||
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/ClusterControlItem.qml")
|
||||
|
||||
# See comments about this hack with the clusterPrintersChanged signal
|
||||
self.printersChanged.connect(self.clusterPrintersChanged)
|
||||
|
@ -89,9 +92,12 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self._finished_jobs = [] # type: List[PrintJobOutputModel]
|
||||
|
||||
self._cluster_size = int(properties.get(b"cluster_size", 0))
|
||||
self._cluster_size = int(properties.get(b"cluster_size", 0)) # type: int
|
||||
|
||||
self._latest_reply_handler = None # type: Optional[QNetworkReply]
|
||||
self._sending_job = None
|
||||
|
||||
self._active_camera = None # type: Optional[NetworkCamera]
|
||||
|
||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||
self.writeStarted.emit(self)
|
||||
|
@ -140,6 +146,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
Logger.log("e", "Missing file or mesh writer!")
|
||||
return
|
||||
self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
|
||||
if self._sending_job is not None:
|
||||
self._sending_job.send(None) # Start the generator.
|
||||
|
||||
if len(self._printers) > 1: # We need to ask the user.
|
||||
|
@ -151,13 +158,13 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
def _spawnPrinterSelectionDialog(self):
|
||||
if self._printer_selection_dialog is None:
|
||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml")
|
||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/PrintWindow.qml")
|
||||
self._printer_selection_dialog = CuraApplication.getInstance().createQmlComponent(path, {"OutputDevice": self})
|
||||
if self._printer_selection_dialog is not None:
|
||||
self._printer_selection_dialog.show()
|
||||
|
||||
@pyqtProperty(int, constant=True)
|
||||
def clusterSize(self):
|
||||
def clusterSize(self) -> int:
|
||||
return self._cluster_size
|
||||
|
||||
## Allows the user to choose a printer to print with from the printer
|
||||
|
@ -165,6 +172,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
# \param target_printer The name of the printer to target.
|
||||
@pyqtSlot(str)
|
||||
def selectPrinter(self, target_printer: str = "") -> None:
|
||||
if self._sending_job is not None:
|
||||
self._sending_job.send(target_printer)
|
||||
|
||||
@pyqtSlot()
|
||||
|
@ -253,6 +261,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
def activePrinter(self) -> Optional[PrinterOutputModel]:
|
||||
return self._active_printer
|
||||
|
||||
@pyqtProperty(QObject, notify=activeCameraChanged)
|
||||
def activeCamera(self) -> Optional[NetworkCamera]:
|
||||
return self._active_camera
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
|
||||
if self._active_printer != printer:
|
||||
|
@ -261,6 +273,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._active_printer = printer
|
||||
self.activePrinterChanged.emit()
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def setActiveCamera(self, camera: Optional[NetworkCamera]) -> None:
|
||||
if self._active_camera != camera:
|
||||
if self._active_camera:
|
||||
self._active_camera.stop()
|
||||
|
||||
self._active_camera = camera
|
||||
|
||||
if self._active_camera:
|
||||
self._active_camera.start()
|
||||
|
||||
self.activeCameraChanged.emit()
|
||||
|
||||
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
|
||||
if self._progress_message:
|
||||
self._progress_message.hide()
|
||||
|
@ -279,8 +304,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
# If successfully sent:
|
||||
if bytes_sent == bytes_total:
|
||||
# Show a confirmation to the user so they know the job was sucessful and provide the option to switch to the
|
||||
# monitor tab.
|
||||
# Show a confirmation to the user so they know the job was sucessful and provide the option to switch to
|
||||
# the monitor tab.
|
||||
self._success_message = Message(
|
||||
i18n_catalog.i18nc("@info:status", "Print job was successfully sent to the printer."),
|
||||
lifetime=5, dismissable=True,
|
||||
|
@ -329,7 +354,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||
def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
|
||||
return [print_job for print_job in self._print_jobs if print_job.state == "queued"]
|
||||
return [print_job for print_job in self._print_jobs if print_job.state == "queued" or print_job.state == "error"]
|
||||
|
||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||
def activePrintJobs(self) -> List[PrintJobOutputModel]:
|
||||
|
@ -348,6 +373,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
result.append({"machine_type": machine_type, "count": str(printer_count[machine_type])})
|
||||
return result
|
||||
|
||||
@pyqtProperty("QVariantList", notify=clusterPrintersChanged)
|
||||
def printers(self):
|
||||
return self._printers
|
||||
|
||||
@pyqtSlot(int, result = str)
|
||||
def formatDuration(self, seconds: int) -> str:
|
||||
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
|
||||
|
@ -364,6 +393,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
|
||||
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def sendJobToTop(self, print_job_uuid: str) -> None:
|
||||
# This function is part of the output device (and not of the printjob output model) as this type of operation
|
||||
# is a modification of the cluster queue and not of the actual job.
|
||||
data = "{\"to_position\": 0}"
|
||||
self.put("print_jobs/{uuid}/move_to_position".format(uuid = print_job_uuid), data, on_finished=None)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def deleteJobFromQueue(self, print_job_uuid: str) -> None:
|
||||
# This function is part of the output device (and not of the printjob output model) as this type of operation
|
||||
# is a modification of the cluster queue and not of the actual job.
|
||||
self.delete("print_jobs/{uuid}".format(uuid = print_job_uuid), on_finished=None)
|
||||
|
||||
def _printJobStateChanged(self) -> None:
|
||||
username = self._getUserName()
|
||||
|
||||
|
@ -392,11 +434,26 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
super().connect()
|
||||
self.sendMaterialProfiles()
|
||||
|
||||
def _onGetPreviewImageFinished(self, reply: QNetworkReply) -> None:
|
||||
reply_url = reply.url().toString()
|
||||
|
||||
uuid = reply_url[reply_url.find("print_jobs/")+len("print_jobs/"):reply_url.rfind("/preview_image")]
|
||||
|
||||
print_job = findByKey(self._print_jobs, uuid)
|
||||
if print_job:
|
||||
image = QImage()
|
||||
image.loadFromData(reply.readAll())
|
||||
print_job.updatePreviewImage(image)
|
||||
|
||||
def _update(self) -> None:
|
||||
super()._update()
|
||||
self.get("printers/", on_finished = self._onGetPrintersDataFinished)
|
||||
self.get("print_jobs/", on_finished = self._onGetPrintJobsFinished)
|
||||
|
||||
for print_job in self._print_jobs:
|
||||
if print_job.getPreviewImage() is None:
|
||||
self.get("print_jobs/{uuid}/preview_image".format(uuid=print_job.key), on_finished=self._onGetPreviewImageFinished)
|
||||
|
||||
def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
|
||||
if not checkValidGetReply(reply):
|
||||
return
|
||||
|
@ -407,16 +464,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
print_jobs_seen = []
|
||||
job_list_changed = False
|
||||
for print_job_data in result:
|
||||
for idx, print_job_data in enumerate(result):
|
||||
print_job = findByKey(self._print_jobs, print_job_data["uuid"])
|
||||
|
||||
if print_job is None:
|
||||
print_job = self._createPrintJobModel(print_job_data)
|
||||
job_list_changed = True
|
||||
elif not job_list_changed:
|
||||
# Check if the order of the jobs has changed since the last check
|
||||
if self._print_jobs.index(print_job) != idx:
|
||||
job_list_changed = True
|
||||
|
||||
self._updatePrintJob(print_job, print_job_data)
|
||||
|
||||
if print_job.state != "queued": # Print job should be assigned to a printer.
|
||||
if print_job.state != "queued" and print_job.state != "error": # Print job should be assigned to a printer.
|
||||
if print_job.state in ["failed", "finished", "aborted", "none"]:
|
||||
# Print job was already completed, so don't attach it to a printer.
|
||||
printer = None
|
||||
|
@ -437,6 +497,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
job_list_changed = job_list_changed or self._removeJob(removed_job)
|
||||
|
||||
if job_list_changed:
|
||||
# Override the old list with the new list (either because jobs were removed / added or order changed)
|
||||
self._print_jobs = print_jobs_seen
|
||||
self.printJobsChanged.emit() # Do a single emit for all print job changes.
|
||||
|
||||
def _onGetPrintersDataFinished(self, reply: QNetworkReply) -> None:
|
||||
|
@ -478,16 +540,59 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
def _createPrintJobModel(self, data: Dict[str, Any]) -> PrintJobOutputModel:
|
||||
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
|
||||
key=data["uuid"], name= data["name"])
|
||||
|
||||
configuration = ConfigurationModel()
|
||||
extruders = [ExtruderConfigurationModel(position = idx) for idx in range(0, self._number_of_extruders)]
|
||||
for index in range(0, self._number_of_extruders):
|
||||
try:
|
||||
extruder_data = data["configuration"][index]
|
||||
except IndexError:
|
||||
continue
|
||||
extruder = extruders[int(data["configuration"][index]["extruder_index"])]
|
||||
extruder.setHotendID(extruder_data.get("print_core_id", ""))
|
||||
extruder.setMaterial(self._createMaterialOutputModel(extruder_data.get("material", {})))
|
||||
|
||||
configuration.setExtruderConfigurations(extruders)
|
||||
print_job.updateConfiguration(configuration)
|
||||
print_job.setCompatibleMachineFamilies(data.get("compatible_machine_families", []))
|
||||
print_job.stateChanged.connect(self._printJobStateChanged)
|
||||
self._print_jobs.append(print_job)
|
||||
return print_job
|
||||
|
||||
def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict[str, Any]) -> None:
|
||||
print_job.updateTimeTotal(data["time_total"])
|
||||
print_job.updateTimeElapsed(data["time_elapsed"])
|
||||
print_job.updateState(data["status"])
|
||||
impediments_to_printing = data.get("impediments_to_printing", [])
|
||||
print_job.updateOwner(data["owner"])
|
||||
|
||||
status_set_by_impediment = False
|
||||
for impediment in impediments_to_printing:
|
||||
if impediment["severity"] == "UNFIXABLE":
|
||||
status_set_by_impediment = True
|
||||
print_job.updateState("error")
|
||||
break
|
||||
|
||||
if not status_set_by_impediment:
|
||||
print_job.updateState(data["status"])
|
||||
|
||||
|
||||
def _createMaterialOutputModel(self, material_data) -> MaterialOutputModel:
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(type="material", GUID=material_data["guid"])
|
||||
if containers:
|
||||
color = containers[0].getMetaDataEntry("color_code")
|
||||
brand = containers[0].getMetaDataEntry("brand")
|
||||
material_type = containers[0].getMetaDataEntry("material")
|
||||
name = containers[0].getName()
|
||||
else:
|
||||
Logger.log("w",
|
||||
"Unable to find material with guid {guid}. Using data as provided by cluster".format(
|
||||
guid=material_data["guid"]))
|
||||
color = material_data["color"]
|
||||
brand = material_data["brand"]
|
||||
material_type = material_data["material"]
|
||||
name = "Empty" if material_data["material"] == "empty" else "Unknown"
|
||||
return MaterialOutputModel(guid=material_data["guid"], type=material_type,
|
||||
brand=brand, color=color, name=name)
|
||||
|
||||
def _updatePrinter(self, printer: PrinterOutputModel, data: Dict[str, Any]) -> None:
|
||||
# For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
|
||||
# Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
|
||||
|
@ -523,24 +628,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
material_data = extruder_data["material"]
|
||||
if extruder.activeMaterial is None or extruder.activeMaterial.guid != material_data["guid"]:
|
||||
containers = ContainerRegistry.getInstance().findInstanceContainers(type="material",
|
||||
GUID=material_data["guid"])
|
||||
if containers:
|
||||
color = containers[0].getMetaDataEntry("color_code")
|
||||
brand = containers[0].getMetaDataEntry("brand")
|
||||
material_type = containers[0].getMetaDataEntry("material")
|
||||
name = containers[0].getName()
|
||||
else:
|
||||
Logger.log("w",
|
||||
"Unable to find material with guid {guid}. Using data as provided by cluster".format(
|
||||
guid=material_data["guid"]))
|
||||
color = material_data["color"]
|
||||
brand = material_data["brand"]
|
||||
material_type = material_data["material"]
|
||||
name = "Empty" if material_data["material"] == "empty" else "Unknown"
|
||||
|
||||
material = MaterialOutputModel(guid=material_data["guid"], type=material_type,
|
||||
brand=brand, color=color, name=name)
|
||||
material = self._createMaterialOutputModel(material_data)
|
||||
extruder.updateActiveMaterial(material)
|
||||
|
||||
def _removeJob(self, job: PrintJobOutputModel) -> bool:
|
||||
|
@ -568,6 +656,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
job = SendMaterialJob(device = self)
|
||||
job.run()
|
||||
|
||||
|
||||
def loadJsonFromReply(reply: QNetworkReply) -> Optional[List[Dict[str, Any]]]:
|
||||
try:
|
||||
result = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
|
@ -586,8 +675,8 @@ def checkValidGetReply(reply: QNetworkReply) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
|
||||
for item in list:
|
||||
def findByKey(lst: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
|
||||
for item in lst:
|
||||
if item.key == key:
|
||||
return item
|
||||
return None
|
|
@ -6,8 +6,6 @@ from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
|||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
|
||||
|
||||
class ClusterUM3PrinterOutputController(PrinterOutputController):
|
||||
def __init__(self, output_device):
|
|
@ -24,7 +24,7 @@ class DiscoverUM3Action(MachineAction):
|
|||
|
||||
def __init__(self) -> None:
|
||||
super().__init__("DiscoverUM3Action", catalog.i18nc("@action","Connect via Network"))
|
||||
self._qml_url = "DiscoverUM3Action.qml"
|
||||
self._qml_url = "resources/qml/DiscoverUM3Action.qml"
|
||||
|
||||
self._network_plugin = None #type: Optional[UM3OutputDevicePlugin]
|
||||
|
||||
|
@ -174,7 +174,7 @@ class DiscoverUM3Action(MachineAction):
|
|||
plugin_path = PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting")
|
||||
if not plugin_path:
|
||||
return
|
||||
path = os.path.join(plugin_path, "UM3InfoComponents.qml")
|
||||
path = os.path.join(plugin_path, "resources/qml/UM3InfoComponents.qml")
|
||||
self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
|
||||
if not self.__additional_components_view:
|
||||
Logger.log("w", "Could not create ui components for UM3.")
|
|
@ -76,7 +76,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self.setIconName("print")
|
||||
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "MonitorItem.qml")
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/MonitorItem.qml")
|
||||
|
||||
self._output_controller = LegacyUM3PrinterOutputController(self)
|
||||
|
|
@ -326,7 +326,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
if self._firmware_name is None:
|
||||
self.sendCommand("M115")
|
||||
|
||||
if (b"ok " in line and b"T:" in line) or b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
|
||||
if (b"ok " in line and b"T:" in line) or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
|
||||
extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
|
||||
# Update all temperature values
|
||||
matched_extruder_nrs = []
|
||||
|
|
|
@ -60,8 +60,8 @@ _RENAMED_MATERIAL_PROFILES = {
|
|||
}
|
||||
|
||||
## Upgrades configurations from the state they were in at version 3.4 to the
|
||||
# state they should be in at version 4.0.
|
||||
class VersionUpgrade34to40(VersionUpgrade):
|
||||
# state they should be in at version 3.5.
|
||||
class VersionUpgrade34to35(VersionUpgrade):
|
||||
|
||||
## Gets the version number from a CFG file in Uranium's 3.3 format.
|
||||
#
|
|
@ -1,9 +1,9 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import VersionUpgrade34to40
|
||||
from . import VersionUpgrade34to35
|
||||
|
||||
upgrade = VersionUpgrade34to40.VersionUpgrade34to40()
|
||||
upgrade = VersionUpgrade34to35.VersionUpgrade34to35()
|
||||
|
||||
|
||||
def getMetaData():
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"name": "Version Upgrade 3.4 to 4.0",
|
||||
"name": "Version Upgrade 3.4 to 3.5",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 3.4 to Cura 4.0.",
|
||||
"description": "Upgrades configurations from Cura 3.4 to Cura 3.5.",
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -4,12 +4,12 @@
|
|||
import configparser #To parse the resulting config files.
|
||||
import pytest #To register tests with.
|
||||
|
||||
import VersionUpgrade34to40 #The module we're testing.
|
||||
import VersionUpgrade34to35 #The module we're testing.
|
||||
|
||||
## Creates an instance of the upgrader to test with.
|
||||
@pytest.fixture
|
||||
def upgrader():
|
||||
return VersionUpgrade34to40.VersionUpgrade34to40()
|
||||
return VersionUpgrade34to35.VersionUpgrade34to35()
|
||||
|
||||
test_upgrade_version_nr_data = [
|
||||
("Empty config file",
|
|
@ -269,7 +269,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
|
||||
buildplate_dict = {} # type: Dict[str, Any]
|
||||
for variant_name, variant_dict in machine_variant_map[definition_id].items():
|
||||
variant_type = variant_dict["variant_node"].metadata["hardware_type"]
|
||||
variant_type = variant_dict["variant_node"].getMetaDataEntry("hardware_type", str(VariantType.NOZZLE))
|
||||
variant_type = VariantType(variant_type)
|
||||
if variant_type == VariantType.NOZZLE:
|
||||
# The hotend identifier is not the containers name, but its "name".
|
||||
|
|