Merge remote-tracking branch 'origin/master' into CURA-5982_fix_crash_no_material

This commit is contained in:
Lipu Fei 2018-12-04 17:15:26 +01:00
commit fb3c1fc11d
75 changed files with 1093 additions and 624 deletions

View file

@ -3,7 +3,7 @@
from PyQt5.QtCore import QObject, QUrl from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtGui import QDesktopServices from PyQt5.QtGui import QDesktopServices
from typing import List, TYPE_CHECKING from typing import List, TYPE_CHECKING, cast
from UM.Event import CallFunctionEvent from UM.Event import CallFunctionEvent
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
@ -61,8 +61,10 @@ class CuraActions(QObject):
operation = GroupedOperation() operation = GroupedOperation()
for node in Selection.getAllSelectedObjects(): for node in Selection.getAllSelectedObjects():
current_node = node current_node = node
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): parent_node = current_node.getParent()
current_node = current_node.getParent() while parent_node and parent_node.callDecoration("isGroup"):
current_node = parent_node
parent_node = current_node.getParent()
# This was formerly done with SetTransformOperation but because of # This was formerly done with SetTransformOperation but because of
# unpredictable matrix deconstruction it was possible that mirrors # unpredictable matrix deconstruction it was possible that mirrors
@ -150,13 +152,13 @@ class CuraActions(QObject):
root = cura.CuraApplication.CuraApplication.getInstance().getController().getScene().getRoot() root = cura.CuraApplication.CuraApplication.getInstance().getController().getScene().getRoot()
nodes_to_change = [] nodes_to_change = [] # type: List[SceneNode]
for node in Selection.getAllSelectedObjects(): for node in Selection.getAllSelectedObjects():
parent_node = node # Find the parent node to change instead parent_node = node # Find the parent node to change instead
while parent_node.getParent() != root: while parent_node.getParent() != root:
parent_node = parent_node.getParent() parent_node = cast(SceneNode, parent_node.getParent())
for single_node in BreadthFirstIterator(parent_node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. for single_node in BreadthFirstIterator(parent_node): # type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
nodes_to_change.append(single_node) nodes_to_change.append(single_node)
if not nodes_to_change: if not nodes_to_change:

View file

@ -21,6 +21,7 @@ class QualityProfilesDropDownMenuModel(ListModel):
AvailableRole = Qt.UserRole + 5 AvailableRole = Qt.UserRole + 5
QualityGroupRole = Qt.UserRole + 6 QualityGroupRole = Qt.UserRole + 6
QualityChangesGroupRole = Qt.UserRole + 7 QualityChangesGroupRole = Qt.UserRole + 7
IsExperimentalRole = Qt.UserRole + 8
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
@ -32,6 +33,7 @@ class QualityProfilesDropDownMenuModel(ListModel):
self.addRoleName(self.AvailableRole, "available") #Whether the quality profile is available in our current nozzle + material. self.addRoleName(self.AvailableRole, "available") #Whether the quality profile is available in our current nozzle + material.
self.addRoleName(self.QualityGroupRole, "quality_group") self.addRoleName(self.QualityGroupRole, "quality_group")
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group") self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
self.addRoleName(self.IsExperimentalRole, "is_experimental")
self._application = Application.getInstance() self._application = Application.getInstance()
self._machine_manager = self._application.getMachineManager() self._machine_manager = self._application.getMachineManager()
@ -74,7 +76,8 @@ class QualityProfilesDropDownMenuModel(ListModel):
"layer_height": layer_height, "layer_height": layer_height,
"layer_height_unit": self._layer_height_unit, "layer_height_unit": self._layer_height_unit,
"available": quality_group.is_available, "available": quality_group.is_available,
"quality_group": quality_group} "quality_group": quality_group,
"is_experimental": quality_group.is_experimental}
item_list.append(item) item_list.append(item)

View file

@ -4,6 +4,9 @@
from typing import Dict, Optional, List, Set from typing import Dict, Optional, List, Set
from PyQt5.QtCore import QObject, pyqtSlot from PyQt5.QtCore import QObject, pyqtSlot
from UM.Util import parseBool
from cura.Machines.ContainerNode import ContainerNode from cura.Machines.ContainerNode import ContainerNode
@ -29,6 +32,7 @@ class QualityGroup(QObject):
self.nodes_for_extruders = {} # type: Dict[int, ContainerNode] self.nodes_for_extruders = {} # type: Dict[int, ContainerNode]
self.quality_type = quality_type self.quality_type = quality_type
self.is_available = False self.is_available = False
self.is_experimental = False
@pyqtSlot(result = str) @pyqtSlot(result = str)
def getName(self) -> str: def getName(self) -> str:
@ -51,3 +55,17 @@ class QualityGroup(QObject):
for extruder_node in self.nodes_for_extruders.values(): for extruder_node in self.nodes_for_extruders.values():
result.append(extruder_node) result.append(extruder_node)
return result return result
def setGlobalNode(self, node: "ContainerNode") -> None:
self.node_for_global = node
# Update is_experimental flag
is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False))
self.is_experimental |= is_experimental
def setExtruderNode(self, position: int, node: "ContainerNode") -> None:
self.nodes_for_extruders[position] = node
# Update is_experimental flag
is_experimental = parseBool(node.getMetaDataEntry("is_experimental", False))
self.is_experimental |= is_experimental

View file

@ -235,7 +235,7 @@ class QualityManager(QObject):
for quality_type, quality_node in node.quality_type_map.items(): for quality_type, quality_node in node.quality_type_map.items():
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type) quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
quality_group.node_for_global = quality_node quality_group.setGlobalNode(quality_node)
quality_group_dict[quality_type] = quality_group quality_group_dict[quality_type] = quality_group
break break
@ -337,7 +337,7 @@ class QualityManager(QObject):
quality_group = quality_group_dict[quality_type] quality_group = quality_group_dict[quality_type]
if position not in quality_group.nodes_for_extruders: if position not in quality_group.nodes_for_extruders:
quality_group.nodes_for_extruders[position] = quality_node quality_group.setExtruderNode(position, quality_node)
# If the machine has its own specific qualities, for extruders, it should skip the global qualities # If the machine has its own specific qualities, for extruders, it should skip the global qualities
# and use the material/variant specific qualities. # and use the material/variant specific qualities.
@ -367,7 +367,7 @@ class QualityManager(QObject):
if node and node.quality_type_map: if node and node.quality_type_map:
for quality_type, quality_node in node.quality_type_map.items(): for quality_type, quality_node in node.quality_type_map.items():
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type) quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
quality_group.node_for_global = quality_node quality_group.setGlobalNode(quality_node)
quality_group_dict[quality_type] = quality_group quality_group_dict[quality_type] = quality_group
break break

View file

@ -107,7 +107,7 @@ class VariantManager:
break break
return variant_node return variant_node
return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name) return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {}).get(variant_name)
def getVariantNodes(self, machine: "GlobalStack", variant_type: "VariantType") -> Dict[str, ContainerNode]: def getVariantNodes(self, machine: "GlobalStack", variant_type: "VariantType") -> Dict[str, ContainerNode]:
machine_definition_id = machine.definition.getId() machine_definition_id = machine.definition.getId()

View file

@ -14,8 +14,7 @@ from UM.Logger import Logger
from UM.Qt.Duration import Duration from UM.Qt.Duration import Duration
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.MimeTypeDatabase import MimeTypeDatabase from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -361,7 +360,7 @@ class PrintInformation(QObject):
try: try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(name) mime_type = MimeTypeDatabase.getMimeTypeForFile(name)
data = mime_type.stripExtension(name) data = mime_type.stripExtension(name)
except: except MimeTypeNotFoundError:
Logger.log("w", "Unsupported Mime Type Database file extension %s", name) Logger.log("w", "Unsupported Mime Type Database file extension %s", name)
if data is not None and check_name is not None: if data is not None and check_name is not None:
@ -416,7 +415,7 @@ class PrintInformation(QObject):
return ''.join(char for char in unicodedata.normalize('NFD', to_strip) if unicodedata.category(char) != 'Mn') return ''.join(char for char in unicodedata.normalize('NFD', to_strip) if unicodedata.category(char) != 'Mn')
@pyqtSlot(result = "QVariantMap") @pyqtSlot(result = "QVariantMap")
def getFeaturePrintTimes(self): def getFeaturePrintTimes(self) -> Dict[str, Duration]:
result = {} result = {}
if self._active_build_plate not in self._print_times_per_feature: if self._active_build_plate not in self._print_times_per_feature:
self._initPrintTimesPerFeature(self._active_build_plate) self._initPrintTimesPerFeature(self._active_build_plate)

View file

@ -147,6 +147,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent) request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
return request return request
def createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
return self._createFormPart(content_header, data, content_type)
def _createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart: def _createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
part = QHttpPart() part = QHttpPart()

View file

@ -118,17 +118,40 @@ class PrintJobOutputModel(QObject):
self.nameChanged.emit() self.nameChanged.emit()
@pyqtProperty(int, notify = timeTotalChanged) @pyqtProperty(int, notify = timeTotalChanged)
def timeTotal(self): def timeTotal(self) -> int:
return self._time_total return self._time_total
@pyqtProperty(int, notify = timeElapsedChanged) @pyqtProperty(int, notify = timeElapsedChanged)
def timeElapsed(self): def timeElapsed(self) -> int:
return self._time_elapsed return self._time_elapsed
@pyqtProperty(int, notify = timeElapsedChanged)
def timeRemaining(self) -> int:
# Never get a negative time remaining
return max(self.timeTotal - self.timeElapsed, 0)
@pyqtProperty(float, notify = timeElapsedChanged)
def progress(self) -> float:
result = self.timeElapsed / self.timeTotal
# Never get a progress past 1.0
return min(result, 1.0)
@pyqtProperty(str, notify=stateChanged) @pyqtProperty(str, notify=stateChanged)
def state(self): def state(self) -> str:
return self._state return self._state
@pyqtProperty(bool, notify=stateChanged)
def isActive(self) -> bool:
inactiveStates = [
"pausing",
"paused",
"resuming",
"wait_cleanup"
]
if self.state in inactiveStates and self.timeRemaining > 0:
return False
return True
def updateTimeTotal(self, new_time_total): def updateTimeTotal(self, new_time_total):
if self._time_total != new_time_total: if self._time_total != new_time_total:
self._time_total = new_time_total self._time_total = new_time_total

View file

@ -1,7 +1,10 @@
# Copyright (c) 2015 Ultimaker B.V. # Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from UM.Application import Application from UM.Application import Application
from UM.Math.Polygon import Polygon
from UM.Qt.QtApplication import QtApplication
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Resources import Resources from UM.Resources import Resources
from UM.Math.Color import Color from UM.Math.Color import Color
@ -16,7 +19,7 @@ class ConvexHullNode(SceneNode):
# location an object uses on the buildplate. This area (or area's in case of one at a time printing) is # location an object uses on the buildplate. This area (or area's in case of one at a time printing) is
# then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded # then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded
# to represent the raft as well. # to represent the raft as well.
def __init__(self, node, hull, thickness, parent = None): def __init__(self, node: SceneNode, hull: Optional[Polygon], thickness: float, parent: Optional[SceneNode] = None) -> None:
super().__init__(parent) super().__init__(parent)
self.setCalculateBoundingBox(False) self.setCalculateBoundingBox(False)
@ -25,7 +28,11 @@ class ConvexHullNode(SceneNode):
# Color of the drawn convex hull # Color of the drawn convex hull
if not Application.getInstance().getIsHeadLess(): if not Application.getInstance().getIsHeadLess():
self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb()) theme = QtApplication.getInstance().getTheme()
if theme:
self._color = Color(*theme.getColor("convex_hull").getRgb())
else:
self._color = Color(0, 0, 0)
else: else:
self._color = Color(0, 0, 0) self._color = Color(0, 0, 0)
@ -75,7 +82,7 @@ class ConvexHullNode(SceneNode):
return True return True
def _onNodeDecoratorsChanged(self, node): def _onNodeDecoratorsChanged(self, node: SceneNode) -> None:
convex_hull_head = self._node.callDecoration("getConvexHullHead") convex_hull_head = self._node.callDecoration("getConvexHullHead")
if convex_hull_head: if convex_hull_head:
convex_hull_head_builder = MeshBuilder() convex_hull_head_builder = MeshBuilder()

