From 0cf46ca48ecab544c0441808fb916406d750de1e Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Sat, 23 Jun 2018 16:14:50 +0200 Subject: [PATCH 01/97] Changed SettingView to show custom menu items --- resources/qml/Settings/SettingView.qml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 1b3f0cbc20..30160cb5fb 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -561,6 +561,27 @@ Item visible: machineExtruderCount.properties.value > 1 } + Instantiator + { + id: customMenuItems + model: Cura.SidebarCustomMenuItemsModel { } + MenuItem + { + text: model.name + onTriggered: + { + customMenuItems.model.callMenuItemMethod(name, model.actions) + } + } + onObjectAdded: contextMenu.insertItem(index, object) + onObjectRemoved: contextMenu.removeItem(object) + } + + MenuSeparator + { + visible: customMenuItems.count > 0 + } + MenuItem { //: Settings context menu action From 0906e3f1e79d74736b96cd5c0dbd7158c0f70b2f Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Sat, 23 Jun 2018 16:16:06 +0200 Subject: [PATCH 02/97] Store custom Menu items in CuraApplication --- cura/CuraApplication.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 08f267d0e1..e5562745de 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -101,6 +101,7 @@ from cura.Settings.UserChangesModel import UserChangesModel from cura.Settings.ExtrudersModel import ExtrudersModel from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.ContainerManager import ContainerManager +from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel from cura.ObjectsModel import ObjectsModel @@ -219,6 +220,8 @@ class CuraApplication(QtApplication): self._need_to_show_user_agreement = True + self._sidebar_custom_menu_items = [] # type: list # Keeps list of custom menu items for the side bar + # Backups self._auto_save = None self._save_data_enabled = True @@ -943,6 +946,7 @@ class CuraApplication(QtApplication): qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel") qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance) + qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel") # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) @@ -1729,3 +1733,10 @@ class CuraApplication(QtApplication): @pyqtSlot() def showMoreInformationDialogForAnonymousDataCollection(self): self._plugin_registry.getPluginObject("SliceInfoPlugin").showMoreInfoDialog() + + + def addSidebarCustomMenuItem(self, menu_item: list) -> None: + self._sidebar_custom_menu_items.append(menu_item) + + def getSidebarCustomMenuItems(self) -> list: + return self._sidebar_custom_menu_items \ No newline at end of file From f577789859952ca1bfcd607268583b0ef650dd6d Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Sat, 23 Jun 2018 16:22:33 +0200 Subject: [PATCH 03/97] Added extra SettingsModel for a QML --- cura/Settings/SidebarCustomMenuItemsModel.py | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 cura/Settings/SidebarCustomMenuItemsModel.py diff --git a/cura/Settings/SidebarCustomMenuItemsModel.py b/cura/Settings/SidebarCustomMenuItemsModel.py new file mode 100644 index 0000000000..71abb0d2ed --- /dev/null +++ b/cura/Settings/SidebarCustomMenuItemsModel.py @@ -0,0 +1,32 @@ +from UM.Qt.ListModel import ListModel +from PyQt5.QtCore import pyqtSlot, Qt + +class SidebarCustomMenuItemsModel(ListModel): + NameRole = Qt.UserRole + 1 + ActionsRole = Qt.UserRole + 2 + MenuItemRole = Qt.UserRole + 3 + + def __init__(self, parent=None): + super().__init__(parent) + self.addRoleName(self.NameRole, "name") + self.addRoleName(self.ActionsRole, "actions") + self.addRoleName(self.MenuItemRole, "menu_item") + self._updateExtensionList() + + def _updateExtensionList(self)-> None: + from cura.CuraApplication import CuraApplication + for menu_item in CuraApplication.getInstance().getSidebarCustomMenuItems(): + + self.appendItem({ + "name": menu_item["name"], + "actions": menu_item["actions"], + "menu_item": menu_item["menu_item"] + }) + + @pyqtSlot(str, "QVariantList") + def callMenuItemMethod(self, menu_item_name: str, menu_item_actions: list)-> None: + for item in self._items: + if menu_item_name == item["name"]: + for method in menu_item_actions: + getattr(item["menu_item"], method)() + break \ No newline at end of file From c4fa50a8fc1ffd9879b973d61a7ab9dfad7c9405 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Mon, 25 Jun 2018 16:51:40 +0200 Subject: [PATCH 04/97] Added icon for custom Menu items --- cura/Settings/SidebarCustomMenuItemsModel.py | 9 ++++++--- resources/qml/Settings/SettingView.qml | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cura/Settings/SidebarCustomMenuItemsModel.py b/cura/Settings/SidebarCustomMenuItemsModel.py index 71abb0d2ed..8c82d288bf 100644 --- a/cura/Settings/SidebarCustomMenuItemsModel.py +++ b/cura/Settings/SidebarCustomMenuItemsModel.py @@ -5,12 +5,14 @@ class SidebarCustomMenuItemsModel(ListModel): NameRole = Qt.UserRole + 1 ActionsRole = Qt.UserRole + 2 MenuItemRole = Qt.UserRole + 3 + MenuItemIconNameRole = Qt.UserRole + 5 def __init__(self, parent=None): super().__init__(parent) self.addRoleName(self.NameRole, "name") self.addRoleName(self.ActionsRole, "actions") self.addRoleName(self.MenuItemRole, "menu_item") + self.addRoleName(self.MenuItemIconNameRole, "iconName") self._updateExtensionList() def _updateExtensionList(self)-> None: @@ -19,14 +21,15 @@ class SidebarCustomMenuItemsModel(ListModel): self.appendItem({ "name": menu_item["name"], + "iconName": menu_item["iconName"], "actions": menu_item["actions"], "menu_item": menu_item["menu_item"] }) - @pyqtSlot(str, "QVariantList") - def callMenuItemMethod(self, menu_item_name: str, menu_item_actions: list)-> None: + @pyqtSlot(str, "QVariantList", "QVariantMap") + def callMenuItemMethod(self, menu_item_name: str, menu_item_actions: list, kwargs)-> None: for item in self._items: if menu_item_name == item["name"]: for method in menu_item_actions: - getattr(item["menu_item"], method)() + getattr(item["menu_item"], method)(kwargs) break \ No newline at end of file diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 30160cb5fb..88876123fe 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -568,9 +568,10 @@ Item MenuItem { text: model.name + iconName: model.iconName onTriggered: { - customMenuItems.model.callMenuItemMethod(name, model.actions) + customMenuItems.model.callMenuItemMethod(name, model.actions, {"key": contextMenu.key}) } } onObjectAdded: contextMenu.insertItem(index, object) From 7e9054616358f237d8c0615ff739dd7e43e48b31 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Fri, 29 Jun 2018 10:34:15 +0200 Subject: [PATCH 05/97] Fix firmware upload on Windows... by correctly creating a local path from a url-encoded path Fixes #3731 and #3987 --- plugins/USBPrinting/USBPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 00eb2f0b25..926cd9001d 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -15,7 +15,7 @@ from cura.PrinterOutput.GenericOutputController import GenericOutputController from .AutoDetectBaudJob import AutoDetectBaudJob from .avr_isp import stk500v2, intelHex -from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty +from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty, QUrl from serial import Serial, SerialException, SerialTimeoutException from threading import Thread, Event @@ -128,7 +128,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice): @pyqtSlot(str) def updateFirmware(self, file): # the file path is qurl encoded. - self._firmware_location = file.replace("file://", "") + self._firmware_location = QUrl(file).toLocalFile() self.showFirmwareInterface() self.setFirmwareUpdateState(FirmwareUpdateState.updating) self._update_firmware_thread.start() From 65ada30c9c45687f6fe860af7ecc74889978a3a1 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Mon, 16 Jul 2018 13:26:51 +0200 Subject: [PATCH 06/97] Fix flashing "default" firmware The url is not QUrl encoded if the "default" firmware is flashed --- plugins/USBPrinting/USBPrinterOutputDevice.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 926cd9001d..6a97a3c163 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -127,8 +127,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice): @pyqtSlot(str) def updateFirmware(self, file): - # the file path is qurl encoded. - self._firmware_location = QUrl(file).toLocalFile() + # the file path could be qurl encoded. + if file[:7] == "file://": + self._firmware_location = QUrl(file).toLocalFile() + else: + self._firmware_location = file self.showFirmwareInterface() self.setFirmwareUpdateState(FirmwareUpdateState.updating) self._update_firmware_thread.start() From bf219c382170fe8502ba4810d0b9200714d21cbc Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Thu, 26 Jul 2018 13:54:35 +0200 Subject: [PATCH 07/97] Added typing CURA-5595 --- cura/Settings/SidebarCustomMenuItemsModel.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cura/Settings/SidebarCustomMenuItemsModel.py b/cura/Settings/SidebarCustomMenuItemsModel.py index 8c82d288bf..d2a4d1a2b1 100644 --- a/cura/Settings/SidebarCustomMenuItemsModel.py +++ b/cura/Settings/SidebarCustomMenuItemsModel.py @@ -1,6 +1,12 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Any + from UM.Qt.ListModel import ListModel from PyQt5.QtCore import pyqtSlot, Qt + class SidebarCustomMenuItemsModel(ListModel): NameRole = Qt.UserRole + 1 ActionsRole = Qt.UserRole + 2 @@ -27,7 +33,7 @@ class SidebarCustomMenuItemsModel(ListModel): }) @pyqtSlot(str, "QVariantList", "QVariantMap") - def callMenuItemMethod(self, menu_item_name: str, menu_item_actions: list, kwargs)-> None: + def callMenuItemMethod(self, menu_item_name: str, menu_item_actions: list, kwargs: Any)-> None: for item in self._items: if menu_item_name == item["name"]: for method in menu_item_actions: From b8cca17d46dfd00e55d4d22b0c097962158211a7 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Fri, 27 Jul 2018 10:01:16 +0100 Subject: [PATCH 08/97] Added the "Within Infill" combing option that functions identically to the original "Not in Skin" option. I've tweaked the blurb to let people know that the original behaviour is still available. --- resources/definitions/fdmprinter.def.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index e29e08228b..0352ae59fd 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3344,13 +3344,14 @@ "retraction_combing": { "label": "Combing Mode", - "description": "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only.", + "description": "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas and also to only comb within the infill. Note that the 'Within Infill' option behaves exactly like the 'Not in Skin' option in earlier Cura releases.", "type": "enum", "options": { "off": "Off", "all": "All", - "noskin": "Not in Skin" + "noskin": "Not in Skin", + "infill": "Within Infill" }, "default_value": "all", "resolve": "'noskin' if 'noskin' in extruderValues('retraction_combing') else ('all' if 'all' in extruderValues('retraction_combing') else 'off')", From 846c55a99d0486bea3623e47412c36d0ab71f0fe Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Tue, 31 Jul 2018 09:47:18 +0200 Subject: [PATCH 09/97] Simplify detection of url-encoded filepatch --- plugins/USBPrinting/USBPrinterOutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 6a97a3c163..7c4d85a5cd 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -127,8 +127,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice): @pyqtSlot(str) def updateFirmware(self, file): - # the file path could be qurl encoded. - if file[:7] == "file://": + # the file path could be url-encoded. + if file.startswith("file://"): self._firmware_location = QUrl(file).toLocalFile() else: self._firmware_location = file From c14539d63ae56836ec3840c3bed7f620d53b6573 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Thu, 2 Aug 2018 13:12:01 +0200 Subject: [PATCH 10/97] Fix the sliders that went out of range when using the arrow keys. --- plugins/SimulationView/LayerSlider.qml | 24 ++++++++++++++++++------ plugins/SimulationView/PathSlider.qml | 11 +++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/plugins/SimulationView/LayerSlider.qml b/plugins/SimulationView/LayerSlider.qml index 22f9d91340..d7e22d601e 100644 --- a/plugins/SimulationView/LayerSlider.qml +++ b/plugins/SimulationView/LayerSlider.qml @@ -40,33 +40,41 @@ Item { property bool layersVisible: true - function getUpperValueFromSliderHandle () { + function getUpperValueFromSliderHandle() { return upperHandle.getValue() } - function setUpperValue (value) { + function setUpperValue(value) { upperHandle.setValue(value) updateRangeHandle() } - function getLowerValueFromSliderHandle () { + function getLowerValueFromSliderHandle() { return lowerHandle.getValue() } - function setLowerValue (value) { + function setLowerValue(value) { lowerHandle.setValue(value) updateRangeHandle() } - function updateRangeHandle () { + function updateRangeHandle() { rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height) } // set the active handle to show only one label at a time - function setActiveHandle (handle) { + function setActiveHandle(handle) { activeHandle = handle } + function normalizeValue(value) { + if (value > sliderRoot.maximumValue) + return sliderRoot.maximumValue + else if (value < sliderRoot.minimumValue) + return sliderRoot.minimumValue + return value + } + // slider track Rectangle { id: track @@ -188,6 +196,8 @@ Item { // set the slider position based on the upper value function setValue (value) { + // Normalize values between range, since using arrow keys will create out-of-the-range values + value = sliderRoot.normalizeValue(value) UM.SimulationView.setCurrentLayer(value) @@ -274,6 +284,8 @@ Item { // set the slider position based on the lower value function setValue (value) { + // Normalize values between range, since using arrow keys will create out-of-the-range values + value = sliderRoot.normalizeValue(value) UM.SimulationView.setMinimumLayer(value) diff --git a/plugins/SimulationView/PathSlider.qml b/plugins/SimulationView/PathSlider.qml index 0a4af904aa..a73cacee69 100644 --- a/plugins/SimulationView/PathSlider.qml +++ b/plugins/SimulationView/PathSlider.qml @@ -29,6 +29,7 @@ Item { // value properties property real maximumValue: 100 + property real minimumValue: 0 property bool roundValues: true property real handleValue: maximumValue @@ -47,6 +48,14 @@ Item { rangeHandle.width = handle.x - sliderRoot.handleSize } + function normalizeValue(value) { + if (value > sliderRoot.maximumValue) + return sliderRoot.maximumValue + else if (value < sliderRoot.minimumValue) + return sliderRoot.minimumValue + return value + } + // slider track Rectangle { id: track @@ -110,6 +119,8 @@ Item { // set the slider position based on the value function setValue (value) { + // Normalize values between range, since using arrow keys will create out-of-the-range values + value = sliderRoot.normalizeValue(value) UM.SimulationView.setCurrentPath(value) From a303f394c8744eba3afee74c53462511ca5250f3 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 2 Aug 2018 16:09:31 +0200 Subject: [PATCH 11/97] Move VariantType to VariantType.py Less circular dependencies for imports. --- cura/Machines/Models/BuildPlateModel.py | 2 +- cura/Machines/Models/NozzleModel.py | 3 ++- cura/Machines/VariantManager.py | 10 +--------- cura/Machines/VariantType.py | 15 +++++++++++++++ cura/Settings/CuraStackBuilder.py | 2 +- plugins/3MFReader/ThreeMFWorkspaceReader.py | 3 +-- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 4 +++- 7 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 cura/Machines/VariantType.py diff --git a/cura/Machines/Models/BuildPlateModel.py b/cura/Machines/Models/BuildPlateModel.py index e1b4f40d8e..82b9db4d64 100644 --- a/cura/Machines/Models/BuildPlateModel.py +++ b/cura/Machines/Models/BuildPlateModel.py @@ -8,7 +8,7 @@ from UM.Logger import Logger from UM.Qt.ListModel import ListModel from UM.Util import parseBool -from cura.Machines.VariantManager import VariantType +from cura.Machines.VariantType import VariantType class BuildPlateModel(ListModel): diff --git a/cura/Machines/Models/NozzleModel.py b/cura/Machines/Models/NozzleModel.py index 0879998b7d..9d97106d6b 100644 --- a/cura/Machines/Models/NozzleModel.py +++ b/cura/Machines/Models/NozzleModel.py @@ -8,6 +8,8 @@ from UM.Logger import Logger from UM.Qt.ListModel import ListModel from UM.Util import parseBool +from cura.Machines.VariantType import VariantType + class NozzleModel(ListModel): IdRole = Qt.UserRole + 1 @@ -43,7 +45,6 @@ class NozzleModel(ListModel): self.setItems([]) return - from cura.Machines.VariantManager import VariantType variant_node_dict = self._variant_manager.getVariantNodes(global_stack, VariantType.NOZZLE) if not variant_node_dict: self.setItems([]) diff --git a/cura/Machines/VariantManager.py b/cura/Machines/VariantManager.py index 036c79d6c7..969fed670e 100644 --- a/cura/Machines/VariantManager.py +++ b/cura/Machines/VariantManager.py @@ -1,7 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from enum import Enum from collections import OrderedDict from typing import Optional, TYPE_CHECKING @@ -11,20 +10,13 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Util import parseBool from cura.Machines.ContainerNode import ContainerNode +from cura.Machines.VariantType import VariantType, ALL_VARIANT_TYPES from cura.Settings.GlobalStack import GlobalStack if TYPE_CHECKING: from UM.Settings.DefinitionContainer import DefinitionContainer -class VariantType(Enum): - BUILD_PLATE = "buildplate" - NOZZLE = "nozzle" - - -ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE) - - # # VariantManager is THE place to look for a specific variant. It maintains two variant lookup tables with the following # structure: diff --git a/cura/Machines/VariantType.py b/cura/Machines/VariantType.py new file mode 100644 index 0000000000..82bb86d7d9 --- /dev/null +++ b/cura/Machines/VariantType.py @@ -0,0 +1,15 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from enum import Enum + + +class VariantType(Enum): + BUILD_PLATE = "buildplate" + NOZZLE = "nozzle" + + +ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE) + + +__all__ = ["VariantType", "ALL_VARIANT_TYPES"] diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 841d45ed31..a794dde651 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -8,7 +8,7 @@ from UM.Logger import Logger from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.InstanceContainer import InstanceContainer -from cura.Machines.VariantManager import VariantType +from cura.Machines.VariantType import VariantType from .GlobalStack import GlobalStack from .ExtruderStack import ExtruderStack diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index f72029524e..389a7da704 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -24,6 +24,7 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.Job import Job from UM.Preferences import Preferences +from cura.Machines.VariantType import VariantType from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.ExtruderStack import ExtruderStack from cura.Settings.GlobalStack import GlobalStack @@ -889,7 +890,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): parser = self._machine_info.variant_info.parser variant_name = parser["general"]["name"] - from cura.Machines.VariantManager import VariantType variant_type = VariantType.BUILD_PLATE node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type) @@ -905,7 +905,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): parser = extruder_info.variant_info.parser variant_name = parser["general"]["name"] - from cura.Machines.VariantManager import VariantType variant_type = VariantType.NOZZLE node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index eac6646197..a799f948c4 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -19,8 +19,11 @@ from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.ConfigurationErrorMessage import ConfigurationErrorMessage +from cura.Machines.VariantType import VariantType + from .XmlMaterialValidator import XmlMaterialValidator + ## Handles serializing and deserializing material containers from an XML file class XmlMaterialProfile(InstanceContainer): CurrentFdmMaterialVersion = "1.3" @@ -269,7 +272,6 @@ class XmlMaterialProfile(InstanceContainer): buildplate_dict = {} # type: Dict[str, Any] for variant_name, variant_dict in machine_variant_map[definition_id].items(): variant_type = variant_dict["variant_node"].metadata["hardware_type"] - from cura.Machines.VariantManager import VariantType variant_type = VariantType(variant_type) if variant_type == VariantType.NOZZLE: # The hotend identifier is not the containers name, but its "name". From 066a00653ab6ba62fcd5267a352646a7078af142 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 2 Aug 2018 17:15:30 +0200 Subject: [PATCH 12/97] Add one more layer to the decision tree --- cura/Machines/MaterialManager.py | 200 +++++++++++------- cura/Machines/QualityManager.py | 125 ++++++----- cura/Settings/GlobalStack.py | 6 + cura/Settings/MachineManager.py | 22 +- .../XmlMaterialProfile/XmlMaterialProfile.py | 199 ++++++++++++----- resources/definitions/fdmprinter.def.json | 1 + 6 files changed, 368 insertions(+), 185 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 5d691fcef4..86f7dea81f 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -4,8 +4,7 @@ from collections import defaultdict, OrderedDict import copy import uuid -from typing import Dict, cast -from typing import Optional, TYPE_CHECKING +from typing import Dict, Optional, TYPE_CHECKING from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot @@ -18,6 +17,7 @@ from UM.Util import parseBool from .MaterialNode import MaterialNode from .MaterialGroup import MaterialGroup +from .VariantType import VariantType if TYPE_CHECKING: from UM.Settings.DefinitionContainer import DefinitionContainer @@ -47,7 +47,7 @@ class MaterialManager(QObject): self._fallback_materials_map = dict() # material_type -> generic material metadata self._material_group_map = dict() # root_material_id -> MaterialGroup - self._diameter_machine_variant_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode) + self._diameter_machine_nozzle_buildplate_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode) # We're using these two maps to convert between the specific diameter material id and the generic material id # because the generic material ids are used in qualities and definitions, while the specific diameter material is meant @@ -186,10 +186,11 @@ class MaterialManager(QObject): for root_material_id in data_dict.values(): self._diameter_material_map[root_material_id] = default_root_material_id + variant_manager = self._application.getVariantManager() + # Map #4 - # "machine" -> "variant_name" -> "root material ID" -> specific material InstanceContainer - # Construct the "machine" -> "variant" -> "root material ID" -> specific material InstanceContainer - self._diameter_machine_variant_material_map = dict() + # "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer + self._diameter_machine_nozzle_buildplate_material_map = dict() for material_metadata in material_metadatas.values(): # We don't store empty material in the lookup tables if material_metadata["id"] == "empty_material": @@ -199,36 +200,62 @@ class MaterialManager(QObject): definition = material_metadata["definition"] approximate_diameter = material_metadata["approximate_diameter"] - if approximate_diameter not in self._diameter_machine_variant_material_map: - self._diameter_machine_variant_material_map[approximate_diameter] = {} + if approximate_diameter not in self._diameter_machine_nozzle_buildplate_material_map: + self._diameter_machine_nozzle_buildplate_material_map[approximate_diameter] = {} - machine_variant_material_map = self._diameter_machine_variant_material_map[approximate_diameter] - if definition not in machine_variant_material_map: - machine_variant_material_map[definition] = MaterialNode() + machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[approximate_diameter] + if definition not in machine_nozzle_buildplate_material_map: + machine_nozzle_buildplate_material_map[definition] = MaterialNode() - machine_node = machine_variant_material_map[definition] - variant_name = material_metadata.get("variant_name") - if not variant_name: - # if there is no variant, this material is for the machine, so put its metadata in the machine node. + machine_node = machine_nozzle_buildplate_material_map[definition] + nozzle_name = material_metadata.get("variant_name") + buildplate_name = material_metadata.get("buildplate_name") + if not nozzle_name: + # if there is no nozzle, this material is for the machine, so put its metadata in the machine node. machine_node.material_map[root_material_id] = MaterialNode(material_metadata) else: - # this material is variant-specific, so we save it in a variant-specific node under the + # this material is nozzle-specific, so we save it in a nozzle-specific node under the # machine-specific node - # Check first if the variant exist in the manager - existing_variant = self._application.getVariantManager().getVariantNode(definition, variant_name) - if existing_variant is not None: - if variant_name not in machine_node.children_map: - machine_node.children_map[variant_name] = MaterialNode() + # Check first if the nozzle exists in the manager + existing_nozzle = variant_manager.getVariantNode(definition, nozzle_name, VariantType.NOZZLE) + if existing_nozzle is not None: + if nozzle_name not in machine_node.children_map: + machine_node.children_map[nozzle_name] = MaterialNode() + + nozzle_node = machine_node.children_map[nozzle_name] + + # Check build plate node + if not buildplate_name: + # if there is no buildplate, this material is for the machine, so put its metadata in the machine node. + if root_material_id in nozzle_node.material_map: # We shouldn't have duplicated nozzle-specific materials for the same machine. + ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) + continue + nozzle_node.material_map[root_material_id] = MaterialNode(material_metadata) + else: + # this material is nozzle-and-buildplate-specific, so we save it in a buildplate-specific node + # under the machine-specific node + + # Check first if the buildplate exists in the manager + existing_buildplate = variant_manager.getVariantNode(definition, buildplate_name, VariantType.BUILD_PLATE) + if existing_buildplate is not None: + if buildplate_name not in nozzle_node.children_map: + nozzle_node.children_map[buildplate_name] = MaterialNode() + + buildplate_node = nozzle_node.children_map[buildplate_name] + if root_material_id in buildplate_node.material_map: # We shouldn't have duplicated nozzle-and-buildplate-specific materials for the same machine. + ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) + continue + buildplate_node.material_map[root_material_id] = MaterialNode(material_metadata) + + else: + # Add this container id to the wrong containers list in the registry + Logger.log("w", "Not adding {id} to the material manager because the buildplate does not exist.".format(id = material_metadata["id"])) + self._container_registry.addWrongContainerId(material_metadata["id"]) - variant_node = machine_node.children_map[variant_name] - if root_material_id in variant_node.material_map: # We shouldn't have duplicated variant-specific materials for the same machine. - ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) - continue - variant_node.material_map[root_material_id] = MaterialNode(material_metadata) else: # Add this container id to the wrong containers list in the registry - Logger.log("w", "Not adding {id} to the material manager because the variant does not exist.".format(id = material_metadata["id"])) + Logger.log("w", "Not adding {id} to the material manager because the nozzle does not exist.".format(id = material_metadata["id"])) self._container_registry.addWrongContainerId(material_metadata["id"]) self.materialsUpdated.emit() @@ -263,45 +290,52 @@ class MaterialManager(QObject): # # Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup. # - def getAvailableMaterials(self, machine_definition: "DefinitionContainer", extruder_variant_name: Optional[str], - diameter: float) -> Dict[str, MaterialNode]: + def getAvailableMaterials(self, machine_definition: "DefinitionContainer", extruder_nozzle_name: Optional[str], + buildplate_name: Optional[str], diameter: float) -> Dict[str, MaterialNode]: # round the diameter to get the approximate diameter rounded_diameter = str(round(diameter)) - if rounded_diameter not in self._diameter_machine_variant_material_map: + if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map: Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter) return dict() machine_definition_id = machine_definition.getId() - # If there are variant materials, get the variant material - machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter] - machine_node = machine_variant_material_map.get(machine_definition_id) - default_machine_node = machine_variant_material_map.get(self._default_machine_definition_id) - variant_node = None - if extruder_variant_name is not None and machine_node is not None: - variant_node = machine_node.getChildNode(extruder_variant_name) + # If there are nozzle-and-or-buildplate materials, get the nozzle-and-or-buildplate material + machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter] + machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id) + default_machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id) + nozzle_node = None + buildplate_node = None + if extruder_nozzle_name is not None and machine_node is not None: + nozzle_node = machine_node.getChildNode(extruder_nozzle_name) + # Get buildplate node if possible + if nozzle_node is not None and buildplate_name is not None: + buildplate_node = nozzle_node.getChildNode(buildplate_name) - nodes_to_check = [variant_node, machine_node, default_machine_node] + nodes_to_check = [buildplate_node, nozzle_node, machine_node, default_machine_node] # Fallback mechanism of finding materials: - # 1. variant-specific material - # 2. machine-specific material - # 3. generic material (for fdmprinter) + # 1. buildplate-specific material + # 2. nozzle-specific material + # 3. machine-specific material + # 4. generic material (for fdmprinter) machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", []) - material_id_metadata_dict = dict() # type: Dict[str, MaterialNode] - for node in nodes_to_check: - if node is not None: - # Only exclude the materials that are explicitly specified in the "exclude_materials" field. - # Do not exclude other materials that are of the same type. - for material_id, node in node.material_map.items(): - if material_id in machine_exclude_materials: - Logger.log("d", "Exclude material [%s] for machine [%s]", - material_id, machine_definition.getId()) - continue + material_id_metadata_dict = dict() # type: Dict[str, MaterialNode] + for current_node in nodes_to_check: + if current_node is None: + continue - if material_id not in material_id_metadata_dict: - material_id_metadata_dict[material_id] = node + # Only exclude the materials that are explicitly specified in the "exclude_materials" field. + # Do not exclude other materials that are of the same type. + for material_id, node in current_node.material_map.items(): + if material_id in machine_exclude_materials: + Logger.log("d", "Exclude material [%s] for machine [%s]", + material_id, machine_definition.getId()) + continue + + if material_id not in material_id_metadata_dict: + material_id_metadata_dict[material_id] = node return material_id_metadata_dict @@ -310,13 +344,14 @@ class MaterialManager(QObject): # def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack", extruder_stack: "ExtruderStack") -> Optional[dict]: - variant_name = None + buildplate_name = machine.getBuildplateName() + nozzle_name = None if extruder_stack.variant.getId() != "empty_variant": - variant_name = extruder_stack.variant.getName() + nozzle_name = extruder_stack.variant.getName() diameter = extruder_stack.approximateMaterialDiameter # Fetch the available materials (ContainerNode) for the current active machine and extruder setup. - return self.getAvailableMaterials(machine.definition, variant_name, diameter) + return self.getAvailableMaterials(machine.definition, nozzle_name, buildplate_name, diameter) # # Gets MaterialNode for the given extruder and machine with the given material name. @@ -324,32 +359,36 @@ class MaterialManager(QObject): # 1. the given machine doesn't have materials; # 2. cannot find any material InstanceContainers with the given settings. # - def getMaterialNode(self, machine_definition_id: str, extruder_variant_name: Optional[str], - diameter: float, root_material_id: str) -> Optional["InstanceContainer"]: + def getMaterialNode(self, machine_definition_id: str, nozzle_name: Optional[str], + buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["InstanceContainer"]: # round the diameter to get the approximate diameter rounded_diameter = str(round(diameter)) - if rounded_diameter not in self._diameter_machine_variant_material_map: + if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map: Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s]) for root material id [%s]", diameter, rounded_diameter, root_material_id) return None - # If there are variant materials, get the variant material - machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter] - machine_node = machine_variant_material_map.get(machine_definition_id) - variant_node = None + # If there are nozzle materials, get the nozzle-specific material + machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter] + machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id) + nozzle_node = None + buildplate_node = None # Fallback for "fdmprinter" if the machine-specific materials cannot be found if machine_node is None: - machine_node = machine_variant_material_map.get(self._default_machine_definition_id) - if machine_node is not None and extruder_variant_name is not None: - variant_node = machine_node.getChildNode(extruder_variant_name) + machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id) + if machine_node is not None and nozzle_name is not None: + nozzle_node = machine_node.getChildNode(nozzle_name) + if nozzle_node is not None and buildplate_name is not None: + buildplate_node = nozzle_node.getChildNode(buildplate_name) # Fallback mechanism of finding materials: - # 1. variant-specific material - # 2. machine-specific material - # 3. generic material (for fdmprinter) - nodes_to_check = [variant_node, machine_node, - machine_variant_material_map.get(self._default_machine_definition_id)] + # 1. buildplate-specific material + # 2. nozzle-specific material + # 3. machine-specific material + # 4. generic material (for fdmprinter) + nodes_to_check = [buildplate_node, nozzle_node, machine_node, + machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id)] material_node = None for node in nodes_to_check: @@ -366,7 +405,8 @@ class MaterialManager(QObject): # 1. the given machine doesn't have materials; # 2. cannot find any material InstanceContainers with the given settings. # - def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, extruder_variant_name: str, material_guid: str) -> Optional["MaterialNode"]: + def getMaterialNodeByType(self, global_stack: "GlobalStack", position: str, nozzle_name: str, + buildplate_name: Optional[str], material_guid: str) -> Optional["MaterialNode"]: node = None machine_definition = global_stack.definition extruder_definition = global_stack.extruders[position].definition @@ -385,7 +425,7 @@ class MaterialManager(QObject): Logger.log("i", "Cannot find materials with guid [%s] ", material_guid) return None - node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name, + node = self.getMaterialNode(machine_definition.getId(), nozzle_name, buildplate_name, material_diameter, root_material_id) return node @@ -413,13 +453,17 @@ class MaterialManager(QObject): else: return None - ## Get default material for given global stack, extruder position and extruder variant name + ## Get default material for given global stack, extruder position and extruder nozzle name # you can provide the extruder_definition and then the position is ignored (useful when building up global stack in CuraStackBuilder) - def getDefaultMaterial(self, global_stack: "GlobalStack", position: str, extruder_variant_name: Optional[str], extruder_definition: Optional["DefinitionContainer"] = None) -> Optional["MaterialNode"]: + def getDefaultMaterial(self, global_stack: "GlobalStack", position: str, nozzle_name: Optional[str], + extruder_definition: Optional["DefinitionContainer"] = None) -> Optional["MaterialNode"]: node = None + + buildplate_name = global_stack.getBuildplateName() machine_definition = global_stack.definition if extruder_definition is None: extruder_definition = global_stack.extruders[position].definition + if extruder_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)): # At this point the extruder_definition is not None material_diameter = extruder_definition.getProperty("material_diameter", "value") @@ -428,7 +472,7 @@ class MaterialManager(QObject): approximate_material_diameter = str(round(material_diameter)) root_material_id = machine_definition.getMetaDataEntry("preferred_material") root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter) - node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name, + node = self.getMaterialNode(machine_definition.getId(), nozzle_name, buildplate_name, material_diameter, root_material_id) return node @@ -515,8 +559,8 @@ class MaterialManager(QObject): if container_to_copy.getMetaDataEntry("definition") != "fdmprinter": new_id += "_" + container_to_copy.getMetaDataEntry("definition") if container_to_copy.getMetaDataEntry("variant_name"): - variant_name = container_to_copy.getMetaDataEntry("variant_name") - new_id += "_" + variant_name.replace(" ", "_") + nozzle_name = container_to_copy.getMetaDataEntry("variant_name") + new_id += "_" + nozzle_name.replace(" ", "_") new_container = copy.deepcopy(container_to_copy) new_container.getMetaData()["id"] = new_id diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 82a11f9960..273f1ae11f 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -45,7 +45,7 @@ class QualityManager(QObject): self._empty_quality_container = self._application.empty_quality_container self._empty_quality_changes_container = self._application.empty_quality_changes_container - self._machine_variant_material_quality_type_to_quality_dict = {} # for quality lookup + self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # for quality lookup self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup self._default_machine_definition_id = "fdmprinter" @@ -64,10 +64,10 @@ class QualityManager(QObject): def initialize(self): # Initialize the lookup tree for quality profiles with following structure: - # -> -> - # -> + # -> -> -> + # -> - self._machine_variant_material_quality_type_to_quality_dict = {} # for quality lookup + self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # for quality lookup self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup quality_metadata_list = self._container_registry.findContainersMetadata(type = "quality") @@ -79,47 +79,58 @@ class QualityManager(QObject): quality_type = metadata["quality_type"] root_material_id = metadata.get("material") - variant_name = metadata.get("variant") + nozzle_name = metadata.get("variant") + buildplate_name = metadata.get("buildplate") is_global_quality = metadata.get("global_quality", False) - is_global_quality = is_global_quality or (root_material_id is None and variant_name is None) + is_global_quality = is_global_quality or (root_material_id is None and nozzle_name is None and buildplate_name is None) # Sanity check: material+variant and is_global_quality cannot be present at the same time - if is_global_quality and (root_material_id or variant_name): + if is_global_quality and (root_material_id or nozzle_name): ConfigurationErrorMessage.getInstance().addFaultyContainers(metadata["id"]) continue - if definition_id not in self._machine_variant_material_quality_type_to_quality_dict: - self._machine_variant_material_quality_type_to_quality_dict[definition_id] = QualityNode() - machine_node = cast(QualityNode, self._machine_variant_material_quality_type_to_quality_dict[definition_id]) + if definition_id not in self._machine_nozzle_buildplate_material_quality_type_to_quality_dict: + self._machine_nozzle_buildplate_material_quality_type_to_quality_dict[definition_id] = QualityNode() + machine_node = cast(QualityNode, self._machine_nozzle_buildplate_material_quality_type_to_quality_dict[definition_id]) if is_global_quality: # For global qualities, save data in the machine node machine_node.addQualityMetadata(quality_type, metadata) continue - if variant_name is not None: - # If variant_name is specified in the quality/quality_changes profile, check if material is specified, - # too. - if variant_name not in machine_node.children_map: - machine_node.children_map[variant_name] = QualityNode() - variant_node = cast(QualityNode, machine_node.children_map[variant_name]) + # Check if nozzle si specified + if nozzle_name is not None: + if nozzle_name not in machine_node.children_map: + machine_node.children_map[nozzle_name] = QualityNode() + nozzle_node = cast(QualityNode, machine_node.children_map[nozzle_name]) + + # Check if buildplate is specified + if buildplate_name is not None: + if buildplate_name not in nozzle_node.children_map: + nozzle_node.children_map[buildplate_name] = QualityNode() + buildplate_node = cast(QualityNode, nozzle_node.children_map[buildplate_name]) + + if root_material_id is None: + buildplate_node.addQualityMetadata(quality_type, metadata) + else: + if root_material_id not in buildplate_node.children_map: + buildplate_node.children_map[root_material_id] = QualityNode() + material_node = cast(QualityNode, buildplate_node.children_map[root_material_id]) + + material_node.addQualityMetadata(quality_type, metadata) - if root_material_id is None: - # If only variant_name is specified but material is not, add the quality/quality_changes metadata - # into the current variant node. - variant_node.addQualityMetadata(quality_type, metadata) else: - # If only variant_name and material are both specified, go one level deeper: create a material node - # under the current variant node, and then add the quality/quality_changes metadata into the - # material node. - if root_material_id not in variant_node.children_map: - variant_node.children_map[root_material_id] = QualityNode() - material_node = cast(QualityNode, variant_node.children_map[root_material_id]) + if root_material_id is None: + nozzle_node.addQualityMetadata(quality_type, metadata) + else: + if root_material_id not in nozzle_node.children_map: + nozzle_node.children_map[root_material_id] = QualityNode() + material_node = cast(QualityNode, nozzle_node.children_map[root_material_id]) - material_node.addQualityMetadata(quality_type, metadata) + material_node.addQualityMetadata(quality_type, metadata) else: - # If variant_name is not specified, check if material is specified. + # If nozzle is not specified, check if material is specified. if root_material_id is not None: if root_material_id not in machine_node.children_map: machine_node.children_map[root_material_id] = QualityNode() @@ -217,8 +228,8 @@ class QualityManager(QObject): # To find the quality container for the GlobalStack, check in the following fall-back manner: # (1) the machine-specific node # (2) the generic node - machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id) - default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(self._default_machine_definition_id) + machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id) + default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(self._default_machine_definition_id) nodes_to_check = [machine_node, default_machine_node] # Iterate over all quality_types in the machine node @@ -238,11 +249,13 @@ class QualityManager(QObject): quality_group_dict[quality_type] = quality_group break + buildplate_name = machine.getBuildplateName() + # Iterate over all extruders to find quality containers for each extruder for position, extruder in machine.extruders.items(): - variant_name = None + nozzle_name = None if extruder.variant.getId() != "empty_variant": - variant_name = extruder.variant.getName() + nozzle_name = extruder.variant.getName() # This is a list of root material IDs to use for searching for suitable quality profiles. # The root material IDs in this list are in prioritized order. @@ -258,34 +271,47 @@ class QualityManager(QObject): # Also try to get the fallback material material_type = extruder.material.getMetaDataEntry("material") fallback_root_material_id = self._material_manager.getFallbackMaterialIdByMaterialType(material_type) - if fallback_root_material_id: + if fallback_root_material_id and root_material_id not in root_material_id_list: root_material_id_list.append(fallback_root_material_id) # Here we construct a list of nodes we want to look for qualities with the highest priority first. # The use case is that, when we look for qualities for a machine, we first want to search in the following # order: - # 1. machine-variant-and-material-specific qualities if exist - # 2. machine-variant-specific qualities if exist - # 3. machine-material-specific qualities if exist - # 4. machine-specific qualities if exist - # 5. generic qualities if exist + # 1. machine-nozzle-buildplate-and-material-specific qualities if exist + # 2. machine-nozzle-and-material-specific qualities if exist + # 3. machine-nozzle-specific qualities if exist + # 4. machine-material-specific qualities if exist + # 5. machine-specific qualities if exist + # 6. generic qualities if exist # Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into # the list with priorities as the order. Later, we just need to loop over each node in this list and fetch # qualities from there. nodes_to_check = [] - if variant_name: - # In this case, we have both a specific variant and a specific material - variant_node = machine_node.getChildNode(variant_name) - if variant_node and has_material: + if nozzle_name: + # In this case, we have both a specific nozzle and a specific material + nozzle_node = machine_node.getChildNode(nozzle_name) + if nozzle_node and has_material: + # Check build plate if exists + if buildplate_name: + buildplate_node = nozzle_node.getChildNode(buildplate_name) + if buildplate_node and has_material: + for root_material_id in root_material_id_list: + material_node = buildplate_node.getChildNode(root_material_id) + if material_node: + nodes_to_check.append(material_node) + break + nodes_to_check.append(buildplate_node) + + # Then add nozzle specific materials for root_material_id in root_material_id_list: - material_node = variant_node.getChildNode(root_material_id) + material_node = nozzle_node.getChildNode(root_material_id) if material_node: nodes_to_check.append(material_node) break - nodes_to_check.append(variant_node) + nodes_to_check.append(nozzle_node) - # In this case, we only have a specific material but NOT a variant + # In this case, we only have a specific material but NOT a nozzle if has_material: for root_material_id in root_material_id_list: material_node = machine_node.getChildNode(root_material_id) @@ -309,8 +335,9 @@ class QualityManager(QObject): quality_group_dict[quality_type] = quality_group quality_group = quality_group_dict[quality_type] - quality_group.nodes_for_extruders[position] = quality_node - break + if position not in quality_group.nodes_for_extruders: + quality_group.nodes_for_extruders[position] = quality_node + #break # Update availabilities for each quality group self._updateQualityGroupsAvailability(machine, quality_group_dict.values()) @@ -323,8 +350,8 @@ class QualityManager(QObject): # To find the quality container for the GlobalStack, check in the following fall-back manner: # (1) the machine-specific node # (2) the generic node - machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id) - default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get( + machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id) + default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get( self._default_machine_definition_id) nodes_to_check = [machine_node, default_machine_node] diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 66f3290b85..35edfe1053 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -55,6 +55,12 @@ class GlobalStack(CuraContainerStack): return "machine_stack" return configuration_type + def getBuildplateName(self) -> Optional[str]: + name = None + if self.variant.getId() != "empty_variant": + name = self.variant.getName() + return name + ## Add an extruder to the list of extruders of this stack. # # \param extruder The extruder to add. diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index ff585deb54..733340e0ce 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1256,13 +1256,17 @@ class MachineManager(QObject): else: position_list = [position] + buildplate_name = None + if self._global_container_stack.variant.getId() != "empty_variant": + buildplate_name = self._global_container_stack.variant.getName() + for position_item in position_list: extruder = self._global_container_stack.extruders[position_item] current_material_base_name = extruder.material.getMetaDataEntry("base_file") - current_variant_name = None + current_nozzle_name = None if extruder.variant.getId() != self._empty_variant_container.getId(): - current_variant_name = extruder.variant.getMetaDataEntry("name") + current_nozzle_name = extruder.variant.getMetaDataEntry("name") from UM.Settings.Interfaces import PropertyEvaluationContext from cura.Settings.CuraContainerStack import _ContainerIndexes @@ -1271,7 +1275,8 @@ class MachineManager(QObject): material_diameter = extruder.getProperty("material_diameter", "value", context) candidate_materials = self._material_manager.getAvailableMaterials( self._global_container_stack.definition, - current_variant_name, + current_nozzle_name, + buildplate_name, material_diameter) if not candidate_materials: @@ -1284,7 +1289,7 @@ class MachineManager(QObject): continue # The current material is not available, find the preferred one - material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, position_item, current_variant_name) + material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, position_item, current_nozzle_name) if material_node is not None: self._setMaterial(position_item, material_node) @@ -1389,12 +1394,17 @@ class MachineManager(QObject): def setMaterialById(self, position: str, root_material_id: str) -> None: if self._global_container_stack is None: return + buildplate_name = None + if self._global_container_stack.variant.getId() != "empty_variant": + buildplate_name = self._global_container_stack.variant.getName() + machine_definition_id = self._global_container_stack.definition.id position = str(position) extruder_stack = self._global_container_stack.extruders[position] - variant_name = extruder_stack.variant.getName() + nozzle_name = extruder_stack.variant.getName() material_diameter = extruder_stack.approximateMaterialDiameter - material_node = self._material_manager.getMaterialNode(machine_definition_id, variant_name, material_diameter, root_material_id) + material_node = self._material_manager.getMaterialNode(machine_definition_id, nozzle_name, buildplate_name, + material_diameter, root_material_id) self.setMaterial(position, material_node) ## global_stack: if you want to provide your own global_stack instead of the current active one diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index a799f948c4..7d9b2aacc3 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -6,19 +6,17 @@ import io import json #To parse the product-to-id mapping file. import os.path #To find the product-to-id mapping. import sys -from typing import Any, Dict, List, Optional, cast +from typing import Any, Dict, List, Optional, Tuple, cast import xml.etree.ElementTree as ET -from typing import Dict -from typing import Iterator from UM.Resources import Resources from UM.Logger import Logger -from cura.CuraApplication import CuraApplication import UM.Dictionary from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.ConfigurationErrorMessage import ConfigurationErrorMessage +from cura.CuraApplication import CuraApplication from cura.Machines.VariantType import VariantType from .XmlMaterialValidator import XmlMaterialValidator @@ -695,74 +693,38 @@ class XmlMaterialProfile(InstanceContainer): if buildplate_id is None: continue - from cura.Machines.VariantManager import VariantType variant_manager = CuraApplication.getInstance().getVariantManager() variant_node = variant_manager.getVariantNode(machine_id, buildplate_id, variant_type = VariantType.BUILD_PLATE) if not variant_node: continue - buildplate_compatibility = machine_compatibility - buildplate_recommended = machine_compatibility - settings = buildplate.iterfind("./um:setting", self.__namespaces) - for entry in settings: - key = entry.get("key") - if key in self.__unmapped_settings: - if key == "hardware compatible": - buildplate_compatibility = self._parseCompatibleValue(entry.text) - elif key == "hardware recommended": - buildplate_recommended = self._parseCompatibleValue(entry.text) - else: - Logger.log("d", "Unsupported material setting %s", key) + _, buildplate_unmapped_settings_dict = self._getSettingsDictForNode(buildplate) + + buildplate_compatibility = buildplate_unmapped_settings_dict.get("hardware compatible", + machine_compatibility) + buildplate_recommended = buildplate_unmapped_settings_dict.get("hardware recommended", + machine_compatibility) buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended hotends = machine.iterfind("./um:hotend", self.__namespaces) for hotend in hotends: - # The "id" field for hotends in material profiles are actually + # The "id" field for hotends in material profiles is actually name hotend_name = hotend.get("id") if hotend_name is None: continue variant_manager = CuraApplication.getInstance().getVariantManager() - variant_node = variant_manager.getVariantNode(machine_id, hotend_name) + variant_node = variant_manager.getVariantNode(machine_id, hotend_name, VariantType.NOZZLE) if not variant_node: continue - hotend_compatibility = machine_compatibility - hotend_setting_values = {} - settings = hotend.iterfind("./um:setting", self.__namespaces) - for entry in settings: - key = entry.get("key") - if key in self.__material_settings_setting_map: - if key == "processing temperature graph": #This setting has no setting text but subtags. - graph_nodes = entry.iterfind("./um:point", self.__namespaces) - graph_points = [] - for graph_node in graph_nodes: - flow = float(graph_node.get("flow")) - temperature = float(graph_node.get("temperature")) - graph_points.append([flow, temperature]) - hotend_setting_values[self.__material_settings_setting_map[key]] = str(graph_points) - else: - hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text - elif key in self.__unmapped_settings: - if key == "hardware compatible": - hotend_compatibility = self._parseCompatibleValue(entry.text) - else: - Logger.log("d", "Unsupported material setting %s", key) - - # Add namespaced Cura-specific settings - settings = hotend.iterfind("./cura:setting", self.__namespaces) - for entry in settings: - value = entry.text - if value.lower() == "yes": - value = True - elif value.lower() == "no": - value = False - key = entry.get("key") - hotend_setting_values[key] = value + hotend_mapped_settings, hotend_unmapped_settings = self._getSettingsDictForNode(hotend) + hotend_compatibility = hotend_unmapped_settings.get("hardware compatible", machine_compatibility) + # Generate container ID for the hotend-specific material container new_hotend_specific_material_id = self.getId() + "_" + machine_id + "_" + hotend_name.replace(" ", "_") # Same as machine compatibility, keep the derived material containers consistent with the parent material @@ -787,7 +749,7 @@ class XmlMaterialProfile(InstanceContainer): new_hotend_material.getMetaData()["buildplate_recommended"] = buildplate_map["buildplate_recommended"] cached_hotend_setting_properties = cached_machine_setting_properties.copy() - cached_hotend_setting_properties.update(hotend_setting_values) + cached_hotend_setting_properties.update(hotend_mapped_settings) new_hotend_material.setCachedValues(cached_hotend_setting_properties) @@ -796,6 +758,61 @@ class XmlMaterialProfile(InstanceContainer): if is_new_material: containers_to_add.append(new_hotend_material) + # + # Build plates in hotend + # + buildplates = hotend.iterfind("./um:buildplate", self.__namespaces) + for buildplate in buildplates: + # The "id" field for buildplate in material profiles is actually name + buildplate_name = buildplate.get("id") + if buildplate_name is None: + continue + + variant_manager = CuraApplication.getInstance().getVariantManager() + variant_node = variant_manager.getVariantNode(machine_id, buildplate_name, VariantType.BUILD_PLATE) + if not variant_node: + continue + + buildplate_mapped_settings, buildplate_unmapped_settings = self._getSettingsDictForNode(buildplate) + buildplate_compatibility = buildplate_unmapped_settings.get("hardware compatible", + buildplate_map["buildplate_compatible"]) + buildplate_recommended = buildplate_unmapped_settings.get("hardware recommended", + buildplate_map["buildplate_recommended"]) + + # Generate container ID for the hotend-and-buildplate-specific material container + new_hotend_and_buildplate_specific_material_id = new_hotend_specific_material_id + "_" + buildplate_name.replace(" ", "_") + + # Same as machine compatibility, keep the derived material containers consistent with the parent material + if ContainerRegistry.getInstance().isLoaded(new_hotend_and_buildplate_specific_material_id): + new_hotend_and_buildplate_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_and_buildplate_specific_material_id)[0] + is_new_material = False + else: + new_hotend_and_buildplate_material = XmlMaterialProfile(new_hotend_and_buildplate_specific_material_id) + is_new_material = True + + new_hotend_and_buildplate_material.setMetaData(copy.deepcopy(new_hotend_material.getMetaData())) + new_hotend_and_buildplate_material.getMetaData()["id"] = new_hotend_and_buildplate_specific_material_id + new_hotend_and_buildplate_material.getMetaData()["name"] = self.getName() + new_hotend_and_buildplate_material.getMetaData()["variant_name"] = hotend_name + new_hotend_and_buildplate_material.getMetaData()["buildplate_name"] = buildplate_name + new_hotend_and_buildplate_material.setDefinition(machine_id) + # Don't use setMetadata, as that overrides it for all materials with same base file + new_hotend_and_buildplate_material.getMetaData()["compatible"] = buildplate_compatibility + new_hotend_and_buildplate_material.getMetaData()["machine_manufacturer"] = machine_manufacturer + new_hotend_and_buildplate_material.getMetaData()["definition"] = machine_id + new_hotend_and_buildplate_material.getMetaData()["buildplate_compatible"] = buildplate_compatibility + new_hotend_and_buildplate_material.getMetaData()["buildplate_recommended"] = buildplate_recommended + + cached_hotend_and_buildplate_setting_properties = cached_hotend_setting_properties.copy() + cached_hotend_and_buildplate_setting_properties.update(buildplate_mapped_settings) + + new_hotend_and_buildplate_material.setCachedValues(cached_hotend_and_buildplate_setting_properties) + + new_hotend_and_buildplate_material._dirty = False + + if is_new_material: + containers_to_add.append(new_hotend_and_buildplate_material) + # there is only one ID for a machine. Once we have reached here, it means we have already found # a workable ID for that machine, so there is no need to continue break @@ -803,6 +820,54 @@ class XmlMaterialProfile(InstanceContainer): for container_to_add in containers_to_add: ContainerRegistry.getInstance().addContainer(container_to_add) + @classmethod + def _getSettingsDictForNode(cls, node) -> Tuple[dict, dict]: + node_mapped_settings_dict = dict() + node_unmapped_settings_dict = dict() + + # Fetch settings in the "um" namespace + um_settings = node.iterfind("./um:setting", cls.__namespaces) + for um_setting_entry in um_settings: + setting_key = um_setting_entry.get("key") + + # Mapped settings + if setting_key in cls.__material_settings_setting_map: + if setting_key == "processing temperature graph": # This setting has no setting text but subtags. + graph_nodes = um_setting_entry.iterfind("./um:point", cls.__namespaces) + graph_points = [] + for graph_node in graph_nodes: + flow = float(graph_node.get("flow")) + temperature = float(graph_node.get("temperature")) + graph_points.append([flow, temperature]) + node_mapped_settings_dict[cls.__material_settings_setting_map[setting_key]] = str( + graph_points) + else: + node_mapped_settings_dict[cls.__material_settings_setting_map[setting_key]] = um_setting_entry.text + + # Unmapped settings + elif setting_key in cls.__unmapped_settings: + if setting_key in ("hardware compatible", "hardware recommended"): + node_unmapped_settings_dict[setting_key] = cls._parseCompatibleValue(um_setting_entry.text) + + # Unknown settings + else: + Logger.log("w", "Unsupported material setting %s", setting_key) + + # Fetch settings in the "cura" namespace + cura_settings = node.iterfind("./cura:setting", cls.__namespaces) + for cura_setting_entry in cura_settings: + value = cura_setting_entry.text + if value.lower() == "yes": + value = True + elif value.lower() == "no": + value = False + key = cura_setting_entry.get("key") + + # Cura settings are all mapped + node_mapped_settings_dict[key] = value + + return node_mapped_settings_dict, node_unmapped_settings_dict + @classmethod def deserializeMetadata(cls, serialized: str, container_id: str) -> List[Dict[str, Any]]: result_metadata = [] #All the metadata that we found except the base (because the base is returned). @@ -985,6 +1050,36 @@ class XmlMaterialProfile(InstanceContainer): result_metadata.append(new_hotend_material_metadata) + # + # Buildplates in Hotends + # + buildplates = hotend.iterfind("./um:buildplate", cls.__namespaces) + for buildplate in buildplates: + # The "id" field for buildplate in material profiles is actually name + buildplate_name = buildplate.get("id") + if buildplate_name is None: + continue + + buildplate_mapped_settings, buildplate_unmapped_settings = cls._getSettingsDictForNode(buildplate) + buildplate_compatibility = buildplate_unmapped_settings.get("hardware compatible", + buildplate_map["buildplate_compatible"]) + buildplate_recommended = buildplate_unmapped_settings.get("hardware recommended", + buildplate_map["buildplate_recommended"]) + + # Generate container ID for the hotend-and-buildplate-specific material container + new_hotend_and_buildplate_specific_material_id = new_hotend_specific_material_id + "_" + buildplate_name.replace( + " ", "_") + + new_hotend_and_buildplate_material_metadata = {} + new_hotend_and_buildplate_material_metadata.update(new_hotend_material_metadata) + new_hotend_and_buildplate_material_metadata["id"] = new_hotend_and_buildplate_specific_material_id + new_hotend_and_buildplate_material_metadata["buildplate_name"] = buildplate_name + new_hotend_and_buildplate_material_metadata["compatible"] = buildplate_compatibility + new_hotend_and_buildplate_material_metadata["buildplate_compatible"] = buildplate_compatibility + new_hotend_and_buildplate_material_metadata["buildplate_recommended"] = buildplate_recommended + + result_metadata.append(new_hotend_and_buildplate_material_metadata) + # there is only one ID for a machine. Once we have reached here, it means we have already found # a workable ID for that machine, so there is no need to continue break diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index b767aac7b9..6134a43b8e 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -2127,6 +2127,7 @@ "type": "float", "default_value": 60, "value": "default_material_bed_temperature", + "resolve": "max(extruderValues('material_bed_temperature'))", "minimum_value": "-273.15", "minimum_value_warning": "0", "maximum_value_warning": "130", From cc39ca09b8568c79f3c045189dda2a262c851eb1 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 2 Aug 2018 17:16:16 +0200 Subject: [PATCH 13/97] Enable build plate selection menu --- resources/qml/Cura.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 09c66f98f9..e1bf8c8b77 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -232,7 +232,7 @@ UM.MainWindow } // TODO Temporary hidden, add back again when feature ready -// BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: Cura.MachineManager.hasVariantBuildplates } + BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: Cura.MachineManager.hasVariantBuildplates } ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); } MenuSeparator { } From e817d94a8310e37fd0a81b3bbbd15f9a46f97165 Mon Sep 17 00:00:00 2001 From: Matteo Spinelli Date: Thu, 2 Aug 2018 17:30:06 +0200 Subject: [PATCH 14/97] Add user editable snap distance This PR requires an update to CuraEngine as well (I'm posting the update right after) --- resources/definitions/fdmprinter.def.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index b767aac7b9..8b3e4e1f05 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -5625,6 +5625,19 @@ "settable_per_mesh": false, "settable_per_extruder": true }, + "snap_distance": + { + "label": "Snap Distance", + "description": "Snap distance between polygons in mm. Controls the polygons that may be filtered out when the model is sliced. Lower values lead to higher resolution mesh at the cost of slicing time. It is meant mostly for high resolution SLA printers and very tiny 3D models with a lot of details.", + "unit": "mm", + "type": "float", + "default_value": 1.0, + "minimum_value": "0.001", + "minimum_value_warning": "0.05", + "maximum_value_warning": "1.0", + "settable_per_mesh": true, + "settable_per_extruder": false + }, "meshfix_maximum_resolution": { "label": "Maximum Resolution", From 1a189ba4f5be0d0a377ddcdd8671e0a59b5d7533 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 3 Aug 2018 09:13:19 +0200 Subject: [PATCH 15/97] Simplify normalization formula. --- plugins/SimulationView/LayerSlider.qml | 6 +----- plugins/SimulationView/PathSlider.qml | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/plugins/SimulationView/LayerSlider.qml b/plugins/SimulationView/LayerSlider.qml index d7e22d601e..6dcaa3f475 100644 --- a/plugins/SimulationView/LayerSlider.qml +++ b/plugins/SimulationView/LayerSlider.qml @@ -68,11 +68,7 @@ Item { } function normalizeValue(value) { - if (value > sliderRoot.maximumValue) - return sliderRoot.maximumValue - else if (value < sliderRoot.minimumValue) - return sliderRoot.minimumValue - return value + return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue) } // slider track diff --git a/plugins/SimulationView/PathSlider.qml b/plugins/SimulationView/PathSlider.qml index a73cacee69..999912e3ba 100644 --- a/plugins/SimulationView/PathSlider.qml +++ b/plugins/SimulationView/PathSlider.qml @@ -49,11 +49,7 @@ Item { } function normalizeValue(value) { - if (value > sliderRoot.maximumValue) - return sliderRoot.maximumValue - else if (value < sliderRoot.minimumValue) - return sliderRoot.minimumValue - return value + return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue) } // slider track From 5320d05b2009bdbd73496698d5ffade0dea5b944 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 2 Aug 2018 17:22:31 +0200 Subject: [PATCH 16/97] Add special quality profiles --- ...5_aa0.4_aluminum_ABS_High_Quality.inst.cfg | 34 +++++++++ ..._aa0.4_aluminum_CPEP_High_Quality.inst.cfg | 53 ++++++++++++++ ...5_aa0.4_aluminum_CPE_High_Quality.inst.cfg | 32 +++++++++ ...s5_aa0.4_aluminum_PC_High_Quality.inst.cfg | 71 +++++++++++++++++++ ...s5_aa0.8_aluminum_ABS_Draft_Print.inst.cfg | 27 +++++++ ...s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg | 43 +++++++++++ ...s5_aa0.8_aluminum_CPE_Draft_Print.inst.cfg | 29 ++++++++ ...m_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg | 36 ++++++++++ ..._s5_aa0.8_aluminum_PP_Draft_Print.inst.cfg | 53 ++++++++++++++ 9 files changed, 378 insertions(+) create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_ABS_High_Quality.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPEP_High_Quality.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPE_High_Quality.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_PC_High_Quality.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_ABS_Draft_Print.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPE_Draft_Print.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg create mode 100644 resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PP_Draft_Print.inst.cfg diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_ABS_High_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_ABS_High_Quality.inst.cfg new file mode 100644 index 0000000000..9a3eef5762 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_ABS_High_Quality.inst.cfg @@ -0,0 +1,34 @@ +[general] +version = 4 +name = Extra Fine +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = high +weight = 1 +material = generic_abs +variant = AA 0.4 +buildplate = Aluminum + +[values] +cool_min_speed = 12 +layer_height_0 = 0.17 +machine_nozzle_cool_down_speed = 0.8 +machine_nozzle_heat_up_speed = 1.5 +material_bed_temperature = 90 +material_bed_temperature_layer_0 = 100 +material_print_temperature = =default_material_print_temperature + 5 +material_initial_print_temperature = =material_print_temperature - 15 +material_final_print_temperature = =material_print_temperature - 20 +prime_blob_enable = False +prime_tower_enable = False +speed_print = 50 +speed_layer_0 = =math.ceil(speed_print * 20 / 50) +speed_topbottom = =math.ceil(speed_print * 30 / 50) +speed_wall = =math.ceil(speed_print * 30 / 50) + +infill_line_width = =round(line_width * 0.4 / 0.35, 2) +speed_infill = =math.ceil(speed_print * 40 / 50) + diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPEP_High_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPEP_High_Quality.inst.cfg new file mode 100644 index 0000000000..8b0b08f731 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPEP_High_Quality.inst.cfg @@ -0,0 +1,53 @@ +[general] +version = 4 +name = Extra Fine +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = high +weight = 1 +material = generic_cpe_plus +variant = AA 0.4 +buildplate = Aluminum + +[values] +acceleration_enabled = True +acceleration_print = 4000 +cool_fan_speed_max = 50 +cool_min_speed = 5 +infill_line_width = =round(line_width * 0.35 / 0.35, 2) +infill_overlap = 0 +infill_wipe_dist = 0 +jerk_enabled = True +jerk_print = 25 +layer_height_0 = 0.17 +machine_min_cool_heat_time_window = 15 +machine_nozzle_cool_down_speed = 0.85 +machine_nozzle_heat_up_speed = 1.5 +material_bed_temperature = 105 +material_bed_temperature_layer_0 = 115 +material_final_print_temperature = =material_print_temperature - 10 +material_initial_print_temperature = =material_print_temperature - 5 +material_print_temperature = =default_material_print_temperature + 2 +material_print_temperature_layer_0 = =material_print_temperature +multiple_mesh_overlap = 0 +prime_blob_enable = False +prime_tower_enable = True +prime_tower_wipe_enabled = True +retraction_combing = off +retraction_extrusion_window = 1 +retraction_hop = 0.2 +retraction_hop_enabled = False +retraction_hop_only_when_collides = True +skin_overlap = 20 +speed_layer_0 = =math.ceil(speed_print * 20 / 40) +speed_print = 40 +speed_topbottom = =math.ceil(speed_print * 30 / 35) + +speed_wall = =math.ceil(speed_print * 35 / 40) +speed_wall_0 = =math.ceil(speed_wall * 30 / 35) +support_bottom_distance = =support_z_distance +support_z_distance = =layer_height +wall_0_inset = 0 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPE_High_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPE_High_Quality.inst.cfg new file mode 100644 index 0000000000..6299071194 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_CPE_High_Quality.inst.cfg @@ -0,0 +1,32 @@ +[general] +version = 4 +name = Extra Fine +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = high +weight = 1 +material = generic_cpe +variant = AA 0.4 +buildplate = Aluminum + +[values] +cool_min_speed = 12 +layer_height_0 = 0.17 +machine_nozzle_cool_down_speed = 0.85 +machine_nozzle_heat_up_speed = 1.5 +material_bed_temperature = 80 +material_bed_temperature_layer_0 = 90 +material_print_temperature = =default_material_print_temperature - 5 +material_initial_print_temperature = =material_print_temperature - 5 +material_final_print_temperature = =material_print_temperature - 10 +prime_blob_enable = False +speed_print = 50 +speed_layer_0 = =math.ceil(speed_print * 20 / 50) +speed_topbottom = =math.ceil(speed_print * 30 / 50) +speed_wall = =math.ceil(speed_print * 30 / 50) + +infill_pattern = zigzag +speed_infill = =math.ceil(speed_print * 40 / 50) \ No newline at end of file diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_PC_High_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_PC_High_Quality.inst.cfg new file mode 100644 index 0000000000..2afaf21de1 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.4_aluminum_PC_High_Quality.inst.cfg @@ -0,0 +1,71 @@ +[general] +version = 4 +name = Extra Fine +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = high +weight = 1 +material = generic_pc +variant = AA 0.4 +buildplate = Aluminum + +[values] +acceleration_enabled = True +acceleration_print = 4000 +adhesion_type = brim +brim_width = 10 +cool_fan_full_at_height = =layer_height_0 + layer_height +cool_fan_speed_max = 50 +cool_min_layer_time_fan_speed_max = 5 +cool_min_speed = 8 +infill_line_width = =round(line_width * 0.4 / 0.35, 2) +infill_overlap = 0 +infill_overlap_mm = 0.05 +infill_pattern = triangles +infill_wipe_dist = 0.1 +jerk_enabled = True +jerk_print = 25 +layer_height_0 = 0.17 +machine_min_cool_heat_time_window = 15 +machine_nozzle_cool_down_speed = 0.85 +machine_nozzle_heat_up_speed = 1.5 +material_bed_temperature = 115 +material_bed_temperature_layer_0 = 125 +material_final_print_temperature = =material_print_temperature - 10 +material_initial_print_temperature = =material_print_temperature - 5 +material_print_temperature = =default_material_print_temperature - 10 +material_standby_temperature = 100 +multiple_mesh_overlap = 0 +ooze_shield_angle = 40 +prime_blob_enable = False +prime_tower_enable = True +prime_tower_wipe_enabled = True +raft_airgap = 0.25 +raft_interface_thickness = =max(layer_height * 1.5, 0.225) +retraction_count_max = 80 +retraction_extrusion_window = 1 +retraction_hop = 2 +retraction_hop_only_when_collides = True +retraction_min_travel = 0.8 +retraction_prime_speed = 15 +skin_overlap = 30 +speed_layer_0 = =math.ceil(speed_print * 25 / 50) +speed_print = 50 +speed_topbottom = =math.ceil(speed_print * 25 / 50) + +speed_wall = =math.ceil(speed_print * 40 / 50) +speed_wall_0 = =math.ceil(speed_wall * 25 / 40) +support_bottom_distance = =support_z_distance +support_interface_density = 87.5 +support_interface_pattern = lines +switch_extruder_prime_speed = 15 +switch_extruder_retraction_amount = 20 +switch_extruder_retraction_speeds = 35 +wall_0_inset = 0 +wall_line_width_x = =round(line_width * 0.4 / 0.35, 2) +wall_thickness = 1.2 + + diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_ABS_Draft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_ABS_Draft_Print.inst.cfg new file mode 100644 index 0000000000..3d984e1dff --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_ABS_Draft_Print.inst.cfg @@ -0,0 +1,27 @@ +[general] +version = 4 +name = Fast +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = draft +weight = -2 +material = generic_abs +variant = AA 0.8 +buildplate = Aluminum + +[values] +layer_height_0 = 0.3 +line_width = =machine_nozzle_size * 0.875 +material_bed_temperature = 90 +material_bed_temperature_layer_0 = 100 +material_print_temperature = =default_material_print_temperature + 20 +material_standby_temperature = 100 +prime_blob_enable = False +speed_print = 50 +speed_topbottom = =math.ceil(speed_print * 30 / 50) +speed_wall = =math.ceil(speed_print * 40 / 50) +speed_wall_0 = =math.ceil(speed_wall * 30 / 40) +retract_at_layer_change = False diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg new file mode 100644 index 0000000000..6fd60c197a --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPEP_Fast_Print.inst.cfg @@ -0,0 +1,43 @@ +[general] +version = 4 +name = Fast - Experimental +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = draft +weight = -2 +material = generic_cpe_plus +variant = AA 0.8 +buildplate = Aluminum + +[values] +brim_width = 14 +cool_fan_full_at_height = =layer_height_0 + 14 * layer_height +infill_before_walls = True +layer_height_0 = 0.3 +line_width = =machine_nozzle_size * 0.9375 +machine_nozzle_cool_down_speed = 0.9 +machine_nozzle_heat_up_speed = 1.4 +material_bed_temperature = 105 +material_bed_temperature_layer_0 = 115 +material_print_temperature = =default_material_print_temperature - 10 +material_print_temperature_layer_0 = =material_print_temperature +material_standby_temperature = 100 +prime_blob_enable = False +prime_tower_enable = True +retraction_combing = off +retraction_hop = 0.1 +retraction_hop_enabled = False +skin_overlap = 0 +speed_layer_0 = =math.ceil(speed_print * 15 / 50) +speed_print = 50 +speed_slowdown_layers = 15 +speed_topbottom = =math.ceil(speed_print * 35 / 50) +speed_wall = =math.ceil(speed_print * 40 / 50) +speed_wall_0 = =math.ceil(speed_wall * 35 / 40) +support_bottom_distance = =support_z_distance +support_line_width = =round(line_width * 0.6 / 0.7, 2) +support_z_distance = =layer_height +top_bottom_thickness = 1.2 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPE_Draft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPE_Draft_Print.inst.cfg new file mode 100644 index 0000000000..37aa25f9d8 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_CPE_Draft_Print.inst.cfg @@ -0,0 +1,29 @@ +[general] +version = 4 +name = Fast +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = draft +weight = -2 +material = generic_cpe +variant = AA 0.8 +buildplate = Aluminum + +[values] +brim_width = 15 +layer_height_0 = 0.3 +line_width = =machine_nozzle_size * 0.875 +material_bed_temperature = 80 +material_bed_temperature_layer_0 = 90 +material_print_temperature = =default_material_print_temperature + 15 +material_standby_temperature = 100 +prime_blob_enable = False +prime_tower_enable = True +speed_print = 40 +speed_topbottom = =math.ceil(speed_print * 25 / 40) +speed_wall = =math.ceil(speed_print * 30 / 40) + +jerk_travel = 50 \ No newline at end of file diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg new file mode 100644 index 0000000000..9ebf2ea151 --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PC_Fast_Print.inst.cfg @@ -0,0 +1,36 @@ +[general] +version = 4 +name = Fast - Experimental +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = draft +weight = 0 +material = generic_pc +variant = AA 0.8 +buildplate = Aluminum + +[values] +brim_width = 10 +cool_fan_full_at_height = =layer_height_0 + 14 * layer_height +infill_before_walls = True +layer_height_0 = 0.3 +line_width = =machine_nozzle_size * 0.875 +material_bed_temperature = 115 +material_bed_temperature_layer_0 = 125 +material_print_temperature = =default_material_print_temperature - 5 +material_print_temperature_layer_0 = =material_print_temperature +material_standby_temperature = 100 +prime_blob_enable = False +raft_airgap = 0.5 +raft_margin = 15 +skin_overlap = 0 +speed_layer_0 = =math.ceil(speed_print * 15 / 50) +speed_print = 50 +speed_slowdown_layers = 15 +speed_topbottom = =math.ceil(speed_print * 25 / 50) +speed_wall = =math.ceil(speed_print * 40 / 50) +speed_wall_0 = =math.ceil(speed_wall * 30 / 40) +support_line_width = =round(line_width * 0.6 / 0.7, 2) diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PP_Draft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PP_Draft_Print.inst.cfg new file mode 100644 index 0000000000..94bede16bd --- /dev/null +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_aluminum_PP_Draft_Print.inst.cfg @@ -0,0 +1,53 @@ +[general] +version = 4 +name = Fast +definition = ultimaker_s5 + +[metadata] +setting_version = 5 +type = quality +quality_type = draft +weight = -2 +material = generic_pp +variant = AA 0.8 +buildplate = Aluminum + +[values] +brim_width = 25 +cool_min_layer_time_fan_speed_max = 6 +cool_min_speed = 17 +top_skin_expand_distance = =line_width * 2 +infill_before_walls = True +infill_line_width = =round(line_width * 0.7 / 0.8, 2) +infill_pattern = tetrahedral +jerk_prime_tower = =math.ceil(jerk_print * 25 / 25) +jerk_support = =math.ceil(jerk_print * 25 / 25) +jerk_wall_0 = =math.ceil(jerk_wall * 15 / 25) +material_bed_temperature_layer_0 = =material_bed_temperature + 5 +material_print_temperature = =default_material_print_temperature - 2 +material_print_temperature_layer_0 = =default_material_print_temperature + 2 +material_standby_temperature = 100 +multiple_mesh_overlap = 0.2 +prime_tower_enable = True +prime_tower_flow = 100 +prime_tower_min_volume = 10 +retract_at_layer_change = False +retraction_count_max = 12 +retraction_extra_prime_amount = 0.5 +retraction_hop = 0.5 +retraction_min_travel = 1.5 +retraction_prime_speed = 15 +skin_line_width = =round(line_width * 0.78 / 0.8, 2) + +speed_wall_x = =math.ceil(speed_wall * 30 / 30) +support_bottom_distance = =support_z_distance +support_line_width = =round(line_width * 0.7 / 0.8, 2) +support_offset = =line_width +switch_extruder_prime_speed = 15 +switch_extruder_retraction_amount = 20 +switch_extruder_retraction_speeds = 45 +top_bottom_thickness = 1.6 +travel_compensate_overlapping_walls_0_enabled = False +wall_0_wipe_dist = =line_width * 2 +wall_line_width_x = =round(line_width * 0.8 / 0.8, 2) +wall_thickness = 1.6 From a7448e4970e9f1e72a82035dc12f2814e4437801 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 3 Aug 2018 09:59:23 +0200 Subject: [PATCH 17/97] Refactor material lookup tree creation --- cura/Machines/MaterialManager.py | 137 ++++++++++++++++--------------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 86f7dea81f..3c9a7bfd59 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -192,74 +192,75 @@ class MaterialManager(QObject): # "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer self._diameter_machine_nozzle_buildplate_material_map = dict() for material_metadata in material_metadatas.values(): - # We don't store empty material in the lookup tables - if material_metadata["id"] == "empty_material": - continue - - root_material_id = material_metadata["base_file"] - definition = material_metadata["definition"] - approximate_diameter = material_metadata["approximate_diameter"] - - if approximate_diameter not in self._diameter_machine_nozzle_buildplate_material_map: - self._diameter_machine_nozzle_buildplate_material_map[approximate_diameter] = {} - - machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[approximate_diameter] - if definition not in machine_nozzle_buildplate_material_map: - machine_nozzle_buildplate_material_map[definition] = MaterialNode() - - machine_node = machine_nozzle_buildplate_material_map[definition] - nozzle_name = material_metadata.get("variant_name") - buildplate_name = material_metadata.get("buildplate_name") - if not nozzle_name: - # if there is no nozzle, this material is for the machine, so put its metadata in the machine node. - machine_node.material_map[root_material_id] = MaterialNode(material_metadata) - else: - # this material is nozzle-specific, so we save it in a nozzle-specific node under the - # machine-specific node - - # Check first if the nozzle exists in the manager - existing_nozzle = variant_manager.getVariantNode(definition, nozzle_name, VariantType.NOZZLE) - if existing_nozzle is not None: - if nozzle_name not in machine_node.children_map: - machine_node.children_map[nozzle_name] = MaterialNode() - - nozzle_node = machine_node.children_map[nozzle_name] - - # Check build plate node - if not buildplate_name: - # if there is no buildplate, this material is for the machine, so put its metadata in the machine node. - if root_material_id in nozzle_node.material_map: # We shouldn't have duplicated nozzle-specific materials for the same machine. - ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) - continue - nozzle_node.material_map[root_material_id] = MaterialNode(material_metadata) - else: - # this material is nozzle-and-buildplate-specific, so we save it in a buildplate-specific node - # under the machine-specific node - - # Check first if the buildplate exists in the manager - existing_buildplate = variant_manager.getVariantNode(definition, buildplate_name, VariantType.BUILD_PLATE) - if existing_buildplate is not None: - if buildplate_name not in nozzle_node.children_map: - nozzle_node.children_map[buildplate_name] = MaterialNode() - - buildplate_node = nozzle_node.children_map[buildplate_name] - if root_material_id in buildplate_node.material_map: # We shouldn't have duplicated nozzle-and-buildplate-specific materials for the same machine. - ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) - continue - buildplate_node.material_map[root_material_id] = MaterialNode(material_metadata) - - else: - # Add this container id to the wrong containers list in the registry - Logger.log("w", "Not adding {id} to the material manager because the buildplate does not exist.".format(id = material_metadata["id"])) - self._container_registry.addWrongContainerId(material_metadata["id"]) - - else: - # Add this container id to the wrong containers list in the registry - Logger.log("w", "Not adding {id} to the material manager because the nozzle does not exist.".format(id = material_metadata["id"])) - self._container_registry.addWrongContainerId(material_metadata["id"]) + self.__addMaterialMetadataIntoLookupTree(material_metadata) self.materialsUpdated.emit() + def __addMaterialMetadataIntoLookupTree(self, material_metadata: dict) -> None: + material_id = material_metadata["id"] + + # We don't store empty material in the lookup tables + if material_id == "empty_material": + return + + root_material_id = material_metadata["base_file"] + definition = material_metadata["definition"] + approximate_diameter = material_metadata["approximate_diameter"] + + if approximate_diameter not in self._diameter_machine_nozzle_buildplate_material_map: + self._diameter_machine_nozzle_buildplate_material_map[approximate_diameter] = {} + + machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[ + approximate_diameter] + if definition not in machine_nozzle_buildplate_material_map: + machine_nozzle_buildplate_material_map[definition] = MaterialNode() + + # This is a list of information regarding the intermediate nodes: + # nozzle -> buildplate + nozzle_name = material_metadata.get("variant_name") + buildplate_name = material_metadata.get("buildplate_name") + intermediate_node_info_list = [(nozzle_name, VariantType.NOZZLE), + (buildplate_name, VariantType.BUILD_PLATE), + ] + + variant_manager = self._application.getVariantManager() + + machine_node = machine_nozzle_buildplate_material_map[definition] + current_node = machine_node + current_intermediate_node_info_idx = 0 + error_message = None # type: Optional[str] + while current_intermediate_node_info_idx < len(intermediate_node_info_list): + variant_name, variant_type = intermediate_node_info_list[current_intermediate_node_info_idx] + if variant_name is not None: + # The new material has a specific variant, so it needs to be added to that specific branch in the tree. + variant = variant_manager.getVariantNode(definition, variant_name, variant_type) + if variant is None: + error_message = "Material {id} contains a variant {name} that does not exist.".format( + id = material_metadata["id"], name = variant_name) + break + + # Update the current node to advance to a more specific branch + if variant_name not in current_node.children_map: + current_node.children_map[variant_name] = MaterialNode() + current_node = current_node.children_map[variant_name] + + current_intermediate_node_info_idx += 1 + + if error_message is not None: + Logger.log("e", "%s It will not be added into the material lookup tree.", error_message) + self._container_registry.addWrongContainerId(material_metadata["id"]) + return + + # Add the material to the current tree node, which is the deepest (the most specific) branch we can find. + # Sanity check: Make sure that there is no duplicated materials. + if root_material_id in current_node.material_map: + Logger.log("e", "Duplicated material [%s] with root ID [%s]. It has already been added.", + material_id, root_material_id) + ConfigurationErrorMessage.getInstance().addFaultyContainers(root_material_id) + return + + current_node.material_map[root_material_id] = MaterialNode(material_metadata) + def _updateMaps(self): Logger.log("i", "Updating material lookup data ...") self.initialize() @@ -290,7 +291,7 @@ class MaterialManager(QObject): # # Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup. # - def getAvailableMaterials(self, machine_definition: "DefinitionContainer", extruder_nozzle_name: Optional[str], + def getAvailableMaterials(self, machine_definition: "DefinitionContainer", nozzle_name: Optional[str], buildplate_name: Optional[str], diameter: float) -> Dict[str, MaterialNode]: # round the diameter to get the approximate diameter rounded_diameter = str(round(diameter)) @@ -306,8 +307,8 @@ class MaterialManager(QObject): default_machine_node = machine_nozzle_buildplate_material_map.get(self._default_machine_definition_id) nozzle_node = None buildplate_node = None - if extruder_nozzle_name is not None and machine_node is not None: - nozzle_node = machine_node.getChildNode(extruder_nozzle_name) + if nozzle_name is not None and machine_node is not None: + nozzle_node = machine_node.getChildNode(nozzle_name) # Get buildplate node if possible if nozzle_node is not None and buildplate_name is not None: buildplate_node = nozzle_node.getChildNode(buildplate_name) From e2a97e3a012287ad8b50affc7692149e10b8669c Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 3 Aug 2018 10:28:21 +0200 Subject: [PATCH 18/97] Refactor quality lookup table creation --- cura/Machines/QualityManager.py | 50 +++++++++------------------------ 1 file changed, 13 insertions(+), 37 deletions(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 273f1ae11f..263b3a9620 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -98,45 +98,22 @@ class QualityManager(QObject): machine_node.addQualityMetadata(quality_type, metadata) continue - # Check if nozzle si specified - if nozzle_name is not None: - if nozzle_name not in machine_node.children_map: - machine_node.children_map[nozzle_name] = QualityNode() - nozzle_node = cast(QualityNode, machine_node.children_map[nozzle_name]) + current_node = machine_node + intermediate_node_info_list = [nozzle_name, buildplate_name, root_material_id] + current_intermediate_node_info_idx = 0 - # Check if buildplate is specified - if buildplate_name is not None: - if buildplate_name not in nozzle_node.children_map: - nozzle_node.children_map[buildplate_name] = QualityNode() - buildplate_node = cast(QualityNode, nozzle_node.children_map[buildplate_name]) + while current_intermediate_node_info_idx < len(intermediate_node_info_list): + node_name = intermediate_node_info_list[current_intermediate_node_info_idx] + if node_name is not None: + # There is specific information, update the current node to go deeper so we can add this quality + # at the most specific branch in the lookup tree. + if node_name not in current_node.children_map: + current_node.children_map[node_name] = QualityNode() + current_node = cast(QualityNode, current_node.children_map[node_name]) - if root_material_id is None: - buildplate_node.addQualityMetadata(quality_type, metadata) - else: - if root_material_id not in buildplate_node.children_map: - buildplate_node.children_map[root_material_id] = QualityNode() - material_node = cast(QualityNode, buildplate_node.children_map[root_material_id]) + current_intermediate_node_info_idx += 1 - material_node.addQualityMetadata(quality_type, metadata) - - else: - if root_material_id is None: - nozzle_node.addQualityMetadata(quality_type, metadata) - else: - if root_material_id not in nozzle_node.children_map: - nozzle_node.children_map[root_material_id] = QualityNode() - material_node = cast(QualityNode, nozzle_node.children_map[root_material_id]) - - material_node.addQualityMetadata(quality_type, metadata) - - else: - # If nozzle is not specified, check if material is specified. - if root_material_id is not None: - if root_material_id not in machine_node.children_map: - machine_node.children_map[root_material_id] = QualityNode() - material_node = cast(QualityNode, machine_node.children_map[root_material_id]) - - material_node.addQualityMetadata(quality_type, metadata) + current_node.addQualityMetadata(quality_type, metadata) # Initialize the lookup tree for quality_changes profiles with following structure: # -> -> @@ -337,7 +314,6 @@ class QualityManager(QObject): quality_group = quality_group_dict[quality_type] if position not in quality_group.nodes_for_extruders: quality_group.nodes_for_extruders[position] = quality_node - #break # Update availabilities for each quality group self._updateQualityGroupsAvailability(machine, quality_group_dict.values()) From 206a32ae5011f7c703a34c542a3eb84ddae7c067 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 3 Aug 2018 10:41:36 +0200 Subject: [PATCH 19/97] Refactor quality lookup --- cura/Machines/QualityManager.py | 45 ++++++++++++++------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 263b3a9620..c0954c005c 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -263,38 +263,31 @@ class QualityManager(QObject): # Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into # the list with priorities as the order. Later, we just need to loop over each node in this list and fetch # qualities from there. + node_info_list_0 = [nozzle_name, buildplate_name, root_material_id] + current_node_info_idx = 0 nodes_to_check = [] - if nozzle_name: - # In this case, we have both a specific nozzle and a specific material - nozzle_node = machine_node.getChildNode(nozzle_name) - if nozzle_node and has_material: - # Check build plate if exists - if buildplate_name: - buildplate_node = nozzle_node.getChildNode(buildplate_name) - if buildplate_node and has_material: - for root_material_id in root_material_id_list: - material_node = buildplate_node.getChildNode(root_material_id) - if material_node: - nodes_to_check.append(material_node) - break - nodes_to_check.append(buildplate_node) + # This function tries to recursively find the deepest (the most specific) branch and add those nodes to + # the search list in the order described above. So, by iterating over that search node list, we first look + # in the more specific branches and then the less specific (generic) ones. + def addNodesToCheck(node, nodes_to_check_list, node_info_list, node_info_idx): + if current_node_info_idx < len(node_info_list): + node_name = node_info_list[node_info_idx] + if node_name is not None: + current_node = node.getChildNode(node_name) + if current_node is not None and has_material: + addNodesToCheck(current_node, nodes_to_check_list, node_info_list, node_info_idx + 1) - # Then add nozzle specific materials - for root_material_id in root_material_id_list: - material_node = nozzle_node.getChildNode(root_material_id) + if has_material: + for rmid in root_material_id_list: + material_node = node.getChildNode(rmid) if material_node: - nodes_to_check.append(material_node) + nodes_to_check_list.append(material_node) break - nodes_to_check.append(nozzle_node) - # In this case, we only have a specific material but NOT a nozzle - if has_material: - for root_material_id in root_material_id_list: - material_node = machine_node.getChildNode(root_material_id) - if material_node: - nodes_to_check.append(material_node) - break + nodes_to_check_list.append(node) + + addNodesToCheck(machine_node, nodes_to_check, node_info_list_0, 0) nodes_to_check += [machine_node, default_machine_node] for node in nodes_to_check: From ea8d10783ce1e3ba58309c2e5fe1cdbb6654c0a3 Mon Sep 17 00:00:00 2001 From: DavidGergely Date: Fri, 3 Aug 2018 14:12:36 +0200 Subject: [PATCH 20/97] Cura-5457 prime tower settings New settings for the prime towers for all profiles expect PP0.8 printcore --- resources/definitions/fdmprinter.def.json | 2 +- .../quality/ultimaker3/um3_aa0.25_ABS_Normal_Quality.inst.cfg | 2 -- .../quality/ultimaker3/um3_aa0.25_CPE_Normal_Quality.inst.cfg | 2 -- resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg | 1 - .../quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg | 1 - .../quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg | 1 - .../ultimaker_s5/um_s5_aa0.25_ABS_Normal_Quality.inst.cfg | 2 -- .../ultimaker_s5/um_s5_aa0.25_CPE_Normal_Quality.inst.cfg | 2 -- .../quality/ultimaker_s5/um_s5_aa0.8_TPU_Draft_Print.inst.cfg | 1 - .../ultimaker_s5/um_s5_aa0.8_TPU_Superdraft_Print.inst.cfg | 1 - .../ultimaker_s5/um_s5_aa0.8_TPU_Verydraft_Print.inst.cfg | 1 - resources/variants/ultimaker3_bb0.8.inst.cfg | 1 - resources/variants/ultimaker3_bb04.inst.cfg | 1 - resources/variants/ultimaker3_extended_bb0.8.inst.cfg | 1 - resources/variants/ultimaker3_extended_bb04.inst.cfg | 1 - resources/variants/ultimaker_s5_bb0.8.inst.cfg | 1 - resources/variants/ultimaker_s5_bb04.inst.cfg | 1 - 17 files changed, 1 insertion(+), 21 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index b767aac7b9..daf09d2701 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -5057,7 +5057,7 @@ "description": "The minimum volume for each layer of the prime tower in order to purge enough material.", "unit": "mm³", "type": "float", - "default_value": 5, + "default_value": 6, "minimum_value": "0", "maximum_value_warning": "((resolveOrValue('prime_tower_size') * 0.5) ** 2 * 3.14159 * resolveOrValue('layer_height') if prime_tower_circular else resolveOrValue('prime_tower_size') ** 2 * resolveOrValue('layer_height')) - sum(extruderValues('prime_tower_min_volume')) + prime_tower_min_volume", "enabled": "resolveOrValue('prime_tower_enable')", diff --git a/resources/quality/ultimaker3/um3_aa0.25_ABS_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.25_ABS_Normal_Quality.inst.cfg index 428f5c1101..5139a1fea8 100644 --- a/resources/quality/ultimaker3/um3_aa0.25_ABS_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.25_ABS_Normal_Quality.inst.cfg @@ -15,8 +15,6 @@ variant = AA 0.25 cool_fan_speed = 40 infill_overlap = 15 material_final_print_temperature = =material_print_temperature - 5 -prime_tower_size = 12 -prime_tower_min_volume = 2 retraction_prime_speed = 25 speed_topbottom = =math.ceil(speed_print * 30 / 55) wall_thickness = 0.92 diff --git a/resources/quality/ultimaker3/um3_aa0.25_CPE_Normal_Quality.inst.cfg b/resources/quality/ultimaker3/um3_aa0.25_CPE_Normal_Quality.inst.cfg index 127032e118..4e81b4f39e 100644 --- a/resources/quality/ultimaker3/um3_aa0.25_CPE_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.25_CPE_Normal_Quality.inst.cfg @@ -12,8 +12,6 @@ material = generic_cpe variant = AA 0.25 [values] -prime_tower_size = 12 -prime_tower_min_volume = 2 retraction_extrusion_window = 0.5 speed_infill = =math.ceil(speed_print * 40 / 55) speed_topbottom = =math.ceil(speed_print * 30 / 55) diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg index 1d5cea601f..b6e6fdecb6 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Draft_Print.inst.cfg @@ -34,7 +34,6 @@ material_standby_temperature = 100 multiple_mesh_overlap = 0.2 prime_tower_enable = True prime_tower_flow = 100 -prime_tower_min_volume = 10 retract_at_layer_change = False retraction_count_max = 12 retraction_extra_prime_amount = 0.5 diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg index 0c94b64159..b64d37310e 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Superdraft_Print.inst.cfg @@ -35,7 +35,6 @@ material_standby_temperature = 100 multiple_mesh_overlap = 0.2 prime_tower_enable = True prime_tower_flow = 100 -prime_tower_min_volume = 15 retract_at_layer_change = False retraction_count_max = 12 retraction_extra_prime_amount = 0.5 diff --git a/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg b/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg index 50282f8b49..d9e8f9ec2e 100644 --- a/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker3/um3_aa0.8_TPU_Verydraft_Print.inst.cfg @@ -34,7 +34,6 @@ material_standby_temperature = 100 multiple_mesh_overlap = 0.2 prime_tower_enable = True prime_tower_flow = 100 -prime_tower_min_volume = 20 retract_at_layer_change = False retraction_count_max = 12 retraction_extra_prime_amount = 0.5 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.25_ABS_Normal_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.25_ABS_Normal_Quality.inst.cfg index e58a1bd87d..f2e05b08e8 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.25_ABS_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.25_ABS_Normal_Quality.inst.cfg @@ -15,8 +15,6 @@ variant = AA 0.25 cool_fan_speed = 40 infill_overlap = 15 material_final_print_temperature = =material_print_temperature - 5 -prime_tower_size = 12 -prime_tower_min_volume = 2 retraction_prime_speed = 25 speed_topbottom = =math.ceil(speed_print * 30 / 55) wall_thickness = 0.92 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.25_CPE_Normal_Quality.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.25_CPE_Normal_Quality.inst.cfg index 1c1833a385..2068ed51c0 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.25_CPE_Normal_Quality.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.25_CPE_Normal_Quality.inst.cfg @@ -12,8 +12,6 @@ material = generic_cpe variant = AA 0.25 [values] -prime_tower_size = 12 -prime_tower_min_volume = 2 retraction_extrusion_window = 0.5 speed_infill = =math.ceil(speed_print * 40 / 55) speed_topbottom = =math.ceil(speed_print * 30 / 55) diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Draft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Draft_Print.inst.cfg index f88da43ac1..e8c58ce32c 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Draft_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Draft_Print.inst.cfg @@ -32,7 +32,6 @@ material_standby_temperature = 100 multiple_mesh_overlap = 0.2 prime_tower_enable = True prime_tower_flow = 100 -prime_tower_min_volume = 10 retract_at_layer_change = False retraction_count_max = 12 retraction_extra_prime_amount = 0.5 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Superdraft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Superdraft_Print.inst.cfg index 2967f3539b..ff723c4ed4 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Superdraft_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Superdraft_Print.inst.cfg @@ -33,7 +33,6 @@ material_standby_temperature = 100 multiple_mesh_overlap = 0.2 prime_tower_enable = True prime_tower_flow = 100 -prime_tower_min_volume = 20 retract_at_layer_change = False retraction_count_max = 12 retraction_extra_prime_amount = 0.5 diff --git a/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Verydraft_Print.inst.cfg b/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Verydraft_Print.inst.cfg index caa87f9437..7e36e9d354 100644 --- a/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Verydraft_Print.inst.cfg +++ b/resources/quality/ultimaker_s5/um_s5_aa0.8_TPU_Verydraft_Print.inst.cfg @@ -32,7 +32,6 @@ material_standby_temperature = 100 multiple_mesh_overlap = 0.2 prime_tower_enable = True prime_tower_flow = 100 -prime_tower_min_volume = 15 retract_at_layer_change = False retraction_count_max = 12 retraction_extra_prime_amount = 0.5 diff --git a/resources/variants/ultimaker3_bb0.8.inst.cfg b/resources/variants/ultimaker3_bb0.8.inst.cfg index 9ad4284b40..ace0bf3a94 100644 --- a/resources/variants/ultimaker3_bb0.8.inst.cfg +++ b/resources/variants/ultimaker3_bb0.8.inst.cfg @@ -40,7 +40,6 @@ material_print_temperature = =default_material_print_temperature + 10 material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = False -prime_tower_min_volume = 20 prime_tower_wipe_enabled = True raft_acceleration = =acceleration_layer_0 raft_airgap = 0 diff --git a/resources/variants/ultimaker3_bb04.inst.cfg b/resources/variants/ultimaker3_bb04.inst.cfg index f07a4a55f9..d571cabc9b 100644 --- a/resources/variants/ultimaker3_bb04.inst.cfg +++ b/resources/variants/ultimaker3_bb04.inst.cfg @@ -22,7 +22,6 @@ jerk_support_bottom = =math.ceil(jerk_support_interface * 1 / 10) machine_nozzle_heat_up_speed = 1.5 machine_nozzle_id = BB 0.4 machine_nozzle_tip_outer_diameter = 1.0 -prime_tower_min_volume = 15 raft_base_speed = 20 raft_interface_speed = 20 raft_speed = 25 diff --git a/resources/variants/ultimaker3_extended_bb0.8.inst.cfg b/resources/variants/ultimaker3_extended_bb0.8.inst.cfg index d7a76d538a..fe760c93b8 100644 --- a/resources/variants/ultimaker3_extended_bb0.8.inst.cfg +++ b/resources/variants/ultimaker3_extended_bb0.8.inst.cfg @@ -40,7 +40,6 @@ material_print_temperature = =default_material_print_temperature + 10 material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = False -prime_tower_min_volume = 20 prime_tower_wipe_enabled = True raft_acceleration = =acceleration_layer_0 raft_airgap = 0 diff --git a/resources/variants/ultimaker3_extended_bb04.inst.cfg b/resources/variants/ultimaker3_extended_bb04.inst.cfg index 6e882cfa04..742dc9896e 100644 --- a/resources/variants/ultimaker3_extended_bb04.inst.cfg +++ b/resources/variants/ultimaker3_extended_bb04.inst.cfg @@ -22,7 +22,6 @@ jerk_support_bottom = =math.ceil(jerk_support_interface * 1 / 10) machine_nozzle_heat_up_speed = 1.5 machine_nozzle_id = BB 0.4 machine_nozzle_tip_outer_diameter = 1.0 -prime_tower_min_volume = 15 raft_base_speed = 20 raft_interface_speed = 20 raft_speed = 25 diff --git a/resources/variants/ultimaker_s5_bb0.8.inst.cfg b/resources/variants/ultimaker_s5_bb0.8.inst.cfg index 6b954041ab..c1c5c1a10b 100644 --- a/resources/variants/ultimaker_s5_bb0.8.inst.cfg +++ b/resources/variants/ultimaker_s5_bb0.8.inst.cfg @@ -40,7 +40,6 @@ material_print_temperature = =default_material_print_temperature + 10 material_standby_temperature = 100 multiple_mesh_overlap = 0 prime_tower_enable = False -prime_tower_min_volume = 20 prime_tower_wipe_enabled = True raft_acceleration = =acceleration_layer_0 raft_airgap = 0 diff --git a/resources/variants/ultimaker_s5_bb04.inst.cfg b/resources/variants/ultimaker_s5_bb04.inst.cfg index 634920ca65..b5ff8d51f6 100644 --- a/resources/variants/ultimaker_s5_bb04.inst.cfg +++ b/resources/variants/ultimaker_s5_bb04.inst.cfg @@ -22,7 +22,6 @@ jerk_support_bottom = =math.ceil(jerk_support_interface * 1 / 10) machine_nozzle_heat_up_speed = 1.5 machine_nozzle_id = BB 0.4 machine_nozzle_tip_outer_diameter = 1.0 -prime_tower_min_volume = 20 raft_base_speed = 20 raft_interface_speed = 20 raft_speed = 25 From 4491d5164a91b6ba5d5b4fe9b6fa7ba3daedb81b Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 6 Aug 2018 09:24:48 +0200 Subject: [PATCH 21/97] Fix the gcode start/end formatter. Fixes #4181. --- plugins/CuraEngineBackend/StartSliceJob.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index eb392de121..76ff1cfc85 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -41,7 +41,7 @@ class StartJobResult(IntEnum): ## Formatter class that handles token expansion in start/end gcode class GcodeStartEndFormatter(Formatter): - def get_value(self, key: str, *args: str, default_extruder_nr: str = "-1", **kwargs) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class] + def get_value(self, key: str, args: str, kwargs: Dict[int, Dict[str, Any]], default_extruder_nr: str = "-1") -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class] # The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key), # and a default_extruder_nr to use when no extruder_nr is specified From bfaaa4a47103da633ce57a34862eb8fd61282944 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 6 Aug 2018 09:32:12 +0200 Subject: [PATCH 22/97] Fix code-style --- plugins/CuraEngineBackend/StartSliceJob.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 76ff1cfc85..0ebcafdbb2 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -41,7 +41,7 @@ class StartJobResult(IntEnum): ## Formatter class that handles token expansion in start/end gcode class GcodeStartEndFormatter(Formatter): - def get_value(self, key: str, args: str, kwargs: Dict[int, Dict[str, Any]], default_extruder_nr: str = "-1") -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class] + def get_value(self, key: str, args: str, kwargs: dict, default_extruder_nr: str = "-1") -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class] # The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key), # and a default_extruder_nr to use when no extruder_nr is specified From 5eb9ae9177cf8f114efd6eb7b8773922a85dcbc1 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Mon, 6 Aug 2018 14:32:59 +0200 Subject: [PATCH 23/97] removed unnecessary variable --- cura/Machines/MaterialManager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 3c9a7bfd59..7c5c901b32 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -186,7 +186,6 @@ class MaterialManager(QObject): for root_material_id in data_dict.values(): self._diameter_material_map[root_material_id] = default_root_material_id - variant_manager = self._application.getVariantManager() # Map #4 # "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer From 95481b856061f18aac6f4a0c58b90366cb90dd40 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 6 Aug 2018 17:11:28 +0200 Subject: [PATCH 24/97] Fix print order for one-at-a-time mode --- cura/OneAtATimeIterator.py | 183 +++++++++++++++--------------- cura/Scene/ConvexHullDecorator.py | 2 +- cura/Settings/GlobalStack.py | 3 + 3 files changed, 93 insertions(+), 95 deletions(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index 84d65bae8e..cb063bfde5 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -1,112 +1,107 @@ -# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import sys + from UM.Scene.Iterator import Iterator from UM.Scene.SceneNode import SceneNode -from functools import cmp_to_key -from UM.Application import Application -## Iterator that returns a list of nodes in the order that they need to be printed -# If there is no solution an empty list is returned. -# Take note that the list of nodes can have children (that may or may not contain mesh data) + +# Iterator that determines the object print order when one-at a time mode is enabled. +# +# In one-at-a-time mode, only one extruder can be enabled to print. In order to maximize the number of objects we can +# print, we need to print from the corner that's closest to the extruder that's being used. Here is an illustration: +# +# +--------------------------------+ +# | | +# | | +# | | - Rectangle represents the complete print head including fans, etc. +# | X X | y - X's are the nozzles +# | (1) (2) | | +# | | | +# +--------------------------------+ +--> x +# +# In this case, the nozzles are symmetric, nozzle (1) is closer to the bottom left corner while (2) is closer to the +# bottom right. If we use nozzle (1) to print, then we better off printing from the bottom left corner so the print +# head will not collide into an object on its top-right side, which is a very large unused area. Following the same +# logic, if we are printing with nozzle (2), then it's better to print from the bottom-right side. +# +# This iterator determines the print order following the rules above. +# class OneAtATimeIterator(Iterator.Iterator): + def __init__(self, scene_node): - super().__init__(scene_node) # Call super to make multiple inheritence work. - self._hit_map = [[]] + from cura.CuraApplication import CuraApplication + self._global_stack = CuraApplication.getInstance().getGlobalContainerStack() self._original_node_list = [] - + super().__init__(scene_node) # Call super to make multiple inheritance work. + + def getMachineNearestCornerToExtruder(self, global_stack): + head_and_fans_coordinates = global_stack.getHeadAndFansCoordinates() + + used_extruder = None + for extruder in global_stack.extruders.values(): + if extruder.isEnabled: + used_extruder = extruder + break + + extruder_offsets = [used_extruder.getProperty("machine_nozzle_offset_x", "value"), + used_extruder.getProperty("machine_nozzle_offset_y", "value")] + + # find the corner that's closest to the origin + min_distance2 = sys.maxsize + min_coord = None + for coord in head_and_fans_coordinates: + x = coord[0] - extruder_offsets[0] + y = coord[1] - extruder_offsets[1] + + distance2 = x**2 + y**2 + if distance2 <= min_distance2: + min_distance2 = distance2 + min_coord = coord + + return min_coord + def _fillStack(self): + min_coord = self.getMachineNearestCornerToExtruder(self._global_stack) + transform_x = -int(round(min_coord[0] / abs(min_coord[0]))) + transform_y = -int(round(min_coord[1] / abs(min_coord[1]))) + + machine_size = [self._global_stack.getProperty("machine_width", "value"), + self._global_stack.getProperty("machine_depth", "value")] + + def flip_x(polygon): + tm2 = [-1, 0, 0, 1, 0, 0] + return affinity.affine_transform(affinity.translate(polygon, xoff = -machine_size[0]), tm2) + def flip_y(polygon): + tm2 = [1, 0, 0, -1, 0, 0] + return affinity.affine_transform(affinity.translate(polygon, yoff = -machine_size[1]), tm2) + + from shapely import affinity + from shapely.geometry import Polygon + node_list = [] for node in self._scene_node.getChildren(): if not issubclass(type(node), SceneNode): continue - if node.callDecoration("getConvexHull"): - node_list.append(node) + convex_hull = node.callDecoration("getConvexHull") + if convex_hull: + xmin = min(x for x, _ in convex_hull._points) + xmax = max(x for x, _ in convex_hull._points) + ymin = min(y for _, y in convex_hull._points) + ymax = max(y for _, y in convex_hull._points) + convex_hull_polygon = Polygon.from_bounds(xmin, ymin, xmax, ymax) + if transform_x < 0: + convex_hull_polygon = flip_x(convex_hull_polygon) + if transform_y < 0: + convex_hull_polygon = flip_y(convex_hull_polygon) - if len(node_list) < 2: - self._node_stack = node_list[:] - return + node_list.append({"node": node, + "min_coord": [convex_hull_polygon.bounds[0], convex_hull_polygon.bounds[1]], + }) - # Copy the list - self._original_node_list = node_list[:] - - ## Initialise the hit map (pre-compute all hits between all objects) - self._hit_map = [[self._checkHit(i,j) for i in node_list] for j in node_list] - - # Check if we have to files that block eachother. If this is the case, there is no solution! - for a in range(0,len(node_list)): - for b in range(0,len(node_list)): - if a != b and self._hit_map[a][b] and self._hit_map[b][a]: - return - - # Sort the original list so that items that block the most other objects are at the beginning. - # This does not decrease the worst case running time, but should improve it in most cases. - sorted(node_list, key = cmp_to_key(self._calculateScore)) - - todo_node_list = [_ObjectOrder([], node_list)] - while len(todo_node_list) > 0: - current = todo_node_list.pop() - for node in current.todo: - # Check if the object can be placed with what we have and still allows for a solution in the future - if not self._checkHitMultiple(node, current.order) and not self._checkBlockMultiple(node, current.todo): - # We found a possible result. Create new todo & order list. - new_todo_list = current.todo[:] - new_todo_list.remove(node) - new_order = current.order[:] + [node] - if len(new_todo_list) == 0: - # We have no more nodes to check, so quit looking. - todo_node_list = None - self._node_stack = new_order - - return - todo_node_list.append(_ObjectOrder(new_order, new_todo_list)) - self._node_stack = [] #No result found! - - - # Check if first object can be printed before the provided list (using the hit map) - def _checkHitMultiple(self, node, other_nodes): - node_index = self._original_node_list.index(node) - for other_node in other_nodes: - other_node_index = self._original_node_list.index(other_node) - if self._hit_map[node_index][other_node_index]: - return True - return False - - def _checkBlockMultiple(self, node, other_nodes): - node_index = self._original_node_list.index(node) - for other_node in other_nodes: - other_node_index = self._original_node_list.index(other_node) - if self._hit_map[other_node_index][node_index] and node_index != other_node_index: - return True - return False - - ## Calculate score simply sums the number of other objects it 'blocks' - def _calculateScore(self, a, b): - score_a = sum(self._hit_map[self._original_node_list.index(a)]) - score_b = sum(self._hit_map[self._original_node_list.index(b)]) - return score_a - score_b - - # Checks if A can be printed before B - def _checkHit(self, a, b): - if a == b: - return False - - overlap = a.callDecoration("getConvexHullBoundary").intersectsPolygon(b.callDecoration("getConvexHullHeadFull")) - if overlap: - return True - else: - return False - - -## Internal object used to keep track of a possible order in which to print objects. -class _ObjectOrder(): - def __init__(self, order, todo): - """ - :param order: List of indexes in which to print objects, ordered by printing order. - :param todo: List of indexes which are not yet inserted into the order list. - """ - self.order = order - self.todo = todo + node_list = sorted(node_list, key = lambda d: d["min_coord"]) + self._node_stack = [d["node"] for d in node_list] diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py index 66bc8a7fc3..367144abfc 100644 --- a/cura/Scene/ConvexHullDecorator.py +++ b/cura/Scene/ConvexHullDecorator.py @@ -229,7 +229,7 @@ class ConvexHullDecorator(SceneNodeDecorator): return offset_hull def _getHeadAndFans(self): - return Polygon(numpy.array(self._global_stack.getProperty("machine_head_with_fans_polygon", "value"), numpy.float32)) + return Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32)) def _compute2DConvexHeadFull(self): return self._compute2DConvexHull().getMinkowskiHull(self._getHeadAndFans()) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 66f3290b85..ea955d24b0 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -172,6 +172,9 @@ class GlobalStack(CuraContainerStack): return False return True + def getHeadAndFansCoordinates(self): + return self.getProperty("machine_head_with_fans_polygon", "value") + ## private: global_stack_mime = MimeType( From aac6fe486066d7244d53c3337f64a03103e2ebc6 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 7 Aug 2018 09:35:50 +0200 Subject: [PATCH 25/97] Fix some code style --- cura/OneAtATimeIterator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index cb063bfde5..990ed37ab7 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -3,6 +3,9 @@ import sys +from shapely import affinity +from shapely.geometry import Polygon + from UM.Scene.Iterator import Iterator from UM.Scene.SceneNode import SceneNode @@ -73,13 +76,11 @@ class OneAtATimeIterator(Iterator.Iterator): def flip_x(polygon): tm2 = [-1, 0, 0, 1, 0, 0] return affinity.affine_transform(affinity.translate(polygon, xoff = -machine_size[0]), tm2) + def flip_y(polygon): tm2 = [1, 0, 0, -1, 0, 0] return affinity.affine_transform(affinity.translate(polygon, yoff = -machine_size[1]), tm2) - from shapely import affinity - from shapely.geometry import Polygon - node_list = [] for node in self._scene_node.getChildren(): if not issubclass(type(node), SceneNode): From 59bb7744368626df2b5333f7b139d8c56c47b0d9 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 7 Aug 2018 10:51:56 +0200 Subject: [PATCH 26/97] Remove unused variables --- plugins/USBPrinting/AutoDetectBaudJob.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/USBPrinting/AutoDetectBaudJob.py b/plugins/USBPrinting/AutoDetectBaudJob.py index 30ab65bc55..f8af61c567 100644 --- a/plugins/USBPrinting/AutoDetectBaudJob.py +++ b/plugins/USBPrinting/AutoDetectBaudJob.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from UM.Job import Job @@ -21,7 +21,6 @@ class AutoDetectBaudJob(Job): def run(self): Logger.log("d", "Auto detect baud rate started.") - timeout = 3 wait_response_timeouts = [3, 15, 30] wait_bootloader_times = [1.5, 5, 15] write_timeout = 3 @@ -52,7 +51,7 @@ class AutoDetectBaudJob(Job): if serial is None: try: serial = Serial(str(self._serial_port), baud_rate, timeout = read_timeout, writeTimeout = write_timeout) - except SerialException as e: + except SerialException: Logger.logException("w", "Unable to create serial") continue else: From 58e5a8123625eaf14279efb3e4c9ac9032cea44d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 7 Aug 2018 13:10:03 +0200 Subject: [PATCH 27/97] Fix units for initial layer print/travel acceleration These two were wrong. Contributes to issue CURA-5495. --- resources/definitions/fdmprinter.def.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index daf09d2701..2ea87a890d 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -3025,7 +3025,7 @@ { "label": "Initial Layer Print Acceleration", "description": "The acceleration during the printing of the initial layer.", - "unit": "mm/s", + "unit": "mm/s²", "type": "float", "default_value": 3000, "value": "acceleration_layer_0", @@ -3039,7 +3039,7 @@ { "label": "Initial Layer Travel Acceleration", "description": "The acceleration for travel moves in the initial layer.", - "unit": "mm/s", + "unit": "mm/s²", "type": "float", "default_value": 3000, "value": "acceleration_layer_0 * acceleration_travel / acceleration_print", From 910ffe0ad53dd71e83d46b74cfad932061a92cd5 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 7 Aug 2018 16:15:03 +0200 Subject: [PATCH 28/97] Fix quality reset upon material deletion CURA-5621 --- cura/Settings/MachineManager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index ff585deb54..f945cbb30c 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1455,9 +1455,13 @@ class MachineManager(QObject): if quality_group.node_for_global is None: Logger.log("e", "Could not set quality group [%s] because it has no node_for_global", str(quality_group)) return + # This is not changing the quality for the active machine !!!!!!!! global_stack.quality = quality_group.node_for_global.getContainer() for extruder_nr, extruder_stack in global_stack.extruders.items(): - extruder_stack.quality = quality_group.nodes_for_extruders[extruder_nr].getContainer() + quality_container = self._empty_quality_container + if extruder_nr in quality_group.nodes_for_extruders: + quality_container = quality_group.nodes_for_extruders[extruder_nr].getContainer() + extruder_stack.quality = quality_container return self.blurSettings.emit() From 55cf1fa137bac81d3f83564e9e656456ac4dfa32 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 7 Aug 2018 16:51:31 +0200 Subject: [PATCH 29/97] Turn on CuraEngine debug mode when front-end is in debug mode You can test this by running Cura's front-end with parameter --debug. --- plugins/CuraEngineBackend/CuraEngineBackend.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 56763a6632..9a5c95b04d 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -1,6 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import argparse #To run the engine in debug mode if the front-end is in debug mode. from collections import defaultdict import os from PyQt5.QtCore import QObject, QTimer, pyqtSlot @@ -179,7 +180,15 @@ class CuraEngineBackend(QObject, Backend): # \return list of commands and args / parameters. def getEngineCommand(self) -> List[str]: json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json") - return [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""] + command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""] + + parser = argparse.ArgumentParser(prog = "cura", add_help = False) + parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.") + known_args = vars(parser.parse_known_args()[0]) + if known_args["debug"]: + command.append("-vvv") + + return command ## Emitted when we get a message containing print duration and material amount. # This also implies the slicing has finished. From 387fb3ce1f7af5f6e8b37c966291e4710cb2d143 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 8 Aug 2018 11:31:21 +0200 Subject: [PATCH 30/97] Fix missing attribute CURA-5622 self._plugins_loaded should be there in the beginning or Cura will crash when it gets used. --- cura/CuraApplication.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 9f4ea7b3d3..6164fc8756 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -226,6 +226,8 @@ class CuraApplication(QtApplication): self._need_to_show_user_agreement = True + self._plugins_loaded = False + # Backups self._auto_save = None self._save_data_enabled = True From 9239e82b1f16e112aaa9da042515f995c6532e93 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 8 Aug 2018 12:56:46 +0200 Subject: [PATCH 31/97] Make mypy happy CURA-5578 --- cura/Settings/MachineManager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index f945cbb30c..a14977df03 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1460,7 +1460,8 @@ class MachineManager(QObject): for extruder_nr, extruder_stack in global_stack.extruders.items(): quality_container = self._empty_quality_container if extruder_nr in quality_group.nodes_for_extruders: - quality_container = quality_group.nodes_for_extruders[extruder_nr].getContainer() + container = quality_group.nodes_for_extruders[extruder_nr].getContainer() + quality_container = container if container is not None else quality_container extruder_stack.quality = quality_container return From d7e8cc9224dfe16028ce46cad2b5e676dbf4173b Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Wed, 8 Aug 2018 17:03:57 +0200 Subject: [PATCH 32/97] Fix rendering support eraser cubes MeshBuilder.addCube creates cubes that don't have per-vertex normals. Geometry like that does not render properly in most shaders used in Cura. This becomes obvious when a user changes the "Mesh Type" using Per Model Settings. --- plugins/SupportEraser/SupportEraser.py | 30 ++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/plugins/SupportEraser/SupportEraser.py b/plugins/SupportEraser/SupportEraser.py index 44d4831c04..9f7e3269bc 100644 --- a/plugins/SupportEraser/SupportEraser.py +++ b/plugins/SupportEraser/SupportEraser.py @@ -25,6 +25,8 @@ from cura.Scene.BuildPlateDecorator import BuildPlateDecorator from UM.Settings.SettingInstance import SettingInstance +import numpy + class SupportEraser(Tool): def __init__(self): super().__init__() @@ -96,8 +98,7 @@ class SupportEraser(Tool): node.setName("Eraser") node.setSelectable(True) - mesh = MeshBuilder() - mesh.addCube(10,10,10) + mesh = self._createCube(10) node.setMeshData(mesh.build()) active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate @@ -160,3 +161,28 @@ class SupportEraser(Tool): self._skip_press = False self._had_selection = has_selection + + def _createCube(self, size): + mesh = MeshBuilder() + + # Can't use MeshBuilder.addCube() because that does not get per-vertex normals + # Per-vertex normals require duplication of vertices + s = size / 2 + verts = [ # 6 faces with 4 corners each + [-s, -s, s], [-s, s, s], [ s, s, s], [ s, -s, s], + [-s, s, -s], [-s, -s, -s], [ s, -s, -s], [ s, s, -s], + [ s, -s, -s], [-s, -s, -s], [-s, -s, s], [ s, -s, s], + [-s, s, -s], [ s, s, -s], [ s, s, s], [-s, s, s], + [-s, -s, s], [-s, -s, -s], [-s, s, -s], [-s, s, s], + [ s, -s, -s], [ s, -s, s], [ s, s, s], [ s, s, -s] + ] + mesh.setVertices(numpy.asarray(verts, dtype=numpy.float32)) + + indices = [] + for i in range(0, 24, 4): # All 6 quads (12 triangles) + indices.append([i, i+2, i+1]) + indices.append([i, i+3, i+2]) + mesh.setIndices(numpy.asarray(indices, dtype=numpy.int32)) + + mesh.calculateNormals() + return mesh From ea770a26a37e8673b24067e814344f01b29bf488 Mon Sep 17 00:00:00 2001 From: ValentinPitre <35694201+ValentinPitre@users.noreply.github.com> Date: Wed, 8 Aug 2018 23:23:32 +0200 Subject: [PATCH 33/97] Add mesh offset --- resources/definitions/tizyx_k25.def.json | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/definitions/tizyx_k25.def.json b/resources/definitions/tizyx_k25.def.json index 55256a29d6..94a20b371e 100644 --- a/resources/definitions/tizyx_k25.def.json +++ b/resources/definitions/tizyx_k25.def.json @@ -9,6 +9,7 @@ "manufacturer": "TiZYX", "file_formats": "text/x-gcode", "platform": "tizyx_k25_platform.stl", + "platform_offset": [0, -4, 0], "exclude_materials": ["chromatik_pla", "dsm_arnitel2045_175", "dsm_novamid1070_175", "fabtotum_abs", "fabtotum_nylon", "fabtotum_pla", "fabtotum_tpu", "fiberlogy_hd_pla", "filo3d_pla", "filo3d_pla_green", "filo3d_pla_red", "generic_abs", "generic_abs_175", "generic_bam", "generic_cpe", "generic_cpe_175", "generic_cpe_plus", "generic_hips", "generic_hips_175", "generic_nylon", "generic_nylon_175", "generic_pc", "generic_pc_175", "generic_petg", "generic_petg_175", "generic_pla", "generic_pla_175", "generic_pp", "generic_pva", "generic_pva_175", "generic_tough_pla", "generic_tpu", "imade3d_petg_green", "imade3d_petg_pink", "imade3d_pla_green", "imade3d_pla_pink", "innofill_innoflex60_175", "octofiber_pla", "polyflex_pla", "polymax_pla", "polyplus_pla", "polywood_pla", "ultimaker_abs_black", "ultimaker_abs_blue", "ultimaker_abs_green", "ultimaker_abs_grey", "ultimaker_abs_orange", "ultimaker_abs_pearl-gold", "ultimaker_abs_red", "ultimaker_abs_silver-metallic", "ultimaker_abs_white", "ultimaker_abs_yellow", "ultimaker_bam", "ultimaker_cpe_black", "ultimaker_cpe_blue", "ultimaker_cpe_dark-grey", "ultimaker_cpe_green", "ultimaker_cpe_light-grey", "ultimaker_cpe_plus_black", "ultimaker_cpe_plus_transparent", "ultimaker_cpe_plus_white", "ultimaker_cpe_red", "ultimaker_cpe_transparent", "ultimaker_cpe_white", "ultimaker_cpe_yellow", "ultimaker_nylon_black", "ultimaker_nylon_transparent", "ultimaker_pc_black", "ultimaker_pc_transparent", "ultimaker_pc_white", "ultimaker_pla_black", "ultimaker_pla_blue", "ultimaker_pla_green", "ultimaker_pla_magenta", "ultimaker_pla_orange", "ultimaker_pla_pearl-white", "ultimaker_pla_red", "ultimaker_pla_silver-metallic", "ultimaker_pla_transparent", "ultimaker_pla_white", "ultimaker_pla_yellow", "ultimaker_pp_transparent", "ultimaker_pva", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white", "ultimaker_tpu_black", "ultimaker_tpu_blue", "ultimaker_tpu_red", "ultimaker_tpu_white", "verbatim_bvoh_175", "Vertex_Delta_ABS", "Vertex_Delta_PET", "Vertex_Delta_PLA", "Vertex_Delta_TPU", "zyyx_pro_flex", "zyyx_pro_pla" ], "preferred_material": "tizyx_pla", "has_machine_quality": true, From 41a892796d29d6240ce135c7f5a838e14dd398c2 Mon Sep 17 00:00:00 2001 From: Andreea Scorojitu Date: Thu, 9 Aug 2018 09:14:25 +0200 Subject: [PATCH 34/97] CURA-5626 Remove UM3 from connection message "This printer is not set up to host a group of Ultimaker 3 printers.", Ultimker 3 was removed from the message. --- plugins/UM3NetworkPrinting/DiscoverUM3Action.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml index e4d75e8f6d..127b3c35bd 100644 --- a/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml +++ b/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml @@ -299,11 +299,11 @@ Cura.MachineAction } else if (base.selectedDevice.clusterSize === 0) { - return catalog.i18nc("@label", "This printer is not set up to host a group of Ultimaker 3 printers."); + return catalog.i18nc("@label", "This printer is not set up to host a group of printers."); } else { - return catalog.i18nc("@label", "This printer is the host for a group of %1 Ultimaker 3 printers.".arg(base.selectedDevice.clusterSize)); + return catalog.i18nc("@label", "This printer is the host for a group of %1 printers.".arg(base.selectedDevice.clusterSize)); } } From 5c860d95928204dbeccc35fed7edfcaf456abbc9 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 9 Aug 2018 11:11:41 +0200 Subject: [PATCH 35/97] Fixed typo --- cura/PrinterOutput/PrinterOutputModel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 6fafa368bb..f10d6bd75b 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -120,7 +120,7 @@ class PrinterOutputModel(QObject): @pyqtProperty(QVariant, notify = headPositionChanged) def headPosition(self): - return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position_z} + return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position.z} def updateHeadPosition(self, x, y, z): if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z: From 1180fad62f41a3af08f412dc6dc220d60d6a8f36 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 9 Aug 2018 11:33:04 +0200 Subject: [PATCH 36/97] Fix binding loop --- resources/qml/PrintMonitor.qml | 1 - resources/qml/PrinterOutput/OutputDeviceHeader.qml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml index b24bcb6d6c..7727f9cb52 100644 --- a/resources/qml/PrintMonitor.qml +++ b/resources/qml/PrintMonitor.qml @@ -26,7 +26,6 @@ Column OutputDeviceHeader { - width: parent.width outputDevice: connectedDevice } diff --git a/resources/qml/PrinterOutput/OutputDeviceHeader.qml b/resources/qml/PrinterOutput/OutputDeviceHeader.qml index d5ce32786c..03e6d78699 100644 --- a/resources/qml/PrinterOutput/OutputDeviceHeader.qml +++ b/resources/qml/PrinterOutput/OutputDeviceHeader.qml @@ -16,7 +16,7 @@ Item Rectangle { - anchors.fill: parent + height: childrenRect.height color: UM.Theme.getColor("setting_category") property var activePrinter: outputDevice != null ? outputDevice.activePrinter : null From aab0cded0ead3720adc034c57f99d94ee8066702 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 9 Aug 2018 12:02:28 +0200 Subject: [PATCH 37/97] Adjust maximum zoom to printer size Previously the maximum distance you could zoom out was 2000. Now it's variable depending on how large your printer is. I've put it to 5 so that it works out to be approximately the same maximum zoom level for normal sized printers (like 25 cube build volume approximately). This should make it possible to zoom out completely for large sized printers. --- cura/BuildVolume.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 87f0eb543e..667247eac9 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -3,6 +3,7 @@ from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Settings.ExtruderManager import ExtruderManager +from UM.Application import Application #To modify the maximum zoom level. from UM.i18n import i18nCatalog from UM.Scene.Platform import Platform from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator @@ -552,6 +553,11 @@ class BuildVolume(SceneNode): if self._engine_ready: self.rebuild() + diagonal_size = math.sqrt(self._width * self._width + self._height * self._height + self._depth * self._depth) + camera = Application.getInstance().getController().getCameraTool() + if camera: + camera.setZoomRange(min = 1, max = diagonal_size * 5) #You can zoom out up to 5 times the diagonal across your screen (to see models bigger than your volume). + def _onEngineCreated(self): self._engine_ready = True self.rebuild() From d31df8ec679281280421a97458e089047e74c8a4 Mon Sep 17 00:00:00 2001 From: Jaime van Kessel Date: Thu, 9 Aug 2018 12:47:49 +0200 Subject: [PATCH 38/97] Fixed that long printernames overlap other GUI elements --- plugins/UM3NetworkPrinting/ClusterControlItem.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/UM3NetworkPrinting/ClusterControlItem.qml b/plugins/UM3NetworkPrinting/ClusterControlItem.qml index b42515de51..5cf550955c 100644 --- a/plugins/UM3NetworkPrinting/ClusterControlItem.qml +++ b/plugins/UM3NetworkPrinting/ClusterControlItem.qml @@ -30,7 +30,12 @@ Component anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: UM.Theme.getSize("default_margin").width + anchors.right:parent.right + anchors.rightMargin: UM.Theme.getSize("default_margin").width text: Cura.MachineManager.printerOutputDevices[0].name + elide: Text.ElideRight } Rectangle From 4912073aedc795c2aa51cc1e858190be0cd328a1 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Thu, 9 Aug 2018 15:14:55 +0200 Subject: [PATCH 39/97] Add API abstraction layer Contributes to CURA-5595 --- cura/API/SidebarContextMenu.py | 34 ++++++++++++++++++++++++++++++++++ cura/API/__init__.py | 4 ++++ 2 files changed, 38 insertions(+) create mode 100644 cura/API/SidebarContextMenu.py diff --git a/cura/API/SidebarContextMenu.py b/cura/API/SidebarContextMenu.py new file mode 100644 index 0000000000..9c7ad4c4b6 --- /dev/null +++ b/cura/API/SidebarContextMenu.py @@ -0,0 +1,34 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from cura.CuraApplication import CuraApplication + +## The back-ups API provides a version-proof bridge between Cura's +# Sidebar Context Menu and plug-ins that hook into it. +# +# Usage: +# ``from cura.API import CuraAPI +# api = CuraAPI() +# api.sidebar_context_menu.getSidebarMenuItems() +# menu_actions = [] +# menu_actions.append("sidebarMenuItemOnClickHander") +# data = { +# "name": "My Plugin Action", +# "iconName": "my-plugin-icon", +# "actions": menu_actions, +# "menu_item": MyPluginAction(self) +# } +# api.sidebar_context_menu.addSidebarMenuItems([])`` +class SidebarContextMenu: + + _application = CuraApplication.getInstance() # type: CuraApplication + + ## Add items to the sidebar context menu. + # \param menu_item dict containing the menu item to add. + def addSidebarMenuItem(self, menu_items: dict) -> None: + self._application.addSidebarCustomMenuItem(menu_items) + + ## Get all custom items currently added to the sidebar context menu. + # \return List containing all custom context menu items. + def getSidebarMenuItems(self) -> list: + return self._application.getSidebarCustomMenuItems() \ No newline at end of file diff --git a/cura/API/__init__.py b/cura/API/__init__.py index 13f6722336..40c07b8371 100644 --- a/cura/API/__init__.py +++ b/cura/API/__init__.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from UM.PluginRegistry import PluginRegistry from cura.API.Backups import Backups +from cura.API.SidebarContextMenu import SidebarContextMenu ## The official Cura API that plug-ins can use to interact with Cura. # @@ -16,3 +17,6 @@ class CuraAPI: # Backups API. backups = Backups() + + # Sidebar Context Menu API + sidebar_context_menu = SidebarContextMenu() From 3217085ebedbdfb0cd763db9c37bca4df2816b07 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Thu, 9 Aug 2018 15:20:53 +0200 Subject: [PATCH 40/97] Fix plural Contributes to CURA-5595 --- cura/API/SidebarContextMenu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/API/SidebarContextMenu.py b/cura/API/SidebarContextMenu.py index 9c7ad4c4b6..fdd2d3ed88 100644 --- a/cura/API/SidebarContextMenu.py +++ b/cura/API/SidebarContextMenu.py @@ -25,8 +25,8 @@ class SidebarContextMenu: ## Add items to the sidebar context menu. # \param menu_item dict containing the menu item to add. - def addSidebarMenuItem(self, menu_items: dict) -> None: - self._application.addSidebarCustomMenuItem(menu_items) + def addSidebarMenuItem(self, menu_item: dict) -> None: + self._application.addSidebarCustomMenuItem(menu_item) ## Get all custom items currently added to the sidebar context menu. # \return List containing all custom context menu items. From 88c4240e3b06a64d6c021cb5f9756a66d3e71e2b Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Thu, 9 Aug 2018 15:21:38 +0200 Subject: [PATCH 41/97] Remove back-ups traces Contributes to CURA-5595 --- cura/API/SidebarContextMenu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/API/SidebarContextMenu.py b/cura/API/SidebarContextMenu.py index fdd2d3ed88..f47b3a062a 100644 --- a/cura/API/SidebarContextMenu.py +++ b/cura/API/SidebarContextMenu.py @@ -3,7 +3,7 @@ from cura.CuraApplication import CuraApplication -## The back-ups API provides a version-proof bridge between Cura's +## The sidebar context menu API provides a version-proof bridge between Cura's # Sidebar Context Menu and plug-ins that hook into it. # # Usage: From 57f04d8940876b6a4b46a4569dc501272c2c90b3 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Thu, 9 Aug 2018 15:39:10 +0200 Subject: [PATCH 42/97] Load 3mf projects CURA-5570 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 389a7da704..e33b88375e 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -928,12 +928,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader): root_material_id = extruder_info.root_material_id root_material_id = self._old_new_materials.get(root_material_id, root_material_id) + build_plate_id = global_stack.variant.getId() + # get material diameter of this extruder machine_material_diameter = extruder_stack.materialDiameter material_node = material_manager.getMaterialNode(global_stack.definition.getId(), extruder_stack.variant.getName(), + build_plate_id, machine_material_diameter, root_material_id) + if material_node is not None and material_node.getContainer() is not None: extruder_stack.material = material_node.getContainer() From 033a00ec20bc952b981af97384700240617db7e7 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Thu, 9 Aug 2018 16:06:09 +0200 Subject: [PATCH 43/97] fixed addNodesToCheck function, wrong max index value CURA-5570 --- cura/Machines/QualityManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index c0954c005c..5496ca9f87 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -271,7 +271,7 @@ class QualityManager(QObject): # the search list in the order described above. So, by iterating over that search node list, we first look # in the more specific branches and then the less specific (generic) ones. def addNodesToCheck(node, nodes_to_check_list, node_info_list, node_info_idx): - if current_node_info_idx < len(node_info_list): + if node_info_idx < len(node_info_list): node_name = node_info_list[node_info_idx] if node_name is not None: current_node = node.getChildNode(node_name) From e4bdd8096a3e99ca304938c8ad26a819add31cd2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 10 Aug 2018 11:02:16 +0200 Subject: [PATCH 44/97] Remap shortcut This is a bit more logical than 'G', I'd say. E is now available since other shortcuts were remapped as well. Contributes to issue CURA-5634 and fixes #3969. --- plugins/SupportEraser/SupportEraser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SupportEraser/SupportEraser.py b/plugins/SupportEraser/SupportEraser.py index 44d4831c04..97850c3f3d 100644 --- a/plugins/SupportEraser/SupportEraser.py +++ b/plugins/SupportEraser/SupportEraser.py @@ -28,7 +28,7 @@ from UM.Settings.SettingInstance import SettingInstance class SupportEraser(Tool): def __init__(self): super().__init__() - self._shortcut_key = Qt.Key_G + self._shortcut_key = Qt.Key_E self._controller = self.getController() self._selection_pass = None From 4a22bb3b695e531a9ddf6f89f3a3d53109d13580 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 10 Aug 2018 11:06:25 +0200 Subject: [PATCH 45/97] Code style --- resources/qml/Toolbar.qml | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/resources/qml/Toolbar.qml b/resources/qml/Toolbar.qml index 613a462db9..6483804c38 100644 --- a/resources/qml/Toolbar.qml +++ b/resources/qml/Toolbar.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2015 Ultimaker B.V. +// Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.2 @@ -21,8 +21,8 @@ Item { id: buttons; - anchors.bottom: parent.bottom; - anchors.left: parent.left; + anchors.bottom: parent.bottom + anchors.left: parent.left spacing: UM.Theme.getSize("button_lining").width Repeater @@ -41,13 +41,15 @@ Item enabled: model.enabled && UM.Selection.hasSelection && UM.Controller.toolsEnabled style: UM.Theme.styles.tool_button - onCheckedChanged: { - if (checked) { - base.activeY = y + onCheckedChanged: + { + if (checked) + { + base.activeY = y; } } - //Workaround since using ToolButton"s onClicked would break the binding of the checked property, instead + //Workaround since using ToolButton's onClicked would break the binding of the checked property, instead //just catch the click so we do not trigger that behaviour. MouseArea { @@ -57,7 +59,7 @@ Item forceActiveFocus() //First grab focus, so all the text fields are updated if(parent.checked) { - UM.Controller.setActiveTool(null) + UM.Controller.setActiveTool(null); } else { @@ -96,11 +98,13 @@ Item width: { - if (panel.item && panel.width > 0){ - return Math.max(panel.width + 2 * UM.Theme.getSize("default_margin").width) + if (panel.item && panel.width > 0) + { + return Math.max(panel.width + 2 * UM.Theme.getSize("default_margin").width); } - else { - return 0 + else + { + return 0; } } height: panel.item ? panel.height + 2 * UM.Theme.getSize("default_margin").height : 0; @@ -124,7 +128,7 @@ Item x: UM.Theme.getSize("default_margin").width; y: UM.Theme.getSize("default_margin").height; - source: UM.ActiveTool.valid ? UM.ActiveTool.activeToolPanel : ""; + source: UM.ActiveTool.valid ? UM.ActiveTool.activeToolPanel : "" enabled: UM.Controller.toolsEnabled; } } @@ -148,6 +152,6 @@ Item anchors.horizontalCenter: parent.horizontalCenter } - visible: toolHint.text != ""; + visible: toolHint.text != "" } } From f247c328c695ba27d9273c53b06cf7f8a73cdc61 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 10 Aug 2018 11:22:33 +0200 Subject: [PATCH 46/97] Show shortcut key in tool tooltips In brackets. I couldn't translate it to an icon easily. It was possible but I'd have to create a giant mapping from every key to the key icons and add a bunch of icons and I don't think that's necessary. Contributes to issue CURA-5634. --- resources/qml/Toolbar.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/Toolbar.qml b/resources/qml/Toolbar.qml index 6483804c38..a04b3650df 100644 --- a/resources/qml/Toolbar.qml +++ b/resources/qml/Toolbar.qml @@ -34,7 +34,7 @@ Item height: childrenRect.height Button { - text: model.name + text: model.name + (model.shortcut ? (" (" + model.shortcut + ")") : "") iconSource: (UM.Theme.getIcon(model.icon) != "") ? UM.Theme.getIcon(model.icon) : "file:///" + model.location + "/" + model.icon checkable: true checked: model.active From 89ed2bcff80a7a414f77c455baa0023c631eae7c Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 10 Aug 2018 11:45:25 +0200 Subject: [PATCH 47/97] Revert "Merge pull request #4203 from Ultimaker/CURA-5538-fix-one-at-a-time-order-2" This reverts commit 82e1a7c5fc43a12d1498779d392286c6e49ee4ea, reversing changes made to 1915659393b72f7e4d4dbd9b73e92b8a665efcdc. --- cura/OneAtATimeIterator.py | 184 +++++++++++++++--------------- cura/Scene/ConvexHullDecorator.py | 2 +- cura/Settings/GlobalStack.py | 3 - 3 files changed, 95 insertions(+), 94 deletions(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index 990ed37ab7..84d65bae8e 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -1,108 +1,112 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import sys - -from shapely import affinity -from shapely.geometry import Polygon - from UM.Scene.Iterator import Iterator from UM.Scene.SceneNode import SceneNode +from functools import cmp_to_key +from UM.Application import Application - -# Iterator that determines the object print order when one-at a time mode is enabled. -# -# In one-at-a-time mode, only one extruder can be enabled to print. In order to maximize the number of objects we can -# print, we need to print from the corner that's closest to the extruder that's being used. Here is an illustration: -# -# +--------------------------------+ -# | | -# | | -# | | - Rectangle represents the complete print head including fans, etc. -# | X X | y - X's are the nozzles -# | (1) (2) | | -# | | | -# +--------------------------------+ +--> x -# -# In this case, the nozzles are symmetric, nozzle (1) is closer to the bottom left corner while (2) is closer to the -# bottom right. If we use nozzle (1) to print, then we better off printing from the bottom left corner so the print -# head will not collide into an object on its top-right side, which is a very large unused area. Following the same -# logic, if we are printing with nozzle (2), then it's better to print from the bottom-right side. -# -# This iterator determines the print order following the rules above. -# +## Iterator that returns a list of nodes in the order that they need to be printed +# If there is no solution an empty list is returned. +# Take note that the list of nodes can have children (that may or may not contain mesh data) class OneAtATimeIterator(Iterator.Iterator): - def __init__(self, scene_node): - from cura.CuraApplication import CuraApplication - self._global_stack = CuraApplication.getInstance().getGlobalContainerStack() + super().__init__(scene_node) # Call super to make multiple inheritence work. + self._hit_map = [[]] self._original_node_list = [] - super().__init__(scene_node) # Call super to make multiple inheritance work. - - def getMachineNearestCornerToExtruder(self, global_stack): - head_and_fans_coordinates = global_stack.getHeadAndFansCoordinates() - - used_extruder = None - for extruder in global_stack.extruders.values(): - if extruder.isEnabled: - used_extruder = extruder - break - - extruder_offsets = [used_extruder.getProperty("machine_nozzle_offset_x", "value"), - used_extruder.getProperty("machine_nozzle_offset_y", "value")] - - # find the corner that's closest to the origin - min_distance2 = sys.maxsize - min_coord = None - for coord in head_and_fans_coordinates: - x = coord[0] - extruder_offsets[0] - y = coord[1] - extruder_offsets[1] - - distance2 = x**2 + y**2 - if distance2 <= min_distance2: - min_distance2 = distance2 - min_coord = coord - - return min_coord - + def _fillStack(self): - min_coord = self.getMachineNearestCornerToExtruder(self._global_stack) - transform_x = -int(round(min_coord[0] / abs(min_coord[0]))) - transform_y = -int(round(min_coord[1] / abs(min_coord[1]))) - - machine_size = [self._global_stack.getProperty("machine_width", "value"), - self._global_stack.getProperty("machine_depth", "value")] - - def flip_x(polygon): - tm2 = [-1, 0, 0, 1, 0, 0] - return affinity.affine_transform(affinity.translate(polygon, xoff = -machine_size[0]), tm2) - - def flip_y(polygon): - tm2 = [1, 0, 0, -1, 0, 0] - return affinity.affine_transform(affinity.translate(polygon, yoff = -machine_size[1]), tm2) - node_list = [] for node in self._scene_node.getChildren(): if not issubclass(type(node), SceneNode): continue - convex_hull = node.callDecoration("getConvexHull") - if convex_hull: - xmin = min(x for x, _ in convex_hull._points) - xmax = max(x for x, _ in convex_hull._points) - ymin = min(y for _, y in convex_hull._points) - ymax = max(y for _, y in convex_hull._points) + if node.callDecoration("getConvexHull"): + node_list.append(node) - convex_hull_polygon = Polygon.from_bounds(xmin, ymin, xmax, ymax) - if transform_x < 0: - convex_hull_polygon = flip_x(convex_hull_polygon) - if transform_y < 0: - convex_hull_polygon = flip_y(convex_hull_polygon) - node_list.append({"node": node, - "min_coord": [convex_hull_polygon.bounds[0], convex_hull_polygon.bounds[1]], - }) + if len(node_list) < 2: + self._node_stack = node_list[:] + return - node_list = sorted(node_list, key = lambda d: d["min_coord"]) + # Copy the list + self._original_node_list = node_list[:] + + ## Initialise the hit map (pre-compute all hits between all objects) + self._hit_map = [[self._checkHit(i,j) for i in node_list] for j in node_list] + + # Check if we have to files that block eachother. If this is the case, there is no solution! + for a in range(0,len(node_list)): + for b in range(0,len(node_list)): + if a != b and self._hit_map[a][b] and self._hit_map[b][a]: + return + + # Sort the original list so that items that block the most other objects are at the beginning. + # This does not decrease the worst case running time, but should improve it in most cases. + sorted(node_list, key = cmp_to_key(self._calculateScore)) + + todo_node_list = [_ObjectOrder([], node_list)] + while len(todo_node_list) > 0: + current = todo_node_list.pop() + for node in current.todo: + # Check if the object can be placed with what we have and still allows for a solution in the future + if not self._checkHitMultiple(node, current.order) and not self._checkBlockMultiple(node, current.todo): + # We found a possible result. Create new todo & order list. + new_todo_list = current.todo[:] + new_todo_list.remove(node) + new_order = current.order[:] + [node] + if len(new_todo_list) == 0: + # We have no more nodes to check, so quit looking. + todo_node_list = None + self._node_stack = new_order + + return + todo_node_list.append(_ObjectOrder(new_order, new_todo_list)) + self._node_stack = [] #No result found! + + + # Check if first object can be printed before the provided list (using the hit map) + def _checkHitMultiple(self, node, other_nodes): + node_index = self._original_node_list.index(node) + for other_node in other_nodes: + other_node_index = self._original_node_list.index(other_node) + if self._hit_map[node_index][other_node_index]: + return True + return False + + def _checkBlockMultiple(self, node, other_nodes): + node_index = self._original_node_list.index(node) + for other_node in other_nodes: + other_node_index = self._original_node_list.index(other_node) + if self._hit_map[other_node_index][node_index] and node_index != other_node_index: + return True + return False + + ## Calculate score simply sums the number of other objects it 'blocks' + def _calculateScore(self, a, b): + score_a = sum(self._hit_map[self._original_node_list.index(a)]) + score_b = sum(self._hit_map[self._original_node_list.index(b)]) + return score_a - score_b + + # Checks if A can be printed before B + def _checkHit(self, a, b): + if a == b: + return False + + overlap = a.callDecoration("getConvexHullBoundary").intersectsPolygon(b.callDecoration("getConvexHullHeadFull")) + if overlap: + return True + else: + return False + + +## Internal object used to keep track of a possible order in which to print objects. +class _ObjectOrder(): + def __init__(self, order, todo): + """ + :param order: List of indexes in which to print objects, ordered by printing order. + :param todo: List of indexes which are not yet inserted into the order list. + """ + self.order = order + self.todo = todo - self._node_stack = [d["node"] for d in node_list] diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py index 367144abfc..66bc8a7fc3 100644 --- a/cura/Scene/ConvexHullDecorator.py +++ b/cura/Scene/ConvexHullDecorator.py @@ -229,7 +229,7 @@ class ConvexHullDecorator(SceneNodeDecorator): return offset_hull def _getHeadAndFans(self): - return Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32)) + return Polygon(numpy.array(self._global_stack.getProperty("machine_head_with_fans_polygon", "value"), numpy.float32)) def _compute2DConvexHeadFull(self): return self._compute2DConvexHull().getMinkowskiHull(self._getHeadAndFans()) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index ea955d24b0..66f3290b85 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -172,9 +172,6 @@ class GlobalStack(CuraContainerStack): return False return True - def getHeadAndFansCoordinates(self): - return self.getProperty("machine_head_with_fans_polygon", "value") - ## private: global_stack_mime = MimeType( From 6924d28ee7eb92c47e5d1df63874a7da5b2b0be4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 10 Aug 2018 11:52:35 +0200 Subject: [PATCH 48/97] Disambiguate translation of 'Settings' from 'Preferences' The 'settings' entry contains the set-up of the printer so Konfiguration is a better word there. Discovered during investigation of #3971. --- resources/i18n/de_DE/cura.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/i18n/de_DE/cura.po b/resources/i18n/de_DE/cura.po index 37b8840468..31ce980615 100644 --- a/resources/i18n/de_DE/cura.po +++ b/resources/i18n/de_DE/cura.po @@ -4036,7 +4036,7 @@ msgstr "&Ansicht" #: /home/ruben/Projects/Cura/resources/qml/Cura.qml:184 msgctxt "@title:menu" msgid "&Settings" -msgstr "&Einstellungen" +msgstr "&Konfiguration" #: /home/ruben/Projects/Cura/resources/qml/Cura.qml:186 msgctxt "@title:menu menubar:toplevel" From f91c696e4bfaa1ace21dabfdb91d4651599768ea Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 10 Aug 2018 13:17:20 +0200 Subject: [PATCH 49/97] Clarify error message a bit more The QA engineer was a bit confused by this one. Let's make the language a bit easier. --- plugins/UM3NetworkPrinting/SendMaterialJob.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/UM3NetworkPrinting/SendMaterialJob.py b/plugins/UM3NetworkPrinting/SendMaterialJob.py index 0ac38843a1..8491e79c29 100644 --- a/plugins/UM3NetworkPrinting/SendMaterialJob.py +++ b/plugins/UM3NetworkPrinting/SendMaterialJob.py @@ -39,12 +39,12 @@ class SendMaterialJob(Job): try: remote_materials_list = json.loads(remote_materials_list) except json.JSONDecodeError: - Logger.log("e", "Current material storage on printer was a corrupted reply.") + Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.") return try: remote_materials_by_guid = {material["guid"]: material for material in remote_materials_list} #Index by GUID. except KeyError: - Logger.log("e", "Current material storage on printer was an invalid reply (missing GUIDs).") + Logger.log("e", "Request material storage on printer: Printer's answer was missing GUIDs.") return container_registry = ContainerRegistry.getInstance() From 6d237b09e842b9930d377f3b039a869d0de1acd7 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 10 Aug 2018 16:08:54 +0200 Subject: [PATCH 50/97] Proposed improvement on API naming --- cura/API/{SidebarContextMenu.py => Sidebar.py} | 12 ++++++------ cura/API/__init__.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) rename cura/API/{SidebarContextMenu.py => Sidebar.py} (72%) diff --git a/cura/API/SidebarContextMenu.py b/cura/API/Sidebar.py similarity index 72% rename from cura/API/SidebarContextMenu.py rename to cura/API/Sidebar.py index f47b3a062a..dd001739f5 100644 --- a/cura/API/SidebarContextMenu.py +++ b/cura/API/Sidebar.py @@ -9,26 +9,26 @@ from cura.CuraApplication import CuraApplication # Usage: # ``from cura.API import CuraAPI # api = CuraAPI() -# api.sidebar_context_menu.getSidebarMenuItems() +# api.sidebar.getContextMenuItems() # menu_actions = [] -# menu_actions.append("sidebarMenuItemOnClickHander") +# menu_actions.append("sidebarMenuItemOnClickHandler") # data = { # "name": "My Plugin Action", # "iconName": "my-plugin-icon", # "actions": menu_actions, # "menu_item": MyPluginAction(self) # } -# api.sidebar_context_menu.addSidebarMenuItems([])`` -class SidebarContextMenu: +# api.sidebar.addContextMenuItem(data)`` +class Sidebar: _application = CuraApplication.getInstance() # type: CuraApplication ## Add items to the sidebar context menu. # \param menu_item dict containing the menu item to add. - def addSidebarMenuItem(self, menu_item: dict) -> None: + def addContextMenuItem(self, menu_item: dict) -> None: self._application.addSidebarCustomMenuItem(menu_item) ## Get all custom items currently added to the sidebar context menu. # \return List containing all custom context menu items. - def getSidebarMenuItems(self) -> list: + def getContextMenuItems(self) -> list: return self._application.getSidebarCustomMenuItems() \ No newline at end of file diff --git a/cura/API/__init__.py b/cura/API/__init__.py index 40c07b8371..4cd0c9436a 100644 --- a/cura/API/__init__.py +++ b/cura/API/__init__.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from UM.PluginRegistry import PluginRegistry from cura.API.Backups import Backups -from cura.API.SidebarContextMenu import SidebarContextMenu +from cura.API.Sidebar import Sidebar ## The official Cura API that plug-ins can use to interact with Cura. # @@ -19,4 +19,4 @@ class CuraAPI: backups = Backups() # Sidebar Context Menu API - sidebar_context_menu = SidebarContextMenu() + sidebar = Sidebar() From 16db29761327adbd9d8463debf3b7bc69d3a0c04 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 10 Aug 2018 16:28:02 +0200 Subject: [PATCH 51/97] Some final tweaks Contributes to CURA-5595 --- cura/API/Backups.py | 1 + .../API/{Sidebar.py => Interface/Settings.py} | 21 ++++++++-------- cura/API/Interface/__init__.py | 24 +++++++++++++++++++ cura/API/__init__.py | 9 +++---- 4 files changed, 40 insertions(+), 15 deletions(-) rename cura/API/{Sidebar.py => Interface/Settings.py} (56%) create mode 100644 cura/API/Interface/__init__.py diff --git a/cura/API/Backups.py b/cura/API/Backups.py index 5964557264..f31933c844 100644 --- a/cura/API/Backups.py +++ b/cura/API/Backups.py @@ -13,6 +13,7 @@ from cura.Backups.BackupsManager import BackupsManager # api = CuraAPI() # api.backups.createBackup() # api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})`` + class Backups: manager = BackupsManager() # Re-used instance of the backups manager. diff --git a/cura/API/Sidebar.py b/cura/API/Interface/Settings.py similarity index 56% rename from cura/API/Sidebar.py rename to cura/API/Interface/Settings.py index dd001739f5..2889db7022 100644 --- a/cura/API/Sidebar.py +++ b/cura/API/Interface/Settings.py @@ -3,32 +3,31 @@ from cura.CuraApplication import CuraApplication -## The sidebar context menu API provides a version-proof bridge between Cura's -# Sidebar Context Menu and plug-ins that hook into it. +## The Interface.Settings API provides a version-proof bridge between Cura's +# (currently) sidebar UI and plug-ins that hook into it. # # Usage: # ``from cura.API import CuraAPI # api = CuraAPI() -# api.sidebar.getContextMenuItems() -# menu_actions = [] -# menu_actions.append("sidebarMenuItemOnClickHandler") +# api.interface.settings.getContextMenuItems() # data = { # "name": "My Plugin Action", # "iconName": "my-plugin-icon", -# "actions": menu_actions, +# "actions": my_menu_actions, # "menu_item": MyPluginAction(self) # } -# api.sidebar.addContextMenuItem(data)`` -class Sidebar: +# api.interface.settings.addContextMenuItem(data)`` - _application = CuraApplication.getInstance() # type: CuraApplication +class Settings: + # Re-used instance of Cura: + application = CuraApplication.getInstance() # type: CuraApplication ## Add items to the sidebar context menu. # \param menu_item dict containing the menu item to add. def addContextMenuItem(self, menu_item: dict) -> None: - self._application.addSidebarCustomMenuItem(menu_item) + self.application.addSidebarCustomMenuItem(menu_item) ## Get all custom items currently added to the sidebar context menu. # \return List containing all custom context menu items. def getContextMenuItems(self) -> list: - return self._application.getSidebarCustomMenuItems() \ No newline at end of file + return self.application.getSidebarCustomMenuItems() \ No newline at end of file diff --git a/cura/API/Interface/__init__.py b/cura/API/Interface/__init__.py new file mode 100644 index 0000000000..b38118949b --- /dev/null +++ b/cura/API/Interface/__init__.py @@ -0,0 +1,24 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from UM.PluginRegistry import PluginRegistry +from cura.API.Interface.Settings import Settings + +## The Interface class serves as a common root for the specific API +# methods for each interface element. +# +# Usage: +# ``from cura.API import CuraAPI +# api = CuraAPI() +# api.interface.settings.addContextMenuItem() +# api.interface.viewport.addOverlay() # Not implemented, just a hypothetical +# api.interface.toolbar.getToolButtonCount() # Not implemented, just a hypothetical +# # etc.`` + +class Interface: + + # For now we use the same API version to be consistent. + VERSION = PluginRegistry.APIVersion + + # API methods specific to the settings portion of the UI + settings = Settings() diff --git a/cura/API/__init__.py b/cura/API/__init__.py index 4cd0c9436a..64d636903d 100644 --- a/cura/API/__init__.py +++ b/cura/API/__init__.py @@ -2,7 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from UM.PluginRegistry import PluginRegistry from cura.API.Backups import Backups -from cura.API.Sidebar import Sidebar +from cura.API.Interface import Interface ## The official Cura API that plug-ins can use to interact with Cura. # @@ -10,13 +10,14 @@ from cura.API.Sidebar import Sidebar # this API provides a version-safe interface with proper deprecation warnings # etc. Usage of any other methods than the ones provided in this API can cause # plug-ins to be unstable. + class CuraAPI: # For now we use the same API version to be consistent. VERSION = PluginRegistry.APIVersion - # Backups API. + # Backups API backups = Backups() - # Sidebar Context Menu API - sidebar = Sidebar() + # Interface API + interface = Interface() From 825a3cd08fc5b796cadf6bea10442f73492d545d Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 13 Aug 2018 09:55:57 +0200 Subject: [PATCH 52/97] CURA-5570 Fix the configuration sync feature. It crashed Cura because it was using an outdated signature of the method getMaterialNodeByType. --- cura/Machines/MaterialManager.py | 1 - cura/Machines/QualityManager.py | 1 - cura/Settings/MachineManager.py | 7 ++++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 7c5c901b32..d5a7d5d089 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -186,7 +186,6 @@ class MaterialManager(QObject): for root_material_id in data_dict.values(): self._diameter_material_map[root_material_id] = default_root_material_id - # Map #4 # "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer self._diameter_machine_nozzle_buildplate_material_map = dict() diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 5496ca9f87..8b4a396164 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -264,7 +264,6 @@ class QualityManager(QObject): # the list with priorities as the order. Later, we just need to loop over each node in this list and fetch # qualities from there. node_info_list_0 = [nozzle_name, buildplate_name, root_material_id] - current_node_info_idx = 0 nodes_to_check = [] # This function tries to recursively find the deepest (the most specific) branch and add those nodes to diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 733340e0ce..2f81f78c95 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1331,7 +1331,12 @@ class MachineManager(QObject): for extruder_configuration in configuration.extruderConfigurations: position = str(extruder_configuration.position) variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(), extruder_configuration.hotendID) - material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack, position, extruder_configuration.hotendID, extruder_configuration.material.guid) + material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack, + position, + extruder_configuration.hotendID, + configuration.buildplateConfiguration, + extruder_configuration.material.guid) + if variant_container_node: self._setVariantNode(position, variant_container_node) else: From 787948f4015b03ab760b5378df11f16899f6c207 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Mon, 13 Aug 2018 10:42:22 +0200 Subject: [PATCH 53/97] Removed "Preparing" button state CURA-5551 --- resources/qml/SaveButton.qml | 50 ++++++++---------------------------- 1 file changed, 11 insertions(+), 39 deletions(-) diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index 9b5e3befb0..0e0eec7277 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -48,15 +48,14 @@ Item { } function sliceOrStopSlicing() { - if ([1, 5].indexOf(base.backendState) != -1) - { - prepareButton.preparingToSlice = true; - CuraApplication.backend.forceSlice(); - } - else - { - prepareButton.preparingToSlice = false; - CuraApplication.backend.stopSlicing(); + try { + if ([1, 5].indexOf(base.backendState) != -1) { + CuraApplication.backend.forceSlice(); + } else { + CuraApplication.backend.stopSlicing(); + } + } catch (e) { + console.log('Could not start or stop slicing', e) } } @@ -168,10 +167,10 @@ Item { // Prepare button, only shows if auto_slice is off Button { id: prepareButton - property bool preparingToSlice: false + tooltip: [1, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@info:tooltip","Slice current printjob") : catalog.i18nc("@info:tooltip","Cancel slicing process") // 1 = not started, 2 = Processing - enabled: !preparingToSlice && base.backendState != "undefined" && ([1, 2].indexOf(base.backendState) != -1) && base.activity + enabled: base.backendState != "undefined" && ([1, 2].indexOf(base.backendState) != -1) && base.activity visible: base.backendState != "undefined" && !autoSlice && ([1, 2, 4].indexOf(base.backendState) != -1) && base.activity property bool autoSlice height: UM.Theme.getSize("save_button_save_to_button").height @@ -181,23 +180,7 @@ Item { anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width // 1 = not started, 4 = error, 5 = disabled - text: { - if (preparingToSlice) - { - return catalog.i18nc("@label:Printjob", "Preparing"); - } - else - { - if ([1, 4, 5].indexOf(base.backendState) != -1) - { - return catalog.i18nc("@label:Printjob", "Prepare"); - } - else - { - return catalog.i18nc("@label:Printjob", "Cancel") - } - } - } + text: [1, 4, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@label:Printjob", "Prepare") : catalog.i18nc("@label:Printjob", "Cancel") onClicked: { sliceOrStopSlicing(); @@ -254,17 +237,6 @@ Item { } label: Item { } } - - Connections { - target: UM.Backend - onStateChanged: - { - if ([2, 3].indexOf(UM.Backend.state) != -1) - { - prepareButton.preparingToSlice = false; - } - } - } } Button { From 3fad11e07fa6acc9b2354721ef6e2c82ed6b0098 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 13 Aug 2018 10:45:03 +0200 Subject: [PATCH 54/97] Code style fixes Contributes to CURA-5595 --- cura/CuraApplication.py | 3 +-- cura/Settings/SidebarCustomMenuItemsModel.py | 28 ++++++++++---------- resources/qml/Settings/SettingView.qml | 2 +- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 2680c9a89c..85aa5a8322 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1736,8 +1736,7 @@ class CuraApplication(QtApplication): def showMoreInformationDialogForAnonymousDataCollection(self): cast(SliceInfo, self._plugin_registry.getPluginObject("SliceInfoPlugin")).showMoreInfoDialog() - - def addSidebarCustomMenuItem(self, menu_item: list) -> None: + def addSidebarCustomMenuItem(self, menu_item: dict) -> None: self._sidebar_custom_menu_items.append(menu_item) def getSidebarCustomMenuItems(self) -> list: diff --git a/cura/Settings/SidebarCustomMenuItemsModel.py b/cura/Settings/SidebarCustomMenuItemsModel.py index d2a4d1a2b1..ec926363f5 100644 --- a/cura/Settings/SidebarCustomMenuItemsModel.py +++ b/cura/Settings/SidebarCustomMenuItemsModel.py @@ -8,17 +8,17 @@ from PyQt5.QtCore import pyqtSlot, Qt class SidebarCustomMenuItemsModel(ListModel): - NameRole = Qt.UserRole + 1 - ActionsRole = Qt.UserRole + 2 - MenuItemRole = Qt.UserRole + 3 - MenuItemIconNameRole = Qt.UserRole + 5 + name_role = Qt.UserRole + 1 + actions_role = Qt.UserRole + 2 + menu_item_role = Qt.UserRole + 3 + menu_item_icon_name_role = Qt.UserRole + 5 def __init__(self, parent=None): super().__init__(parent) - self.addRoleName(self.NameRole, "name") - self.addRoleName(self.ActionsRole, "actions") - self.addRoleName(self.MenuItemRole, "menu_item") - self.addRoleName(self.MenuItemIconNameRole, "iconName") + self.addRoleName(self.name_role, "name") + self.addRoleName(self.actions_role, "actions") + self.addRoleName(self.menu_item_role, "menu_item") + self.addRoleName(self.menu_item_icon_name_role, "iconName") self._updateExtensionList() def _updateExtensionList(self)-> None: @@ -26,14 +26,14 @@ class SidebarCustomMenuItemsModel(ListModel): for menu_item in CuraApplication.getInstance().getSidebarCustomMenuItems(): self.appendItem({ - "name": menu_item["name"], - "iconName": menu_item["iconName"], - "actions": menu_item["actions"], - "menu_item": menu_item["menu_item"] - }) + "name": menu_item["name"], + "icon_name": menu_item["icon_name"], + "actions": menu_item["actions"], + "menu_item": menu_item["menu_item"] + }) @pyqtSlot(str, "QVariantList", "QVariantMap") - def callMenuItemMethod(self, menu_item_name: str, menu_item_actions: list, kwargs: Any)-> None: + def callMenuItemMethod(self, menu_item_name: str, menu_item_actions: list, kwargs: Any) -> None: for item in self._items: if menu_item_name == item["name"]: for method in menu_item_actions: diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 88876123fe..e17f11bf99 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -568,7 +568,7 @@ Item MenuItem { text: model.name - iconName: model.iconName + iconName: model.icon_name onTriggered: { customMenuItems.model.callMenuItemMethod(name, model.actions, {"key": contextMenu.key}) From 98a0bb50025ba71739834a82ebebf48340ea39df Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 13 Aug 2018 11:15:32 +0200 Subject: [PATCH 55/97] CURA-5570 Remove a check that caused empty list of profiles. --- cura/Machines/QualityManager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura/Machines/QualityManager.py b/cura/Machines/QualityManager.py index 8b4a396164..df3bec0827 100644 --- a/cura/Machines/QualityManager.py +++ b/cura/Machines/QualityManager.py @@ -238,6 +238,7 @@ class QualityManager(QObject): # The root material IDs in this list are in prioritized order. root_material_id_list = [] has_material = False # flag indicating whether this extruder has a material assigned + root_material_id = None if extruder.material.getId() != "empty_material": has_material = True root_material_id = extruder.material.getMetaDataEntry("base_file") @@ -248,7 +249,7 @@ class QualityManager(QObject): # Also try to get the fallback material material_type = extruder.material.getMetaDataEntry("material") fallback_root_material_id = self._material_manager.getFallbackMaterialIdByMaterialType(material_type) - if fallback_root_material_id and root_material_id not in root_material_id_list: + if fallback_root_material_id: root_material_id_list.append(fallback_root_material_id) # Here we construct a list of nodes we want to look for qualities with the highest priority first. From bd47dfef754c6d979d563c0817de3e70ec767a4f Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 13 Aug 2018 11:23:10 +0200 Subject: [PATCH 56/97] Revert "Revert "Merge pull request #4203 from Ultimaker/CURA-5538-fix-one-at-a-time-order-2"" This reverts commit 89ed2bcff80a7a414f77c455baa0023c631eae7c. --- cura/OneAtATimeIterator.py | 184 +++++++++++++++--------------- cura/Scene/ConvexHullDecorator.py | 2 +- cura/Settings/GlobalStack.py | 3 + 3 files changed, 94 insertions(+), 95 deletions(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index 84d65bae8e..990ed37ab7 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -1,112 +1,108 @@ -# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import sys + +from shapely import affinity +from shapely.geometry import Polygon + from UM.Scene.Iterator import Iterator from UM.Scene.SceneNode import SceneNode -from functools import cmp_to_key -from UM.Application import Application -## Iterator that returns a list of nodes in the order that they need to be printed -# If there is no solution an empty list is returned. -# Take note that the list of nodes can have children (that may or may not contain mesh data) + +# Iterator that determines the object print order when one-at a time mode is enabled. +# +# In one-at-a-time mode, only one extruder can be enabled to print. In order to maximize the number of objects we can +# print, we need to print from the corner that's closest to the extruder that's being used. Here is an illustration: +# +# +--------------------------------+ +# | | +# | | +# | | - Rectangle represents the complete print head including fans, etc. +# | X X | y - X's are the nozzles +# | (1) (2) | | +# | | | +# +--------------------------------+ +--> x +# +# In this case, the nozzles are symmetric, nozzle (1) is closer to the bottom left corner while (2) is closer to the +# bottom right. If we use nozzle (1) to print, then we better off printing from the bottom left corner so the print +# head will not collide into an object on its top-right side, which is a very large unused area. Following the same +# logic, if we are printing with nozzle (2), then it's better to print from the bottom-right side. +# +# This iterator determines the print order following the rules above. +# class OneAtATimeIterator(Iterator.Iterator): + def __init__(self, scene_node): - super().__init__(scene_node) # Call super to make multiple inheritence work. - self._hit_map = [[]] + from cura.CuraApplication import CuraApplication + self._global_stack = CuraApplication.getInstance().getGlobalContainerStack() self._original_node_list = [] - + super().__init__(scene_node) # Call super to make multiple inheritance work. + + def getMachineNearestCornerToExtruder(self, global_stack): + head_and_fans_coordinates = global_stack.getHeadAndFansCoordinates() + + used_extruder = None + for extruder in global_stack.extruders.values(): + if extruder.isEnabled: + used_extruder = extruder + break + + extruder_offsets = [used_extruder.getProperty("machine_nozzle_offset_x", "value"), + used_extruder.getProperty("machine_nozzle_offset_y", "value")] + + # find the corner that's closest to the origin + min_distance2 = sys.maxsize + min_coord = None + for coord in head_and_fans_coordinates: + x = coord[0] - extruder_offsets[0] + y = coord[1] - extruder_offsets[1] + + distance2 = x**2 + y**2 + if distance2 <= min_distance2: + min_distance2 = distance2 + min_coord = coord + + return min_coord + def _fillStack(self): + min_coord = self.getMachineNearestCornerToExtruder(self._global_stack) + transform_x = -int(round(min_coord[0] / abs(min_coord[0]))) + transform_y = -int(round(min_coord[1] / abs(min_coord[1]))) + + machine_size = [self._global_stack.getProperty("machine_width", "value"), + self._global_stack.getProperty("machine_depth", "value")] + + def flip_x(polygon): + tm2 = [-1, 0, 0, 1, 0, 0] + return affinity.affine_transform(affinity.translate(polygon, xoff = -machine_size[0]), tm2) + + def flip_y(polygon): + tm2 = [1, 0, 0, -1, 0, 0] + return affinity.affine_transform(affinity.translate(polygon, yoff = -machine_size[1]), tm2) + node_list = [] for node in self._scene_node.getChildren(): if not issubclass(type(node), SceneNode): continue - if node.callDecoration("getConvexHull"): - node_list.append(node) + convex_hull = node.callDecoration("getConvexHull") + if convex_hull: + xmin = min(x for x, _ in convex_hull._points) + xmax = max(x for x, _ in convex_hull._points) + ymin = min(y for _, y in convex_hull._points) + ymax = max(y for _, y in convex_hull._points) + convex_hull_polygon = Polygon.from_bounds(xmin, ymin, xmax, ymax) + if transform_x < 0: + convex_hull_polygon = flip_x(convex_hull_polygon) + if transform_y < 0: + convex_hull_polygon = flip_y(convex_hull_polygon) - if len(node_list) < 2: - self._node_stack = node_list[:] - return + node_list.append({"node": node, + "min_coord": [convex_hull_polygon.bounds[0], convex_hull_polygon.bounds[1]], + }) - # Copy the list - self._original_node_list = node_list[:] - - ## Initialise the hit map (pre-compute all hits between all objects) - self._hit_map = [[self._checkHit(i,j) for i in node_list] for j in node_list] - - # Check if we have to files that block eachother. If this is the case, there is no solution! - for a in range(0,len(node_list)): - for b in range(0,len(node_list)): - if a != b and self._hit_map[a][b] and self._hit_map[b][a]: - return - - # Sort the original list so that items that block the most other objects are at the beginning. - # This does not decrease the worst case running time, but should improve it in most cases. - sorted(node_list, key = cmp_to_key(self._calculateScore)) - - todo_node_list = [_ObjectOrder([], node_list)] - while len(todo_node_list) > 0: - current = todo_node_list.pop() - for node in current.todo: - # Check if the object can be placed with what we have and still allows for a solution in the future - if not self._checkHitMultiple(node, current.order) and not self._checkBlockMultiple(node, current.todo): - # We found a possible result. Create new todo & order list. - new_todo_list = current.todo[:] - new_todo_list.remove(node) - new_order = current.order[:] + [node] - if len(new_todo_list) == 0: - # We have no more nodes to check, so quit looking. - todo_node_list = None - self._node_stack = new_order - - return - todo_node_list.append(_ObjectOrder(new_order, new_todo_list)) - self._node_stack = [] #No result found! - - - # Check if first object can be printed before the provided list (using the hit map) - def _checkHitMultiple(self, node, other_nodes): - node_index = self._original_node_list.index(node) - for other_node in other_nodes: - other_node_index = self._original_node_list.index(other_node) - if self._hit_map[node_index][other_node_index]: - return True - return False - - def _checkBlockMultiple(self, node, other_nodes): - node_index = self._original_node_list.index(node) - for other_node in other_nodes: - other_node_index = self._original_node_list.index(other_node) - if self._hit_map[other_node_index][node_index] and node_index != other_node_index: - return True - return False - - ## Calculate score simply sums the number of other objects it 'blocks' - def _calculateScore(self, a, b): - score_a = sum(self._hit_map[self._original_node_list.index(a)]) - score_b = sum(self._hit_map[self._original_node_list.index(b)]) - return score_a - score_b - - # Checks if A can be printed before B - def _checkHit(self, a, b): - if a == b: - return False - - overlap = a.callDecoration("getConvexHullBoundary").intersectsPolygon(b.callDecoration("getConvexHullHeadFull")) - if overlap: - return True - else: - return False - - -## Internal object used to keep track of a possible order in which to print objects. -class _ObjectOrder(): - def __init__(self, order, todo): - """ - :param order: List of indexes in which to print objects, ordered by printing order. - :param todo: List of indexes which are not yet inserted into the order list. - """ - self.order = order - self.todo = todo + node_list = sorted(node_list, key = lambda d: d["min_coord"]) + self._node_stack = [d["node"] for d in node_list] diff --git a/cura/Scene/ConvexHullDecorator.py b/cura/Scene/ConvexHullDecorator.py index 66bc8a7fc3..367144abfc 100644 --- a/cura/Scene/ConvexHullDecorator.py +++ b/cura/Scene/ConvexHullDecorator.py @@ -229,7 +229,7 @@ class ConvexHullDecorator(SceneNodeDecorator): return offset_hull def _getHeadAndFans(self): - return Polygon(numpy.array(self._global_stack.getProperty("machine_head_with_fans_polygon", "value"), numpy.float32)) + return Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32)) def _compute2DConvexHeadFull(self): return self._compute2DConvexHull().getMinkowskiHull(self._getHeadAndFans()) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 66f3290b85..ea955d24b0 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -172,6 +172,9 @@ class GlobalStack(CuraContainerStack): return False return True + def getHeadAndFansCoordinates(self): + return self.getProperty("machine_head_with_fans_polygon", "value") + ## private: global_stack_mime = MimeType( From b1c073aab63ba5869b0cc9d6efad05ef09d15dcd Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 13 Aug 2018 11:50:50 +0200 Subject: [PATCH 57/97] CURA-5570 Expose the CuraSDKVersion to qml and show the buildplate selector only when "dev" version is selected. --- cura/CuraApplication.py | 4 +++- resources/qml/Cura.qml | 4 ++-- resources/qml/SidebarHeader.qml | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 6164fc8756..a2c4f6ae4c 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -117,11 +117,12 @@ if TYPE_CHECKING: numpy.seterr(all = "ignore") try: - from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode + from cura.CuraVersion import CuraVersion, CuraBuildType, CuraDebugMode, CuraSDKVersion except ImportError: CuraVersion = "master" # [CodeStyle: Reflecting imported value] CuraBuildType = "" CuraDebugMode = False + CuraSDKVerion = "" class CuraApplication(QtApplication): @@ -910,6 +911,7 @@ class CuraApplication(QtApplication): engine.rootContext().setContextProperty("CuraApplication", self) engine.rootContext().setContextProperty("PrintInformation", self._print_information) engine.rootContext().setContextProperty("CuraActions", self._cura_actions) + engine.rootContext().setContextProperty("CuraSDKVersion", CuraSDKVersion) qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index e1bf8c8b77..60f6e77ea9 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -231,8 +231,8 @@ UM.MainWindow onObjectRemoved: settingsMenu.removeItem(object) } - // TODO Temporary hidden, add back again when feature ready - BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: Cura.MachineManager.hasVariantBuildplates } + // TODO Only show in dev mode. Remove check when feature ready + BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: CuraSDKVersion == "dev" ? Cura.MachineManager.hasVariantBuildplates : false } ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); } MenuSeparator { } diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index 4e8911b3c1..4bc0e8b2ef 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -476,8 +476,8 @@ Column { id: buildplateRow height: UM.Theme.getSize("sidebar_setup").height - // TODO Temporary hidden, add back again when feature ready - visible: false //Cura.MachineManager.hasVariantBuildplates && !sidebar.hideSettings + // TODO Only show in dev mode. Remove check when feature ready + visible: CuraSDKVersion == "dev" ? Cura.MachineManager.hasVariantBuildplates && !sidebar.hideSettings : false anchors { From d45d6add4026f898e4c47fc3ecfbe069215d5b5f Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 13 Aug 2018 12:56:41 +0200 Subject: [PATCH 58/97] Add collision detection in on-at-a-time iterator --- cura/OneAtATimeIterator.py | 45 +++++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index 990ed37ab7..e7ad833e1c 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -6,7 +6,7 @@ import sys from shapely import affinity from shapely.geometry import Polygon -from UM.Scene.Iterator import Iterator +from UM.Scene.Iterator.Iterator import Iterator from UM.Scene.SceneNode import SceneNode @@ -20,7 +20,7 @@ from UM.Scene.SceneNode import SceneNode # | | # | | - Rectangle represents the complete print head including fans, etc. # | X X | y - X's are the nozzles -# | (1) (2) | | +# | (1) (2) | ^ # | | | # +--------------------------------+ +--> x # @@ -31,12 +31,13 @@ from UM.Scene.SceneNode import SceneNode # # This iterator determines the print order following the rules above. # -class OneAtATimeIterator(Iterator.Iterator): +class OneAtATimeIterator(Iterator): def __init__(self, scene_node): from cura.CuraApplication import CuraApplication self._global_stack = CuraApplication.getInstance().getGlobalContainerStack() self._original_node_list = [] + super().__init__(scene_node) # Call super to make multiple inheritance work. def getMachineNearestCornerToExtruder(self, global_stack): @@ -65,6 +66,40 @@ class OneAtATimeIterator(Iterator.Iterator): return min_coord + def _checkForCollisions(self) -> bool: + all_nodes = [] + for node in self._scene_node.getChildren(): + if not issubclass(type(node), SceneNode): + continue + convex_hull = node.callDecoration("getConvexHullHead") + if not convex_hull: + continue + + bounding_box = node.getBoundingBox() + from UM.Math.Polygon import Polygon + bounding_box_polygon = Polygon([[bounding_box.left, bounding_box.front], + [bounding_box.left, bounding_box.back], + [bounding_box.right, bounding_box.back], + [bounding_box.right, bounding_box.front]]) + + all_nodes.append({"node": node, + "bounding_box": bounding_box_polygon, + "convex_hull": convex_hull}) + + has_collisions = False + for i, node_dict in enumerate(all_nodes): + for j, other_node_dict in enumerate(all_nodes): + if i == j: + continue + if node_dict["bounding_box"].intersectsPolygon(other_node_dict["convex_hull"]): + has_collisions = True + break + + if has_collisions: + break + + return has_collisions + def _fillStack(self): min_coord = self.getMachineNearestCornerToExtruder(self._global_stack) transform_x = -int(round(min_coord[0] / abs(min_coord[0]))) @@ -81,6 +116,10 @@ class OneAtATimeIterator(Iterator.Iterator): tm2 = [1, 0, 0, -1, 0, 0] return affinity.affine_transform(affinity.translate(polygon, yoff = -machine_size[1]), tm2) + if self._checkForCollisions(): + self._node_stack = [] + return + node_list = [] for node in self._scene_node.getChildren(): if not issubclass(type(node), SceneNode): From 42b509cd4a5eb51d66ee6474817b6a78ed97ce2b Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 13 Aug 2018 13:44:07 +0200 Subject: [PATCH 59/97] Fix a wrong type hinting --- cura/Settings/MachineManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 7bf4b1394c..1941324eed 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -49,7 +49,7 @@ class MachineManager(QObject): def __init__(self, parent: QObject = None) -> None: super().__init__(parent) - self._active_container_stack = None # type: Optional[ExtruderManager] + self._active_container_stack = None # type: Optional[ExtruderStack] self._global_container_stack = None # type: Optional[GlobalStack] self._current_root_material_id = {} # type: Dict[str, str] From 25e6fd8becebbb2ddf180daa8a7eb6561bcb4dc0 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 13 Aug 2018 13:47:26 +0200 Subject: [PATCH 60/97] Fix incorrect type hinting --- cura/Settings/MachineManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 1941324eed..bdb96b373e 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1183,7 +1183,7 @@ class MachineManager(QObject): if not self._global_container_stack.variant: self._global_container_stack.variant = self._application.empty_variant_container - def _setMaterial(self, position: str, container_node: ContainerNode = None) -> None: + def _setMaterial(self, position: str, container_node: Optional["ContainerNode"] = None) -> None: if self._global_container_stack is None: return if container_node and container_node.getContainer(): From 8e891b69b006d079ed98b3bdcf5434ef4b507cb5 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 13 Aug 2018 13:48:00 +0200 Subject: [PATCH 61/97] Move type hinting imports into TYPE_CHECKING block --- cura/Settings/MachineManager.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index bdb96b373e..d65bbfddd9 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -21,9 +21,6 @@ from UM.Settings.SettingFunction import SettingFunction from UM.Signal import postponeSignals, CompressTechnique import cura.CuraApplication -from cura.Machines.ContainerNode import ContainerNode #For typing. -from cura.Machines.QualityChangesGroup import QualityChangesGroup #For typing. -from cura.Machines.QualityGroup import QualityGroup #For typing. from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutput.ConfigurationModel import ConfigurationModel @@ -44,6 +41,10 @@ if TYPE_CHECKING: from cura.Machines.MaterialManager import MaterialManager from cura.Machines.QualityManager import QualityManager from cura.Machines.VariantManager import VariantManager + from cura.Machines.ContainerNode import ContainerNode + from cura.Machines.QualityChangesGroup import QualityChangesGroup + from cura.Machines.QualityGroup import QualityGroup + class MachineManager(QObject): def __init__(self, parent: QObject = None) -> None: @@ -1087,7 +1088,7 @@ class MachineManager(QObject): self.activeQualityGroupChanged.emit() self.activeQualityChangesGroupChanged.emit() - def _setQualityGroup(self, quality_group: Optional[QualityGroup], empty_quality_changes: bool = True) -> None: + def _setQualityGroup(self, quality_group: Optional["QualityGroup"], empty_quality_changes: bool = True) -> None: if self._global_container_stack is None: return if quality_group is None: @@ -1118,7 +1119,7 @@ class MachineManager(QObject): self.activeQualityGroupChanged.emit() self.activeQualityChangesGroupChanged.emit() - def _fixQualityChangesGroupToNotSupported(self, quality_changes_group: QualityChangesGroup) -> None: + def _fixQualityChangesGroupToNotSupported(self, quality_changes_group: "QualityChangesGroup") -> None: nodes = [quality_changes_group.node_for_global] + list(quality_changes_group.nodes_for_extruders.values()) containers = [n.getContainer() for n in nodes if n is not None] for container in containers: @@ -1126,7 +1127,7 @@ class MachineManager(QObject): container.setMetaDataEntry("quality_type", "not_supported") quality_changes_group.quality_type = "not_supported" - def _setQualityChangesGroup(self, quality_changes_group: QualityChangesGroup) -> None: + def _setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None: if self._global_container_stack is None: return #Can't change that. quality_type = quality_changes_group.quality_type @@ -1170,13 +1171,13 @@ class MachineManager(QObject): self.activeQualityGroupChanged.emit() self.activeQualityChangesGroupChanged.emit() - def _setVariantNode(self, position: str, container_node: ContainerNode) -> None: + def _setVariantNode(self, position: str, container_node: "ContainerNode") -> None: if container_node.getContainer() is None or self._global_container_stack is None: return self._global_container_stack.extruders[position].variant = container_node.getContainer() self.activeVariantChanged.emit() - def _setGlobalVariant(self, container_node: ContainerNode) -> None: + def _setGlobalVariant(self, container_node: "ContainerNode") -> None: if self._global_container_stack is None: return self._global_container_stack.variant = container_node.getContainer() @@ -1388,7 +1389,7 @@ class MachineManager(QObject): return bool(containers) @pyqtSlot("QVariant") - def setGlobalVariant(self, container_node: ContainerNode) -> None: + def setGlobalVariant(self, container_node: "ContainerNode") -> None: self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setGlobalVariant(container_node) @@ -1438,7 +1439,7 @@ class MachineManager(QObject): self.setVariant(position, variant_node) @pyqtSlot(str, "QVariant") - def setVariant(self, position: str, container_node: ContainerNode) -> None: + def setVariant(self, position: str, container_node: "ContainerNode") -> None: position = str(position) self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): @@ -1462,7 +1463,7 @@ class MachineManager(QObject): ## Optionally provide global_stack if you want to use your own # The active global_stack is treated differently. @pyqtSlot(QObject) - def setQualityGroup(self, quality_group: QualityGroup, no_dialog: bool = False, global_stack: Optional["GlobalStack"] = None) -> None: + def setQualityGroup(self, quality_group: "QualityGroup", no_dialog: bool = False, global_stack: Optional["GlobalStack"] = None) -> None: if global_stack is not None and global_stack != self._global_container_stack: if quality_group is None: Logger.log("e", "Could not set quality group because quality group is None") @@ -1489,11 +1490,11 @@ class MachineManager(QObject): self._application.discardOrKeepProfileChanges() @pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged) - def activeQualityGroup(self) -> Optional[QualityGroup]: + def activeQualityGroup(self) -> Optional["QualityGroup"]: return self._current_quality_group @pyqtSlot(QObject) - def setQualityChangesGroup(self, quality_changes_group: QualityChangesGroup, no_dialog: bool = False) -> None: + def setQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", no_dialog: bool = False) -> None: self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setQualityChangesGroup(quality_changes_group) @@ -1512,7 +1513,7 @@ class MachineManager(QObject): stack.userChanges.clear() @pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged) - def activeQualityChangesGroup(self) -> Optional[QualityChangesGroup]: + def activeQualityChangesGroup(self) -> Optional["QualityChangesGroup"]: return self._current_quality_changes_group @pyqtProperty(str, notify = activeQualityGroupChanged) From e0ad7a9fb4bce64d3c5503877cc1b37086f8c2ab Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 13 Aug 2018 14:24:25 +0200 Subject: [PATCH 62/97] Add answer commments to some puzzling code --- cura/Settings/GlobalStack.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index 49c7b4f04a..dda21f3719 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -102,6 +102,9 @@ class GlobalStack(CuraContainerStack): # Handle the "resolve" property. #TODO: Why the hell does this involve threading? + # Answer: Because if multiple threads start resolving properties that have the same underlying properties that's + # related, without taking a note of which thread a resolve paths belongs to, they can bump into each other and + # generate unexpected behaviours. if self._shouldResolve(key, property_name, context): current_thread = threading.current_thread() self._resolving_settings[current_thread.name].add(key) From 6ffe0a9083029d6f78003bd81b2a5b78d5de33ca Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 13 Aug 2018 14:47:25 +0200 Subject: [PATCH 63/97] Fix indent and typo --- plugins/UM3NetworkPrinting/PrinterVideoStream.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/UM3NetworkPrinting/PrinterVideoStream.qml b/plugins/UM3NetworkPrinting/PrinterVideoStream.qml index e34924ba88..68758e095e 100644 --- a/plugins/UM3NetworkPrinting/PrinterVideoStream.qml +++ b/plugins/UM3NetworkPrinting/PrinterVideoStream.qml @@ -16,9 +16,9 @@ Item MouseArea { - anchors.fill: parent - onClicked: OutputDevice.setActivePrinter(null) - z: 0 + anchors.fill: parent + onClicked: OutputDevice.setActivePrinter(null) + z: 0 } Button @@ -28,7 +28,7 @@ Item anchors.bottomMargin: UM.Theme.getSize("default_margin").width anchors.right: cameraImage.right - // TODO: Harcoded sizes + // TODO: Hardcoded sizes width: 20 * screenScaleFactor height: 20 * screenScaleFactor From f91254c7c05faa7451a8dcd217b81eb0ccf1c028 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 13 Aug 2018 15:27:01 +0200 Subject: [PATCH 64/97] Fix typo in comment --- cura/CuraApplication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index b1a3dadc8e..78986d82ee 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -631,7 +631,7 @@ class CuraApplication(QtApplication): # Cura has multiple locations where instance containers need to be saved, so we need to handle this differently. def saveSettings(self): if not self.started or not self._save_data_enabled: - # Do not do saving during application start or when data should not be safed on quit. + # Do not do saving during application start or when data should not be saved on quit. return ContainerRegistry.getInstance().saveDirtyContainers() self.savePreferences() From 848e99a4ba46e11ed9acf498ab105ad1fce15b40 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 13 Aug 2018 17:04:13 +0200 Subject: [PATCH 65/97] Don't show the hint message when the buildplate selector is shown. Contributes to CURA-5570. --- resources/qml/SidebarHeader.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index 4bc0e8b2ef..6ee33dd2f2 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -533,6 +533,7 @@ Column rightMargin: UM.Theme.getSize("sidebar_margin").width } + // TODO This was added to replace the buildplate selector. Remove this component when the feature is ready Label { id: materialCompatibilityLabel @@ -542,7 +543,7 @@ Column text: catalog.i18nc("@label", "Use glue with this material combination") font: UM.Theme.getFont("very_small") color: UM.Theme.getColor("text") - visible: buildplateCompatibilityError || buildplateCompatibilityWarning + visible: CuraSDKVersion == "dev" ? false : buildplateCompatibilityError || buildplateCompatibilityWarning wrapMode: Text.WordWrap opacity: 0.5 } From db7f1242cd0cd028219f5ef8dfbdbc0ec039d8fb Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 14 Aug 2018 10:41:38 +0200 Subject: [PATCH 66/97] Fix some unit tests. Add fixtures to the conftest file and clean-up the code a bit. Contributes to CURA-5628 --- tests/Settings/TestCuraContainerRegistry.py | 62 +++----------------- tests/Settings/TestExtruderStack.py | 58 +++++-------------- tests/Settings/TestGlobalStack.py | 2 +- tests/Settings/conftest.py | 19 +++++++ tests/TestArrange.py | 6 +- tests/TestMachineAction.py | 63 ++++++++++----------- tests/TestProfileRequirements.py | 4 ++ tests/conftest.py | 38 +++++++++++++ 8 files changed, 118 insertions(+), 134 deletions(-) create mode 100644 tests/Settings/conftest.py create mode 100644 tests/conftest.py diff --git a/tests/Settings/TestCuraContainerRegistry.py b/tests/Settings/TestCuraContainerRegistry.py index 38b2e0f6ea..dd78f2fd68 100644 --- a/tests/Settings/TestCuraContainerRegistry.py +++ b/tests/Settings/TestCuraContainerRegistry.py @@ -1,61 +1,14 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import os #To find the directory with test files and find the test files. -import pytest #This module contains unit tests. -import shutil #To copy files to make a temporary file. import unittest.mock #To mock and monkeypatch stuff. -import urllib.parse -import copy -import cura.CuraApplication -from cura.Settings.CuraContainerRegistry import CuraContainerRegistry #The class we're testing. from cura.Settings.ExtruderStack import ExtruderStack #Testing for returning the correct types of stacks. from cura.Settings.GlobalStack import GlobalStack #Testing for returning the correct types of stacks. -from UM.Resources import Resources #Mocking some functions of this. import UM.Settings.InstanceContainer #Creating instance containers to register. import UM.Settings.ContainerRegistry #Making empty container stacks. import UM.Settings.ContainerStack #Setting the container registry here properly. -from UM.Settings.DefinitionContainer import DefinitionContainer -from UM.Settings.ContainerRegistry import ContainerRegistry - -def creteEmptyContainers(): - empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() - empty_variant_container = copy.deepcopy(empty_container) - empty_variant_container.setMetaDataEntry("id", "empty_variant") - empty_variant_container.setMetaDataEntry("type", "variant") - ContainerRegistry.getInstance().addContainer(empty_variant_container) - - empty_material_container = copy.deepcopy(empty_container) - empty_material_container.setMetaDataEntry("id", "empty_material") - empty_material_container.setMetaDataEntry("type", "material") - ContainerRegistry.getInstance().addContainer(empty_material_container) - - empty_quality_container = copy.deepcopy(empty_container) - empty_quality_container.setMetaDataEntry("id", "empty_quality") - empty_quality_container.setName("Not Supported") - empty_quality_container.setMetaDataEntry("quality_type", "not_supported") - empty_quality_container.setMetaDataEntry("type", "quality") - empty_quality_container.setMetaDataEntry("supported", False) - ContainerRegistry.getInstance().addContainer(empty_quality_container) - - empty_quality_changes_container = copy.deepcopy(empty_container) - empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") - empty_quality_changes_container.setMetaDataEntry("type", "quality_changes") - ContainerRegistry.getInstance().addContainer(empty_quality_changes_container) - -## Gives a fresh CuraContainerRegistry instance. -@pytest.fixture() -def container_registry(): - registry = CuraContainerRegistry() - UM.Settings.InstanceContainer.setContainerRegistry(registry) - UM.Settings.ContainerStack.setContainerRegistry(registry) - return registry - -## Gives an arbitrary definition container. -@pytest.fixture() -def definition_container(): - return DefinitionContainer(container_id = "Test Definition") def teardown(): #If the temporary file for the legacy file rename test still exists, remove it. @@ -65,10 +18,9 @@ def teardown(): ## Tests whether addContainer properly converts to ExtruderStack. def test_addContainerExtruderStack(container_registry, definition_container): - creteEmptyContainers() container_registry.addContainer(definition_container) - container_stack = UM.Settings.ContainerStack.ContainerStack(stack_id = "Test Container Stack") #A container we're going to convert. + container_stack = UM.Settings.ContainerStack.ContainerStack(stack_id = "Test Extruder Stack") #A container we're going to convert. container_stack.setMetaDataEntry("type", "extruder_train") #This is now an extruder train. container_stack.insertContainer(0, definition_container) #Add a definition to it so it doesn't complain. @@ -84,7 +36,7 @@ def test_addContainerExtruderStack(container_registry, definition_container): def test_addContainerGlobalStack(container_registry, definition_container): container_registry.addContainer(definition_container) - container_stack = UM.Settings.ContainerStack.ContainerStack(stack_id = "Test Container Stack") #A container we're going to convert. + container_stack = UM.Settings.ContainerStack.ContainerStack(stack_id = "Test Global Stack") #A container we're going to convert. container_stack.setMetaDataEntry("type", "machine") #This is now a global stack. container_stack.insertContainer(0, definition_container) #Must have a definition. @@ -101,7 +53,7 @@ def test_addContainerGoodSettingVersion(container_registry, definition_container definition_container.getMetaData()["setting_version"] = CuraApplication.SettingVersion container_registry.addContainer(definition_container) - instance = UM.Settings.InstanceContainer.InstanceContainer(container_id = "Test Instance") + instance = UM.Settings.InstanceContainer.InstanceContainer(container_id = "Test Instance Right Version") instance.setMetaDataEntry("setting_version", CuraApplication.SettingVersion) instance.setDefinition(definition_container.getId()) @@ -116,7 +68,7 @@ def test_addContainerNoSettingVersion(container_registry, definition_container): definition_container.getMetaData()["setting_version"] = CuraApplication.SettingVersion container_registry.addContainer(definition_container) - instance = UM.Settings.InstanceContainer.InstanceContainer(container_id = "Test Instance") + instance = UM.Settings.InstanceContainer.InstanceContainer(container_id = "Test Instance No Version") #Don't add setting_version metadata. instance.setDefinition(definition_container.getId()) @@ -131,7 +83,7 @@ def test_addContainerBadSettingVersion(container_registry, definition_container) definition_container.getMetaData()["setting_version"] = CuraApplication.SettingVersion container_registry.addContainer(definition_container) - instance = UM.Settings.InstanceContainer.InstanceContainer(container_id = "Test Instance") + instance = UM.Settings.InstanceContainer.InstanceContainer(container_id = "Test Instance Wrong Version") instance.setMetaDataEntry("setting_version", 9001) #Wrong version! instance.setDefinition(definition_container.getId()) @@ -174,4 +126,4 @@ def test_addContainerBadSettingVersion(container_registry, definition_container) # assert type(container) == output_class # break # else: -# assert False #Container stack with specified ID was not loaded. \ No newline at end of file +# assert False #Container stack with specified ID was not loaded. diff --git a/tests/Settings/TestExtruderStack.py b/tests/Settings/TestExtruderStack.py index 7c463fb9be..a19f99571d 100644 --- a/tests/Settings/TestExtruderStack.py +++ b/tests/Settings/TestExtruderStack.py @@ -1,9 +1,8 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import pytest #This module contains automated tests. import unittest.mock #For the mocking and monkeypatching functionality. -import copy import cura.CuraApplication import UM.Settings.ContainerRegistry #To create empty instance containers. @@ -14,29 +13,27 @@ import cura.Settings.ExtruderStack #The module we're testing. from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationError #To check whether the correct exceptions are raised. from cura.Settings.ExtruderManager import ExtruderManager -from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Settings.GlobalStack import GlobalStack ## Fake container registry that always provides all containers you ask of. -@pytest.yield_fixture() -def container_registry(): - registry = unittest.mock.MagicMock() - registry.return_value = unittest.mock.NonCallableMagicMock() - registry.findInstanceContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] - registry.findDefinitionContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] - - UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = registry - UM.Settings.ContainerStack._containerRegistry = registry - - yield registry - - UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = None - UM.Settings.ContainerStack._containerRegistry = None +# @pytest.yield_fixture() +# def container_registry(): +# registry = unittest.mock.MagicMock() +# registry.return_value = unittest.mock.NonCallableMagicMock() +# registry.findInstanceContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] +# registry.findDefinitionContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] +# +# UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = registry +# UM.Settings.ContainerStack._containerRegistry = registry +# +# yield registry +# +# UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = None +# UM.Settings.ContainerStack._containerRegistry = None ## An empty extruder stack to test with. @pytest.fixture() def extruder_stack() -> cura.Settings.ExtruderStack.ExtruderStack: - creteEmptyContainers() return cura.Settings.ExtruderStack.ExtruderStack("TestStack") ## Gets an instance container with a specified container type. @@ -48,31 +45,6 @@ def getInstanceContainer(container_type) -> InstanceContainer: container.setMetaDataEntry("type", container_type) return container -def creteEmptyContainers(): - empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() - empty_variant_container = copy.deepcopy(empty_container) - empty_variant_container.setMetaDataEntry("id", "empty_variant") - empty_variant_container.setMetaDataEntry("type", "variant") - ContainerRegistry.getInstance().addContainer(empty_variant_container) - - empty_material_container = copy.deepcopy(empty_container) - empty_material_container.setMetaDataEntry("id", "empty_material") - empty_material_container.setMetaDataEntry("type", "material") - ContainerRegistry.getInstance().addContainer(empty_material_container) - - empty_quality_container = copy.deepcopy(empty_container) - empty_quality_container.setMetaDataEntry("id", "empty_quality") - empty_quality_container.setName("Not Supported") - empty_quality_container.setMetaDataEntry("quality_type", "not_supported") - empty_quality_container.setMetaDataEntry("type", "quality") - empty_quality_container.setMetaDataEntry("supported", False) - ContainerRegistry.getInstance().addContainer(empty_quality_container) - - empty_quality_changes_container = copy.deepcopy(empty_container) - empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") - empty_quality_changes_container.setMetaDataEntry("type", "quality_changes") - ContainerRegistry.getInstance().addContainer(empty_quality_changes_container) - class DefinitionContainerSubClass(DefinitionContainer): def __init__(self): super().__init__(container_id = "SubDefinitionContainer") diff --git a/tests/Settings/TestGlobalStack.py b/tests/Settings/TestGlobalStack.py index 3e74e3e575..1b583a9c98 100755 --- a/tests/Settings/TestGlobalStack.py +++ b/tests/Settings/TestGlobalStack.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import pytest #This module contains unit tests. diff --git a/tests/Settings/conftest.py b/tests/Settings/conftest.py new file mode 100644 index 0000000000..1ca3a865e9 --- /dev/null +++ b/tests/Settings/conftest.py @@ -0,0 +1,19 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Uranium is released under the terms of the LGPLv3 or higher. + +# The purpose of this class is to create fixtures or methods that can be shared among all settings tests. + +import pytest +import copy + +from UM.Settings.DefinitionContainer import DefinitionContainer #To provide definition containers in the registry fixtures. + +# Returns the CuraContainerRegistry instance with some empty containers. +@pytest.fixture() +def container_registry(application): + return application.getContainerRegistry() + +# Gives an arbitrary definition container. +@pytest.fixture() +def definition_container(): + return DefinitionContainer(container_id = "Test Definition") \ No newline at end of file diff --git a/tests/TestArrange.py b/tests/TestArrange.py index f383fc0cf3..7de3ec1d8d 100755 --- a/tests/TestArrange.py +++ b/tests/TestArrange.py @@ -1,9 +1,11 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + import numpy from cura.Arranging.Arrange import Arrange from cura.Arranging.ShapeArray import ShapeArray - ## Triangle of area 12 def gimmeTriangle(): return numpy.array([[-3, 1], [3, 1], [0, -3]], dtype=numpy.int32) @@ -102,7 +104,7 @@ def test_centerFirst_rectangular(): ## Test centerFirst -def test_centerFirst_rectangular(): +def test_centerFirst_rectangular2(): ar = Arrange(10, 20, 5, 10, scale = 1) ar.centerFirst() print(ar._priority) diff --git a/tests/TestMachineAction.py b/tests/TestMachineAction.py index 0118874a0b..7121fcc218 100755 --- a/tests/TestMachineAction.py +++ b/tests/TestMachineAction.py @@ -1,11 +1,10 @@ -#Todo: Write tests +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. import pytest -# QtApplication needs to be imported first to prevent import errors. -from UM.Qt.QtApplication import QtApplication from cura.MachineAction import MachineAction -from cura.MachineActionManager import MachineActionManager, NotUniqueMachineActionError, UnknownMachineActionError +from cura.MachineActionManager import NotUniqueMachineActionError, UnknownMachineActionError class Machine: def __init__(self, key = ""): @@ -14,66 +13,64 @@ class Machine: def getKey(self): return self._key -def test_addMachineAction(): - - machine_manager = MachineActionManager() +def test_addMachineAction(machine_action_manager): test_action = MachineAction(key = "test_action") test_action_2 = MachineAction(key = "test_action_2") test_machine = Machine("test_machine") - machine_manager.addMachineAction(test_action) - machine_manager.addMachineAction(test_action_2) + machine_action_manager.addMachineAction(test_action) + machine_action_manager.addMachineAction(test_action_2) - assert machine_manager.getMachineAction("test_action") == test_action - assert machine_manager.getMachineAction("key_that_doesnt_exist") is None + assert machine_action_manager.getMachineAction("test_action") == test_action + assert machine_action_manager.getMachineAction("key_that_doesnt_exist") is None # Adding the same machine action is not allowed. with pytest.raises(NotUniqueMachineActionError): - machine_manager.addMachineAction(test_action) + machine_action_manager.addMachineAction(test_action) # Check that the machine has no supported actions yet. - assert machine_manager.getSupportedActions(test_machine) == list() + assert machine_action_manager.getSupportedActions(test_machine) == list() # Check if adding a supported action works. - machine_manager.addSupportedAction(test_machine, "test_action") - assert machine_manager.getSupportedActions(test_machine) == [test_action, ] + machine_action_manager.addSupportedAction(test_machine, "test_action") + assert machine_action_manager.getSupportedActions(test_machine) == [test_action, ] # Check that adding a unknown action doesn't change anything. - machine_manager.addSupportedAction(test_machine, "key_that_doesnt_exist") - assert machine_manager.getSupportedActions(test_machine) == [test_action, ] + machine_action_manager.addSupportedAction(test_machine, "key_that_doesnt_exist") + assert machine_action_manager.getSupportedActions(test_machine) == [test_action, ] # Check if adding multiple supported actions works. - machine_manager.addSupportedAction(test_machine, "test_action_2") - assert machine_manager.getSupportedActions(test_machine) == [test_action, test_action_2] + machine_action_manager.addSupportedAction(test_machine, "test_action_2") + assert machine_action_manager.getSupportedActions(test_machine) == [test_action, test_action_2] # Check that the machine has no required actions yet. - assert machine_manager.getRequiredActions(test_machine) == set() + assert machine_action_manager.getRequiredActions(test_machine) == set() ## Ensure that only known actions can be added. with pytest.raises(UnknownMachineActionError): - machine_manager.addRequiredAction(test_machine, "key_that_doesnt_exist") + machine_action_manager.addRequiredAction(test_machine, "key_that_doesnt_exist") ## Check if adding single required action works - machine_manager.addRequiredAction(test_machine, "test_action") - assert machine_manager.getRequiredActions(test_machine) == [test_action, ] + machine_action_manager.addRequiredAction(test_machine, "test_action") + assert machine_action_manager.getRequiredActions(test_machine) == [test_action, ] # Check if adding multiple required actions works. - machine_manager.addRequiredAction(test_machine, "test_action_2") - assert machine_manager.getRequiredActions(test_machine) == [test_action, test_action_2] + machine_action_manager.addRequiredAction(test_machine, "test_action_2") + assert machine_action_manager.getRequiredActions(test_machine) == [test_action, test_action_2] # Ensure that firstStart actions are empty by default. - assert machine_manager.getFirstStartActions(test_machine) == [] + assert machine_action_manager.getFirstStartActions(test_machine) == [] # Check if adding multiple (the same) actions to first start actions work. - machine_manager.addFirstStartAction(test_machine, "test_action") - machine_manager.addFirstStartAction(test_machine, "test_action") - assert machine_manager.getFirstStartActions(test_machine) == [test_action, test_action] + machine_action_manager.addFirstStartAction(test_machine, "test_action") + machine_action_manager.addFirstStartAction(test_machine, "test_action") + assert machine_action_manager.getFirstStartActions(test_machine) == [test_action, test_action] # Check if inserting an action works - machine_manager.addFirstStartAction(test_machine, "test_action_2", index = 1) - assert machine_manager.getFirstStartActions(test_machine) == [test_action, test_action_2, test_action] + machine_action_manager.addFirstStartAction(test_machine, "test_action_2", index = 1) + assert machine_action_manager.getFirstStartActions(test_machine) == [test_action, test_action_2, test_action] # Check that adding a unknown action doesn't change anything. - machine_manager.addFirstStartAction(test_machine, "key_that_doesnt_exist", index = 1) - assert machine_manager.getFirstStartActions(test_machine) == [test_action, test_action_2, test_action] + machine_action_manager.addFirstStartAction(test_machine, "key_that_doesnt_exist", index = 1) + assert machine_action_manager.getFirstStartActions(test_machine) == [test_action, test_action_2, test_action] diff --git a/tests/TestProfileRequirements.py b/tests/TestProfileRequirements.py index f75ca9da8d..97641fe753 100644 --- a/tests/TestProfileRequirements.py +++ b/tests/TestProfileRequirements.py @@ -1,3 +1,6 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + import configparser #To read the profiles. import os #To join paths. import pytest @@ -11,6 +14,7 @@ import pytest # often that we updated the variants for the UM3 but forgot about the UM3E. @pytest.mark.parametrize("um3_file, um3e_file", [ #List the corresponding files below. + ("ultimaker3_aa0.25.inst.cfg", "ultimaker3_extended_aa0.25.inst.cfg"), ("ultimaker3_aa0.8.inst.cfg", "ultimaker3_extended_aa0.8.inst.cfg"), ("ultimaker3_aa04.inst.cfg", "ultimaker3_extended_aa04.inst.cfg"), ("ultimaker3_bb0.8.inst.cfg", "ultimaker3_extended_bb0.8.inst.cfg"), diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000..32e2dc621f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,38 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +# The purpose of this class is to create fixtures or methods that can be shared among all tests. + +import pytest +from UM.Qt.QtApplication import QtApplication #QTApplication import is required, even though it isn't used. +from cura.CuraApplication import CuraApplication +from cura.MachineActionManager import MachineActionManager + +# Create a CuraApplication object that will be shared among all tests. It needs to be initialized. +# Since we need to use it more that once, we create the application the first time and use its instance afterwards. +@pytest.fixture() +def application() -> CuraApplication: + application = CuraApplication.getInstance() + if application is None: + application = CuraApplication() + application.initialize() + return application + +# Returns a MachineActionManager instance. +@pytest.fixture() +def machine_action_manager(application) -> MachineActionManager: + return application.getMachineActionManager() + +# @pytest.fixture() +# def plugin_registry(application): +# PluginRegistry._PluginRegistry__instance = None +# plugin_registry = PluginRegistry(application) +# plugin_registry._plugin_locations = [] # Clear pre-defined plugin locations +# return plugin_registry +# +# @pytest.fixture() +# def upgrade_manager(application): +# VersionUpgradeManager._VersionUpgradeManager__instance = None +# upgrade_manager = VersionUpgradeManager(application) +# return upgrade_manager + From 5869644b0be12cc35f57f53012ae84bb58c79f1d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 14 Aug 2018 12:02:11 +0200 Subject: [PATCH 67/97] Fix typo --- cura/CuraApplication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 78986d82ee..df25522be3 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -123,7 +123,7 @@ except ImportError: CuraVersion = "master" # [CodeStyle: Reflecting imported value] CuraBuildType = "" CuraDebugMode = False - CuraSDKVerion = "" + CuraSDKVersion = "" class CuraApplication(QtApplication): From c8af4f45cedf1e3ae8e56df1fdbf4169a0fe6430 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 14 Aug 2018 13:25:56 +0200 Subject: [PATCH 68/97] Decouple the creation of the stack containers from the process that add them to the container registry, that is done in CuraApplication. That allow us to have access to the empty containers and so we can unit test easily without mocking up stuff. Contributes to CURA-5628. --- cura/CuraApplication.py | 46 ++++++++--------------------- cura/CuraEmptyInstanceContainers.py | 29 ++++++++++++++++++ cura/Settings/CuraContainerStack.py | 16 +++++----- 3 files changed, 49 insertions(+), 42 deletions(-) create mode 100644 cura/CuraEmptyInstanceContainers.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 78986d82ee..24043b83fe 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1,11 +1,10 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import copy import os import sys import time -from typing import cast, TYPE_CHECKING, Optional +from typing import cast, TYPE_CHECKING import numpy @@ -105,6 +104,7 @@ from cura.Settings.ExtrudersModel import ExtrudersModel from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.ContainerManager import ContainerManager from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel +import cura.CuraEmptyInstanceContainers from cura.ObjectsModel import ObjectsModel @@ -368,42 +368,20 @@ class CuraApplication(QtApplication): # Add empty variant, material and quality containers. # Since they are empty, they should never be serialized and instead just programmatically created. # We need them to simplify the switching between materials. - empty_container = self._container_registry.getEmptyInstanceContainer() - self.empty_container = empty_container + self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_definition_changes_container) + self.empty_definition_changes_container = cura.CuraEmptyInstanceContainers.empty_definition_changes_container - empty_definition_changes_container = copy.deepcopy(empty_container) - empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes") - empty_definition_changes_container.setMetaDataEntry("type", "definition_changes") - self._container_registry.addContainer(empty_definition_changes_container) - self.empty_definition_changes_container = empty_definition_changes_container + self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_variant_container) + self.empty_variant_container = cura.CuraEmptyInstanceContainers.empty_variant_container - empty_variant_container = copy.deepcopy(empty_container) - empty_variant_container.setMetaDataEntry("id", "empty_variant") - empty_variant_container.setMetaDataEntry("type", "variant") - self._container_registry.addContainer(empty_variant_container) - self.empty_variant_container = empty_variant_container + self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_material_container) + self.empty_material_container = cura.CuraEmptyInstanceContainers.empty_material_container - empty_material_container = copy.deepcopy(empty_container) - empty_material_container.setMetaDataEntry("id", "empty_material") - empty_material_container.setMetaDataEntry("type", "material") - self._container_registry.addContainer(empty_material_container) - self.empty_material_container = empty_material_container + self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_quality_container) + self.empty_quality_container = cura.CuraEmptyInstanceContainers.empty_quality_container - empty_quality_container = copy.deepcopy(empty_container) - empty_quality_container.setMetaDataEntry("id", "empty_quality") - empty_quality_container.setName("Not Supported") - empty_quality_container.setMetaDataEntry("quality_type", "not_supported") - empty_quality_container.setMetaDataEntry("type", "quality") - empty_quality_container.setMetaDataEntry("supported", False) - self._container_registry.addContainer(empty_quality_container) - self.empty_quality_container = empty_quality_container - - empty_quality_changes_container = copy.deepcopy(empty_container) - empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") - empty_quality_changes_container.setMetaDataEntry("type", "quality_changes") - empty_quality_changes_container.setMetaDataEntry("quality_type", "not_supported") - self._container_registry.addContainer(empty_quality_changes_container) - self.empty_quality_changes_container = empty_quality_changes_container + self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_quality_changes_container) + self.empty_quality_changes_container = cura.CuraEmptyInstanceContainers.empty_quality_changes_container # Initializes the version upgrade manager with by providing the paths for each resource type and the latest # versions. diff --git a/cura/CuraEmptyInstanceContainers.py b/cura/CuraEmptyInstanceContainers.py new file mode 100644 index 0000000000..3f5d3e7c59 --- /dev/null +++ b/cura/CuraEmptyInstanceContainers.py @@ -0,0 +1,29 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from UM.Settings.EmptyInstanceContainer import empty_container +import copy + +empty_definition_changes_container = copy.deepcopy(empty_container) +empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes") +empty_definition_changes_container.setMetaDataEntry("type", "definition_changes") + +empty_variant_container = copy.deepcopy(empty_container) +empty_variant_container.setMetaDataEntry("id", "empty_variant") +empty_variant_container.setMetaDataEntry("type", "variant") + +empty_material_container = copy.deepcopy(empty_container) +empty_material_container.setMetaDataEntry("id", "empty_material") +empty_material_container.setMetaDataEntry("type", "material") + +empty_quality_container = copy.deepcopy(empty_container) +empty_quality_container.setMetaDataEntry("id", "empty_quality") +empty_quality_container.setName("Not Supported") +empty_quality_container.setMetaDataEntry("quality_type", "not_supported") +empty_quality_container.setMetaDataEntry("type", "quality") +empty_quality_container.setMetaDataEntry("supported", False) + +empty_quality_changes_container = copy.deepcopy(empty_container) +empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") +empty_quality_changes_container.setMetaDataEntry("type", "quality_changes") +empty_quality_changes_container.setMetaDataEntry("quality_type", "not_supported") diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index bd3380dfb2..dabed97011 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Any, cast, List, Optional, Union +from typing import Any, cast, List, Optional from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject from UM.Application import Application @@ -13,6 +13,8 @@ from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.Interfaces import ContainerInterface, DefinitionContainerInterface +from UM.Settings.EmptyInstanceContainer import empty_container +from cura import CuraEmptyInstanceContainers from . import Exceptions @@ -39,14 +41,12 @@ class CuraContainerStack(ContainerStack): def __init__(self, container_id: str) -> None: super().__init__(container_id) - self._container_registry = ContainerRegistry.getInstance() #type: ContainerRegistry + self._empty_instance_container = empty_container #type: InstanceContainer - self._empty_instance_container = self._container_registry.getEmptyInstanceContainer() #type: InstanceContainer - - self._empty_quality_changes = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0] #type: InstanceContainer - self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0] #type: InstanceContainer - self._empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0] #type: InstanceContainer - self._empty_variant = self._container_registry.findInstanceContainers(id = "empty_variant")[0] #type: InstanceContainer + self._empty_quality_changes = CuraEmptyInstanceContainers.empty_quality_changes_container #type: InstanceContainer + self._empty_quality = CuraEmptyInstanceContainers.empty_quality_container #type: InstanceContainer + self._empty_material = CuraEmptyInstanceContainers.empty_material_container #type: InstanceContainer + self._empty_variant = CuraEmptyInstanceContainers.empty_variant_container #type: InstanceContainer self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] #type: List[ContainerInterface] self._containers[_ContainerIndexes.QualityChanges] = self._empty_quality_changes From 1364370ede4ecbe71c077b56371a55971639a9fa Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 14 Aug 2018 13:49:37 +0200 Subject: [PATCH 69/97] Fix global stack tests. Contributes to CURA-5628. --- tests/Settings/TestGlobalStack.py | 117 +++++++----------------------- tests/Settings/conftest.py | 27 ++++++- tests/conftest.py | 17 +---- 3 files changed, 50 insertions(+), 111 deletions(-) diff --git a/tests/Settings/TestGlobalStack.py b/tests/Settings/TestGlobalStack.py index 1b583a9c98..9d52257245 100755 --- a/tests/Settings/TestGlobalStack.py +++ b/tests/Settings/TestGlobalStack.py @@ -3,41 +3,16 @@ import pytest #This module contains unit tests. import unittest.mock #To monkeypatch some mocks in place of dependencies. -import copy -import cura.CuraApplication -import cura.Settings.GlobalStack #The module we're testing. import cura.Settings.CuraContainerStack #To get the list of container types. -from cura.Settings.Exceptions import TooManyExtrudersError, InvalidContainerError, InvalidOperationError #To test raising these errors. +from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationError #To test raising these errors. from UM.Settings.DefinitionContainer import DefinitionContainer #To test against the class DefinitionContainer. from UM.Settings.InstanceContainer import InstanceContainer #To test against the class InstanceContainer. from UM.Settings.SettingInstance import InstanceState +from UM.Settings.EmptyInstanceContainer import empty_container import UM.Settings.ContainerRegistry import UM.Settings.ContainerStack import UM.Settings.SettingDefinition #To add settings to the definition. -from UM.Settings.ContainerRegistry import ContainerRegistry - -## Fake container registry that always provides all containers you ask of. -@pytest.yield_fixture() -def container_registry(): - registry = unittest.mock.MagicMock() - registry.return_value = unittest.mock.NonCallableMagicMock() - registry.findInstanceContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] - registry.findDefinitionContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] - - UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = registry - UM.Settings.ContainerStack._containerRegistry = registry - - yield registry - - UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = None - UM.Settings.ContainerStack._containerRegistry = None - -#An empty global stack to test with. -@pytest.fixture() -def global_stack() -> cura.Settings.GlobalStack.GlobalStack: - creteEmptyContainers() - return cura.Settings.GlobalStack.GlobalStack("TestStack") ## Gets an instance container with a specified container type. # @@ -48,31 +23,6 @@ def getInstanceContainer(container_type) -> InstanceContainer: container.setMetaDataEntry("type", container_type) return container -def creteEmptyContainers(): - empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() - empty_variant_container = copy.deepcopy(empty_container) - empty_variant_container.setMetaDataEntry("id", "empty_variant") - empty_variant_container.setMetaDataEntry("type", "variant") - ContainerRegistry.getInstance().addContainer(empty_variant_container) - - empty_material_container = copy.deepcopy(empty_container) - empty_material_container.setMetaDataEntry("id", "empty_material") - empty_material_container.setMetaDataEntry("type", "material") - ContainerRegistry.getInstance().addContainer(empty_material_container) - - empty_quality_container = copy.deepcopy(empty_container) - empty_quality_container.setMetaDataEntry("id", "empty_quality") - empty_quality_container.setName("Not Supported") - empty_quality_container.setMetaDataEntry("quality_type", "not_supported") - empty_quality_container.setMetaDataEntry("type", "quality") - empty_quality_container.setMetaDataEntry("supported", False) - ContainerRegistry.getInstance().addContainer(empty_quality_container) - - empty_quality_changes_container = copy.deepcopy(empty_container) - empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") - empty_quality_changes_container.setMetaDataEntry("type", "quality_changes") - ContainerRegistry.getInstance().addContainer(empty_quality_changes_container) - class DefinitionContainerSubClass(DefinitionContainer): def __init__(self): super().__init__(container_id = "SubDefinitionContainer") @@ -241,23 +191,22 @@ def test_constrainVariantInvalid(container, global_stack): def test_constrainDefinitionValid(container, global_stack): global_stack.definition = container #Should not give an error. -## Tests whether deserialising completes the missing containers with empty -# ones. -@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now. -def test_deserializeCompletesEmptyContainers(global_stack: cura.Settings.GlobalStack): - global_stack._containers = [DefinitionContainer(container_id = "definition")] #Set the internal state of this stack manually. +## Tests whether deserialising completes the missing containers with empty ones. The initial containers are just the +# definition and the definition_changes (that cannot be empty after CURA-5281) +def test_deserializeCompletesEmptyContainers(global_stack): + global_stack._containers = [DefinitionContainer(container_id = "definition"), global_stack.definitionChanges] #Set the internal state of this stack manually. with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. global_stack.deserialize("") assert len(global_stack.getContainers()) == len(cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap) #Needs a slot for every type. for container_type_index in cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap: - if container_type_index == cura.Settings.CuraContainerStack._ContainerIndexes.Definition: #We're not checking the definition. + if container_type_index in \ + (cura.Settings.CuraContainerStack._ContainerIndexes.Definition, cura.Settings.CuraContainerStack._ContainerIndexes.DefinitionChanges): #We're not checking the definition or definition_changes continue - assert global_stack.getContainer(container_type_index).getId() == "empty" #All others need to be empty. + assert global_stack.getContainer(container_type_index) == empty_container #All others need to be empty. -## Tests whether an instance container with the wrong type gets removed when -# deserialising. +## Tests whether an instance container with the wrong type gets removed when deserialising. def test_deserializeRemovesWrongInstanceContainer(global_stack): global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "wrong type") global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") @@ -267,8 +216,7 @@ def test_deserializeRemovesWrongInstanceContainer(global_stack): assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty. -## Tests whether a container with the wrong class gets removed when -# deserialising. +## Tests whether a container with the wrong class gets removed when deserialising. def test_deserializeRemovesWrongContainerClass(global_stack): global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = DefinitionContainer(container_id = "wrong class") global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") @@ -278,8 +226,7 @@ def test_deserializeRemovesWrongContainerClass(global_stack): assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty. -## Tests whether an instance container in the definition spot results in an -# error. +## Tests whether an instance container in the definition spot results in an error. def test_deserializeWrongDefinitionClass(global_stack): global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = getInstanceContainer(container_type = "definition") #Correct type but wrong class. @@ -287,8 +234,7 @@ def test_deserializeWrongDefinitionClass(global_stack): with pytest.raises(UM.Settings.ContainerStack.InvalidContainerStackError): #Must raise an error that there is no definition container. global_stack.deserialize("") -## Tests whether an instance container with the wrong type is moved into the -# correct slot by deserialising. +## Tests whether an instance container with the wrong type is moved into the correct slot by deserialising. def test_deserializeMoveInstanceContainer(global_stack): global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "material") #Not in the correct spot. global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") @@ -296,25 +242,20 @@ def test_deserializeMoveInstanceContainer(global_stack): with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. global_stack.deserialize("") - assert global_stack.quality.getId() == "empty" - assert global_stack.material.getId() != "empty" + assert global_stack.quality == empty_container + assert global_stack.material != empty_container -## Tests whether a definition container in the wrong spot is moved into the -# correct spot by deserialising. -@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now. +## Tests whether a definition container in the wrong spot is moved into the correct spot by deserialising. def test_deserializeMoveDefinitionContainer(global_stack): global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot. with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. global_stack.deserialize("") - assert global_stack.material.getId() == "empty" - assert global_stack.definition.getId() != "empty" + assert global_stack.material == empty_container + assert global_stack.definition != empty_container - UM.Settings.ContainerStack._containerRegistry = None - -## Tests whether getProperty properly applies the stack-like behaviour on its -# containers. +## Tests whether getProperty properly applies the stack-like behaviour on its containers. def test_getPropertyFallThrough(global_stack): #A few instance container mocks to put in the stack. mock_layer_heights = {} #For each container type, a mock container that defines layer height to something unique. @@ -365,8 +306,7 @@ def test_getPropertyNoResolveInDefinition(global_stack): global_stack.definition = value assert global_stack.getProperty("material_bed_temperature", "value") == 10 #No resolve, so fall through to value. -## In definitions, when the value is asked and there is a resolve function, it -# must get the resolve first. +## In definitions, when the value is asked and there is a resolve function, it must get the resolve first. def test_getPropertyResolveInDefinition(global_stack): resolve_and_value = unittest.mock.MagicMock() #Sets the resolve and value for bed temperature. resolve_and_value.getProperty = lambda key, property, context = None: (7.5 if property == "resolve" else 5) if (key == "material_bed_temperature" and property in ("resolve", "value")) else None #7.5 resolve, 5 value. @@ -375,8 +315,7 @@ def test_getPropertyResolveInDefinition(global_stack): global_stack.definition = resolve_and_value assert global_stack.getProperty("material_bed_temperature", "value") == 7.5 #Resolve wins in the definition. -## In instance containers, when the value is asked and there is a resolve -# function, it must get the value first. +## In instance containers, when the value is asked and there is a resolve function, it must get the value first. def test_getPropertyResolveInInstance(global_stack): container_indices = cura.Settings.CuraContainerStack._ContainerIndexes instance_containers = {} @@ -402,8 +341,7 @@ def test_getPropertyResolveInInstance(global_stack): global_stack.userChanges = instance_containers[container_indices.UserChanges] assert global_stack.getProperty("material_bed_temperature", "value") == 5 -## Tests whether the value in instances gets evaluated before the resolve in -# definitions. +## Tests whether the value in instances gets evaluated before the resolve in definitions. def test_getPropertyInstancesBeforeResolve(global_stack): value = unittest.mock.MagicMock() #Sets just the value. value.getProperty = lambda key, property, context = None: (10 if property == "value" else (InstanceState.User if property != "limit_to_extruder" else "-1")) if key == "material_bed_temperature" else None @@ -417,8 +355,7 @@ def test_getPropertyInstancesBeforeResolve(global_stack): assert global_stack.getProperty("material_bed_temperature", "value") == 10 -## Tests whether the hasUserValue returns true for settings that are changed in -# the user-changes container. +## Tests whether the hasUserValue returns true for settings that are changed in the user-changes container. def test_hasUserValueUserChanges(global_stack): container = unittest.mock.MagicMock() container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user") @@ -429,8 +366,7 @@ def test_hasUserValueUserChanges(global_stack): assert not global_stack.hasUserValue("infill_sparse_density") assert not global_stack.hasUserValue("") -## Tests whether the hasUserValue returns true for settings that are changed in -# the quality-changes container. +## Tests whether the hasUserValue returns true for settings that are changed in the quality-changes container. def test_hasUserValueQualityChanges(global_stack): container = unittest.mock.MagicMock() container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality_changes") @@ -441,8 +377,7 @@ def test_hasUserValueQualityChanges(global_stack): assert not global_stack.hasUserValue("infill_sparse_density") assert not global_stack.hasUserValue("") -## Tests whether a container in some other place on the stack is correctly not -# recognised as user value. +## Tests whether a container in some other place on the stack is correctly not recognised as user value. def test_hasNoUserValue(global_stack): container = unittest.mock.MagicMock() container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality") @@ -481,4 +416,4 @@ def test_setPropertyUser(key, property, value, global_stack): global_stack.setProperty(key, property, value) #The actual test. - global_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call. \ No newline at end of file + global_stack.userChanges.setProperty.assert_called_once_with(key, property, value, None, False) #Make sure that the user container gets a setProperty call. \ No newline at end of file diff --git a/tests/Settings/conftest.py b/tests/Settings/conftest.py index 1ca3a865e9..f7affc94b2 100644 --- a/tests/Settings/conftest.py +++ b/tests/Settings/conftest.py @@ -4,16 +4,35 @@ # The purpose of this class is to create fixtures or methods that can be shared among all settings tests. import pytest -import copy from UM.Settings.DefinitionContainer import DefinitionContainer #To provide definition containers in the registry fixtures. +from UM.Settings.InstanceContainer import InstanceContainer +from cura.Settings.CuraContainerRegistry import CuraContainerRegistry +from cura.Settings.ExtruderStack import ExtruderStack +from cura.Settings.GlobalStack import GlobalStack +import cura.Settings.CuraContainerStack # Returns the CuraContainerRegistry instance with some empty containers. @pytest.fixture() -def container_registry(application): +def container_registry(application) -> CuraContainerRegistry: return application.getContainerRegistry() # Gives an arbitrary definition container. @pytest.fixture() -def definition_container(): - return DefinitionContainer(container_id = "Test Definition") \ No newline at end of file +def definition_container() -> DefinitionContainer: + return DefinitionContainer(container_id = "Test Definition") + +#An empty global stack to test with. +@pytest.fixture() +def global_stack() -> GlobalStack: + global_stack = GlobalStack("TestGlobalStack") + # There is a restriction here that the definition changes cannot be an empty container. Added in CURA-5281 + definition_changes_container = InstanceContainer(container_id = "InstanceContainer") + definition_changes_container.setMetaDataEntry("type", "definition_changes") + global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.DefinitionChanges] = definition_changes_container + return global_stack + +## An empty extruder stack to test with. +@pytest.fixture() +def extruder_stack() -> ExtruderStack: + return ExtruderStack("TestExtruderStack") \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 32e2dc621f..d5bb5c8109 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ # The purpose of this class is to create fixtures or methods that can be shared among all tests. import pytest -from UM.Qt.QtApplication import QtApplication #QTApplication import is required, even though it isn't used. +from UM.Qt.QtApplication import QtApplication #QtApplication import is required, even though it isn't used. from cura.CuraApplication import CuraApplication from cura.MachineActionManager import MachineActionManager @@ -15,24 +15,9 @@ def application() -> CuraApplication: application = CuraApplication.getInstance() if application is None: application = CuraApplication() - application.initialize() return application # Returns a MachineActionManager instance. @pytest.fixture() def machine_action_manager(application) -> MachineActionManager: return application.getMachineActionManager() - -# @pytest.fixture() -# def plugin_registry(application): -# PluginRegistry._PluginRegistry__instance = None -# plugin_registry = PluginRegistry(application) -# plugin_registry._plugin_locations = [] # Clear pre-defined plugin locations -# return plugin_registry -# -# @pytest.fixture() -# def upgrade_manager(application): -# VersionUpgradeManager._VersionUpgradeManager__instance = None -# upgrade_manager = VersionUpgradeManager(application) -# return upgrade_manager - From 05bfee81586e295073c4144dcc3e139e7597e87d Mon Sep 17 00:00:00 2001 From: DavidGergely Date: Tue, 14 Aug 2018 15:26:52 +0200 Subject: [PATCH 70/97] Fix UM3extended 0.25 variant file Set ultimaker3_extended_aa0.25.inst.cfg to be the same as the ultimaker3_aa0.25.inst.cfg. --- .../variants/ultimaker3_extended_aa0.25.inst.cfg | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/resources/variants/ultimaker3_extended_aa0.25.inst.cfg b/resources/variants/ultimaker3_extended_aa0.25.inst.cfg index b2f81e101c..714b017653 100644 --- a/resources/variants/ultimaker3_extended_aa0.25.inst.cfg +++ b/resources/variants/ultimaker3_extended_aa0.25.inst.cfg @@ -11,7 +11,6 @@ hardware_type = nozzle [values] brim_width = 7 infill_line_width = 0.23 -infill_overlap = 0 layer_height_0 = 0.17 line_width = 0.23 machine_nozzle_cool_down_speed = 0.85 @@ -21,10 +20,18 @@ machine_nozzle_size = 0.25 machine_nozzle_tip_outer_diameter = 0.65 material_final_print_temperature = =material_print_temperature - 10 material_initial_print_temperature = =material_print_temperature - 5 +raft_airgap = 0.3 +raft_base_thickness = =resolveOrValue('layer_height_0') * 1.2 +raft_interface_line_spacing = =raft_interface_line_width + 0.2 +raft_interface_line_width = =line_width * 2 raft_interface_thickness = =layer_height * 1.5 +raft_jerk = =jerk_print +raft_margin = 15 +raft_surface_layers = 2 retraction_count_max = 25 retraction_extrusion_window = 1 retraction_min_travel = 0.7 +retraction_prime_speed = =retraction_speed skin_overlap = 15 speed_layer_0 = 20 speed_print = 55 @@ -32,8 +39,12 @@ speed_topbottom = 20 speed_wall = =math.ceil(speed_print * 30 / 55) support_angle = 60 support_bottom_distance = =support_z_distance / 2 +support_pattern = zigzag support_top_distance = =support_z_distance +support_use_towers = True support_z_distance = =layer_height * 2 +switch_extruder_prime_speed = =switch_extruder_retraction_speeds +switch_extruder_retraction_amount = =machine_heat_zone_length top_bottom_thickness = 1.2 wall_line_width_x = 0.23 wall_thickness = 1.3 From ce9782b3af2e2ffbdfe79d97e92c703259c98edc Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 14 Aug 2018 15:32:04 +0200 Subject: [PATCH 71/97] Initialize the empty_container in CuraApplication. Contributes to CURA-5628. --- cura/CuraApplication.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 24043b83fe..d688052369 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -368,6 +368,8 @@ class CuraApplication(QtApplication): # Add empty variant, material and quality containers. # Since they are empty, they should never be serialized and instead just programmatically created. # We need them to simplify the switching between materials. + self.empty_container = cura.CuraEmptyInstanceContainers.empty_container + self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_definition_changes_container) self.empty_definition_changes_container = cura.CuraEmptyInstanceContainers.empty_definition_changes_container From e1fd9b03a4a90e5cfa5b19c39f295975cc6ba7a2 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 14 Aug 2018 15:33:35 +0200 Subject: [PATCH 72/97] Remove all dependencies of the CuraContainerStack with the ContainerRegistry. That will be very helpful for creating unit tests. Also this is not needed because the next stack is always set in the machine or extruder manager when switching printers. Contributes to CURA-5628. --- cura/Settings/ExtruderStack.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index 47846fc1dd..ca687e358b 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -139,9 +139,6 @@ class ExtruderStack(CuraContainerStack): super().deserialize(contents, file_name) if "enabled" not in self.getMetaData(): self.setMetaDataEntry("enabled", "True") - stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", "")) - if stacks: - self.setNextStack(stacks[0]) def _onPropertiesChanged(self, key: str, properties: Dict[str, Any]) -> None: # When there is a setting that is not settable per extruder that depends on a value from a setting that is, From b85950b1281c7d1498d0a51fd09a6a600bca0f84 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 14 Aug 2018 15:40:11 +0200 Subject: [PATCH 73/97] Fix tests in the ExtruderStack. Contributes to CURA-5628. --- tests/Settings/TestExtruderStack.py | 99 ++++++++++++----------------- tests/Settings/TestGlobalStack.py | 2 +- tests/Settings/conftest.py | 7 +- 3 files changed, 49 insertions(+), 59 deletions(-) diff --git a/tests/Settings/TestExtruderStack.py b/tests/Settings/TestExtruderStack.py index a19f99571d..b2fa171068 100644 --- a/tests/Settings/TestExtruderStack.py +++ b/tests/Settings/TestExtruderStack.py @@ -4,37 +4,14 @@ import pytest #This module contains automated tests. import unittest.mock #For the mocking and monkeypatching functionality. -import cura.CuraApplication +import cura.Settings.CuraContainerStack #To get the list of container types. import UM.Settings.ContainerRegistry #To create empty instance containers. import UM.Settings.ContainerStack #To set the container registry the container stacks use. from UM.Settings.DefinitionContainer import DefinitionContainer #To check against the class of DefinitionContainer. from UM.Settings.InstanceContainer import InstanceContainer #To check against the class of InstanceContainer. -import cura.Settings.ExtruderStack #The module we're testing. +from UM.Settings.EmptyInstanceContainer import empty_container from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationError #To check whether the correct exceptions are raised. - from cura.Settings.ExtruderManager import ExtruderManager -from cura.Settings.GlobalStack import GlobalStack - -## Fake container registry that always provides all containers you ask of. -# @pytest.yield_fixture() -# def container_registry(): -# registry = unittest.mock.MagicMock() -# registry.return_value = unittest.mock.NonCallableMagicMock() -# registry.findInstanceContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] -# registry.findDefinitionContainers = lambda *args, registry = registry, **kwargs: [registry.return_value] -# -# UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = registry -# UM.Settings.ContainerStack._containerRegistry = registry -# -# yield registry -# -# UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = None -# UM.Settings.ContainerStack._containerRegistry = None - -## An empty extruder stack to test with. -@pytest.fixture() -def extruder_stack() -> cura.Settings.ExtruderStack.ExtruderStack: - return cura.Settings.ExtruderStack.ExtruderStack("TestStack") ## Gets an instance container with a specified container type. # @@ -151,12 +128,30 @@ def test_constrainVariantInvalid(container, extruder_stack): def test_constrainVariantValid(container, extruder_stack): extruder_stack.variant = container #Should not give an error. +#Tests setting definition changes profiles to invalid containers. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "wrong container type"), + getInstanceContainer(container_type = "material"), #Existing, but still wrong type. + DefinitionContainer(container_id = "wrong class") +]) +def test_constrainDefinitionChangesInvalid(container, global_stack): + with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. + global_stack.definitionChanges = container + +#Test setting definition changes profiles. +@pytest.mark.parametrize("container", [ + getInstanceContainer(container_type = "definition_changes"), + InstanceContainerSubClass(container_type = "definition_changes") +]) +def test_constrainDefinitionChangesValid(container, global_stack): + global_stack.definitionChanges = container #Should not give an error. + #Tests setting definitions to invalid containers. @pytest.mark.parametrize("container", [ getInstanceContainer(container_type = "wrong class"), getInstanceContainer(container_type = "material"), #Existing, but still wrong class. ]) -def test_constrainVariantInvalid(container, extruder_stack): +def test_constrainDefinitionInvalid(container, extruder_stack): with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. extruder_stack.definition = container @@ -168,23 +163,22 @@ def test_constrainVariantInvalid(container, extruder_stack): def test_constrainDefinitionValid(container, extruder_stack): extruder_stack.definition = container #Should not give an error. -## Tests whether deserialising completes the missing containers with empty -# ones. -@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now. -def test_deserializeCompletesEmptyContainers(extruder_stack: cura.Settings.ExtruderStack): - extruder_stack._containers = [DefinitionContainer(container_id = "definition")] #Set the internal state of this stack manually. +## Tests whether deserialising completes the missing containers with empty ones. +def test_deserializeCompletesEmptyContainers(extruder_stack): + extruder_stack._containers = [DefinitionContainer(container_id = "definition"), extruder_stack.definitionChanges] #Set the internal state of this stack manually. with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. extruder_stack.deserialize("") assert len(extruder_stack.getContainers()) == len(cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap) #Needs a slot for every type. for container_type_index in cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap: - if container_type_index == cura.Settings.CuraContainerStack._ContainerIndexes.Definition: #We're not checking the definition. + if container_type_index in \ + (cura.Settings.CuraContainerStack._ContainerIndexes.Definition, + cura.Settings.CuraContainerStack._ContainerIndexes.DefinitionChanges): # We're not checking the definition or definition_changes continue - assert extruder_stack.getContainer(container_type_index).getId() == "empty" #All others need to be empty. + assert extruder_stack.getContainer(container_type_index) == empty_container #All others need to be empty. -## Tests whether an instance container with the wrong type gets removed when -# deserialising. +## Tests whether an instance container with the wrong type gets removed when deserialising. def test_deserializeRemovesWrongInstanceContainer(extruder_stack): extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "wrong type") extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") @@ -194,8 +188,7 @@ def test_deserializeRemovesWrongInstanceContainer(extruder_stack): assert extruder_stack.quality == extruder_stack._empty_instance_container #Replaced with empty. -## Tests whether a container with the wrong class gets removed when -# deserialising. +## Tests whether a container with the wrong class gets removed when deserialising. def test_deserializeRemovesWrongContainerClass(extruder_stack): extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = DefinitionContainer(container_id = "wrong class") extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") @@ -205,8 +198,7 @@ def test_deserializeRemovesWrongContainerClass(extruder_stack): assert extruder_stack.quality == extruder_stack._empty_instance_container #Replaced with empty. -## Tests whether an instance container in the definition spot results in an -# error. +## Tests whether an instance container in the definition spot results in an error. def test_deserializeWrongDefinitionClass(extruder_stack): extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = getInstanceContainer(container_type = "definition") #Correct type but wrong class. @@ -214,8 +206,7 @@ def test_deserializeWrongDefinitionClass(extruder_stack): with pytest.raises(UM.Settings.ContainerStack.InvalidContainerStackError): #Must raise an error that there is no definition container. extruder_stack.deserialize("") -## Tests whether an instance container with the wrong type is moved into the -# correct slot by deserialising. +## Tests whether an instance container with the wrong type is moved into the correct slot by deserialising. def test_deserializeMoveInstanceContainer(extruder_stack): extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "material") #Not in the correct spot. extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition") @@ -223,26 +214,21 @@ def test_deserializeMoveInstanceContainer(extruder_stack): with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. extruder_stack.deserialize("") - assert extruder_stack.quality.getId() == "empty" - assert extruder_stack.material.getId() != "empty" -from UM.Settings.Validator import Validator -## Tests whether a definition container in the wrong spot is moved into the -# correct spot by deserialising. -@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now. + assert extruder_stack.quality == empty_container + assert extruder_stack.material != empty_container + +## Tests whether a definition container in the wrong spot is moved into the correct spot by deserialising. def test_deserializeMoveDefinitionContainer(extruder_stack): extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot. with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize. extruder_stack.deserialize("") - assert extruder_stack.material.getId() == "empty" - assert extruder_stack.definition.getId() != "empty" + assert extruder_stack.material == empty_container + assert extruder_stack.definition != empty_container - UM.Settings.ContainerStack._containerRegistry = None - -## Tests whether getProperty properly applies the stack-like behaviour on its -# containers. -def test_getPropertyFallThrough(extruder_stack): +## Tests whether getProperty properly applies the stack-like behaviour on its containers. +def test_getPropertyFallThrough(global_stack, extruder_stack): # ExtruderStack.setNextStack calls registerExtruder for backward compatibility, but we do not need a complete extruder manager ExtruderManager._ExtruderManager__instance = unittest.mock.MagicMock() @@ -272,8 +258,7 @@ def test_getPropertyFallThrough(extruder_stack): with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. extruder_stack.definition = mock_layer_heights[container_indices.Definition] #There's a layer height in here! - stack = GlobalStack("PyTest GlobalStack") - extruder_stack.setNextStack(stack) + extruder_stack.setNextStack(global_stack) assert extruder_stack.getProperty("layer_height", "value") == container_indices.Definition extruder_stack.variant = mock_layer_heights[container_indices.Variant] @@ -312,4 +297,4 @@ def test_setPropertyUser(key, property, value, extruder_stack): extruder_stack.setProperty(key, property, value) #The actual test. - extruder_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call. \ No newline at end of file + extruder_stack.userChanges.setProperty.assert_called_once_with(key, property, value, None, False) #Make sure that the user container gets a setProperty call. \ No newline at end of file diff --git a/tests/Settings/TestGlobalStack.py b/tests/Settings/TestGlobalStack.py index 9d52257245..7aba53c4e5 100755 --- a/tests/Settings/TestGlobalStack.py +++ b/tests/Settings/TestGlobalStack.py @@ -179,7 +179,7 @@ def test_constrainDefinitionChangesValid(container, global_stack): getInstanceContainer(container_type = "wrong class"), getInstanceContainer(container_type = "material"), #Existing, but still wrong class. ]) -def test_constrainVariantInvalid(container, global_stack): +def test_constrainDefinitionInvalid(container, global_stack): with pytest.raises(InvalidContainerError): #Invalid container, should raise an error. global_stack.definition = container diff --git a/tests/Settings/conftest.py b/tests/Settings/conftest.py index f7affc94b2..8b519fd261 100644 --- a/tests/Settings/conftest.py +++ b/tests/Settings/conftest.py @@ -35,4 +35,9 @@ def global_stack() -> GlobalStack: ## An empty extruder stack to test with. @pytest.fixture() def extruder_stack() -> ExtruderStack: - return ExtruderStack("TestExtruderStack") \ No newline at end of file + extruder_stack= ExtruderStack("TestExtruderStack") + # There is a restriction here that the definition changes cannot be an empty container. Added in CURA-5281 + definition_changes_container = InstanceContainer(container_id = "InstanceContainer") + definition_changes_container.setMetaDataEntry("type", "definition_changes") + extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.DefinitionChanges] = definition_changes_container + return extruder_stack \ No newline at end of file From 77c5a94db2cb1f6d5ce1b88c6feff66841042b9f Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 14 Aug 2018 17:14:30 +0200 Subject: [PATCH 74/97] Fix some test in the CuraContainerRegistry. Delete a test that needs a ContainerProvider. We need to discuss if we want to add it back, depending if we are still keeping the providers. Contributes to CURA-5628. --- tests/Settings/TestCuraContainerRegistry.py | 56 +++++-------------- tests/Settings/conftest.py | 33 +++++++---- tests/Settings/stacks/Complete.extruder.cfg | 12 ---- tests/Settings/stacks/Complete.global.cfg | 13 ----- .../Settings/stacks/ExtruderLegacy.stack.cfg | 11 ---- tests/Settings/stacks/Global.global.cfg | 8 --- tests/Settings/stacks/Global.stack.cfg | 11 ---- tests/Settings/stacks/Left.extruder.cfg | 8 --- tests/Settings/stacks/MachineLegacy.stack.cfg | 11 ---- .../stacks/OnlyDefinition.extruder.cfg | 7 --- .../Settings/stacks/OnlyDefinition.global.cfg | 7 --- .../stacks/OnlyDefinitionChanges.global.cfg | 8 --- .../Settings/stacks/OnlyMaterial.extruder.cfg | 8 --- tests/Settings/stacks/OnlyMaterial.global.cfg | 8 --- .../Settings/stacks/OnlyQuality.extruder.cfg | 8 --- tests/Settings/stacks/OnlyQuality.global.cfg | 8 --- .../stacks/OnlyQualityChanges.extruder.cfg | 8 --- .../stacks/OnlyQualityChanges.global.cfg | 8 --- tests/Settings/stacks/OnlyUser.extruder.cfg | 8 --- tests/Settings/stacks/OnlyUser.global.cfg | 8 --- .../Settings/stacks/OnlyVariant.extruder.cfg | 8 --- tests/Settings/stacks/OnlyVariant.global.cfg | 8 --- tests/conftest.py | 2 +- 23 files changed, 36 insertions(+), 231 deletions(-) delete mode 100644 tests/Settings/stacks/Complete.extruder.cfg delete mode 100644 tests/Settings/stacks/Complete.global.cfg delete mode 100644 tests/Settings/stacks/ExtruderLegacy.stack.cfg delete mode 100644 tests/Settings/stacks/Global.global.cfg delete mode 100644 tests/Settings/stacks/Global.stack.cfg delete mode 100644 tests/Settings/stacks/Left.extruder.cfg delete mode 100644 tests/Settings/stacks/MachineLegacy.stack.cfg delete mode 100644 tests/Settings/stacks/OnlyDefinition.extruder.cfg delete mode 100644 tests/Settings/stacks/OnlyDefinition.global.cfg delete mode 100644 tests/Settings/stacks/OnlyDefinitionChanges.global.cfg delete mode 100644 tests/Settings/stacks/OnlyMaterial.extruder.cfg delete mode 100644 tests/Settings/stacks/OnlyMaterial.global.cfg delete mode 100644 tests/Settings/stacks/OnlyQuality.extruder.cfg delete mode 100644 tests/Settings/stacks/OnlyQuality.global.cfg delete mode 100644 tests/Settings/stacks/OnlyQualityChanges.extruder.cfg delete mode 100644 tests/Settings/stacks/OnlyQualityChanges.global.cfg delete mode 100644 tests/Settings/stacks/OnlyUser.extruder.cfg delete mode 100644 tests/Settings/stacks/OnlyUser.global.cfg delete mode 100644 tests/Settings/stacks/OnlyVariant.extruder.cfg delete mode 100644 tests/Settings/stacks/OnlyVariant.global.cfg diff --git a/tests/Settings/TestCuraContainerRegistry.py b/tests/Settings/TestCuraContainerRegistry.py index dd78f2fd68..af478c3b2b 100644 --- a/tests/Settings/TestCuraContainerRegistry.py +++ b/tests/Settings/TestCuraContainerRegistry.py @@ -4,6 +4,7 @@ import os #To find the directory with test files and find the test files. import unittest.mock #To mock and monkeypatch stuff. +from UM.Settings.DefinitionContainer import DefinitionContainer from cura.Settings.ExtruderStack import ExtruderStack #Testing for returning the correct types of stacks. from cura.Settings.GlobalStack import GlobalStack #Testing for returning the correct types of stacks. import UM.Settings.InstanceContainer #Creating instance containers to register. @@ -17,36 +18,40 @@ def teardown(): os.remove(temporary_file) ## Tests whether addContainer properly converts to ExtruderStack. -def test_addContainerExtruderStack(container_registry, definition_container): +def test_addContainerExtruderStack(container_registry, definition_container, definition_changes_container): container_registry.addContainer(definition_container) + container_registry.addContainer(definition_changes_container) container_stack = UM.Settings.ContainerStack.ContainerStack(stack_id = "Test Extruder Stack") #A container we're going to convert. container_stack.setMetaDataEntry("type", "extruder_train") #This is now an extruder train. container_stack.insertContainer(0, definition_container) #Add a definition to it so it doesn't complain. + container_stack.insertContainer(1, definition_changes_container) mock_super_add_container = unittest.mock.MagicMock() #Takes the role of the Uranium-ContainerRegistry where the resulting containers get registered. with unittest.mock.patch("UM.Settings.ContainerRegistry.ContainerRegistry.addContainer", mock_super_add_container): container_registry.addContainer(container_stack) - assert len(mock_super_add_container.call_args_list) == 2 #Called only once. - assert len(mock_super_add_container.call_args_list[1][0]) == 1 #Called with one parameter. - assert type(mock_super_add_container.call_args_list[1][0][0]) == ExtruderStack + assert len(mock_super_add_container.call_args_list) == 1 #Called only once. + assert len(mock_super_add_container.call_args_list[0][0]) == 1 #Called with one parameter. + assert type(mock_super_add_container.call_args_list[0][0][0]) == ExtruderStack ## Tests whether addContainer properly converts to GlobalStack. -def test_addContainerGlobalStack(container_registry, definition_container): +def test_addContainerGlobalStack(container_registry, definition_container, definition_changes_container): container_registry.addContainer(definition_container) + container_registry.addContainer(definition_changes_container) container_stack = UM.Settings.ContainerStack.ContainerStack(stack_id = "Test Global Stack") #A container we're going to convert. container_stack.setMetaDataEntry("type", "machine") #This is now a global stack. container_stack.insertContainer(0, definition_container) #Must have a definition. + container_stack.insertContainer(1, definition_changes_container) #Must have a definition changes. mock_super_add_container = unittest.mock.MagicMock() #Takes the role of the Uranium-ContainerRegistry where the resulting containers get registered. with unittest.mock.patch("UM.Settings.ContainerRegistry.ContainerRegistry.addContainer", mock_super_add_container): container_registry.addContainer(container_stack) - assert len(mock_super_add_container.call_args_list) == 2 #Called only once. - assert len(mock_super_add_container.call_args_list[1][0]) == 1 #Called with one parameter. - assert type(mock_super_add_container.call_args_list[1][0][0]) == GlobalStack + assert len(mock_super_add_container.call_args_list) == 1 #Called only once. + assert len(mock_super_add_container.call_args_list[0][0]) == 1 #Called with one parameter. + assert type(mock_super_add_container.call_args_list[0][0][0]) == GlobalStack def test_addContainerGoodSettingVersion(container_registry, definition_container): from cura.CuraApplication import CuraApplication @@ -92,38 +97,3 @@ def test_addContainerBadSettingVersion(container_registry, definition_container) container_registry.addContainer(instance) mock_super_add_container.assert_not_called() #Should not get passed on to UM.Settings.ContainerRegistry.addContainer, because the setting_version doesn't match its definition! - -## Tests whether loading gives objects of the correct type. -# @pytest.mark.parametrize("filename, output_class", [ -# ("ExtruderLegacy.stack.cfg", ExtruderStack), -# ("MachineLegacy.stack.cfg", GlobalStack), -# ("Left.extruder.cfg", ExtruderStack), -# ("Global.global.cfg", GlobalStack), -# ("Global.stack.cfg", GlobalStack) -# ]) -# def test_loadTypes(filename, output_class, container_registry): -# #Mock some dependencies. -# Resources.getAllResourcesOfType = unittest.mock.MagicMock(return_value = [os.path.join(os.path.dirname(os.path.abspath(__file__)), "stacks", filename)]) #Return just this tested file. -# -# def findContainers(container_type = 0, id = None): -# if id == "some_instance": -# return [UM.Settings.ContainerRegistry._EmptyInstanceContainer(id)] -# elif id == "some_definition": -# return [DefinitionContainer(container_id = id)] -# else: -# return [] -# -# container_registry.findContainers = findContainers -# -# with unittest.mock.patch("cura.Settings.GlobalStack.GlobalStack.findContainer"): -# with unittest.mock.patch("os.remove"): -# container_registry.load() -# -# #Check whether the resulting type was correct. -# stack_id = filename.split(".")[0] -# for container_id, container in container_registry._containers.items(): #Stupid ContainerRegistry class doesn't expose any way of getting at this except by prodding the privates. -# if container_id == stack_id: #This is the one we're testing. -# assert type(container) == output_class -# break -# else: -# assert False #Container stack with specified ID was not loaded. diff --git a/tests/Settings/conftest.py b/tests/Settings/conftest.py index 8b519fd261..c2d8854f05 100644 --- a/tests/Settings/conftest.py +++ b/tests/Settings/conftest.py @@ -5,6 +5,8 @@ import pytest +from UM.Settings.ContainerRegistry import ContainerRegistry +from UM.Settings.ContainerStack import setContainerRegistry from UM.Settings.DefinitionContainer import DefinitionContainer #To provide definition containers in the registry fixtures. from UM.Settings.InstanceContainer import InstanceContainer from cura.Settings.CuraContainerRegistry import CuraContainerRegistry @@ -15,29 +17,38 @@ import cura.Settings.CuraContainerStack # Returns the CuraContainerRegistry instance with some empty containers. @pytest.fixture() def container_registry(application) -> CuraContainerRegistry: - return application.getContainerRegistry() + ContainerRegistry._ContainerRegistry__instance= None # Need to reset since we only allow one instance + registry = CuraContainerRegistry(application) + setContainerRegistry(registry) + return registry # Gives an arbitrary definition container. @pytest.fixture() def definition_container() -> DefinitionContainer: return DefinitionContainer(container_id = "Test Definition") -#An empty global stack to test with. +# Gives an arbitrary definition changes container. @pytest.fixture() -def global_stack() -> GlobalStack: - global_stack = GlobalStack("TestGlobalStack") - # There is a restriction here that the definition changes cannot be an empty container. Added in CURA-5281 - definition_changes_container = InstanceContainer(container_id = "InstanceContainer") +def definition_changes_container() -> InstanceContainer: + definition_changes_container = InstanceContainer(container_id = "Test Definition Changes") definition_changes_container.setMetaDataEntry("type", "definition_changes") + # Add current setting version to the instance container + from cura.CuraApplication import CuraApplication + definition_changes_container.getMetaData()["setting_version"] = CuraApplication.SettingVersion + return definition_changes_container + +# An empty global stack to test with. +# There is a restriction here that the definition changes cannot be an empty container. Added in CURA-5281 +@pytest.fixture() +def global_stack(definition_changes_container) -> GlobalStack: + global_stack = GlobalStack("TestGlobalStack") global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.DefinitionChanges] = definition_changes_container return global_stack -## An empty extruder stack to test with. +# An empty extruder stack to test with. +# There is a restriction here that the definition changes cannot be an empty container. Added in CURA-5281 @pytest.fixture() -def extruder_stack() -> ExtruderStack: +def extruder_stack(definition_changes_container) -> ExtruderStack: extruder_stack= ExtruderStack("TestExtruderStack") - # There is a restriction here that the definition changes cannot be an empty container. Added in CURA-5281 - definition_changes_container = InstanceContainer(container_id = "InstanceContainer") - definition_changes_container.setMetaDataEntry("type", "definition_changes") extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.DefinitionChanges] = definition_changes_container return extruder_stack \ No newline at end of file diff --git a/tests/Settings/stacks/Complete.extruder.cfg b/tests/Settings/stacks/Complete.extruder.cfg deleted file mode 100644 index 789c0978f3..0000000000 --- a/tests/Settings/stacks/Complete.extruder.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[general] -version = 3 -name = Complete -id = Complete - -[containers] -0 = some_user_changes -1 = some_quality_changes -2 = some_quality -3 = some_material -4 = some_variant -5 = some_definition diff --git a/tests/Settings/stacks/Complete.global.cfg b/tests/Settings/stacks/Complete.global.cfg deleted file mode 100644 index f7f613991a..0000000000 --- a/tests/Settings/stacks/Complete.global.cfg +++ /dev/null @@ -1,13 +0,0 @@ -[general] -version = 3 -name = Complete -id = Complete - -[containers] -0 = some_user_changes -1 = some_quality_changes -2 = some_quality -3 = some_material -4 = some_variant -5 = some_definition_changes -6 = some_definition diff --git a/tests/Settings/stacks/ExtruderLegacy.stack.cfg b/tests/Settings/stacks/ExtruderLegacy.stack.cfg deleted file mode 100644 index 4a6c419e40..0000000000 --- a/tests/Settings/stacks/ExtruderLegacy.stack.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[general] -version = 3 -name = Legacy Extruder Stack -id = ExtruderLegacy - -[metadata] -type = extruder_train - -[containers] -3 = some_instance -5 = some_definition diff --git a/tests/Settings/stacks/Global.global.cfg b/tests/Settings/stacks/Global.global.cfg deleted file mode 100644 index 9034c1d0d0..0000000000 --- a/tests/Settings/stacks/Global.global.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Global -id = Global - -[containers] -3 = some_instance -6 = some_definition diff --git a/tests/Settings/stacks/Global.stack.cfg b/tests/Settings/stacks/Global.stack.cfg deleted file mode 100644 index aa1693d878..0000000000 --- a/tests/Settings/stacks/Global.stack.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[general] -version = 3 -name = Global -id = Global - -[metadata] -type = machine - -[containers] -3 = some_instance -6 = some_definition diff --git a/tests/Settings/stacks/Left.extruder.cfg b/tests/Settings/stacks/Left.extruder.cfg deleted file mode 100644 index 8ba45d6754..0000000000 --- a/tests/Settings/stacks/Left.extruder.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Left -id = Left - -[containers] -3 = some_instance -5 = some_definition diff --git a/tests/Settings/stacks/MachineLegacy.stack.cfg b/tests/Settings/stacks/MachineLegacy.stack.cfg deleted file mode 100644 index 147d63c596..0000000000 --- a/tests/Settings/stacks/MachineLegacy.stack.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[general] -version = 3 -name = Legacy Global Stack -id = MachineLegacy - -[metadata] -type = machine - -[containers] -3 = some_instance -6 = some_definition \ No newline at end of file diff --git a/tests/Settings/stacks/OnlyDefinition.extruder.cfg b/tests/Settings/stacks/OnlyDefinition.extruder.cfg deleted file mode 100644 index e58512b27f..0000000000 --- a/tests/Settings/stacks/OnlyDefinition.extruder.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[general] -version = 3 -name = Only Definition -id = OnlyDefinition - -[containers] -5 = some_definition diff --git a/tests/Settings/stacks/OnlyDefinition.global.cfg b/tests/Settings/stacks/OnlyDefinition.global.cfg deleted file mode 100644 index 9534353ed5..0000000000 --- a/tests/Settings/stacks/OnlyDefinition.global.cfg +++ /dev/null @@ -1,7 +0,0 @@ -[general] -version = 3 -name = Only Definition -id = OnlyDefinition - -[containers] -6 = some_definition diff --git a/tests/Settings/stacks/OnlyDefinitionChanges.global.cfg b/tests/Settings/stacks/OnlyDefinitionChanges.global.cfg deleted file mode 100644 index 39e2105b7d..0000000000 --- a/tests/Settings/stacks/OnlyDefinitionChanges.global.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only Definition Changes -id = OnlyDefinitionChanges - -[containers] -5 = some_instance -6 = some_definition diff --git a/tests/Settings/stacks/OnlyMaterial.extruder.cfg b/tests/Settings/stacks/OnlyMaterial.extruder.cfg deleted file mode 100644 index 49a9d12389..0000000000 --- a/tests/Settings/stacks/OnlyMaterial.extruder.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only Material -id = OnlyMaterial - -[containers] -3 = some_instance -5 = some_definition diff --git a/tests/Settings/stacks/OnlyMaterial.global.cfg b/tests/Settings/stacks/OnlyMaterial.global.cfg deleted file mode 100644 index 715651a9b9..0000000000 --- a/tests/Settings/stacks/OnlyMaterial.global.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only Material -id = OnlyMaterial - -[containers] -3 = some_instance -6 = some_definition diff --git a/tests/Settings/stacks/OnlyQuality.extruder.cfg b/tests/Settings/stacks/OnlyQuality.extruder.cfg deleted file mode 100644 index aaf7fb30c5..0000000000 --- a/tests/Settings/stacks/OnlyQuality.extruder.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only Quality -id = OnlyQuality - -[containers] -2 = some_instance -5 = some_definition diff --git a/tests/Settings/stacks/OnlyQuality.global.cfg b/tests/Settings/stacks/OnlyQuality.global.cfg deleted file mode 100644 index f07a35666e..0000000000 --- a/tests/Settings/stacks/OnlyQuality.global.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only Quality -id = OnlyQuality - -[containers] -2 = some_instance -6 = some_definition diff --git a/tests/Settings/stacks/OnlyQualityChanges.extruder.cfg b/tests/Settings/stacks/OnlyQualityChanges.extruder.cfg deleted file mode 100644 index 653bad840c..0000000000 --- a/tests/Settings/stacks/OnlyQualityChanges.extruder.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only Quality Changes -id = OnlyQualityChanges - -[containers] -1 = some_instance -5 = some_definition diff --git a/tests/Settings/stacks/OnlyQualityChanges.global.cfg b/tests/Settings/stacks/OnlyQualityChanges.global.cfg deleted file mode 100644 index 17d279377a..0000000000 --- a/tests/Settings/stacks/OnlyQualityChanges.global.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only Quality Changes -id = OnlyQualityChanges - -[containers] -1 = some_instance -6 = some_definition diff --git a/tests/Settings/stacks/OnlyUser.extruder.cfg b/tests/Settings/stacks/OnlyUser.extruder.cfg deleted file mode 100644 index abf812a859..0000000000 --- a/tests/Settings/stacks/OnlyUser.extruder.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only User -id = OnlyUser - -[containers] -0 = some_instance -5 = some_definition diff --git a/tests/Settings/stacks/OnlyUser.global.cfg b/tests/Settings/stacks/OnlyUser.global.cfg deleted file mode 100644 index 31371d2c51..0000000000 --- a/tests/Settings/stacks/OnlyUser.global.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only User -id = OnlyUser - -[containers] -0 = some_instance -6 = some_definition diff --git a/tests/Settings/stacks/OnlyVariant.extruder.cfg b/tests/Settings/stacks/OnlyVariant.extruder.cfg deleted file mode 100644 index a31997a6fd..0000000000 --- a/tests/Settings/stacks/OnlyVariant.extruder.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only Variant -id = OnlyVariant - -[containers] -4 = some_instance -5 = some_definition diff --git a/tests/Settings/stacks/OnlyVariant.global.cfg b/tests/Settings/stacks/OnlyVariant.global.cfg deleted file mode 100644 index 158d533ac8..0000000000 --- a/tests/Settings/stacks/OnlyVariant.global.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[general] -version = 3 -name = Only Variant -id = OnlyVariant - -[containers] -4 = some_instance -6 = some_definition diff --git a/tests/conftest.py b/tests/conftest.py index d5bb5c8109..f2c709d8d8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,4 +20,4 @@ def application() -> CuraApplication: # Returns a MachineActionManager instance. @pytest.fixture() def machine_action_manager(application) -> MachineActionManager: - return application.getMachineActionManager() + return MachineActionManager(application) From 3a7fff42b5d8c07c1cd17b69e5170c69c2d8c001 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 15 Aug 2018 09:14:37 +0200 Subject: [PATCH 75/97] Fix code style. Contributes to CURA-5628. --- cura/OneAtATimeIterator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py index e7ad833e1c..a08f3ed2bf 100644 --- a/cura/OneAtATimeIterator.py +++ b/cura/OneAtATimeIterator.py @@ -76,6 +76,8 @@ class OneAtATimeIterator(Iterator): continue bounding_box = node.getBoundingBox() + if not bounding_box: + continue from UM.Math.Polygon import Polygon bounding_box_polygon = Polygon([[bounding_box.left, bounding_box.front], [bounding_box.left, bounding_box.back], From da39e842f111206b9c362e4a6f354cc3b9ac06ce Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 15 Aug 2018 09:25:24 +0200 Subject: [PATCH 76/97] Move getting diagonal size out to separate function --- cura/BuildVolume.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 667247eac9..5aa3873c89 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -171,6 +171,12 @@ class BuildVolume(SceneNode): if shape: self._shape = shape + ## Get the length of the 3D diagonal through the build volume. + # + # This gives a sense of the scale of the build volume in general. + def getDiagonalSize(self) -> float: + return math.sqrt(self._width * self._width + self._height * self._height + self._depth * self._depth) + def getDisallowedAreas(self) -> List[Polygon]: return self._disallowed_areas @@ -553,10 +559,9 @@ class BuildVolume(SceneNode): if self._engine_ready: self.rebuild() - diagonal_size = math.sqrt(self._width * self._width + self._height * self._height + self._depth * self._depth) camera = Application.getInstance().getController().getCameraTool() if camera: - camera.setZoomRange(min = 1, max = diagonal_size * 5) #You can zoom out up to 5 times the diagonal across your screen (to see models bigger than your volume). + camera.setZoomRange(min = 1, max = self.getDiagonalSize() * 5) #You can zoom out up to 5 times the diagonal. This gives some space around the volume. def _onEngineCreated(self): self._engine_ready = True From f1a7b23a5ca6b517396b5a51fce28a65fb5fb1c5 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 15 Aug 2018 09:52:39 +0200 Subject: [PATCH 77/97] Adjust default position of camera based on diagonal size --- cura/CuraActions.py | 3 ++- cura/CuraApplication.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 1b2c6c576c..93a18318df 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -50,7 +50,8 @@ class CuraActions(QObject): scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene() camera = scene.getActiveCamera() if camera: - camera.setPosition(Vector(-80, 250, 700)) + diagonal_size = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getDiagonalSize() + camera.setPosition(Vector(-80, 250, 700) * diagonal_size / 375) camera.setPerspective(True) camera.lookAt(Vector(0, 0, 0)) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 6164fc8756..955bc4df5f 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -776,7 +776,7 @@ class CuraApplication(QtApplication): # Initialize camera root = controller.getScene().getRoot() camera = Camera("3d", root) - camera.setPosition(Vector(-80, 250, 700)) + camera.setPosition(Vector(-80, 250, 700) * self.getBuildVolume().getDiagonalSize() / 375) camera.setPerspective(True) camera.lookAt(Vector(0, 0, 0)) controller.getScene().setActiveCamera("3d") From c20274e356f043e5a56393f5ae2647e48168b1fe Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 15 Aug 2018 11:04:09 +0200 Subject: [PATCH 78/97] Move constant instance containers to a separate file This way we separate the class/type definitions and the actual constants. --- cura/CuraApplication.py | 25 +++++---- cura/CuraEmptyInstanceContainers.py | 29 ---------- cura/Settings/CuraContainerStack.py | 13 ++--- .../cura_empty_instance_containers.py | 56 +++++++++++++++++++ tests/Settings/TestExtruderStack.py | 2 +- tests/Settings/TestGlobalStack.py | 3 +- 6 files changed, 78 insertions(+), 50 deletions(-) delete mode 100644 cura/CuraEmptyInstanceContainers.py create mode 100644 cura/Settings/cura_empty_instance_containers.py diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 908b718e1b..0d110ced54 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -104,7 +104,7 @@ from cura.Settings.ExtrudersModel import ExtrudersModel from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.ContainerManager import ContainerManager from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel -import cura.CuraEmptyInstanceContainers +import cura.Settings.cura_empty_instance_containers from cura.ObjectsModel import ObjectsModel @@ -368,22 +368,23 @@ class CuraApplication(QtApplication): # Add empty variant, material and quality containers. # Since they are empty, they should never be serialized and instead just programmatically created. # We need them to simplify the switching between materials. - self.empty_container = cura.CuraEmptyInstanceContainers.empty_container + self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container - self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_definition_changes_container) - self.empty_definition_changes_container = cura.CuraEmptyInstanceContainers.empty_definition_changes_container + self._container_registry.addContainer( + cura.Settings.cura_empty_instance_containers.empty_definition_changes_container) + self.empty_definition_changes_container = cura.Settings.cura_empty_instance_containers.empty_definition_changes_container - self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_variant_container) - self.empty_variant_container = cura.CuraEmptyInstanceContainers.empty_variant_container + self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_variant_container) + self.empty_variant_container = cura.Settings.cura_empty_instance_containers.empty_variant_container - self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_material_container) - self.empty_material_container = cura.CuraEmptyInstanceContainers.empty_material_container + self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_material_container) + self.empty_material_container = cura.Settings.cura_empty_instance_containers.empty_material_container - self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_quality_container) - self.empty_quality_container = cura.CuraEmptyInstanceContainers.empty_quality_container + self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_quality_container) + self.empty_quality_container = cura.Settings.cura_empty_instance_containers.empty_quality_container - self._container_registry.addContainer(cura.CuraEmptyInstanceContainers.empty_quality_changes_container) - self.empty_quality_changes_container = cura.CuraEmptyInstanceContainers.empty_quality_changes_container + self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_quality_changes_container) + self.empty_quality_changes_container = cura.Settings.cura_empty_instance_containers.empty_quality_changes_container # Initializes the version upgrade manager with by providing the paths for each resource type and the latest # versions. diff --git a/cura/CuraEmptyInstanceContainers.py b/cura/CuraEmptyInstanceContainers.py deleted file mode 100644 index 3f5d3e7c59..0000000000 --- a/cura/CuraEmptyInstanceContainers.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2018 Ultimaker B.V. -# Cura is released under the terms of the LGPLv3 or higher. - -from UM.Settings.EmptyInstanceContainer import empty_container -import copy - -empty_definition_changes_container = copy.deepcopy(empty_container) -empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes") -empty_definition_changes_container.setMetaDataEntry("type", "definition_changes") - -empty_variant_container = copy.deepcopy(empty_container) -empty_variant_container.setMetaDataEntry("id", "empty_variant") -empty_variant_container.setMetaDataEntry("type", "variant") - -empty_material_container = copy.deepcopy(empty_container) -empty_material_container.setMetaDataEntry("id", "empty_material") -empty_material_container.setMetaDataEntry("type", "material") - -empty_quality_container = copy.deepcopy(empty_container) -empty_quality_container.setMetaDataEntry("id", "empty_quality") -empty_quality_container.setName("Not Supported") -empty_quality_container.setMetaDataEntry("quality_type", "not_supported") -empty_quality_container.setMetaDataEntry("type", "quality") -empty_quality_container.setMetaDataEntry("supported", False) - -empty_quality_changes_container = copy.deepcopy(empty_container) -empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") -empty_quality_changes_container.setMetaDataEntry("type", "quality_changes") -empty_quality_changes_container.setMetaDataEntry("quality_type", "not_supported") diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index dabed97011..c8d1d9e370 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -13,8 +13,7 @@ from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.Interfaces import ContainerInterface, DefinitionContainerInterface -from UM.Settings.EmptyInstanceContainer import empty_container -from cura import CuraEmptyInstanceContainers +from cura.Settings import cura_empty_instance_containers from . import Exceptions @@ -41,12 +40,12 @@ class CuraContainerStack(ContainerStack): def __init__(self, container_id: str) -> None: super().__init__(container_id) - self._empty_instance_container = empty_container #type: InstanceContainer + self._empty_instance_container = cura_empty_instance_containers.empty_container #type: InstanceContainer - self._empty_quality_changes = CuraEmptyInstanceContainers.empty_quality_changes_container #type: InstanceContainer - self._empty_quality = CuraEmptyInstanceContainers.empty_quality_container #type: InstanceContainer - self._empty_material = CuraEmptyInstanceContainers.empty_material_container #type: InstanceContainer - self._empty_variant = CuraEmptyInstanceContainers.empty_variant_container #type: InstanceContainer + self._empty_quality_changes = cura_empty_instance_containers.empty_quality_changes_container #type: InstanceContainer + self._empty_quality = cura_empty_instance_containers.empty_quality_container #type: InstanceContainer + self._empty_material = cura_empty_instance_containers.empty_material_container #type: InstanceContainer + self._empty_variant = cura_empty_instance_containers.empty_variant_container #type: InstanceContainer self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))] #type: List[ContainerInterface] self._containers[_ContainerIndexes.QualityChanges] = self._empty_quality_changes diff --git a/cura/Settings/cura_empty_instance_containers.py b/cura/Settings/cura_empty_instance_containers.py new file mode 100644 index 0000000000..d76407ed79 --- /dev/null +++ b/cura/Settings/cura_empty_instance_containers.py @@ -0,0 +1,56 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import copy + +from UM.Settings.constant_instance_containers import EMPTY_CONTAINER_ID, empty_container + + +# Empty definition changes +EMPTY_DEFINITION_CHANGES_CONTAINER_ID = "empty_definition_changes" +empty_definition_changes_container = copy.deepcopy(empty_container) +empty_definition_changes_container.setMetaDataEntry("id", EMPTY_DEFINITION_CHANGES_CONTAINER_ID) +empty_definition_changes_container.setMetaDataEntry("type", "definition_changes") + +# Empty variant +EMPTY_VARIANT_CONTAINER_ID = "empty_variant" +empty_variant_container = copy.deepcopy(empty_container) +empty_variant_container.setMetaDataEntry("id", EMPTY_VARIANT_CONTAINER_ID) +empty_variant_container.setMetaDataEntry("type", "variant") + +# Empty material +EMPTY_MATERIAL_CONTAINER_ID = "empty_material" +empty_material_container = copy.deepcopy(empty_container) +empty_material_container.setMetaDataEntry("id", EMPTY_MATERIAL_CONTAINER_ID) +empty_material_container.setMetaDataEntry("type", "material") + +# Empty quality +EMPTY_QUALITY_CONTAINER_ID = "empty_quality" +empty_quality_container = copy.deepcopy(empty_container) +empty_quality_container.setMetaDataEntry("id", EMPTY_QUALITY_CONTAINER_ID) +empty_quality_container.setName("Not Supported") +empty_quality_container.setMetaDataEntry("quality_type", "not_supported") +empty_quality_container.setMetaDataEntry("type", "quality") +empty_quality_container.setMetaDataEntry("supported", False) + +# Empty quality changes +EMPTY_QUALITY_CHANGES_CONTAINER_ID = "empty_quality_changes" +empty_quality_changes_container = copy.deepcopy(empty_container) +empty_quality_changes_container.setMetaDataEntry("id", EMPTY_QUALITY_CHANGES_CONTAINER_ID) +empty_quality_changes_container.setMetaDataEntry("type", "quality_changes") +empty_quality_changes_container.setMetaDataEntry("quality_type", "not_supported") + + +__all__ = ["EMPTY_CONTAINER_ID", + "empty_container", # For convenience + "EMPTY_DEFINITION_CHANGES_CONTAINER_ID", + "empty_definition_changes_container", + "EMPTY_VARIANT_CONTAINER_ID", + "empty_variant_container", + "EMPTY_MATERIAL_CONTAINER_ID", + "empty_material_container", + "EMPTY_QUALITY_CHANGES_CONTAINER_ID", + "empty_quality_changes_container", + "EMPTY_QUALITY_CONTAINER_ID", + "empty_quality_container" + ] diff --git a/tests/Settings/TestExtruderStack.py b/tests/Settings/TestExtruderStack.py index b2fa171068..df2e1075d1 100644 --- a/tests/Settings/TestExtruderStack.py +++ b/tests/Settings/TestExtruderStack.py @@ -9,9 +9,9 @@ import UM.Settings.ContainerRegistry #To create empty instance containers. import UM.Settings.ContainerStack #To set the container registry the container stacks use. from UM.Settings.DefinitionContainer import DefinitionContainer #To check against the class of DefinitionContainer. from UM.Settings.InstanceContainer import InstanceContainer #To check against the class of InstanceContainer. -from UM.Settings.EmptyInstanceContainer import empty_container from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationError #To check whether the correct exceptions are raised. from cura.Settings.ExtruderManager import ExtruderManager +from cura.Settings.cura_empty_instance_containers import empty_container ## Gets an instance container with a specified container type. # diff --git a/tests/Settings/TestGlobalStack.py b/tests/Settings/TestGlobalStack.py index 7aba53c4e5..f8052aa4bb 100755 --- a/tests/Settings/TestGlobalStack.py +++ b/tests/Settings/TestGlobalStack.py @@ -9,11 +9,12 @@ from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationErro from UM.Settings.DefinitionContainer import DefinitionContainer #To test against the class DefinitionContainer. from UM.Settings.InstanceContainer import InstanceContainer #To test against the class InstanceContainer. from UM.Settings.SettingInstance import InstanceState -from UM.Settings.EmptyInstanceContainer import empty_container import UM.Settings.ContainerRegistry import UM.Settings.ContainerStack import UM.Settings.SettingDefinition #To add settings to the definition. +from cura.Settings.cura_empty_instance_containers import empty_container + ## Gets an instance container with a specified container type. # # \param container_type The type metadata for the instance container. From e1e9c600160aba08345771a02dfeda365f80fdce Mon Sep 17 00:00:00 2001 From: Simon Edwards Date: Wed, 15 Aug 2018 13:56:51 +0200 Subject: [PATCH 79/97] Send UFP to Ultimakers provided the firmware version is sufficient. CL-980 --- plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index 84e0a66170..e85961f619 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -113,7 +113,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";") machine_file_formats = [file_type.strip() for file_type in machine_file_formats] #Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format. - if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"): + if "application/x-ufp" not in machine_file_formats and Version(self.firmwareVersion) >= Version("4.4"): machine_file_formats = ["application/x-ufp"] + machine_file_formats # Take the intersection between file_formats and machine_file_formats. @@ -590,4 +590,4 @@ def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: s for item in list: if item.key == key: return item - return None \ No newline at end of file + return None From 6c1d380602ed9ff0fc901716d2376ff3969fb55b Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Thu, 16 Aug 2018 13:45:27 +0200 Subject: [PATCH 80/97] Renamed snap_distance to minimum_polygon_circumference and changed the description a bit. CURA-5623 --- resources/definitions/fdmprinter.def.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 8b3e4e1f05..4ea5f8a600 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -5625,10 +5625,10 @@ "settable_per_mesh": false, "settable_per_extruder": true }, - "snap_distance": + "minimum_polygon_circumference": { - "label": "Snap Distance", - "description": "Snap distance between polygons in mm. Controls the polygons that may be filtered out when the model is sliced. Lower values lead to higher resolution mesh at the cost of slicing time. It is meant mostly for high resolution SLA printers and very tiny 3D models with a lot of details.", + "label": "Minimum polygon circumference", + "description": "Polygons in sliced layers that have a circumference smaller than this amount will be filtered out. Lower values lead to higher resolution mesh at the cost of slicing time. It is meant mostly for high resolution SLA printers and very tiny 3D models with a lot of details.", "unit": "mm", "type": "float", "default_value": 1.0, From 376b6a53c935035ac9dc1b0aab29360d9b18a762 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Thu, 16 Aug 2018 13:47:32 +0200 Subject: [PATCH 81/97] Capital Letters In Setting Name. CURA-5623 --- resources/definitions/fdmprinter.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index daf0af4d64..d26b2b03f4 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -5628,7 +5628,7 @@ }, "minimum_polygon_circumference": { - "label": "Minimum polygon circumference", + "label": "Minimum Polygon Circumference", "description": "Polygons in sliced layers that have a circumference smaller than this amount will be filtered out. Lower values lead to higher resolution mesh at the cost of slicing time. It is meant mostly for high resolution SLA printers and very tiny 3D models with a lot of details.", "unit": "mm", "type": "float", From f42dc24d950df902cae9cf76dd817e7f2d13a9a2 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 16 Aug 2018 15:16:30 +0200 Subject: [PATCH 82/97] Do not show gcode.gz as an option in save file dialog CURA-5649 --- cura/CuraApplication.py | 20 -------------------- cura/Settings/GlobalStack.py | 4 ++++ plugins/GCodeGzWriter/__init__.py | 3 ++- resources/qml/SaveButton.qml | 4 +++- 4 files changed, 9 insertions(+), 22 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 23a11a2bb3..dab3de3ab8 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -215,7 +215,6 @@ class CuraApplication(QtApplication): self._message_box_callback = None self._message_box_callback_arguments = [] - self._preferred_mimetype = "" self._i18n_catalog = None self._currently_loading_files = [] @@ -514,9 +513,6 @@ class CuraApplication(QtApplication): self.applicationShuttingDown.connect(self.saveSettings) self.engineCreatedSignal.connect(self._onEngineCreated) - self.globalContainerStackChanged.connect(self._onGlobalContainerChanged) - self._onGlobalContainerChanged() - self.getCuraSceneController().setActiveBuildPlate(0) # Initialize CuraApplication.Created = True @@ -997,30 +993,14 @@ class CuraApplication(QtApplication): self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition()) self._camera_animation.start() - def _onGlobalContainerChanged(self): - if self._global_container_stack is not None: - machine_file_formats = [file_type.strip() for file_type in self._global_container_stack.getMetaDataEntry("file_formats").split(";")] - new_preferred_mimetype = "" - if machine_file_formats: - new_preferred_mimetype = machine_file_formats[0] - - if new_preferred_mimetype != self._preferred_mimetype: - self._preferred_mimetype = new_preferred_mimetype - self.preferredOutputMimetypeChanged.emit() - requestAddPrinter = pyqtSignal() activityChanged = pyqtSignal() sceneBoundingBoxChanged = pyqtSignal() - preferredOutputMimetypeChanged = pyqtSignal() @pyqtProperty(bool, notify = activityChanged) def platformActivity(self): return self._platform_activity - @pyqtProperty(str, notify=preferredOutputMimetypeChanged) - def preferredOutputMimetype(self): - return self._preferred_mimetype - @pyqtProperty(str, notify = sceneBoundingBoxChanged) def getSceneBoundingBoxString(self): return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()} diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index dda21f3719..36084b7d4d 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -61,6 +61,10 @@ class GlobalStack(CuraContainerStack): name = self.variant.getName() return name + @pyqtProperty(str, constant = True) + def preferred_output_file_formats(self) -> str: + return self.getMetaDataEntry("file_formats") + ## Add an extruder to the list of extruders of this stack. # # \param extruder The extruder to add. diff --git a/plugins/GCodeGzWriter/__init__.py b/plugins/GCodeGzWriter/__init__.py index e257bcb011..95949eee74 100644 --- a/plugins/GCodeGzWriter/__init__.py +++ b/plugins/GCodeGzWriter/__init__.py @@ -16,7 +16,8 @@ def getMetaData(): "extension": file_extension, "description": catalog.i18nc("@item:inlistbox", "Compressed G-code File"), "mime_type": "application/gzip", - "mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode + "mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode, + "hide_in_file_dialog": True, }] } } diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index 0e0eec7277..2a0a523026 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -7,6 +7,7 @@ import QtQuick.Controls.Styles 1.1 import QtQuick.Layouts 1.1 import UM 1.1 as UM +import Cura 1.0 as Cura Item { id: base; @@ -257,7 +258,8 @@ Item { onClicked: { forceActiveFocus(); - UM.OutputDeviceManager.requestWriteToDevice(UM.OutputDeviceManager.activeDevice, PrintInformation.jobName, { "filter_by_machine": true, "preferred_mimetype":Printer.preferredOutputMimetype }); + UM.OutputDeviceManager.requestWriteToDevice(UM.OutputDeviceManager.activeDevice, PrintInformation.jobName, + { "filter_by_machine": true, "preferred_mimetypes": Cura.MachineManager.activeMachine.preferred_output_file_formats }); } style: ButtonStyle { From 31e283110f97189e7b5a38b025969fe25ed7e8e7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 17 Aug 2018 09:29:08 +0200 Subject: [PATCH 83/97] Check first if preferred quality exists Fixes #3784. --- cura/Settings/CuraStackBuilder.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index a794dde651..31ebc04de2 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -108,16 +108,20 @@ class CuraStackBuilder: preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type") quality_group_dict = quality_manager.getQualityGroups(new_global_stack) - quality_group = quality_group_dict.get(preferred_quality_type) + if quality_group_dict: #There are any quality profiles. + if preferred_quality_type not in quality_group_dict: + Logger.log("w", "The preferred quality {quality_type} doesn't exist for this set-up. Choosing a random one.".format(quality_type = preferred_quality_type)) + preferred_quality_type = next(iter(quality_group_dict)) + quality_group = quality_group_dict.get(preferred_quality_type) - new_global_stack.quality = quality_group.node_for_global.getContainer() - if not new_global_stack.quality: - new_global_stack.quality = application.empty_quality_container - for position, extruder_stack in new_global_stack.extruders.items(): - if position in quality_group.nodes_for_extruders and quality_group.nodes_for_extruders[position].getContainer(): - extruder_stack.quality = quality_group.nodes_for_extruders[position].getContainer() - else: - extruder_stack.quality = application.empty_quality_container + new_global_stack.quality = quality_group.node_for_global.getContainer() + if not new_global_stack.quality: + new_global_stack.quality = application.empty_quality_container + for position, extruder_stack in new_global_stack.extruders.items(): + if position in quality_group.nodes_for_extruders and quality_group.nodes_for_extruders[position].getContainer(): + extruder_stack.quality = quality_group.nodes_for_extruders[position].getContainer() + else: + extruder_stack.quality = application.empty_quality_container # Register the global stack after the extruder stacks are created. This prevents the registry from adding another # extruder stack because the global stack didn't have one yet (which is enforced since Cura 3.1). From 013032279db10b858b5bbd8b2115de762723665e Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 17 Aug 2018 10:00:28 +0200 Subject: [PATCH 84/97] Better handling of no quality group in CuraStackBuilder --- cura/Settings/CuraStackBuilder.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 31ebc04de2..12fe732e3e 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -108,7 +108,14 @@ class CuraStackBuilder: preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type") quality_group_dict = quality_manager.getQualityGroups(new_global_stack) - if quality_group_dict: #There are any quality profiles. + if not quality_group_dict: + # There is no available quality group, set all quality containers to empty. + new_global_stack.quality = application.empty_quality_container + for extruder_stack in new_global_stack.extruders.values(): + extruder_stack.quality = application.empty_quality_container + else: + # Set the quality containers to the preferred quality type if available, otherwise use the first quality + # type that's available. if preferred_quality_type not in quality_group_dict: Logger.log("w", "The preferred quality {quality_type} doesn't exist for this set-up. Choosing a random one.".format(quality_type = preferred_quality_type)) preferred_quality_type = next(iter(quality_group_dict)) From 1079a9be2c72de8d4c32895701e9842e1521fd09 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 17 Aug 2018 10:53:01 +0200 Subject: [PATCH 85/97] Do not add saved gcodes to recent files list --- plugins/GCodeGzWriter/GCodeGzWriter.py | 4 ++++ plugins/GCodeWriter/GCodeWriter.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/GCodeGzWriter/GCodeGzWriter.py b/plugins/GCodeGzWriter/GCodeGzWriter.py index e191a9c427..cbbfb8f986 100644 --- a/plugins/GCodeGzWriter/GCodeGzWriter.py +++ b/plugins/GCodeGzWriter/GCodeGzWriter.py @@ -17,6 +17,10 @@ catalog = i18nCatalog("cura") # # If you're zipping g-code, you might as well use gzip! class GCodeGzWriter(MeshWriter): + + def __init__(self) -> None: + super().__init__(add_to_recent_files = False) + ## Writes the gzipped g-code to a stream. # # Note that even though the function accepts a collection of nodes, the diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index 59e9a29691..5d5e3578cd 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -47,7 +47,7 @@ class GCodeWriter(MeshWriter): _setting_keyword = ";SETTING_" def __init__(self): - super().__init__() + super().__init__(add_to_recent_files = False) self._application = Application.getInstance() From 284f90f3ff7ec3422fe490d1bd0942f506fc35af Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 17 Aug 2018 09:57:37 +0200 Subject: [PATCH 86/97] Fix camera position before adding a printer Don't use the diagonal size then, because that returns 0. --- cura/CuraApplication.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 23a11a2bb3..ef59d5a7f6 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -780,7 +780,10 @@ class CuraApplication(QtApplication): # Initialize camera root = controller.getScene().getRoot() camera = Camera("3d", root) - camera.setPosition(Vector(-80, 250, 700) * self.getBuildVolume().getDiagonalSize() / 375) + diagonal = self.getBuildVolume().getDiagonalSize() + if diagonal < 1: #No printer added yet. Set a default camera distance for normal-sized printers. + diagonal = 375 + camera.setPosition(Vector(-80, 250, 700) * diagonal / 375) camera.setPerspective(True) camera.lookAt(Vector(0, 0, 0)) controller.getScene().setActiveCamera("3d") From 9f190590b1e5ceac0fcd0942bfcf39807071d617 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 17 Aug 2018 10:56:29 +0200 Subject: [PATCH 87/97] Only change camera range if camera range is valid This was a debugging test to see if this fixed an issue. It turned out to not be the issue in question, but this is still a defensive coding thing that would be good to have. --- cura/BuildVolume.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 5aa3873c89..a25552aac5 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -561,7 +561,9 @@ class BuildVolume(SceneNode): camera = Application.getInstance().getController().getCameraTool() if camera: - camera.setZoomRange(min = 1, max = self.getDiagonalSize() * 5) #You can zoom out up to 5 times the diagonal. This gives some space around the volume. + diagonal = self.getDiagonalSize() + if diagonal > 1: + camera.setZoomRange(min = 0.1, max = diagonal * 5) #You can zoom out up to 5 times the diagonal. This gives some space around the volume. def _onEngineCreated(self): self._engine_ready = True From 6740c2bee9a0732daf77224cd4ef34f0eb736364 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 17 Aug 2018 11:24:39 +0200 Subject: [PATCH 88/97] Fix offset of initial layer height It would detect the height of the raft by looking at what the first Z coordinate is that it encounters on layer 0. This Z coordinate also includes the initial layer height though. If you pause lower than the initial layer height (but higher than 0), you'd expect to pause in the initial layer. --- plugins/PostProcessingPlugin/scripts/PauseAtHeight.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py index 6354dd4f04..8b50a88b7f 100644 --- a/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py +++ b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py @@ -28,7 +28,7 @@ class PauseAtHeight(Script): "pause_height": { "label": "Pause Height", - "description": "At what height should the pause occur", + "description": "At what height should the pause occur?", "unit": "mm", "type": "float", "default_value": 5.0, @@ -39,7 +39,7 @@ class PauseAtHeight(Script): "pause_layer": { "label": "Pause Layer", - "description": "At what layer should the pause occur", + "description": "At what layer should the pause occur?", "type": "int", "value": "math.floor((pause_height - 0.27) / 0.1) + 1", "minimum_value": "0", @@ -142,13 +142,14 @@ class PauseAtHeight(Script): standby_temperature = self.getSettingValueByKey("standby_temperature") firmware_retract = Application.getInstance().getGlobalContainerStack().getProperty("machine_firmware_retract", "value") control_temperatures = Application.getInstance().getGlobalContainerStack().getProperty("machine_nozzle_temp_enabled", "value") + initial_layer_height = Application.getInstance().getGlobalContainerStack().getProperty("layer_height_0", "value") is_griffin = False # T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value") # use offset to calculate the current height: = - - layer_0_z = 0. + layer_0_z = 0 current_z = 0 got_first_g_cmd_on_layer_0 = False current_t = 0 #Tracks the current extruder for tracking the target temperature. @@ -195,11 +196,10 @@ class PauseAtHeight(Script): # This block is executed once, the first time there is a G # command, to get the z offset (z for first positive layer) if not got_first_g_cmd_on_layer_0: - layer_0_z = current_z + layer_0_z = current_z - initial_layer_height got_first_g_cmd_on_layer_0 = True current_height = current_z - layer_0_z - if current_height < pause_height: break # Try the next layer. From 1468ac1d59ec4fbf4472c03b7d7a727c636c2a6e Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 20 Aug 2018 13:19:49 +0200 Subject: [PATCH 89/97] Check if dict has key before accessing in BuildVolume --- cura/BuildVolume.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index a25552aac5..b029665abd 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -242,6 +242,8 @@ class BuildVolume(SceneNode): # Mark the node as outside build volume if the set extruder is disabled extruder_position = node.callDecoration("getActiveExtruderPosition") + if extruder_position not in self._global_container_stack.extruders: + continue if not self._global_container_stack.extruders[extruder_position].isEnabled: node.setOutsideBuildArea(True) continue From ca25638c07d699002980dab7564a9b5743582b50 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 21 Aug 2018 11:01:35 +0200 Subject: [PATCH 90/97] .ufp should also not show up in recent files since it's a gcode format --- .pytest_cache/README.md | 8 +++ .pytest_cache/v/cache/lastfailed | 100 +++++++++++++++++++++++++++++++ .pytest_cache/v/cache/nodeids | 3 + plugins/UFPWriter/UFPWriter.py | 2 +- 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 .pytest_cache/README.md create mode 100644 .pytest_cache/v/cache/lastfailed create mode 100644 .pytest_cache/v/cache/nodeids diff --git a/.pytest_cache/README.md b/.pytest_cache/README.md new file mode 100644 index 0000000000..bb78ba07ee --- /dev/null +++ b/.pytest_cache/README.md @@ -0,0 +1,8 @@ +# pytest cache directory # + +This directory contains data from the pytest's cache plugin, +which provides the `--lf` and `--ff` options, as well as the `cache` fixture. + +**Do not** commit this to version control. + +See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information. diff --git a/.pytest_cache/v/cache/lastfailed b/.pytest_cache/v/cache/lastfailed new file mode 100644 index 0000000000..adb96c8331 --- /dev/null +++ b/.pytest_cache/v/cache/lastfailed @@ -0,0 +1,100 @@ +{ + "tests/Settings/TestCuraContainerRegistry.py::test_addContainerBadSettingVersion": true, + "tests/Settings/TestCuraContainerRegistry.py::test_addContainerExtruderStack": true, + "tests/Settings/TestCuraContainerRegistry.py::test_addContainerGlobalStack": true, + "tests/Settings/TestCuraContainerRegistry.py::test_addContainerGoodSettingVersion": true, + "tests/Settings/TestCuraContainerRegistry.py::test_addContainerNoSettingVersion": true, + "tests/Settings/TestExtruderStack.py::test_addContainer": true, + "tests/Settings/TestExtruderStack.py::test_constrainDefinitionValid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainDefinitionValid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_constrainMaterialInvalid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainMaterialInvalid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_constrainMaterialInvalid[container2]": true, + "tests/Settings/TestExtruderStack.py::test_constrainMaterialValid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainMaterialValid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_constrainQualityChangesInvalid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainQualityChangesInvalid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_constrainQualityChangesInvalid[container2]": true, + "tests/Settings/TestExtruderStack.py::test_constrainQualityChangesValid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainQualityChangesValid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_constrainQualityInvalid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainQualityInvalid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_constrainQualityInvalid[container2]": true, + "tests/Settings/TestExtruderStack.py::test_constrainQualityValid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainQualityValid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_constrainUserChangesInvalid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainUserChangesInvalid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_constrainUserChangesInvalid[container2]": true, + "tests/Settings/TestExtruderStack.py::test_constrainUserChangesValid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainUserChangesValid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_constrainVariantInvalid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainVariantInvalid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_constrainVariantValid[container0]": true, + "tests/Settings/TestExtruderStack.py::test_constrainVariantValid[container1]": true, + "tests/Settings/TestExtruderStack.py::test_deserializeMoveInstanceContainer": true, + "tests/Settings/TestExtruderStack.py::test_deserializeRemovesWrongContainerClass": true, + "tests/Settings/TestExtruderStack.py::test_deserializeRemovesWrongInstanceContainer": true, + "tests/Settings/TestExtruderStack.py::test_deserializeWrongDefinitionClass": true, + "tests/Settings/TestExtruderStack.py::test_getPropertyFallThrough": true, + "tests/Settings/TestExtruderStack.py::test_insertContainer": true, + "tests/Settings/TestExtruderStack.py::test_removeContainer": true, + "tests/Settings/TestExtruderStack.py::test_setPropertyUser[foo-value-100]": true, + "tests/Settings/TestExtruderStack.py::test_setPropertyUser[layer_height-default_value-0.1337]": true, + "tests/Settings/TestExtruderStack.py::test_setPropertyUser[layer_height-is_bright_pink-of course]": true, + "tests/Settings/TestExtruderStack.py::test_setPropertyUser[layer_height-value-0.1337]": true, + "tests/Settings/TestExtruderStack.py::test_setPropertyUser[support_enabled-value-True]": true, + "tests/Settings/TestGlobalStack.py::test_addContainer": true, + "tests/Settings/TestGlobalStack.py::test_addExtruder": true, + "tests/Settings/TestGlobalStack.py::test_constrainDefinitionChangesInvalid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainDefinitionChangesInvalid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainDefinitionChangesInvalid[container2]": true, + "tests/Settings/TestGlobalStack.py::test_constrainDefinitionChangesValid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainDefinitionChangesValid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainDefinitionValid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainDefinitionValid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainMaterialInvalid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainMaterialInvalid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainMaterialInvalid[container2]": true, + "tests/Settings/TestGlobalStack.py::test_constrainMaterialValid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainMaterialValid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainQualityChangesInvalid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainQualityChangesInvalid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainQualityChangesInvalid[container2]": true, + "tests/Settings/TestGlobalStack.py::test_constrainQualityChangesValid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainQualityChangesValid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainQualityInvalid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainQualityInvalid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainQualityInvalid[container2]": true, + "tests/Settings/TestGlobalStack.py::test_constrainQualityValid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainQualityValid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainUserChangesInvalid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainUserChangesInvalid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainUserChangesInvalid[container2]": true, + "tests/Settings/TestGlobalStack.py::test_constrainUserChangesValid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainUserChangesValid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainVariantInvalid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainVariantInvalid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_constrainVariantValid[container0]": true, + "tests/Settings/TestGlobalStack.py::test_constrainVariantValid[container1]": true, + "tests/Settings/TestGlobalStack.py::test_deserializeMoveInstanceContainer": true, + "tests/Settings/TestGlobalStack.py::test_deserializeRemovesWrongContainerClass": true, + "tests/Settings/TestGlobalStack.py::test_deserializeRemovesWrongInstanceContainer": true, + "tests/Settings/TestGlobalStack.py::test_deserializeWrongDefinitionClass": true, + "tests/Settings/TestGlobalStack.py::test_getPropertyFallThrough": true, + "tests/Settings/TestGlobalStack.py::test_getPropertyInstancesBeforeResolve": true, + "tests/Settings/TestGlobalStack.py::test_getPropertyNoResolveInDefinition": true, + "tests/Settings/TestGlobalStack.py::test_getPropertyResolveInDefinition": true, + "tests/Settings/TestGlobalStack.py::test_getPropertyResolveInInstance": true, + "tests/Settings/TestGlobalStack.py::test_hasNoUserValue": true, + "tests/Settings/TestGlobalStack.py::test_hasUserValueQualityChanges": true, + "tests/Settings/TestGlobalStack.py::test_hasUserValueUserChanges": true, + "tests/Settings/TestGlobalStack.py::test_insertContainer": true, + "tests/Settings/TestGlobalStack.py::test_removeContainer": true, + "tests/Settings/TestGlobalStack.py::test_setNextStack": true, + "tests/Settings/TestGlobalStack.py::test_setPropertyUser[foo-value-100]": true, + "tests/Settings/TestGlobalStack.py::test_setPropertyUser[layer_height-default_value-0.1337]": true, + "tests/Settings/TestGlobalStack.py::test_setPropertyUser[layer_height-is_bright_pink-of course]": true, + "tests/Settings/TestGlobalStack.py::test_setPropertyUser[layer_height-value-0.1337]": true, + "tests/Settings/TestGlobalStack.py::test_setPropertyUser[support_enabled-value-True]": true, + "tests/TestMachineAction.py::test_addMachineAction": true +} \ No newline at end of file diff --git a/.pytest_cache/v/cache/nodeids b/.pytest_cache/v/cache/nodeids new file mode 100644 index 0000000000..d548ce2ff2 --- /dev/null +++ b/.pytest_cache/v/cache/nodeids @@ -0,0 +1,3 @@ +[ + "plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py::test_upgradeVersionNr[Empty config file-[general]\\n version = 5\\n [metadata]\\n setting_version = 4\\n]" +] \ No newline at end of file diff --git a/plugins/UFPWriter/UFPWriter.py b/plugins/UFPWriter/UFPWriter.py index 9344bf54da..ceb9d79087 100644 --- a/plugins/UFPWriter/UFPWriter.py +++ b/plugins/UFPWriter/UFPWriter.py @@ -20,7 +20,7 @@ catalog = i18nCatalog("cura") class UFPWriter(MeshWriter): def __init__(self): - super().__init__() + super().__init__(add_to_recent_files = False) self._snapshot = None Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._createSnapshot) From 4635c8117d9085925904d66913ce13a9636c092e Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Tue, 21 Aug 2018 11:49:13 +0200 Subject: [PATCH 91/97] Add changed_settings(retraction_combing) to Version Upgrade Cura3.4 CURA-5505 --- .../VersionUpgrade34to40/VersionUpgrade34to40.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/VersionUpgrade/VersionUpgrade34to40/VersionUpgrade34to40.py b/plugins/VersionUpgrade/VersionUpgrade34to40/VersionUpgrade34to40.py index 83609206ef..a61aae06bb 100644 --- a/plugins/VersionUpgrade/VersionUpgrade34to40/VersionUpgrade34to40.py +++ b/plugins/VersionUpgrade/VersionUpgrade34to40/VersionUpgrade34to40.py @@ -8,6 +8,9 @@ from UM.VersionUpgrade import VersionUpgrade deleted_settings = {"prime_tower_wall_thickness", "dual_pre_wipe", "prime_tower_purge_volume"} +changed_settings = {'retraction_combing': 'noskin'} +updated_settings = {'retraction_combing': 'infill'} + _RENAMED_MATERIAL_PROFILES = { "dsm_arnitel2045_175_cartesio_0.25_mm": "dsm_arnitel2045_175_cartesio_0.25mm_thermoplastic_extruder", "dsm_arnitel2045_175_cartesio_0.4_mm": "dsm_arnitel2045_175_cartesio_0.4mm_thermoplastic_extruder", @@ -127,6 +130,13 @@ class VersionUpgrade34to40(VersionUpgrade): continue del parser["values"][deleted_setting] + for setting_key in changed_settings: + if setting_key not in parser["values"]: + continue + + if parser["values"][setting_key] == changed_settings[setting_key]: + parser["values"][setting_key] = updated_settings[setting_key] + result = io.StringIO() parser.write(result) return [filename], [result.getvalue()] From 10e474ad27929b9d4a0dc799f5fcfba8c2251312 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 21 Aug 2018 13:38:54 +0200 Subject: [PATCH 92/97] Add draft shield distance into the prime tower position fomulas CURA-5644 --- resources/definitions/fdmprinter.def.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index c0c7e44d97..3eb7cb1c32 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -5074,7 +5074,7 @@ "unit": "mm", "enabled": "resolveOrValue('prime_tower_enable')", "default_value": 200, - "value": "machine_width - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - 1", + "value": "machine_width - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0) - 1", "maximum_value": "machine_width / 2 if machine_center_is_zero else machine_width", "minimum_value": "resolveOrValue('prime_tower_size') - machine_width / 2 if machine_center_is_zero else resolveOrValue('prime_tower_size')", "settable_per_mesh": false, @@ -5088,7 +5088,7 @@ "unit": "mm", "enabled": "resolveOrValue('prime_tower_enable')", "default_value": 200, - "value": "machine_depth - prime_tower_size - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - 1", + "value": "machine_depth - prime_tower_size - max(extruderValue(adhesion_extruder_nr, 'brim_width') * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 if adhesion_type == 'brim' else (extruderValue(adhesion_extruder_nr, 'raft_margin') if adhesion_type == 'raft' else (extruderValue(adhesion_extruder_nr, 'skirt_gap') if adhesion_type == 'skirt' else 0)), max(extruderValues('travel_avoid_distance'))) - max(extruderValues('support_offset')) - sum(extruderValues('skirt_brim_line_width')) * extruderValue(adhesion_extruder_nr, 'initial_layer_line_width_factor') / 100 - (resolveOrValue('draft_shield_dist') if resolveOrValue('draft_shield_enabled') else 0) - 1", "maximum_value": "machine_depth / 2 - resolveOrValue('prime_tower_size') if machine_center_is_zero else machine_depth - resolveOrValue('prime_tower_size')", "minimum_value": "machine_depth / -2 if machine_center_is_zero else 0", "settable_per_mesh": false, From f35005c8bafe238ff36b079b28f845bd9227047b Mon Sep 17 00:00:00 2001 From: Cherubim Date: Tue, 21 Aug 2018 14:24:07 +0200 Subject: [PATCH 93/97] Brackets on new line As per our code style. --- resources/qml/Preferences/MaterialView.qml | 47 ++++++++++++++-------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index 0929f1790a..97184ab558 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -30,20 +30,24 @@ TabView property bool reevaluateLinkedMaterials: false property string linkedMaterialNames: { - if (reevaluateLinkedMaterials) { + if (reevaluateLinkedMaterials) + { reevaluateLinkedMaterials = false; } - if (!base.containerId || !base.editingEnabled) { + if (!base.containerId || !base.editingEnabled) + { return "" } var linkedMaterials = Cura.ContainerManager.getLinkedMaterials(base.currentMaterialNode, true); - if (linkedMaterials.length == 0) { + if (linkedMaterials.length == 0) + { return "" } return linkedMaterials.join(", "); } - function getApproximateDiameter(diameter) { + function getApproximateDiameter(diameter) + { return Math.round(diameter); } @@ -154,13 +158,15 @@ TabView } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") } - Row { + Row + { width: scrollView.columnWidth height: parent.rowHeight spacing: Math.round(UM.Theme.getSize("default_margin").width / 2) // color indicator square - Rectangle { + Rectangle + { id: colorSelector color: properties.color_code @@ -171,7 +177,8 @@ TabView anchors.verticalCenter: parent.verticalCenter // open the color selection dialog on click - MouseArea { + MouseArea + { anchors.fill: parent onClicked: colorDialog.open() enabled: base.editingEnabled @@ -179,7 +186,8 @@ TabView } // pretty color name text field - ReadOnlyTextField { + ReadOnlyTextField + { id: colorLabel; text: properties.color_name; readOnly: !base.editingEnabled @@ -188,7 +196,8 @@ TabView // popup dialog to select a new color // if successful it sets the properties.color_code value to the new color - ColorDialog { + ColorDialog + { id: colorDialog color: properties.color_code onAccepted: base.setMetaDataEntry("color_code", properties.color_code, color) @@ -258,7 +267,8 @@ TabView decimals: 2 maximumValue: 100000000 - onValueChanged: { + onValueChanged: + { base.setMaterialPreferenceValue(properties.guid, "spool_cost", parseFloat(value)) updateCostPerMeter() } @@ -275,7 +285,8 @@ TabView decimals: 0 maximumValue: 10000 - onValueChanged: { + onValueChanged: + { base.setMaterialPreferenceValue(properties.guid, "spool_weight", parseFloat(value)) updateCostPerMeter() } @@ -401,7 +412,8 @@ TabView { id: spinBox anchors.left: label.right - value: { + value: + { // In case the setting is not in the material... if (!isNaN(parseFloat(materialPropertyProvider.properties.value))) { @@ -493,8 +505,10 @@ TabView } // Tiny convenience function to check if a value really changed before trying to set it. - function setMetaDataEntry(entry_name, old_value, new_value) { - if (old_value != new_value) { + function setMetaDataEntry(entry_name, old_value, new_value) + { + if (old_value != new_value) + { Cura.ContainerManager.setContainerMetaDataEntry(base.currentMaterialNode, entry_name, new_value) // make sure the UI properties are updated as well since we don't re-fetch the entire model here // When the entry_name is something like properties/diameter, we take the last part of the entry_name @@ -546,10 +560,11 @@ TabView } // update the display name of the material - function updateMaterialDisplayName (old_name, new_name) + function updateMaterialDisplayName(old_name, new_name) { // don't change when new name is the same - if (old_name == new_name) { + if (old_name == new_name) + { return; } From af64d91587c4363ca30a9ed38d0ce03eeae701ed Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 21 Aug 2018 16:07:16 +0200 Subject: [PATCH 94/97] Fix stack handling in project loading CURA-5663 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index e33b88375e..16ab22473b 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -630,6 +630,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader): type = "extruder_train") extruder_stack_dict = {stack.getMetaDataEntry("position"): stack for stack in extruder_stacks} + # Make sure that those extruders have the global stack as the next stack or later some value evaluation + # will fail. + for stack in extruder_stacks: + stack.setNextStack(global_stack, connect_signals = False) + Logger.log("d", "Workspace loading is checking definitions...") # Get all the definition files & check if they exist. If not, add them. definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] From 94ce9205c3fc10f69c7508a189ca3a9641a4b123 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 22 Aug 2018 13:30:59 +0200 Subject: [PATCH 95/97] Remove pytest cache --- .gitignore | 1 + .pytest_cache/README.md | 8 --- .pytest_cache/v/cache/lastfailed | 100 ------------------------------- .pytest_cache/v/cache/nodeids | 3 - 4 files changed, 1 insertion(+), 111 deletions(-) delete mode 100644 .pytest_cache/README.md delete mode 100644 .pytest_cache/v/cache/lastfailed delete mode 100644 .pytest_cache/v/cache/nodeids diff --git a/.gitignore b/.gitignore index 98eaa6f414..5cc60c2f4c 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ LC_MESSAGES .cache *.qmlc .mypy_cache +.pytest_cache #MacOS .DS_Store diff --git a/.pytest_cache/README.md b/.pytest_cache/README.md deleted file mode 100644 index bb78ba07ee..0000000000 --- a/.pytest_cache/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# pytest cache directory # - -This directory contains data from the pytest's cache plugin, -which provides the `--lf` and `--ff` options, as well as the `cache` fixture. - -**Do not** commit this to version control. - -See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information. diff --git a/.pytest_cache/v/cache/lastfailed b/.pytest_cache/v/cache/lastfailed deleted file mode 100644 index adb96c8331..0000000000 --- a/.pytest_cache/v/cache/lastfailed +++ /dev/null @@ -1,100 +0,0 @@ -{ - "tests/Settings/TestCuraContainerRegistry.py::test_addContainerBadSettingVersion": true, - "tests/Settings/TestCuraContainerRegistry.py::test_addContainerExtruderStack": true, - "tests/Settings/TestCuraContainerRegistry.py::test_addContainerGlobalStack": true, - "tests/Settings/TestCuraContainerRegistry.py::test_addContainerGoodSettingVersion": true, - "tests/Settings/TestCuraContainerRegistry.py::test_addContainerNoSettingVersion": true, - "tests/Settings/TestExtruderStack.py::test_addContainer": true, - "tests/Settings/TestExtruderStack.py::test_constrainDefinitionValid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainDefinitionValid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_constrainMaterialInvalid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainMaterialInvalid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_constrainMaterialInvalid[container2]": true, - "tests/Settings/TestExtruderStack.py::test_constrainMaterialValid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainMaterialValid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_constrainQualityChangesInvalid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainQualityChangesInvalid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_constrainQualityChangesInvalid[container2]": true, - "tests/Settings/TestExtruderStack.py::test_constrainQualityChangesValid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainQualityChangesValid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_constrainQualityInvalid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainQualityInvalid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_constrainQualityInvalid[container2]": true, - "tests/Settings/TestExtruderStack.py::test_constrainQualityValid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainQualityValid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_constrainUserChangesInvalid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainUserChangesInvalid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_constrainUserChangesInvalid[container2]": true, - "tests/Settings/TestExtruderStack.py::test_constrainUserChangesValid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainUserChangesValid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_constrainVariantInvalid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainVariantInvalid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_constrainVariantValid[container0]": true, - "tests/Settings/TestExtruderStack.py::test_constrainVariantValid[container1]": true, - "tests/Settings/TestExtruderStack.py::test_deserializeMoveInstanceContainer": true, - "tests/Settings/TestExtruderStack.py::test_deserializeRemovesWrongContainerClass": true, - "tests/Settings/TestExtruderStack.py::test_deserializeRemovesWrongInstanceContainer": true, - "tests/Settings/TestExtruderStack.py::test_deserializeWrongDefinitionClass": true, - "tests/Settings/TestExtruderStack.py::test_getPropertyFallThrough": true, - "tests/Settings/TestExtruderStack.py::test_insertContainer": true, - "tests/Settings/TestExtruderStack.py::test_removeContainer": true, - "tests/Settings/TestExtruderStack.py::test_setPropertyUser[foo-value-100]": true, - "tests/Settings/TestExtruderStack.py::test_setPropertyUser[layer_height-default_value-0.1337]": true, - "tests/Settings/TestExtruderStack.py::test_setPropertyUser[layer_height-is_bright_pink-of course]": true, - "tests/Settings/TestExtruderStack.py::test_setPropertyUser[layer_height-value-0.1337]": true, - "tests/Settings/TestExtruderStack.py::test_setPropertyUser[support_enabled-value-True]": true, - "tests/Settings/TestGlobalStack.py::test_addContainer": true, - "tests/Settings/TestGlobalStack.py::test_addExtruder": true, - "tests/Settings/TestGlobalStack.py::test_constrainDefinitionChangesInvalid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainDefinitionChangesInvalid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainDefinitionChangesInvalid[container2]": true, - "tests/Settings/TestGlobalStack.py::test_constrainDefinitionChangesValid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainDefinitionChangesValid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainDefinitionValid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainDefinitionValid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainMaterialInvalid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainMaterialInvalid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainMaterialInvalid[container2]": true, - "tests/Settings/TestGlobalStack.py::test_constrainMaterialValid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainMaterialValid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainQualityChangesInvalid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainQualityChangesInvalid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainQualityChangesInvalid[container2]": true, - "tests/Settings/TestGlobalStack.py::test_constrainQualityChangesValid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainQualityChangesValid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainQualityInvalid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainQualityInvalid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainQualityInvalid[container2]": true, - "tests/Settings/TestGlobalStack.py::test_constrainQualityValid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainQualityValid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainUserChangesInvalid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainUserChangesInvalid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainUserChangesInvalid[container2]": true, - "tests/Settings/TestGlobalStack.py::test_constrainUserChangesValid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainUserChangesValid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainVariantInvalid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainVariantInvalid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_constrainVariantValid[container0]": true, - "tests/Settings/TestGlobalStack.py::test_constrainVariantValid[container1]": true, - "tests/Settings/TestGlobalStack.py::test_deserializeMoveInstanceContainer": true, - "tests/Settings/TestGlobalStack.py::test_deserializeRemovesWrongContainerClass": true, - "tests/Settings/TestGlobalStack.py::test_deserializeRemovesWrongInstanceContainer": true, - "tests/Settings/TestGlobalStack.py::test_deserializeWrongDefinitionClass": true, - "tests/Settings/TestGlobalStack.py::test_getPropertyFallThrough": true, - "tests/Settings/TestGlobalStack.py::test_getPropertyInstancesBeforeResolve": true, - "tests/Settings/TestGlobalStack.py::test_getPropertyNoResolveInDefinition": true, - "tests/Settings/TestGlobalStack.py::test_getPropertyResolveInDefinition": true, - "tests/Settings/TestGlobalStack.py::test_getPropertyResolveInInstance": true, - "tests/Settings/TestGlobalStack.py::test_hasNoUserValue": true, - "tests/Settings/TestGlobalStack.py::test_hasUserValueQualityChanges": true, - "tests/Settings/TestGlobalStack.py::test_hasUserValueUserChanges": true, - "tests/Settings/TestGlobalStack.py::test_insertContainer": true, - "tests/Settings/TestGlobalStack.py::test_removeContainer": true, - "tests/Settings/TestGlobalStack.py::test_setNextStack": true, - "tests/Settings/TestGlobalStack.py::test_setPropertyUser[foo-value-100]": true, - "tests/Settings/TestGlobalStack.py::test_setPropertyUser[layer_height-default_value-0.1337]": true, - "tests/Settings/TestGlobalStack.py::test_setPropertyUser[layer_height-is_bright_pink-of course]": true, - "tests/Settings/TestGlobalStack.py::test_setPropertyUser[layer_height-value-0.1337]": true, - "tests/Settings/TestGlobalStack.py::test_setPropertyUser[support_enabled-value-True]": true, - "tests/TestMachineAction.py::test_addMachineAction": true -} \ No newline at end of file diff --git a/.pytest_cache/v/cache/nodeids b/.pytest_cache/v/cache/nodeids deleted file mode 100644 index d548ce2ff2..0000000000 --- a/.pytest_cache/v/cache/nodeids +++ /dev/null @@ -1,3 +0,0 @@ -[ - "plugins/VersionUpgrade/VersionUpgrade34to40/tests/TestVersionUpgrade34to40.py::test_upgradeVersionNr[Empty config file-[general]\\n version = 5\\n [metadata]\\n setting_version = 4\\n]" -] \ No newline at end of file From 29180740de7100f58853556ce8603c54d2741f58 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 22 Aug 2018 13:36:17 +0200 Subject: [PATCH 96/97] Fix missed names --- resources/qml/Cura.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 60f6e77ea9..046a887a71 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -120,7 +120,7 @@ UM.MainWindow text: catalog.i18nc("@title:menu menubar:file","&Save...") onTriggered: { - var args = { "filter_by_machine": false, "file_type": "workspace", "preferred_mimetype": "application/x-curaproject+xml" }; + var args = { "filter_by_machine": false, "file_type": "workspace", "preferred_mimetypes": "application/x-curaproject+xml" }; if(UM.Preferences.getValue("cura/dialog_on_project_save")) { saveWorkspaceDialog.args = args; @@ -142,7 +142,7 @@ UM.MainWindow onTriggered: { var localDeviceId = "local_file"; - UM.OutputDeviceManager.requestWriteToDevice(localDeviceId, PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"}); + UM.OutputDeviceManager.requestWriteToDevice(localDeviceId, PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetypes": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"}); } } @@ -151,7 +151,7 @@ UM.MainWindow text: catalog.i18nc("@action:inmenu menubar:file", "Export Selection..."); enabled: UM.Selection.hasSelection; iconName: "document-save-as"; - onTriggered: UM.OutputDeviceManager.requestWriteSelectionToDevice("local_file", PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetype": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"}); + onTriggered: UM.OutputDeviceManager.requestWriteSelectionToDevice("local_file", PrintInformation.jobName, { "filter_by_machine": false, "preferred_mimetypes": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml"}); } MenuSeparator { } From 49e3c23d1438c7133e739af62b23f5f9297313b1 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 22 Aug 2018 16:53:27 +0200 Subject: [PATCH 97/97] Brackets on new line As per our code style. --- plugins/SimulationView/SimulationView.qml | 240 +++++++++++++++------- 1 file changed, 161 insertions(+), 79 deletions(-) diff --git a/plugins/SimulationView/SimulationView.qml b/plugins/SimulationView/SimulationView.qml index a3a8ced4cf..be767e93ab 100644 --- a/plugins/SimulationView/SimulationView.qml +++ b/plugins/SimulationView/SimulationView.qml @@ -1,4 +1,4 @@ -// Copyright (c) 2017 Ultimaker B.V. +// Copyright (c) 2018 Ultimaker B.V. // Cura is released under the terms of the LGPLv3 or higher. import QtQuick 2.4 @@ -12,30 +12,43 @@ import Cura 1.0 as Cura Item { id: base - width: { - if (UM.SimulationView.compatibilityMode) { + width: + { + if (UM.SimulationView.compatibilityMode) + { return UM.Theme.getSize("layerview_menu_size_compatibility").width; - } else { + } + else + { return UM.Theme.getSize("layerview_menu_size").width; } } height: { - if (viewSettings.collapsed) { - if (UM.SimulationView.compatibilityMode) { + if (viewSettings.collapsed) + { + if (UM.SimulationView.compatibilityMode) + { return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height; } return UM.Theme.getSize("layerview_menu_size_collapsed").height; - } else if (UM.SimulationView.compatibilityMode) { + } + else if (UM.SimulationView.compatibilityMode) + { return UM.Theme.getSize("layerview_menu_size_compatibility").height; - } else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) { + } + else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) + { return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height) - } else { + } + else + { return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height) } } Behavior on height { NumberAnimation { duration: 100 } } - property var buttonTarget: { + property var buttonTarget: + { if(parent != null) { var force_binding = parent.y; // ensure this gets reevaluated when the panel moves @@ -44,7 +57,8 @@ Item return Qt.point(0,0) } - Rectangle { + Rectangle + { id: layerViewMenu anchors.right: parent.right anchors.top: parent.top @@ -83,7 +97,8 @@ Item } } - ColumnLayout { + ColumnLayout + { id: viewSettings property bool collapsed: false @@ -195,7 +210,8 @@ Item width: width } - Connections { + Connections + { target: UM.Preferences onPreferenceChanged: { @@ -212,18 +228,22 @@ Item } } - Repeater { + Repeater + { model: Cura.ExtrudersModel{} - CheckBox { + CheckBox + { id: extrudersModelCheckBox checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == "" - onClicked: { + onClicked: + { viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0 UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|")); } visible: !UM.SimulationView.compatibilityMode enabled: index + 1 <= 4 - Rectangle { + Rectangle + { anchors.verticalCenter: parent.verticalCenter anchors.right: extrudersModelCheckBox.right width: UM.Theme.getSize("layerview_legend_size").width @@ -253,8 +273,10 @@ Item } } - Repeater { - model: ListModel { + Repeater + { + model: ListModel + { id: typesLegendModel Component.onCompleted: { @@ -285,13 +307,16 @@ Item } } - CheckBox { + CheckBox + { id: legendModelCheckBox checked: model.initialValue - onClicked: { + onClicked: + { UM.Preferences.setValue(model.preference, checked); } - Rectangle { + Rectangle + { anchors.verticalCenter: parent.verticalCenter anchors.right: legendModelCheckBox.right width: UM.Theme.getSize("layerview_legend_size").width @@ -320,18 +345,22 @@ Item } } - CheckBox { + CheckBox + { checked: viewSettings.only_show_top_layers - onClicked: { + onClicked: + { UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0); } text: catalog.i18nc("@label", "Only Show Top Layers") visible: UM.SimulationView.compatibilityMode style: UM.Theme.styles.checkbox } - CheckBox { + CheckBox + { checked: viewSettings.top_layer_count == 5 - onClicked: { + onClicked: + { UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1); } text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top") @@ -339,8 +368,10 @@ Item style: UM.Theme.styles.checkbox } - Repeater { - model: ListModel { + Repeater + { + model: ListModel + { id: typesLegendModelNoCheck Component.onCompleted: { @@ -355,11 +386,13 @@ Item } } - Label { + Label + { text: label visible: viewSettings.show_legend id: typesLegendModelLabel - Rectangle { + Rectangle + { anchors.verticalCenter: parent.verticalCenter anchors.right: typesLegendModelLabel.right width: UM.Theme.getSize("layerview_legend_size").width @@ -378,30 +411,37 @@ Item } // Text for the minimum, maximum and units for the feedrates and layer thickness - Item { + Item + { id: gradientLegend visible: viewSettings.show_gradient width: parent.width height: UM.Theme.getSize("layerview_row").height - anchors { + anchors + { topMargin: UM.Theme.getSize("slider_layerview_margin").height horizontalCenter: parent.horizontalCenter } - Label { + Label + { text: minText() anchors.left: parent.left color: UM.Theme.getColor("setting_control_text") font: UM.Theme.getFont("default") - function minText() { - if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) { + function minText() + { + if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) + { // Feedrate selected - if (UM.Preferences.getValue("layerview/layer_view_type") == 2) { + if (UM.Preferences.getValue("layerview/layer_view_type") == 2) + { return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2) } // Layer thickness selected - if (UM.Preferences.getValue("layerview/layer_view_type") == 3) { + if (UM.Preferences.getValue("layerview/layer_view_type") == 3) + { return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2) } } @@ -409,20 +449,25 @@ Item } } - Label { + Label + { text: unitsText() anchors.horizontalCenter: parent.horizontalCenter color: UM.Theme.getColor("setting_control_text") font: UM.Theme.getFont("default") - function unitsText() { - if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) { + function unitsText() + { + if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) + { // Feedrate selected - if (UM.Preferences.getValue("layerview/layer_view_type") == 2) { + if (UM.Preferences.getValue("layerview/layer_view_type") == 2) + { return "mm/s" } // Layer thickness selected - if (UM.Preferences.getValue("layerview/layer_view_type") == 3) { + if (UM.Preferences.getValue("layerview/layer_view_type") == 3) + { return "mm" } } @@ -430,20 +475,25 @@ Item } } - Label { + Label + { text: maxText() anchors.right: parent.right color: UM.Theme.getColor("setting_control_text") font: UM.Theme.getFont("default") - function maxText() { - if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) { + function maxText() + { + if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) + { // Feedrate selected - if (UM.Preferences.getValue("layerview/layer_view_type") == 2) { + if (UM.Preferences.getValue("layerview/layer_view_type") == 2) + { return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2) } // Layer thickness selected - if (UM.Preferences.getValue("layerview/layer_view_type") == 3) { + if (UM.Preferences.getValue("layerview/layer_view_type") == 3) + { return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2) } } @@ -453,7 +503,8 @@ Item } // Gradient colors for feedrate - Rectangle { // In QML 5.9 can be changed by LinearGradient + Rectangle + { // In QML 5.9 can be changed by LinearGradient // Invert values because then the bar is rotated 90 degrees id: feedrateGradient visible: viewSettings.show_feedrate_gradient @@ -463,20 +514,25 @@ Item border.width: UM.Theme.getSize("default_lining").width border.color: UM.Theme.getColor("lining") transform: Rotation {origin.x: 0; origin.y: 0; angle: 90} - gradient: Gradient { - GradientStop { + gradient: Gradient + { + GradientStop + { position: 0.000 color: Qt.rgba(1, 0.5, 0, 1) } - GradientStop { + GradientStop + { position: 0.625 color: Qt.rgba(0.375, 0.5, 0, 1) } - GradientStop { + GradientStop + { position: 0.75 color: Qt.rgba(0.25, 1, 0, 1) } - GradientStop { + GradientStop + { position: 1.0 color: Qt.rgba(0, 0, 1, 1) } @@ -484,7 +540,8 @@ Item } // Gradient colors for layer thickness (similar to parula colormap) - Rectangle { // In QML 5.9 can be changed by LinearGradient + Rectangle // In QML 5.9 can be changed by LinearGradient + { // Invert values because then the bar is rotated 90 degrees id: thicknessGradient visible: viewSettings.show_thickness_gradient @@ -494,24 +551,30 @@ Item border.width: UM.Theme.getSize("default_lining").width border.color: UM.Theme.getColor("lining") transform: Rotation {origin.x: 0; origin.y: 0; angle: 90} - gradient: Gradient { - GradientStop { + gradient: Gradient + { + GradientStop + { position: 0.000 color: Qt.rgba(1, 1, 0, 1) } - GradientStop { + GradientStop + { position: 0.25 color: Qt.rgba(1, 0.75, 0.25, 1) } - GradientStop { + GradientStop + { position: 0.5 color: Qt.rgba(0, 0.75, 0.5, 1) } - GradientStop { + GradientStop + { position: 0.75 color: Qt.rgba(0, 0.375, 0.75, 1) } - GradientStop { + GradientStop + { position: 1.0 color: Qt.rgba(0, 0, 0.5, 1) } @@ -520,19 +583,22 @@ Item } } - Item { + Item + { id: slidersBox width: parent.width visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity - anchors { + anchors + { top: parent.bottom topMargin: UM.Theme.getSize("slider_layerview_margin").height left: parent.left } - PathSlider { + PathSlider + { id: pathSlider height: UM.Theme.getSize("slider_handle").width @@ -553,25 +619,29 @@ Item rangeColor: UM.Theme.getColor("slider_groove_fill") // update values when layer data changes - Connections { + Connections + { target: UM.SimulationView onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath) onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath) } // make sure the slider handlers show the correct value after switching views - Component.onCompleted: { + Component.onCompleted: + { pathSlider.setHandleValue(UM.SimulationView.currentPath) } } - LayerSlider { + LayerSlider + { id: layerSlider width: UM.Theme.getSize("slider_handle").width height: UM.Theme.getSize("layerview_menu_size").height - anchors { + anchors + { top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0 right: parent.right @@ -593,7 +663,8 @@ Item handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width // update values when layer data changes - Connections { + Connections + { target: UM.SimulationView onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer) onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer) @@ -601,45 +672,54 @@ Item } // make sure the slider handlers show the correct value after switching views - Component.onCompleted: { + Component.onCompleted: + { layerSlider.setLowerValue(UM.SimulationView.minimumLayer) layerSlider.setUpperValue(UM.SimulationView.currentLayer) } } // Play simulation button - Button { + Button + { id: playButton iconSource: "./resources/simulation_resume.svg" style: UM.Theme.styles.small_tool_button visible: !UM.SimulationView.compatibilityMode - anchors { + anchors + { verticalCenter: pathSlider.verticalCenter } property var status: 0 // indicates if it's stopped (0) or playing (1) - onClicked: { - switch(status) { - case 0: { + onClicked: + { + switch(status) + { + case 0: + { resumeSimulation() break } - case 1: { + case 1: + { pauseSimulation() break } } } - function pauseSimulation() { + function pauseSimulation() + { UM.SimulationView.setSimulationRunning(false) iconSource = "./resources/simulation_resume.svg" simulationTimer.stop() status = 0 } - function resumeSimulation() { + function resumeSimulation() + { UM.SimulationView.setSimulationRunning(true) iconSource = "./resources/simulation_pause.svg" simulationTimer.start() @@ -652,7 +732,8 @@ Item interval: 100 running: false repeat: true - onTriggered: { + onTriggered: + { var currentPath = UM.SimulationView.currentPath var numPaths = UM.SimulationView.numPaths var currentLayer = UM.SimulationView.currentLayer @@ -697,7 +778,8 @@ Item } } - FontMetrics { + FontMetrics + { id: fontMetrics font: UM.Theme.getFont("default") }