View file

@ -64,7 +64,7 @@ class MachineManager(QObject):
self.machine_extruder_material_update_dict = collections.defaultdict(list) #type: Dict[str, List[Callable[[], None]]] self.machine_extruder_material_update_dict = collections.defaultdict(list) #type: Dict[str, List[Callable[[], None]]]
self._instance_container_timer = QTimer() #type: QTimer self._instance_container_timer = QTimer() # type: QTimer
self._instance_container_timer.setInterval(250) self._instance_container_timer.setInterval(250)
self._instance_container_timer.setSingleShot(True) self._instance_container_timer.setSingleShot(True)
self._instance_container_timer.timeout.connect(self.__emitChangedSignals) self._instance_container_timer.timeout.connect(self.__emitChangedSignals)
@ -74,7 +74,7 @@ class MachineManager(QObject):
self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged) self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
self._container_registry.containerLoadComplete.connect(self._onContainersChanged) self._container_registry.containerLoadComplete.connect(self._onContainersChanged)
## When the global container is changed, active material probably needs to be updated. # When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged) self.globalContainerChanged.connect(self.activeMaterialChanged)
self.globalContainerChanged.connect(self.activeVariantChanged) self.globalContainerChanged.connect(self.activeVariantChanged)
self.globalContainerChanged.connect(self.activeQualityChanged) self.globalContainerChanged.connect(self.activeQualityChanged)
@ -115,15 +115,15 @@ class MachineManager(QObject):
self._material_incompatible_message = Message(catalog.i18nc("@info:status", self._material_incompatible_message = Message(catalog.i18nc("@info:status",
"The selected material is incompatible with the selected machine or configuration."), "The selected material is incompatible with the selected machine or configuration."),
title = catalog.i18nc("@info:title", "Incompatible Material")) #type: Message title = catalog.i18nc("@info:title", "Incompatible Material")) # type: Message
containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) #type: List[InstanceContainer] containers = CuraContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId) # type: List[InstanceContainer]
if containers: if containers:
containers[0].nameChanged.connect(self._onMaterialNameChanged) containers[0].nameChanged.connect(self._onMaterialNameChanged)
self._material_manager = self._application.getMaterialManager() #type: MaterialManager self._material_manager = self._application.getMaterialManager() # type: MaterialManager
self._variant_manager = self._application.getVariantManager() #type: VariantManager self._variant_manager = self._application.getVariantManager() # type: VariantManager
self._quality_manager = self._application.getQualityManager() #type: QualityManager self._quality_manager = self._application.getQualityManager() # type: QualityManager
# When the materials lookup table gets updated, it can mean that a material has its name changed, which should # When the materials lookup table gets updated, it can mean that a material has its name changed, which should
# be reflected on the GUI. This signal emission makes sure that it happens. # be reflected on the GUI. This signal emission makes sure that it happens.
@ -201,7 +201,7 @@ class MachineManager(QObject):
extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != empty_variant_container else None extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != empty_variant_container else None
self._current_printer_configuration.extruderConfigurations.append(extruder_configuration) self._current_printer_configuration.extruderConfigurations.append(extruder_configuration)
# an empty build plate configuration from the network printer is presented as an empty string, so use "" for an # An empty build plate configuration from the network printer is presented as an empty string, so use "" for an
# empty build plate. # empty build plate.
self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != empty_variant_container else "" self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != empty_variant_container else ""
self.currentConfigurationChanged.emit() self.currentConfigurationChanged.emit()
@ -247,7 +247,7 @@ class MachineManager(QObject):
self.updateNumberExtrudersEnabled() self.updateNumberExtrudersEnabled()
self.globalContainerChanged.emit() self.globalContainerChanged.emit()
# after switching the global stack we reconnect all the signals and set the variant and material references # After switching the global stack we reconnect all the signals and set the variant and material references
if self._global_container_stack: if self._global_container_stack:
self._application.getPreferences().setValue("cura/active_machine", self._global_container_stack.getId()) self._application.getPreferences().setValue("cura/active_machine", self._global_container_stack.getId())
@ -261,7 +261,7 @@ class MachineManager(QObject):
if global_variant.getMetaDataEntry("hardware_type") != "buildplate": if global_variant.getMetaDataEntry("hardware_type") != "buildplate":
self._global_container_stack.setVariant(empty_variant_container) self._global_container_stack.setVariant(empty_variant_container)
# set the global material to empty as we now use the extruder stack at all times - CURA-4482 # Set the global material to empty as we now use the extruder stack at all times - CURA-4482
global_material = self._global_container_stack.material global_material = self._global_container_stack.material
if global_material != empty_material_container: if global_material != empty_material_container:
self._global_container_stack.setMaterial(empty_material_container) self._global_container_stack.setMaterial(empty_material_container)
@ -419,7 +419,7 @@ class MachineManager(QObject):
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are # 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") machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
count = 1 # we start with the global stack count = 1 # We start with the global stack
for stack in extruder_stacks: for stack in extruder_stacks:
md = stack.getMetaData() md = stack.getMetaData()
if "position" in md and int(md["position"]) >= machine_extruder_count: if "position" in md and int(md["position"]) >= machine_extruder_count:
@ -616,6 +616,14 @@ class MachineManager(QObject):
is_supported = self._current_quality_group.is_available is_supported = self._current_quality_group.is_available
return is_supported return is_supported
@pyqtProperty(bool, notify = activeQualityGroupChanged)
def isActiveQualityExperimental(self) -> bool:
is_experimental = False
if self._global_container_stack:
if self._current_quality_group:
is_experimental = self._current_quality_group.is_experimental
return is_experimental
## Returns whether there is anything unsupported in the current set-up. ## Returns whether there is anything unsupported in the current set-up.
# #
# The current set-up signifies the global stack and all extruder stacks, # The current set-up signifies the global stack and all extruder stacks,
@ -646,7 +654,7 @@ class MachineManager(QObject):
new_value = self._active_container_stack.getProperty(key, "value") new_value = self._active_container_stack.getProperty(key, "value")
extruder_stacks = [stack for stack in ExtruderManager.getInstance().getActiveExtruderStacks()] extruder_stacks = [stack for stack in ExtruderManager.getInstance().getActiveExtruderStacks()]
# check in which stack the value has to be replaced # Check in which stack the value has to be replaced
for extruder_stack in extruder_stacks: for extruder_stack in extruder_stacks:
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value: if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved
@ -662,7 +670,7 @@ class MachineManager(QObject):
for key in self._active_container_stack.userChanges.getAllKeys(): for key in self._active_container_stack.userChanges.getAllKeys():
new_value = self._active_container_stack.getProperty(key, "value") new_value = self._active_container_stack.getProperty(key, "value")
# check if the value has to be replaced # Check if the value has to be replaced
extruder_stack.userChanges.setProperty(key, "value", new_value) extruder_stack.userChanges.setProperty(key, "value", new_value)
@pyqtProperty(str, notify = activeVariantChanged) @pyqtProperty(str, notify = activeVariantChanged)
@ -731,7 +739,7 @@ class MachineManager(QObject):
# If the machine that is being removed is the currently active machine, set another machine as the active machine. # If the machine that is being removed is the currently active machine, set another machine as the active machine.
activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id) activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id)
# activate a new machine before removing a machine because this is safer # Activate a new machine before removing a machine because this is safer
if activate_new_machine: if activate_new_machine:
machine_stacks = CuraContainerRegistry.getInstance().findContainerStacksMetadata(type = "machine") machine_stacks = CuraContainerRegistry.getInstance().findContainerStacksMetadata(type = "machine")
other_machine_stacks = [s for s in machine_stacks if s["id"] != machine_id] other_machine_stacks = [s for s in machine_stacks if s["id"] != machine_id]
@ -915,9 +923,12 @@ class MachineManager(QObject):
if settable_per_extruder: if settable_per_extruder:
limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
extruder_position = str(max(0, limit_to_extruder)) extruder_position = max(0, limit_to_extruder)
extruder_stack = self._global_container_stack.extruders[extruder_position] extruder_stack = self.getExtruder(extruder_position)
if extruder_stack:
extruder_stack.userChanges.setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) extruder_stack.userChanges.setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
else:
Logger.log("e", "Unable to find extruder on position %s", extruder_position)
global_user_container.removeInstance(setting_key) global_user_container.removeInstance(setting_key)
# Signal that the global stack has changed # Signal that the global stack has changed
@ -926,10 +937,9 @@ class MachineManager(QObject):
@pyqtSlot(int, result = QObject) @pyqtSlot(int, result = QObject)
def getExtruder(self, position: int) -> Optional[ExtruderStack]: def getExtruder(self, position: int) -> Optional[ExtruderStack]:
extruder = None
if self._global_container_stack: if self._global_container_stack:
extruder = self._global_container_stack.extruders.get(str(position)) return self._global_container_stack.extruders.get(str(position))
return extruder return None
def updateDefaultExtruder(self) -> None: def updateDefaultExtruder(self) -> None:
if self._global_container_stack is None: if self._global_container_stack is None:
@ -995,12 +1005,12 @@ class MachineManager(QObject):
if not enabled and position == ExtruderManager.getInstance().activeExtruderIndex: if not enabled and position == ExtruderManager.getInstance().activeExtruderIndex:
ExtruderManager.getInstance().setActiveExtruderIndex(int(self._default_extruder_position)) ExtruderManager.getInstance().setActiveExtruderIndex(int(self._default_extruder_position))
# ensure that the quality profile is compatible with current combination, or choose a compatible one if available # Ensure that the quality profile is compatible with current combination, or choose a compatible one if available
self._updateQualityWithMaterial() self._updateQualityWithMaterial()
self.extruderChanged.emit() self.extruderChanged.emit()
# update material compatibility color # Update material compatibility color
self.activeQualityGroupChanged.emit() self.activeQualityGroupChanged.emit()
# update items in SettingExtruder # Update items in SettingExtruder
ExtruderManager.getInstance().extrudersChanged.emit(self._global_container_stack.getId()) ExtruderManager.getInstance().extrudersChanged.emit(self._global_container_stack.getId())
# Make sure the front end reflects changes # Make sure the front end reflects changes
self.forceUpdateAllSettings() self.forceUpdateAllSettings()
@ -1074,7 +1084,6 @@ class MachineManager(QObject):
return result return result
#
# Sets all quality and quality_changes containers to empty_quality and empty_quality_changes containers # Sets all quality and quality_changes containers to empty_quality and empty_quality_changes containers
# for all stacks in the currently active machine. # for all stacks in the currently active machine.
# #
@ -1133,7 +1142,7 @@ class MachineManager(QObject):
def _setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None: def _setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
if self._global_container_stack is None: if self._global_container_stack is None:
return #Can't change that. return # Can't change that.
quality_type = quality_changes_group.quality_type quality_type = quality_changes_group.quality_type
# A custom quality can be created based on "not supported". # A custom quality can be created based on "not supported".
# In that case, do not set quality containers to empty. # In that case, do not set quality containers to empty.
@ -1203,7 +1212,7 @@ class MachineManager(QObject):
self.rootMaterialChanged.emit() self.rootMaterialChanged.emit()
def activeMaterialsCompatible(self) -> bool: def activeMaterialsCompatible(self) -> bool:
# check material - variant compatibility # Check material - variant compatibility
if self._global_container_stack is not None: if self._global_container_stack is not None:
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)): if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
for position, extruder in self._global_container_stack.extruders.items(): for position, extruder in self._global_container_stack.extruders.items():
@ -1413,7 +1422,7 @@ class MachineManager(QObject):
material_diameter, root_material_id) material_diameter, root_material_id)
self.setMaterial(position, material_node) self.setMaterial(position, material_node)
## global_stack: if you want to provide your own global_stack instead of the current active one ## Global_stack: if you want to provide your own global_stack instead of the current active one
# if you update an active machine, special measures have to be taken. # if you update an active machine, special measures have to be taken.
@pyqtSlot(str, "QVariant") @pyqtSlot(str, "QVariant")
def setMaterial(self, position: str, container_node, global_stack: Optional["GlobalStack"] = None) -> None: def setMaterial(self, position: str, container_node, global_stack: Optional["GlobalStack"] = None) -> None:

View file

@ -72,7 +72,7 @@ class GcodeStartEndFormatter(Formatter):
# "-1" is global stack, and if the setting value exists in the global stack, use it as the fallback value. # "-1" is global stack, and if the setting value exists in the global stack, use it as the fallback value.
if key in kwargs["-1"]: if key in kwargs["-1"]:
value = kwargs["-1"] value = kwargs["-1"]
if key in kwargs[str(extruder_nr)]: if str(extruder_nr) in kwargs and key in kwargs[str(extruder_nr)]:
value = kwargs[str(extruder_nr)][key] value = kwargs[str(extruder_nr)][key]
if value == default_value_str: if value == default_value_str:

View file

@ -55,14 +55,14 @@ class PostProcessingPlugin(QObject, Extension):
def selectedScriptDefinitionId(self) -> Optional[str]: def selectedScriptDefinitionId(self) -> Optional[str]:
try: try:
return self._script_list[self._selected_script_index].getDefinitionId() return self._script_list[self._selected_script_index].getDefinitionId()
except: except IndexError:
return "" return ""
@pyqtProperty(str, notify=selectedIndexChanged) @pyqtProperty(str, notify=selectedIndexChanged)
def selectedScriptStackId(self) -> Optional[str]: def selectedScriptStackId(self) -> Optional[str]:
try: try:
return self._script_list[self._selected_script_index].getStackId() return self._script_list[self._selected_script_index].getStackId()
except: except IndexError:
return "" return ""
## Execute all post-processing scripts on the gcode. ## Execute all post-processing scripts on the gcode.

View file

@ -0,0 +1,3 @@
A good example script is SearchAndReplace.py.
If you have any questions please ask them at:
https://github.com/Ultimaker/Cura/issues

View file

@ -1,43 +0,0 @@
# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
from ..Script import Script
class ExampleScript(Script):
def __init__(self):
super().__init__()
def getSettingDataString(self):
return """{
"name":"Example script",
"key": "ExampleScript",
"metadata": {},
"version": 2,
"settings":
{
"test":
{
"label": "Test",
"description": "None",
"unit": "mm",
"type": "float",
"default_value": 0.5,
"minimum_value": "0",
"minimum_value_warning": "0.1",
"maximum_value_warning": "1"
},
"derp":
{
"label": "zomg",
"description": "afgasgfgasfgasf",
"unit": "mm",
"type": "float",
"default_value": 0.5,
"minimum_value": "0",
"minimum_value_warning": "0.1",
"maximum_value_warning": "1"
}
}
}"""
def execute(self, data):
return data

View file

@ -36,12 +36,19 @@ Item
var pg_name = "printingGuidelines" var pg_name = "printingGuidelines"
return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
} }
property var materialWebsiteUrl:
{
var pg_name = "website"
return (pg_name in packageData.links) ? packageData.links[pg_name] : undefined
}
anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.topMargin: UM.Theme.getSize("default_margin").height
height: visible ? childrenRect.height : 0 height: visible ? childrenRect.height : 0
visible: packageData.type == "material" && visible: packageData.type == "material" &&
(packageData.has_configs || technicalDataSheetUrl !== undefined || (packageData.has_configs || technicalDataSheetUrl !== undefined ||
safetyDataSheetUrl !== undefined || printingGuidelinesUrl !== undefined) safetyDataSheetUrl !== undefined || printingGuidelinesUrl !== undefined ||
materialWebsiteUrl !== undefined)
Item Item
{ {
@ -180,7 +187,8 @@ Item
anchors.top: combatibilityItem.bottom anchors.top: combatibilityItem.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height / 2 anchors.topMargin: UM.Theme.getSize("default_margin").height / 2
visible: base.technicalDataSheetUrl !== undefined || visible: base.technicalDataSheetUrl !== undefined ||
base.safetyDataSheetUrl !== undefined || base.printingGuidelinesUrl !== undefined base.safetyDataSheetUrl !== undefined || base.printingGuidelinesUrl !== undefined ||
base.materialWebsiteUrl !== undefined
height: visible ? contentHeight : 0 height: visible ? contentHeight : 0
text: text:
{ {
@ -208,6 +216,16 @@ Item
var pg_name = catalog.i18nc("@action:label", "Printing Guidelines") var pg_name = catalog.i18nc("@action:label", "Printing Guidelines")
result += "<a href='%1'>%2</a>".arg(base.printingGuidelinesUrl).arg(pg_name) result += "<a href='%1'>%2</a>".arg(base.printingGuidelinesUrl).arg(pg_name)
} }
if (base.materialWebsiteUrl !== undefined)
{
if (result.length > 0)
{
result += "<br/>"
}
var pg_name = catalog.i18nc("@action:label", "Website")
result += "<a href='%1'>%2</a>".arg(base.materialWebsiteUrl).arg(pg_name)
}
return result return result
} }
font: UM.Theme.getFont("very_small") font: UM.Theme.getFont("very_small")

View file

@ -144,10 +144,6 @@ Item
{ {
return "" return ""
} }
if (details.author_email)
{
return "<a href=\"mailto:" + details.author_email+"?Subject=Cura: " + details.name + "\">" + details.author_name + "</a>"
}
else else
{ {
return "<a href=\"" + details.website + "\">" + details.author_name + "</a>" return "<a href=\"" + details.website + "\">" + details.author_name + "</a>"

View file

@ -37,7 +37,7 @@ Item
anchors.top: packageName.bottom anchors.top: packageName.bottom
width: parent.width width: parent.width
text: model.description text: model.description
maximumLineCount: 6 maximumLineCount: 25
elide: Text.ElideRight elide: Text.ElideRight
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,014 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -9,10 +9,10 @@ import Cura 1.0 as Cura
Rectangle { Rectangle {
property var iconSource: null; property var iconSource: null;
color: clickArea.containsMouse ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary"); // "Cura Blue" color: "#0a0850" // TODO: Theme!
height: width; height: width;
radius: Math.round(0.5 * width); radius: Math.round(0.5 * width);
width: 36 * screenScaleFactor; width: 24 * screenScaleFactor;
UM.RecolorImage { UM.RecolorImage {
id: icon; id: icon;

View file

@ -10,14 +10,13 @@ import QtGraphicalEffects 1.0
Component Component
{ {
Rectangle Item
{ {
id: monitorFrame id: monitorFrame
property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight") property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight")
property var cornerRadius: UM.Theme.getSize("monitor_corner_radius").width property var cornerRadius: UM.Theme.getSize("monitor_corner_radius").width
color: "transparent"
height: maximumHeight height: maximumHeight
onVisibleChanged: onVisibleChanged:
{ {
@ -48,13 +47,45 @@ Component
} }
} }
ScrollView
{
id: printers
anchors
{
left: queue.left
right: queue.right
top: parent.top
topMargin: 48 * screenScaleFactor // TODO: Theme!
}
height: 264 * screenScaleFactor // TODO: Theme!
Row
{
spacing: 60 * screenScaleFactor // TODO: Theme!
Repeater
{
model: OutputDevice.printers
MonitorPrinterCard
{
printer: modelData
}
}
}
}
Item Item
{ {
id: queue id: queue
width: Math.min(834 * screenScaleFactor, maximumWidth)
anchors.fill: parent anchors {
anchors.top: parent.top bottom: parent.bottom
anchors.topMargin: 400 * screenScaleFactor // TODO: Insert carousel here horizontalCenter: parent.horizontalCenter
top: printers.bottom
topMargin: 48 * screenScaleFactor // TODO: Theme!
}
Label Label
{ {
@ -105,7 +136,6 @@ Component
} }
} }
MouseArea MouseArea
{ {
anchors.fill: manageQueueLabel anchors.fill: manageQueueLabel
@ -187,7 +217,7 @@ Component
} }
style: UM.Theme.styles.scrollview style: UM.Theme.styles.scrollview
visible: OutputDevice.receivedPrintJobs visible: OutputDevice.receivedPrintJobs
width: Math.min(834 * screenScaleFactor, maximumWidth) width: parent.width
ListView ListView
{ {
@ -214,5 +244,4 @@ Component
visible: OutputDevice.activeCameraUrl != "" visible: OutputDevice.activeCameraUrl != ""
} }
} }
} }

View file

@ -21,7 +21,14 @@ Item
{ {
id: previewImage id: previewImage
anchors.fill: parent anchors.fill: parent
opacity: printJob && printJob.state == "error" ? 0.5 : 1.0 opacity:
{
if (printJob && (printJob.state == "error" || !printJob.isActive))
{
return 0.5
}
return 1.0
}
source: printJob ? printJob.previewImageUrl : "" source: printJob ? printJob.previewImageUrl : ""
visible: printJob visible: printJob
} }
@ -47,11 +54,28 @@ Item
UM.RecolorImage UM.RecolorImage
{ {
id: statusImage id: overlayIcon
anchors.centerIn: printJobPreview anchors.centerIn: printJobPreview
color: UM.Theme.getColor("monitor_image_overlay") color: UM.Theme.getColor("monitor_image_overlay")
height: 0.5 * printJobPreview.height height: 0.5 * printJobPreview.height
source: printJob && printJob.state == "error" ? "../svg/aborted-icon.svg" : "" source:
{
switch(printJob.state)
{
case "error":
return "../svg/aborted-icon.svg"
case "wait_cleanup":
return printJob.timeTotal > printJob.timeElapsed ? "../svg/aborted-icon.svg" : ""
case "pausing":
return "../svg/paused-icon.svg"
case "paused":
return "../svg/paused-icon.svg"
case "resuming":
return "../svg/paused-icon.svg"
default:
return ""
}
}
sourceSize sourceSize
{ {
height: height height: height

View file

@ -0,0 +1,117 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Controls.Styles 1.3
import QtQuick.Controls 1.4
import UM 1.3 as UM
/**
* NOTE: For most labels, a fixed height with vertical alignment is used to make
* layouts more deterministic (like the fixed-size textboxes used in original
* mock-ups). This is also a stand-in for CSS's 'line-height' property. Denoted
* with '// FIXED-LINE-HEIGHT:'.
*/
Item
{
id: base
// The print job which all other information is dervied from
property var printJob: null
width: childrenRect.width
height: 18 * screenScaleFactor // TODO: Theme!
ProgressBar
{
id: progressBar
anchors
{
verticalCenter: parent.verticalCenter
}
value: printJob ? printJob.progress : 0
style: ProgressBarStyle
{
background: Rectangle
{
color: printJob && printJob.isActive ? "#e4e4f2" : "#f3f3f9" // TODO: Theme!
implicitHeight: visible ? 8 * screenScaleFactor : 0 // TODO: Theme!
implicitWidth: 180 * screenScaleFactor // TODO: Theme!
radius: 4 * screenScaleFactor // TODO: Theme!
}
progress: Rectangle
{
id: progressItem;
color: printJob && printJob.isActive ? "#0a0850" : "#9392b2" // TODO: Theme!
radius: 4 * screenScaleFactor // TODO: Theme!
}
}
}
Label
{
id: percentLabel
anchors
{
left: progressBar.right
leftMargin: 18 * screenScaleFactor // TODO: Theme!
}
text: Math.round(printJob.progress * 100) + "%"
color: printJob && printJob.isActive ? "#374355" : "#babac1" // TODO: Theme!
width: contentWidth
font: UM.Theme.getFont("medium") // 14pt, regular
// FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter
}
Label
{
id: statusLabel
anchors
{
left: percentLabel.right
leftMargin: 18 * screenScaleFactor // TODO: Theme!
}
color: "#374355" // TODO: Theme!
font: UM.Theme.getFont("medium") // 14pt, regular
text:
{
if (!printJob)
{
return ""
}
switch (printJob.state)
{
case "wait_cleanup":
if (printJob.timeTotal > printJob.timeElapsed)
{
return catalog.i18nc("@label:status", "Aborted")
}
return catalog.i18nc("@label:status", "Finished")
case "sent_to_printer":
return catalog.i18nc("@label:status", "Preparing...")
case "pre_print":
return catalog.i18nc("@label:status", "Preparing...")
case "aborting": // NOTE: Doesn't exist but maybe should someday
return catalog.i18nc("@label:status", "Aborting...")
case "aborted": // NOTE: Unused, see above
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:
return catalog.i18nc("@label:status", "Finishes %1 at %2".arg(OutputDevice.getDateCompleted( printJob.timeRemaining )).arg(OutputDevice.getTimeCompleted( printJob.timeRemaining )))
}
}
width: contentWidth
// FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter
}
}

View file

@ -0,0 +1,269 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Controls 2.0
import UM 1.3 as UM
/**
* A Printer Card is has two main components: the printer portion and the print
* job portion, the latter being paired in the UI when a print job is paired
* a printer in-cluster.
*
* NOTE: For most labels, a fixed height with vertical alignment is used to make
* layouts more deterministic (like the fixed-size textboxes used in original
* mock-ups). This is also a stand-in for CSS's 'line-height' property. Denoted
* with '// FIXED-LINE-HEIGHT:'.
*/
Item
{
id: base
// The printer which all printer data is derived from
property var printer: null
property var borderSize: 1 * screenScaleFactor // TODO: Theme, and remove from here
width: 834 * screenScaleFactor // TODO: Theme!
height: 216 * screenScaleFactor // TODO: Theme!
// Printer portion
Rectangle
{
id: printerInfo
border
{
color: "#EAEAEC" // TODO: Theme!
width: borderSize // TODO: Remove once themed
}
color: "white" // TODO: Theme!
width: parent.width
height: 144 * screenScaleFactor // TODO: Theme!
Row
{
anchors
{
left: parent.left
leftMargin: 36 * screenScaleFactor // TODO: Theme!
verticalCenter: parent.verticalCenter
}
spacing: 18 * screenScaleFactor // TODO: Theme!
Image
{
id: printerImage
width: 108 * screenScaleFactor // TODO: Theme!
height: 108 * screenScaleFactor // TODO: Theme!
fillMode: Image.PreserveAspectFit
source: "../png/" + printer.type + ".png"
mipmap: true
}
Item
{
anchors
{
verticalCenter: parent.verticalCenter
}
width: 216 * screenScaleFactor // TODO: Theme!
height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme!
Label
{
id: printerNameLabel
text: printer && printer.name ? printer.name : ""
color: "#414054" // TODO: Theme!
elide: Text.ElideRight
font: UM.Theme.getFont("large") // 16pt, bold
width: parent.width
// FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter
}
MonitorPrinterPill
{
id: printerFamilyPill
anchors
{
top: printerNameLabel.bottom
topMargin: 6 * screenScaleFactor // TODO: Theme!
left: printerNameLabel.left
}
text: printer.type
}
}
MonitorPrinterConfiguration
{
id: printerConfiguration
anchors.verticalCenter: parent.verticalCenter
buildplate: "Glass"
configurations:
[
base.printer.printerConfiguration.extruderConfigurations[0],
base.printer.printerConfiguration.extruderConfigurations[1]
]
height: 72 * screenScaleFactor // TODO: Theme!
}
}
PrintJobContextMenu
{
id: contextButton
anchors
{
right: parent.right
rightMargin: 12 * screenScaleFactor // TODO: Theme!
top: parent.top
topMargin: 12 * screenScaleFactor // TODO: Theme!
}
printJob: printer.activePrintJob
width: 36 * screenScaleFactor // TODO: Theme!
height: 36 * screenScaleFactor // TODO: Theme!
}
CameraButton
{
id: cameraButton;
anchors
{
right: parent.right
rightMargin: 20 * screenScaleFactor // TODO: Theme!
bottom: parent.bottom
bottomMargin: 20 * screenScaleFactor // TODO: Theme!
}
iconSource: "../svg/icons/camera.svg"
}
}
// Print job portion
Rectangle
{
id: printJobInfo
anchors
{
top: printerInfo.bottom
topMargin: -borderSize * screenScaleFactor // TODO: Theme!
}
border
{
color: "#EAEAEC" // TODO: Theme!
width: borderSize // TODO: Remove once themed
}
color: "white" // TODO: Theme!
height: 84 * screenScaleFactor + borderSize // TODO: Remove once themed
width: parent.width
Row
{
anchors
{
fill: parent
topMargin: 12 * screenScaleFactor + borderSize // TODO: Theme!
bottomMargin: 12 * screenScaleFactor // TODO: Theme!
leftMargin: 36 * screenScaleFactor // TODO: Theme!
}
height: childrenRect.height
spacing: 18 * screenScaleFactor // TODO: Theme!
Label
{
id: printerStatus
anchors
{
verticalCenter: parent.verticalCenter
}
color: "#414054" // TODO: Theme!
font: UM.Theme.getFont("large") // 16pt, bold
text: {
if (printer && printer.state == "disabled"){
return catalog.i18nc("@label:status", "Unavailable")
}
if (printer && printer.state == "unreachable"){
return catalog.i18nc("@label:status", "Unavailable")
}
if (printer && !printer.activePrintJob)
{
return catalog.i18nc("@label:status", "Idle")
}
return ""
}
}
Item
{
anchors
{
verticalCenter: parent.verticalCenter
}
width: printerImage.width
height: 60 * screenScaleFactor // TODO: Theme!
MonitorPrintJobPreview
{
anchors.centerIn: parent
printJob: base.printer.activePrintJob
size: parent.height
}
visible: printer.activePrintJob
}
Item
{
anchors
{
verticalCenter: parent.verticalCenter
}
width: 216 * screenScaleFactor // TODO: Theme!
height: printerNameLabel.height + printerFamilyPill.height + 6 * screenScaleFactor // TODO: Theme!
visible: printer.activePrintJob
Label
{
id: printerJobNameLabel
color: printer.activePrintJob && printer.activePrintJob.isActive ? "#414054" : "#babac1" // TODO: Theme!
elide: Text.ElideRight
font: UM.Theme.getFont("large") // 16pt, bold
text: base.printer.activePrintJob ? base.printer.activePrintJob.name : "Untitled" // TODO: I18N
width: parent.width
// FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter
}
Label
{
id: printerJobOwnerLabel
anchors
{
top: printerJobNameLabel.bottom
topMargin: 6 * screenScaleFactor // TODO: Theme!
left: printerJobNameLabel.left
}
color: printer.activePrintJob && printer.activePrintJob.isActive ? "#53657d" : "#babac1" // TODO: Theme!
elide: Text.ElideRight
font: UM.Theme.getFont("very_small") // 12pt, regular
text: printer.activePrintJob ? printer.activePrintJob.owner : "Anonymous" // TODO: I18N
width: parent.width
// FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter
}
}
MonitorPrintJobProgressBar
{
anchors
{
verticalCenter: parent.verticalCenter
}
printJob: printer.activePrintJob
visible: printer.activePrintJob
}
}
}
}

View file

@ -25,7 +25,7 @@ Item {
} }
contentItem: Label { contentItem: Label {
color: UM.Theme.getColor("monitor_context_menu_dots"); color: UM.Theme.getColor("monitor_context_menu_dots");
font.pixelSize: 25 * screenScaleFactor; font.pixelSize: 32 * screenScaleFactor;
horizontalAlignment: Text.AlignHCenter; horizontalAlignment: Text.AlignHCenter;
text: button.text; text: button.text;
verticalAlignment: Text.AlignVCenter; verticalAlignment: Text.AlignVCenter;
@ -41,7 +41,7 @@ Item {
var states = ["queued", "sent_to_printer", "pre_print", "printing", "pausing", "paused", "resuming"]; var states = ["queued", "sent_to_printer", "pre_print", "printing", "pausing", "paused", "resuming"];
return states.indexOf(printJob.state) !== -1; return states.indexOf(printJob.state) !== -1;
} }
width: 35 * screenScaleFactor; // TODO: Theme! width: 36 * screenScaleFactor; // TODO: Theme!
} }
Popup { Popup {

View file

@ -0,0 +1,5 @@
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M4.298828,0.998047 L7.7,0.998047 L8.7,3 L12,3 L12,10 L0,10 L0,3 L3.3,3 L4.298828,0.998047 Z M6,4 C4.625211,4 3.5,5.1252 3.5,6.5 C3.5,7.8748 4.625211,9 6,9 C7.37479,9 8.5,7.8748 8.5,6.5 C8.5,5.1252 7.37479,4 6,4 Z M6,5 C6.83435,5 7.5,5.6657 7.5,6.5 C7.5,7.3343 6.83435,8 6,8 C5.165651,8 4.5,7.3343 4.5,6.5 C4.5,5.6657 5.165651,5 6,5 Z" id="Combined-Shape" fill="#0A0850" fill-rule="nonzero"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 658 B

View file

@ -65,7 +65,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._received_print_jobs = False # type: bool self._received_print_jobs = False # type: bool
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/ClusterMonitorItem.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 # See comments about this hack with the clusterPrintersChanged signal
self.printersChanged.connect(self.clusterPrintersChanged) self.printersChanged.connect(self.clusterPrintersChanged)
@ -387,8 +386,24 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
@pyqtSlot(int, result = str) @pyqtSlot(int, result = str)
def getDateCompleted(self, time_remaining: int) -> str: def getDateCompleted(self, time_remaining: int) -> str:
current_time = time() current_time = time()
datetime_completed = datetime.fromtimestamp(current_time + time_remaining) completed = datetime.fromtimestamp(current_time + time_remaining)
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper() today = datetime.fromtimestamp(current_time)
# If finishing date is more than 7 days out, using "Mon Dec 3 at HH:MM" format
if completed.toordinal() > today.toordinal() + 7:
return completed.strftime("%a %b ") + "{day}".format(day=completed.day)
# If finishing date is within the next week, use "Monday at HH:MM" format
elif completed.toordinal() > today.toordinal() + 1:
return completed.strftime("%a")
# If finishing tomorrow, use "tomorrow at HH:MM" format
elif completed.toordinal() > today.toordinal():
return "tomorrow"
# If finishing today, use "today at HH:MM" format
else:
return "today"
@pyqtSlot(str) @pyqtSlot(str)
def sendJobToTop(self, print_job_uuid: str) -> None: def sendJobToTop(self, print_job_uuid: str) -> None:

View file

@ -0,0 +1,43 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
## Base model that maps kwargs to instance attributes.
class BaseModel:
def __init__(self, **kwargs) -> None:
self.__dict__.update(kwargs)
self.validate()
def validate(self) -> None:
pass
## Class representing a material that was fetched from the cluster API.
class ClusterMaterial(BaseModel):
def __init__(self, guid: str, version: int, **kwargs) -> None:
self.guid = guid # type: str
self.version = version # type: int
super().__init__(**kwargs)
def validate(self) -> None:
if not self.guid:
raise ValueError("guid is required on ClusterMaterial")
if not self.version:
raise ValueError("version is required on ClusterMaterial")
## Class representing a local material that was fetched from the container registry.
class LocalMaterial(BaseModel):
def __init__(self, GUID: str, id: str, version: int, **kwargs) -> None:
self.GUID = GUID # type: str
self.id = id # type: str
self.version = version # type: int
super().__init__(**kwargs)
def validate(self) -> None:
if not self.GUID:
raise ValueError("guid is required on LocalMaterial")
if not self.version:
raise ValueError("version is required on LocalMaterial")
if not self.id:
raise ValueError("id is required on LocalMaterial")

View file

@ -1,99 +1,197 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import json
import os
import urllib.parse
from typing import Dict, TYPE_CHECKING, Set
import json #To understand the list of materials from the printer reply. from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
import os #To walk over material files.
import os.path #To filter on material files.
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest #To listen to the reply from the printer.
from typing import Any, Dict, Set, TYPE_CHECKING
import urllib.parse #For getting material IDs from their file names.
from UM.Job import Job #The interface we're implementing. from UM.Application import Application
from UM.Job import Job
from UM.Logger import Logger from UM.Logger import Logger
from UM.MimeTypeDatabase import MimeTypeDatabase #To strip the extensions of the material profile files. from UM.MimeTypeDatabase import MimeTypeDatabase
from UM.Resources import Resources from UM.Resources import Resources
from UM.Settings.ContainerRegistry import ContainerRegistry #To find the GUIDs of materials. from cura.CuraApplication import CuraApplication
# Absolute imports don't work in plugins
from cura.CuraApplication import CuraApplication #For the resource types. from .Models import ClusterMaterial, LocalMaterial
if TYPE_CHECKING: if TYPE_CHECKING:
from .ClusterUM3OutputDevice import ClusterUM3OutputDevice from .ClusterUM3OutputDevice import ClusterUM3OutputDevice
## Asynchronous job to send material profiles to the printer. ## Asynchronous job to send material profiles to the printer.
# #
# This way it won't freeze up the interface while sending those materials. # This way it won't freeze up the interface while sending those materials.
class SendMaterialJob(Job): class SendMaterialJob(Job):
def __init__(self, device: "ClusterUM3OutputDevice") -> None: def __init__(self, device: "ClusterUM3OutputDevice") -> None:
super().__init__() super().__init__()
self.device = device #type: ClusterUM3OutputDevice self.device = device # type: ClusterUM3OutputDevice
## Send the request to the printer and register a callback
def run(self) -> None: def run(self) -> None:
self.device.get("materials/", on_finished = self.sendMissingMaterials) self.device.get("materials/", on_finished = self._onGetRemoteMaterials)
def sendMissingMaterials(self, reply: QNetworkReply) -> None: ## Process the materials reply from the printer.
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: #Got an error from the HTTP request. #
Logger.log("e", "Couldn't request current material storage on printer. Not syncing materials.") # \param reply The reply from the printer, a json file.
def _onGetRemoteMaterials(self, reply: QNetworkReply) -> None:
# Got an error from the HTTP request. If we did not receive a 200 something happened.
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
Logger.log("e", "Error fetching materials from printer: %s", reply.errorString())
return return
remote_materials_list = reply.readAll().data().decode("utf-8") # Collect materials from the printer's reply and send the missing ones if needed.
try: remote_materials_by_guid = self._parseReply(reply)
remote_materials_list = json.loads(remote_materials_list) if remote_materials_by_guid:
except json.JSONDecodeError: self._sendMissingMaterials(remote_materials_by_guid)
Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.")
return ## Determine which materials should be updated and send them to the printer.
try: #
remote_materials_by_guid = {material["guid"]: material for material in remote_materials_list} #Index by GUID. # \param remote_materials_by_guid The remote materials by GUID.
except KeyError: def _sendMissingMaterials(self, remote_materials_by_guid: Dict[str, ClusterMaterial]) -> None:
Logger.log("e", "Request material storage on printer: Printer's answer was missing GUIDs.")
# Collect local materials
local_materials_by_guid = self._getLocalMaterials()
if len(local_materials_by_guid) == 0:
Logger.log("d", "There are no local materials to synchronize with the printer.")
return return
container_registry = ContainerRegistry.getInstance() # Find out what materials are new or updated and must be sent to the printer
local_materials_list = filter(lambda material: ("GUID" in material and "version" in material and "id" in material), container_registry.findContainersMetadata(type = "material")) material_ids_to_send = self._determineMaterialsToSend(local_materials_by_guid, remote_materials_by_guid)
local_materials_by_guid = {material["GUID"]: material for material in local_materials_list if material["id"] == material["base_file"]} if len(material_ids_to_send) == 0:
for material in local_materials_list: #For each GUID get the material with the highest version number. Logger.log("d", "There are no remote materials to update.")
try:
if int(material["version"]) > local_materials_by_guid[material["GUID"]]["version"]:
local_materials_by_guid[material["GUID"]] = material
except ValueError:
Logger.log("e", "Material {material_id} has invalid version number {number}.".format(material_id = material["id"], number = material["version"]))
continue
materials_to_send = set() #type: Set[Dict[str, Any]]
for guid, material in local_materials_by_guid.items():
if guid not in remote_materials_by_guid:
materials_to_send.add(material["id"])
continue
try:
if int(material["version"]) > remote_materials_by_guid[guid]["version"]:
materials_to_send.add(material["id"])
continue
except KeyError:
Logger.log("e", "Current material storage on printer was an invalid reply (missing version).")
return return
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.MaterialInstanceContainer): # Send materials to the printer
self._sendMaterials(material_ids_to_send)
## From the local and remote materials, determine which ones should be synchronized.
#
# Makes a Set of id's containing only the id's of the materials that are not on the printer yet or the ones that
# are newer in Cura.
#
# \param local_materials The local materials by GUID.
# \param remote_materials The remote materials by GUID.
@staticmethod
def _determineMaterialsToSend(local_materials: Dict[str, LocalMaterial],
remote_materials: Dict[str, ClusterMaterial]) -> Set[str]:
return {
material.id
for guid, material in local_materials.items()
if guid not in remote_materials or material.version > remote_materials[guid].version
}
## Send the materials to the printer.
#
# The given materials will be loaded from disk en sent to to printer.
# The given id's will be matched with filenames of the locally stored materials.
#
# \param materials_to_send A set with id's of materials that must be sent.
def _sendMaterials(self, materials_to_send: Set[str]) -> None:
file_paths = Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.MaterialInstanceContainer)
# Find all local material files and send them if needed.
for file_path in file_paths:
try: try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path) mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
except MimeTypeDatabase.MimeTypeNotFoundError: except MimeTypeDatabase.MimeTypeNotFoundError:
continue #Not the sort of file we'd like to send then.
_, file_name = os.path.split(file_path)
material_id = urllib.parse.unquote_plus(mime_type.stripExtension(file_name))
if material_id not in materials_to_send:
continue continue
file_name = os.path.basename(file_path)
material_id = urllib.parse.unquote_plus(mime_type.stripExtension(file_name))
if material_id not in materials_to_send:
# If the material does not have to be sent we skip it.
continue
self._sendMaterialFile(file_path, file_name, material_id)
## Send a single material file to the printer.
#
# Also add the material signature file if that is available.
#
# \param file_path The path of the material file.
# \param file_name The name of the material file.
# \param material_id The ID of the material in the file.
def _sendMaterialFile(self, file_path: str, file_name: str, material_id: str) -> None:
parts = [] parts = []
# Add the material file.
with open(file_path, "rb") as f: with open(file_path, "rb") as f:
parts.append(self.device._createFormPart("name=\"file\"; filename=\"{file_name}\"".format(file_name = file_name), f.read())) parts.append(self.device.createFormPart("name=\"file\"; filename=\"{file_name}\""
signature_file_path = file_path + ".sig" .format(file_name = file_name), f.read()))
# Add the material signature file if needed.
signature_file_path = "{}.sig".format(file_path)
if os.path.exists(signature_file_path): if os.path.exists(signature_file_path):
_, signature_file_name = os.path.split(signature_file_path) signature_file_name = os.path.basename(signature_file_path)
with open(signature_file_path, "rb") as f: with open(signature_file_path, "rb") as f:
parts.append(self.device._createFormPart("name=\"signature_file\"; filename=\"{file_name}\"".format(file_name = signature_file_name), f.read())) parts.append(self.device.createFormPart("name=\"signature_file\"; filename=\"{file_name}\""
.format(file_name = signature_file_name), f.read()))
Logger.log("d", "Syncing material {material_id} with cluster.".format(material_id = material_id)) Logger.log("d", "Syncing material {material_id} with cluster.".format(material_id = material_id))
self.device.postFormWithParts(target = "materials/", parts = parts, on_finished = self.sendingFinished) self.device.postFormWithParts(target = "materials/", parts = parts, on_finished = self.sendingFinished)
def sendingFinished(self, reply: QNetworkReply): ## Check a reply from an upload to the printer and log an error when the call failed
@staticmethod
def sendingFinished(reply: QNetworkReply) -> None:
if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200: if reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) != 200:
Logger.log("e", "Received error code from printer when syncing material: {code}".format(code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute))) Logger.log("e", "Received error code from printer when syncing material: {code}, {text}".format(
Logger.log("e", reply.readAll().data().decode("utf-8")) code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute),
text = reply.errorString()
))
## Parse the reply from the printer
#
# Parses the reply to a "/materials" request to the printer
#
# \return a dictionary of ClusterMaterial objects by GUID
# \throw KeyError Raised when on of the materials does not include a valid guid
@classmethod
def _parseReply(cls, reply: QNetworkReply) -> Dict[str, ClusterMaterial]:
try:
remote_materials = json.loads(reply.readAll().data().decode("utf-8"))
return {material["guid"]: ClusterMaterial(**material) for material in remote_materials}
except UnicodeDecodeError:
Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.")
except json.JSONDecodeError:
Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.")
except ValueError:
Logger.log("e", "Request material storage on printer: Printer's answer had an incorrect value.")
except TypeError:
Logger.log("e", "Request material storage on printer: Printer's answer was missing a required value.")
## Retrieves a list of local materials
#
# Only the new newest version of the local materials is returned
#
# \return a dictionary of LocalMaterial objects by GUID
def _getLocalMaterials(self) -> Dict[str, LocalMaterial]:
result = {} # type: Dict[str, LocalMaterial]
container_registry = Application.getInstance().getContainerRegistry()
material_containers = container_registry.findContainersMetadata(type = "material")
# Find the latest version of all material containers in the registry.
for material in material_containers:
try:
# material version must be an int
material["version"] = int(material["version"])
# Create a new local material
local_material = LocalMaterial(**material)
if local_material.GUID not in result or \
local_material.version > result.get(local_material.GUID).version:
result[local_material.GUID] = local_material
except KeyError:
Logger.logException("w", "Local material {} has missing values.".format(material["id"]))
except ValueError:
Logger.logException("w", "Local material {} has invalid values.".format(material["id"]))
except TypeError:
Logger.logException("w", "Local material {} has invalid values.".format(material["id"]))
return result

View file

@ -341,7 +341,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
# Request more data if info is not complete # Request more data if info is not complete
if not info.address: if not info.address:
Logger.log("d", "Trying to get address of %s", name)
info = zero_conf.get_service_info(service_type, name) info = zero_conf.get_service_info(service_type, name)
if info: if info:

View file

@ -0,0 +1,190 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import io
import json
from unittest import TestCase, mock
from unittest.mock import patch, call
from PyQt5.QtCore import QByteArray
from UM.MimeTypeDatabase import MimeType
from UM.Application import Application
from plugins.UM3NetworkPrinting.src.SendMaterialJob import SendMaterialJob
@patch("builtins.open", lambda _, __: io.StringIO("<xml></xml>"))
@patch("UM.MimeTypeDatabase.MimeTypeDatabase.getMimeTypeForFile",
lambda _: MimeType(name = "application/x-ultimaker-material-profile", comment = "Ultimaker Material Profile",
suffixes = ["xml.fdm_material"]))
@patch("UM.Resources.Resources.getAllResourcesOfType", lambda _: ["/materials/generic_pla_white.xml.fdm_material"])
@patch("plugins.UM3NetworkPrinting.src.ClusterUM3OutputDevice")
@patch("PyQt5.QtNetwork.QNetworkReply")
class TestSendMaterialJob(TestCase):
_LOCAL_MATERIAL_WHITE = {"type": "material", "status": "unknown", "id": "generic_pla_white",
"base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA",
"brand": "Generic", "material": "PLA", "color_name": "White",
"GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "1", "color_code": "#ffffff",
"description": "Test PLA White", "adhesion_info": "Use glue.", "approximate_diameter": "3",
"properties": {"density": "1.00", "diameter": "2.85", "weight": "750"},
"definition": "fdmprinter", "compatible": True}
_LOCAL_MATERIAL_BLACK = {"type": "material", "status": "unknown", "id": "generic_pla_black",
"base_file": "generic_pla_black", "setting_version": "5", "name": "Yellow CPE",
"brand": "Ultimaker", "material": "CPE", "color_name": "Black",
"GUID": "5fbb362a-41f9-4818-bb43-15ea6df34aa4", "version": "1", "color_code": "#000000",
"description": "Test PLA Black", "adhesion_info": "Use glue.", "approximate_diameter": "3",
"properties": {"density": "1.01", "diameter": "2.85", "weight": "750"},
"definition": "fdmprinter", "compatible": True}
_REMOTE_MATERIAL_WHITE = {
"guid": "badb0ee7-87c8-4f3f-9398-938587b67dce",
"material": "PLA",
"brand": "Generic",
"version": 1,
"color": "White",
"density": 1.00
}
_REMOTE_MATERIAL_BLACK = {
"guid": "5fbb362a-41f9-4818-bb43-15ea6df34aa4",
"material": "PLA",
"brand": "Generic",
"version": 2,
"color": "Black",
"density": 1.00
}
def test_run(self, device_mock, reply_mock):
job = SendMaterialJob(device_mock)
job.run()
# We expect the materials endpoint to be called when the job runs.
device_mock.get.assert_called_with("materials/", on_finished = job._onGetRemoteMaterials)
def test__onGetRemoteMaterials_withFailedRequest(self, reply_mock, device_mock):
reply_mock.attribute.return_value = 404
job = SendMaterialJob(device_mock)
job._onGetRemoteMaterials(reply_mock)
# We expect the device not to be called for any follow up.
self.assertEqual(0, device_mock.createFormPart.call_count)
def test__onGetRemoteMaterials_withWrongEncoding(self, reply_mock, device_mock):
reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("cp500"))
job = SendMaterialJob(device_mock)
job._onGetRemoteMaterials(reply_mock)
# Given that the parsing fails we do no expect the device to be called for any follow up.
self.assertEqual(0, device_mock.createFormPart.call_count)
def test__onGetRemoteMaterials_withBadJsonAnswer(self, reply_mock, device_mock):
reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(b"Six sick hicks nick six slick bricks with picks and sticks.")
job = SendMaterialJob(device_mock)
job._onGetRemoteMaterials(reply_mock)
# Given that the parsing fails we do no expect the device to be called for any follow up.
self.assertEqual(0, device_mock.createFormPart.call_count)
def test__onGetRemoteMaterials_withMissingGuidInRemoteMaterial(self, reply_mock, device_mock):
reply_mock.attribute.return_value = 200
remote_material_without_guid = self._REMOTE_MATERIAL_WHITE.copy()
del remote_material_without_guid["guid"]
reply_mock.readAll.return_value = QByteArray(json.dumps([remote_material_without_guid]).encode("ascii"))
job = SendMaterialJob(device_mock)
job._onGetRemoteMaterials(reply_mock)
# Given that parsing fails we do not expect the device to be called for any follow up.
self.assertEqual(0, device_mock.createFormPart.call_count)
@patch("cura.Settings.CuraContainerRegistry")
@patch("UM.Application")
def test__onGetRemoteMaterials_withInvalidVersionInLocalMaterial(self, application_mock, container_registry_mock,
reply_mock, device_mock):
reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
localMaterialWhiteWithInvalidVersion = self._LOCAL_MATERIAL_WHITE.copy()
localMaterialWhiteWithInvalidVersion["version"] = "one"
container_registry_mock.findContainersMetadata.return_value = [localMaterialWhiteWithInvalidVersion]
application_mock.getContainerRegistry.return_value = container_registry_mock
with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
job = SendMaterialJob(device_mock)
job._onGetRemoteMaterials(reply_mock)
self.assertEqual(0, device_mock.createFormPart.call_count)
@patch("cura.Settings.CuraContainerRegistry")
@patch("UM.Application")
def test__onGetRemoteMaterials_withNoUpdate(self, application_mock, container_registry_mock, reply_mock,
device_mock):
application_mock.getContainerRegistry.return_value = container_registry_mock
device_mock.createFormPart.return_value = "_xXx_"
container_registry_mock.findContainersMetadata.return_value = [self._LOCAL_MATERIAL_WHITE]
reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
job = SendMaterialJob(device_mock)
job._onGetRemoteMaterials(reply_mock)
self.assertEqual(0, device_mock.createFormPart.call_count)
self.assertEqual(0, device_mock.postFormWithParts.call_count)
@patch("cura.Settings.CuraContainerRegistry")
@patch("UM.Application")
def test__onGetRemoteMaterials_withUpdatedMaterial(self, application_mock, container_registry_mock, reply_mock,
device_mock):
application_mock.getContainerRegistry.return_value = container_registry_mock
device_mock.createFormPart.return_value = "_xXx_"
localMaterialWhiteWithHigherVersion = self._LOCAL_MATERIAL_WHITE.copy()
localMaterialWhiteWithHigherVersion["version"] = "2"
container_registry_mock.findContainersMetadata.return_value = [localMaterialWhiteWithHigherVersion]
reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii"))
with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
job = SendMaterialJob(device_mock)
job._onGetRemoteMaterials(reply_mock)
self.assertEqual(1, device_mock.createFormPart.call_count)
self.assertEqual(1, device_mock.postFormWithParts.call_count)
self.assertEquals(
[call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", "<xml></xml>"),
call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
device_mock.method_calls)
@patch("cura.Settings.CuraContainerRegistry")
@patch("UM.Application")
def test__onGetRemoteMaterials_withNewMaterial(self, application_mock, container_registry_mock, reply_mock,
device_mock):
application_mock.getContainerRegistry.return_value = container_registry_mock
device_mock.createFormPart.return_value = "_xXx_"
container_registry_mock.findContainersMetadata.return_value = [self._LOCAL_MATERIAL_WHITE,
self._LOCAL_MATERIAL_BLACK]
reply_mock.attribute.return_value = 200
reply_mock.readAll.return_value = QByteArray(json.dumps([self._REMOTE_MATERIAL_BLACK]).encode("ascii"))
with mock.patch.object(Application, "getInstance", new = lambda: application_mock):
job = SendMaterialJob(device_mock)
job._onGetRemoteMaterials(reply_mock)
self.assertEqual(1, device_mock.createFormPart.call_count)
self.assertEqual(1, device_mock.postFormWithParts.call_count)
self.assertEquals(
[call.createFormPart("name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", "<xml></xml>"),
call.postFormWithParts(target = "materials/", parts = ["_xXx_"], on_finished = job.sendingFinished)],
device_mock.method_calls)

View file

@ -4,6 +4,7 @@
from UM.Job import Job from UM.Job import Job
from UM.Logger import Logger from UM.Logger import Logger
from .avr_isp import ispBase
from .avr_isp.stk500v2 import Stk500v2 from .avr_isp.stk500v2 import Stk500v2
from time import time, sleep from time import time, sleep
@ -14,12 +15,12 @@ from serial import Serial, SerialException
# It tries a pre-set list of baud rates. All these baud rates are validated by requesting the temperature a few times # It tries a pre-set list of baud rates. All these baud rates are validated by requesting the temperature a few times
# and checking if the results make sense. If getResult() is not None, it was able to find a correct baud rate. # and checking if the results make sense. If getResult() is not None, it was able to find a correct baud rate.
class AutoDetectBaudJob(Job): class AutoDetectBaudJob(Job):
def __init__(self, serial_port): def __init__(self, serial_port: int) -> None:
super().__init__() super().__init__()
self._serial_port = serial_port self._serial_port = serial_port
self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600] self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
def run(self): def run(self) -> None:
Logger.log("d", "Auto detect baud rate started.") Logger.log("d", "Auto detect baud rate started.")
wait_response_timeouts = [3, 15, 30] wait_response_timeouts = [3, 15, 30]
wait_bootloader_times = [1.5, 5, 15] wait_bootloader_times = [1.5, 5, 15]
@ -32,7 +33,7 @@ class AutoDetectBaudJob(Job):
try: try:
programmer.connect(self._serial_port) programmer.connect(self._serial_port)
serial = programmer.leaveISP() serial = programmer.leaveISP()
except: except ispBase.IspError:
programmer.close() programmer.close()
for retry in range(tries): for retry in range(tries):
@ -58,7 +59,7 @@ class AutoDetectBaudJob(Job):
# We already have a serial connection, just change the baud rate. # We already have a serial connection, just change the baud rate.
try: try:
serial.baudrate = baud_rate serial.baudrate = baud_rate
except: except ValueError:
continue continue
sleep(wait_bootloader) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number sleep(wait_bootloader) # Ensure that we are not talking to the boot loader. 1.5 seconds seems to be the magic number
successful_responses = 0 successful_responses = 0

View file

@ -1,7 +1,7 @@
{ {
"name": "USB printing", "name": "USB printing",
"author": "Ultimaker B.V.", "author": "Ultimaker B.V.",
"version": "1.0.0", "version": "1.0.1",
"api": 5, "api": 5,
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.", "description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
"i18n-catalog": "cura" "i18n-catalog": "cura"

View file

@ -515,7 +515,7 @@
"package_type": "plugin", "package_type": "plugin",
"display_name": "USB Printing", "display_name": "USB Printing",
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.", "description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
"package_version": "1.0.0", "package_version": "1.0.1",
"sdk_version": 5, "sdk_version": 5,
"website": "https://ultimaker.com", "website": "https://ultimaker.com",
"author": { "author": {

View file

@ -7,7 +7,7 @@
"author": "Samuel Pinches", "author": "Samuel Pinches",
"manufacturer": "Alfawise", "manufacturer": "Alfawise",
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"preferred_quality_type": "fine", "preferred_quality_type": "fast",
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "alfawise_u20_extruder_0" "0": "alfawise_u20_extruder_0"
@ -53,9 +53,6 @@
"material_bed_temperature": { "material_bed_temperature": {
"default_value": 50 "default_value": 50
}, },
"layer_height": {
"default_value": 0.15
},
"layer_height_0": { "layer_height_0": {
"default_value": 0.2 "default_value": 0.2
}, },

View file

@ -37,9 +37,6 @@
"machine_heated_bed": { "machine_heated_bed": {
"default_value": true "default_value": true
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_nozzle_heat_up_speed": { "machine_nozzle_heat_up_speed": {
"default_value": 2 "default_value": 2
}, },
@ -66,9 +63,6 @@
] ]
] ]
}, },
"material_diameter": {
"default_value": 1.75
},
"gantry_height": { "gantry_height": {
"default_value": 12 "default_value": 12
}, },

View file

@ -1,98 +0,0 @@
{
"id": "BIBO2 single E1",
"version": 2,
"name": "BIBO2 single E1",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "na",
"manufacturer": "BIBO",
"category": "Other",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains": {
"0": "bibo2_single_extruder_0_0",
"1": "bibo2_single_extruder_0_1"
},
"first_start_actions": [
"MachineSettingsAction"
]
},
"overrides": {
"machine_name": {
"default_value": "BIBO2 single Extruder 1 (right)"
},
"machine_width": {
"default_value": 214
},
"machine_height": {
"default_value": 160
},
"machine_depth": {
"default_value": 186
},
"machine_center_is_zero": {
"default_value": true
},
"machine_heated_bed": {
"default_value": true
},
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_nozzle_heat_up_speed": {
"default_value": 2
},
"machine_nozzle_cool_down_speed": {
"default_value": 2
},
"machine_head_with_fans_polygon": {
"default_value": [
[
-68.18,
64.63
],
[
-68.18,
-47.38
],
[
35.18,
64.63
],
[
35.18,
-47.38
]
]
},
"material_diameter": {
"default_value": 1.75
},
"gantry_height": {
"default_value": 12
},
"machine_use_extruder_offset_to_offset_coords": {
"default_value": true
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
"default_value": ";Startcode BIBO printers\nM109 T1 S170 ;preheat the other extruder, so it will not knock or ruin the print\nG90 ; absolute mode\nG21 ; metric values\nM82 ; Extruder in absolute mode\nM107\nG28\nG1 Z2 F400\nT0\nG90\nG92 E0\nG28\nG1 Y0 F1200 E0\nG92 E0\nG1 X-15.0 Y-92.9 Z0.3 F2400.0\t\t; move to start-line position\nG1 X15.0 Y-92.9 Z0.3 F1000.0 E2\t\t; draw 1st line\nG1 X15.0 Y-92.6 Z0.3 F3000.0\t\t; move to side a little\nG1 X-15.0 Y-92.6 Z0.3 F1000.0 E4\t\t; draw 2nd line\nG1 X-15.0 Y-92.3 Z0.3 F3000.0\t\t; move to side a little\nG1 X15.0 Y-92.3 Z0.3 F1000.0 E6\t\t; draw 3rd line\nG1 X15.0 Y-92 Z0.3 F3000.0\t\t; move to side a little\nG1 X-15.0 Y-92 Z0.3 F1000.0 E8\t\t; draw 4th line\nG1 X-16.0 Y-91.7 Z0.3 F3000.0\t\t; move to side a little\nG1 X16.0 Y-91.7 Z0.3 F1000.0 E10\t\t; draw 5th line\nG1 X16.0 Y-91.4 Z0.3 F3000.0\t\t; move to side a little\nG1 X-16.0 Y-91.4 Z0.3 F1000.0 E12\t\t; draw 5th line\nG1 E11.5 F2400\t\t\t\t; retract filament 0.5mm\nG92 E0\nM117 BIBO Printing..."
},
"machine_end_gcode": {
"default_value": ";BIBO End GCode\nM107\nG91 ; Relative positioning\nG1 Z1 F100\nM104 T0 S0\nM104 T1 S0\nG1 X-20 Y-20 F3000\nG28 X0 Y0\nG90 ; Absolute positioning\nG92 E0 ; Reset extruder position\nM140 S0 ; Disable heated bed\nM84 ; Turn steppers off\nM117 BIBO Print complete\n "
},
"machine_extruder_count": {
"default_value": 2
},
"prime_tower_position_x": {
"default_value": 50
},
"prime_tower_position_y": {
"default_value": 50
}
}
}

View file

@ -1,98 +0,0 @@
{
"id": "BIBO2 single E2",
"version": 2,
"name": "BIBO2 single E2",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "na",
"manufacturer": "BIBO",
"category": "Other",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains": {
"0": "bibo2_single_extruder_1_0",
"1": "bibo2_single_extruder_1_1"
},
"first_start_actions": [
"MachineSettingsAction"
]
},
"overrides": {
"machine_name": {
"default_value": "BIBO2 single Extruder 2 (left)"
},
"machine_width": {
"default_value": 214
},
"machine_height": {
"default_value": 160
},
"machine_depth": {
"default_value": 186
},
"machine_center_is_zero": {
"default_value": true
},
"machine_heated_bed": {
"default_value": true
},
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_nozzle_heat_up_speed": {
"default_value": 2
},
"machine_nozzle_cool_down_speed": {
"default_value": 2
},
"machine_head_with_fans_polygon": {
"default_value": [
[
-68.18,
64.63
],
[
-68.18,
-47.38
],
[
35.18,
64.63
],
[
35.18,
-47.38
]
]
},
"material_diameter": {
"default_value": 1.75
},
"gantry_height": {
"default_value": 12
},
"machine_use_extruder_offset_to_offset_coords": {
"default_value": true
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
"default_value": ";Startcode BIBO printers\nM109 T0 S170 ;preheat the other extruder, so it will not knock or ruin the print\nG90 ; absolute mode\nG21 ; metric values\nM82 ; Extruder in absolute mode\nM107\nG28\nG1 Z2 F400\nT0\nG90\nG92 E0\nG28\nG1 Y0 F1200 E0\nG92 E0\nT1\nG92 E0\nG1 X-15.0 Y-92.9 Z0.3 F2400.0\t\t; move to start-line position\nG1 X15.0 Y-92.9 Z0.3 F1000.0 E2\t\t; draw 1st line\nG1 X15.0 Y-92.6 Z0.3 F3000.0\t\t; move to side a little\nG1 X-15.0 Y-92.6 Z0.3 F1000.0 E4\t\t; draw 2nd line\nG1 X-15.0 Y-92.3 Z0.3 F3000.0\t\t; move to side a little\nG1 X15.0 Y-92.3 Z0.3 F1000.0 E6\t\t; draw 3rd line\nG1 X15.0 Y-92 Z0.3 F3000.0\t\t; move to side a little\nG1 X-15.0 Y-92 Z0.3 F1000.0 E8\t\t; draw 4th line\nG1 X-16.0 Y-91.7 Z0.3 F3000.0\t\t; move to side a little\nG1 X16.0 Y-91.7 Z0.3 F1000.0 E10\t\t; draw 5th line\nG1 X16.0 Y-91.4 Z0.3 F3000.0\t\t; move to side a little\nG1 X-16.0 Y-91.4 Z0.3 F1000.0 E12\t\t; draw 5th line\nG1 E11.5 F2400\t\t\t\t; retract filament 0.5mm\nG92 E0\nM117 BIBO Printing..."
},
"machine_end_gcode": {
"default_value": ";BIBO End GCode\nM107\nG91 ; Relative positioning\nG1 Z1 F100\nM104 T0 S0\nM104 T1 S0\nG1 X-20 Y-20 F3000\nG28 X0 Y0\nG90 ; Absolute positioning\nG92 E0 ; Reset extruder position\nM140 S0 ; Disable heated bed\nM84 ; Turn steppers off\nM117 BIBO Print complete\n "
},
"machine_extruder_count": {
"default_value": 2
},
"prime_tower_position_x": {
"default_value": 50
},
"prime_tower_position_y": {
"default_value": 50
}
}
}

View file

@ -1,11 +1,11 @@
{ {
"name": "Cocoon Create ModelMaker & Wanhao Duplicator i3 Mini", "name": "Cocoon Create ModelMaker",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",
"metadata": { "metadata": {
"visible": true, "visible": true,
"author": "Samuel Pinches", "author": "Samuel Pinches",
"manufacturer": "Cocoon Create / Wanhao", "manufacturer": "Cocoon Create",
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"preferred_quality_type": "fine", "preferred_quality_type": "fine",
"machine_extruder_trains": "machine_extruder_trains":
@ -15,7 +15,7 @@
}, },
"overrides": { "overrides": {
"machine_name": { "machine_name": {
"default_value": "Cocoon Create ModelMaker & Wanhao Duplicator i3 Mini" "default_value": "Cocoon Create ModelMaker"
}, },
"machine_start_gcode": { "machine_start_gcode": {
"default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 ;home all axis\nG92 E0 ;zero the extruded length\nG1 Z1 F1000 ;move up slightly\nG1 X60.0 Z0 E9.0 F1000.0;intro line\nG1 X100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\n; -- end of START GCODE --" "default_value": "; -- START GCODE --\nG21 ;set units to millimetres\nG90 ;set to absolute positioning\nM106 S0 ;set fan speed to zero (turned off)\nG28 ;home all axis\nG92 E0 ;zero the extruded length\nG1 Z1 F1000 ;move up slightly\nG1 X60.0 Z0 E9.0 F1000.0;intro line\nG1 X100.0 E21.5 F1000.0 ;continue line\nG92 E0 ;zero the extruded length again\n; -- end of START GCODE --"
@ -51,7 +51,7 @@
"default_value": 220 "default_value": 220
}, },
"layer_height": { "layer_height": {
"default_value": 0.15 "default_value": 0.10
}, },
"layer_height_0": { "layer_height_0": {
"default_value": 0.2 "default_value": 0.2

View file

@ -7,7 +7,7 @@
"author": "Samuel Pinches", "author": "Samuel Pinches",
"manufacturer": "JGAurora", "manufacturer": "JGAurora",
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"preferred_quality_type": "fine", "preferred_quality_type": "fast",
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "jgaurora_a1_extruder_0" "0": "jgaurora_a1_extruder_0"
@ -53,9 +53,6 @@
"material_bed_temperature": { "material_bed_temperature": {
"default_value": 67 "default_value": 67
}, },
"layer_height": {
"default_value": 0.15
},
"layer_height_0": { "layer_height_0": {
"default_value": 0.12 "default_value": 0.12
}, },

View file

@ -9,7 +9,7 @@
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"platform": "jgaurora_a5.stl", "platform": "jgaurora_a5.stl",
"platform_offset": [-242, -101, 273], "platform_offset": [-242, -101, 273],
"preferred_quality_type": "fine", "preferred_quality_type": "fast",
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "jgaurora_a5_extruder_0" "0": "jgaurora_a5_extruder_0"
@ -55,9 +55,6 @@
"material_bed_temperature": { "material_bed_temperature": {
"default_value": 67 "default_value": 67
}, },
"layer_height": {
"default_value": 0.15
},
"layer_height_0": { "layer_height_0": {
"default_value": 0.12 "default_value": 0.12
}, },

View file

@ -7,7 +7,7 @@
"author": "Samuel Pinches", "author": "Samuel Pinches",
"manufacturer": "JGAurora", "manufacturer": "JGAurora",
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"preferred_quality_type": "fine", "preferred_quality_type": "fast",
"machine_extruder_trains": "machine_extruder_trains":
{ {
"0": "jgaurora_z_603s_extruder_0" "0": "jgaurora_z_603s_extruder_0"
@ -53,9 +53,6 @@
"material_bed_temperature": { "material_bed_temperature": {
"default_value": 55 "default_value": 55
}, },
"layer_height": {
"default_value": 0.15
},
"layer_height_0": { "layer_height_0": {
"default_value": 0.2 "default_value": 0.2
}, },

View file

@ -12,6 +12,12 @@
"default_value": 0, "default_value": 0,
"maximum_value": "1" "maximum_value": "1"
}, },
"material_diameter": {
"default_value": 1.75
},
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_nozzle_offset_x": { "machine_nozzle_offset_x": {
"default_value": 0.0 "default_value": 0.0
}, },

View file

@ -12,11 +12,17 @@
"default_value": 1, "default_value": 1,
"maximum_value": "1" "maximum_value": "1"
}, },
"material_diameter": {
"default_value": 1.75
},
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_nozzle_offset_x": { "machine_nozzle_offset_x": {
"default_value": 0 "default_value": 0.0
}, },
"machine_nozzle_offset_y": { "machine_nozzle_offset_y": {
"default_value": 0 "default_value": 0.0
}, },
"machine_extruder_start_pos_abs": { "machine_extruder_start_pos_abs": {
"default_value": true "default_value": true

View file

@ -1,40 +0,0 @@
{
"id": "BIBO2 E1a",
"version": 2,
"name": "BIBO2 E1",
"inherits": "fdmextruder",
"metadata": {
"machine": "BIBO2 single E1",
"position": "0"
},
"overrides": {
"extruder_nr": {
"default_value": 0,
"maximum_value": "1"
},
"machine_nozzle_offset_x": {
"default_value": 0.0
},
"machine_nozzle_offset_y": {
"default_value": 0.0
},
"machine_extruder_start_pos_abs": {
"default_value": true
},
"machine_extruder_start_pos_x": {
"value": "prime_tower_position_x"
},
"machine_extruder_start_pos_y": {
"value": "prime_tower_position_y"
},
"machine_extruder_end_pos_abs": {
"default_value": true
},
"machine_extruder_end_pos_x": {
"value": "prime_tower_position_x"
},
"machine_extruder_end_pos_y": {
"value": "prime_tower_position_y"
}
}
}

View file

@ -1,40 +0,0 @@
{
"id": "BIBO2 E1b",
"version": 2,
"name": "E2 not used",
"inherits": "fdmextruder",
"metadata": {
"machine": "BIBO2 single E1",
"position": "1"
},
"overrides": {
"extruder_nr": {
"default_value": 1,
"maximum_value": "1"
},
"machine_nozzle_offset_x": {
"default_value": 0.0
},
"machine_nozzle_offset_y": {
"default_value": 0.0
},
"machine_extruder_start_pos_abs": {
"default_value": true
},
"machine_extruder_start_pos_x": {
"value": "prime_tower_position_x"
},
"machine_extruder_start_pos_y": {
"value": "prime_tower_position_y"
},
"machine_extruder_end_pos_abs": {
"default_value": true
},
"machine_extruder_end_pos_x": {
"value": "prime_tower_position_x"
},
"machine_extruder_end_pos_y": {
"value": "prime_tower_position_y"
}
}
}

View file

@ -1,40 +0,0 @@
{
"id": "BIBO2 E2a",
"version": 2,
"name": "E1 not used",
"inherits": "fdmextruder",
"metadata": {
"machine": "BIBO2 single E2",
"position": "0"
},
"overrides": {
"extruder_nr": {
"default_value": 0,
"maximum_value": "1"
},
"machine_nozzle_offset_x": {
"default_value": 0
},
"machine_nozzle_offset_y": {
"default_value": 0
},
"machine_extruder_start_pos_abs": {
"default_value": true
},
"machine_extruder_start_pos_x": {
"value": "prime_tower_position_x"
},
"machine_extruder_start_pos_y": {
"value": "prime_tower_position_y"
},
"machine_extruder_end_pos_abs": {
"default_value": true
},
"machine_extruder_end_pos_x": {
"value": "prime_tower_position_x"
},
"machine_extruder_end_pos_y": {
"value": "prime_tower_position_y"
}
}
}

View file

@ -1,40 +0,0 @@
{
"id": "BIBO2 E2b",
"version": 2,
"name": "BIBO2 E2",
"inherits": "fdmextruder",
"metadata": {
"machine": "BIBO2 single E2",
"position": "1"
},
"overrides": {
"extruder_nr": {
"default_value": 1,
"maximum_value": "1"
},
"machine_nozzle_offset_x": {
"default_value": 0
},
"machine_nozzle_offset_y": {
"default_value": 0
},
"machine_extruder_start_pos_abs": {
"default_value": true
},
"machine_extruder_start_pos_x": {
"value": "prime_tower_position_x"
},
"machine_extruder_start_pos_y": {
"value": "prime_tower_position_y"
},
"machine_extruder_end_pos_abs": {
"default_value": true
},
"machine_extruder_end_pos_x": {
"value": "prime_tower_position_x"
},
"machine_extruder_end_pos_y": {
"value": "prime_tower_position_y"
}
}
}

View file

@ -213,28 +213,6 @@ UM.Dialog
PropertyChanges { target: machineButton; opacity: 0; height: 0; } PropertyChanges { target: machineButton; opacity: 0; height: 0; }
} }
transitions:
[
Transition
{
to: "collapsed";
SequentialAnimation
{
NumberAnimation { property: "opacity"; duration: 75; }
NumberAnimation { property: "height"; duration: 75; }
}
},
Transition
{
from: "collapsed";
SequentialAnimation
{
NumberAnimation { property: "height"; duration: 75; }
NumberAnimation { property: "opacity"; duration: 75; }
}
}
]
} }
} }
} }

View file

@ -17,18 +17,21 @@ Menu
MenuItem MenuItem
{ {
text: (model.layer_height != "") ? model.name + " - " + model.layer_height + model.layer_height_unit : model.name text:
{
var full_text = (model.layer_height != "") ? model.name + " - " + model.layer_height + model.layer_height_unit : model.name
full_text += model.is_experimental ? " - Experimental" : ""
return full_text
}
checkable: true checkable: true
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
exclusiveGroup: group exclusiveGroup: group
onTriggered: { onTriggered: Cura.MachineManager.setQualityGroup(model.quality_group)
Cura.MachineManager.setQualityGroup(model.quality_group)
}
visible: model.available visible: model.available
} }
onObjectAdded: menu.insertItem(index, object); onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object); onObjectRemoved: menu.removeItem(object)
} }
MenuSeparator MenuSeparator

View file

@ -151,7 +151,6 @@ UM.PreferencesPage
{ {
id: languageLabel id: languageLabel
text: catalog.i18nc("@label","Language:") text: catalog.i18nc("@label","Language:")
anchors.verticalCenter: languageComboBox.verticalCenter
} }
ComboBox ComboBox
@ -219,7 +218,6 @@ UM.PreferencesPage
{ {
id: currencyLabel id: currencyLabel
text: catalog.i18nc("@label","Currency:") text: catalog.i18nc("@label","Currency:")
anchors.verticalCenter: currencyField.verticalCenter
} }
TextField TextField
@ -233,7 +231,6 @@ UM.PreferencesPage
{ {
id: themeLabel id: themeLabel
text: catalog.i18nc("@label","Theme:") text: catalog.i18nc("@label","Theme:")
anchors.verticalCenter: themeComboBox.verticalCenter
} }
ComboBox ComboBox

View file

@ -25,11 +25,7 @@ UM.PreferencesPage
function reset() function reset()
{ {
UM.Preferences.resetPreference("general/visible_settings") settingVisibilityPresetsModel.setActivePreset("basic")
// After calling this function update Setting visibility preset combobox.
// Reset should set default setting preset ("Basic")
visibilityPreset.currentIndex = 1
} }
resetEnabled: true; resetEnabled: true;
@ -115,21 +111,22 @@ UM.PreferencesPage
currentIndex: currentIndex:
{ {
var idx = -1;
for(var i = 0; i < settingVisibilityPresetsModel.items.length; ++i) for(var i = 0; i < settingVisibilityPresetsModel.items.length; ++i)
{ {
if(settingVisibilityPresetsModel.items[i].id == settingVisibilityPresetsModel.activePreset) if(settingVisibilityPresetsModel.items[i].presetId == settingVisibilityPresetsModel.activePreset)
{ {
currentIndex = i; idx = i;
return; break;
} }
} }
return -1 return idx;
} }
onActivated: onActivated:
{ {
var preset_id = settingVisibilityPresetsModel.items[index].id; var preset_id = settingVisibilityPresetsModel.items[index].presetId
settingVisibilityPresetsModel.setActivePreset(preset_id); settingVisibilityPresetsModel.setActivePreset(preset_id)
} }
} }

View file

@ -64,11 +64,18 @@ Item
activeFocusOnPress: true activeFocusOnPress: true
menu: ProfileMenu { } menu: ProfileMenu { }
function generateActiveQualityText () { function generateActiveQualityText ()
var result = Cura.MachineManager.activeQualityOrQualityChangesName; {
var result = Cura.MachineManager.activeQualityOrQualityChangesName
if (Cura.MachineManager.isActiveQualityExperimental)
{
result += " (Experimental)"
}
if (Cura.MachineManager.isActiveQualitySupported) { if (Cura.MachineManager.isActiveQualitySupported)
if (Cura.MachineManager.activeQualityLayerHeight > 0) { {
if (Cura.MachineManager.activeQualityLayerHeight > 0)
{
result += " <font color=\"" + UM.Theme.getColor("text_detail") + "\">" result += " <font color=\"" + UM.Theme.getColor("text_detail") + "\">"
result += " - " result += " - "
result += Cura.MachineManager.activeQualityLayerHeight + "mm" result += Cura.MachineManager.activeQualityLayerHeight + "mm"

View file

@ -8,6 +8,7 @@ setting_version = 5
type = quality type = quality
quality_type = draft quality_type = draft
weight = 0 weight = 0
global_quality = True
[values] [values]
acceleration_enabled = True acceleration_enabled = True

View file

@ -8,6 +8,7 @@ setting_version = 5
type = quality type = quality
quality_type = high quality_type = high
weight = 2 weight = 2
global_quality = True
[values] [values]
acceleration_enabled = True acceleration_enabled = True

View file

@ -8,6 +8,7 @@ setting_version = 5
type = quality type = quality
quality_type = normal quality_type = normal
weight = 1 weight = 1
global_quality = True
[values] [values]
acceleration_enabled = True acceleration_enabled = True

View file

@ -10,6 +10,7 @@ quality_type = normal
weight = 0 weight = 0
material = generic_pc material = generic_pc
variant = AA 0.25 variant = AA 0.25
is_experimental = True
[values] [values]
acceleration_enabled = True acceleration_enabled = True

View file

@ -10,6 +10,7 @@ quality_type = normal
weight = 0 weight = 0
material = generic_pp material = generic_pp
variant = AA 0.25 variant = AA 0.25
is_experimental = True
[values] [values]
acceleration_enabled = True acceleration_enabled = True

View file

@ -10,6 +10,7 @@ quality_type = draft
weight = -2 weight = -2
material = generic_cpe_plus material = generic_cpe_plus
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = superdraft
weight = -4 weight = -4
material = generic_cpe_plus material = generic_cpe_plus
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = verydraft
weight = -3 weight = -3
material = generic_cpe_plus material = generic_cpe_plus
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = draft
weight = 0 weight = 0
material = generic_pc material = generic_pc
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = superdraft
weight = -2 weight = -2
material = generic_pc material = generic_pc
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = verydraft
weight = -1 weight = -1
material = generic_pc material = generic_pc
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = normal
weight = 0 weight = 0
material = generic_pc material = generic_pc
variant = AA 0.25 variant = AA 0.25
is_experimental = True
[values] [values]
acceleration_enabled = True acceleration_enabled = True

View file

@ -10,6 +10,7 @@ quality_type = normal
weight = 0 weight = 0
material = generic_pp material = generic_pp
variant = AA 0.25 variant = AA 0.25
is_experimental = True
[values] [values]
acceleration_enabled = True acceleration_enabled = True

View file

@ -10,6 +10,7 @@ quality_type = draft
weight = -2 weight = -2
material = generic_cpe_plus material = generic_cpe_plus
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = superdraft
weight = -4 weight = -4
material = generic_cpe_plus material = generic_cpe_plus
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = verydraft
weight = -3 weight = -3
material = generic_cpe_plus material = generic_cpe_plus
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = draft
weight = 0 weight = 0
material = generic_pc material = generic_pc
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = superdraft
weight = -2 weight = -2
material = generic_pc material = generic_pc
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -10,6 +10,7 @@ quality_type = verydraft
weight = -1 weight = -1
material = generic_pc material = generic_pc
variant = AA 0.8 variant = AA 0.8
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -11,6 +11,7 @@ weight = -2
material = generic_cpe_plus material = generic_cpe_plus
variant = AA 0.8 variant = AA 0.8
buildplate = Aluminum buildplate = Aluminum
is_experimental = True
[values] [values]
brim_width = 14 brim_width = 14

View file

@ -11,6 +11,7 @@ weight = 0
material = generic_pc material = generic_pc
variant = AA 0.8 variant = AA 0.8
buildplate = Aluminum buildplate = Aluminum
is_experimental = True
[values] [values]
brim_width = 10 brim_width = 10