From 860169cfe59c6acb49f243bd5ff5bf04703b7203 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Wed, 24 Jan 2018 14:31:00 +0000 Subject: [PATCH 01/85] Add bridge settings to experimental category. --- resources/definitions/fdmprinter.def.json | 79 +++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index aef5533ead..44d5cbe457 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6189,6 +6189,85 @@ "settable_per_mesh": false, "settable_per_extruder": false, "settable_per_meshgroup": false + }, + "bridge_settings_enabled": + { + "label": "Enable Bridge Settings", + "description": "Detect bridges and modify print speed, flow and fan settings while bridges are printed.", + "type": "bool", + "default_value": true, + "settable_per_mesh": true, + "settable_per_extruder": false, + "settable_per_meshgroup": false + }, + "bridge_skin_line_width": + { + "label": "Bridge Skin Line Width", + "description": "Width of a single skin line used when bridging.", + "unit": "mm", + "minimum_value": "0.001", + "minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size", + "maximum_value_warning": "2 * machine_nozzle_size", + "default_value": 0.4, + "type": "float", + "value": "line_width", + "enabled": "bridge_settings_enabled", + "limit_to_extruder": "top_bottom_extruder_nr", + "settable_per_mesh": true + }, + "bridge_skin_speed": + { + "label": "Bridge Skin Speed", + "description": "The speed at which bridge skin regions are printed.", + "unit": "mm/s", + "type": "float", + "minimum_value": "0.1", + "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", + "maximum_value_warning": "300", + "default_value": 20, + "value": "speed_topbottom / 2", + "enabled": "bridge_settings_enabled", + "settable_per_mesh": true + }, + "bridge_wall_speed": + { + "label": "Bridge Wall Speed", + "description": "The speed at which the bridge walls are printed.", + "unit": "mm/s", + "type": "float", + "minimum_value": "0.1", + "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", + "maximum_value_warning": "300", + "default_value": 20, + "value": "speed_wall_0 / 2", + "enabled": "bridge_settings_enabled", + "settable_per_mesh": true + }, + "bridge_material_flow": + { + "label": "Bridge Flow", + "description": "Flow compensation: the amount of material extruded when bridging is multiplied by this value.", + "unit": "%", + "default_value": 100, + "type": "float", + "minimum_value": "5", + "minimum_value_warning": "50", + "maximum_value_warning": "150", + "enabled": "bridge_settings_enabled", + "settable_per_mesh": true + }, + "bridge_fan_speed": + { + "label": "Bridge Fan Speed", + "description": "Fan speed to use when printing bridge walls and skin.", + "unit": "%", + "minimum_value": "0", + "maximum_value": "100", + "default_value": 100, + "type": "float", + "enabled": "bridge_settings_enabled", + "settable_per_mesh": false, + "settable_per_extruder": true } } }, From 2ae7b488507e987146d72a1b1317c824004817cb Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 25 Jan 2018 18:23:25 +0000 Subject: [PATCH 02/85] Added bridge_skin_support_threshold setting. --- resources/definitions/fdmprinter.def.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 44d5cbe457..608577c7f0 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6200,6 +6200,18 @@ "settable_per_extruder": false, "settable_per_meshgroup": false }, + "bridge_skin_support_threshold": + { + "label": "Bridge Skin Support Threshold", + "description": "If a skin region is supported for less than this percentage of its area, print it using the bridge settings. Otherwise it is printed using the normal skin settings.", + "unit": "%", + "default_value": 50, + "type": "float", + "minimum_value": "0", + "maximum_value": "100", + "enabled": "bridge_settings_enabled", + "settable_per_mesh": true + }, "bridge_skin_line_width": { "label": "Bridge Skin Line Width", From 8d69e845633f87455c1b79ffb2afa03acf7bb951 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Mon, 29 Jan 2018 08:58:47 +0000 Subject: [PATCH 03/85] Refactor bridge settings. Added bridge_wall_max_air_gap, bridge_wall_material_flow and bridge_skin_material_flow. Removed bridge_skin_line_width and bridge_material_flow. --- resources/definitions/fdmprinter.def.json | 36 +++++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 608577c7f0..d18f22c7b8 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6212,19 +6212,16 @@ "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, - "bridge_skin_line_width": + "bridge_wall_max_air_gap": { - "label": "Bridge Skin Line Width", - "description": "Width of a single skin line used when bridging.", - "unit": "mm", - "minimum_value": "0.001", - "minimum_value_warning": "0.1 + 0.4 * machine_nozzle_size", - "maximum_value_warning": "2 * machine_nozzle_size", - "default_value": 0.4, + "label": "Bridge Wall Max Air Gap", + "description": "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings.", + "unit": "%", + "default_value": 50, "type": "float", - "value": "line_width", + "minimum_value": "0", + "maximum_value": "100", "enabled": "bridge_settings_enabled", - "limit_to_extruder": "top_bottom_extruder_nr", "settable_per_mesh": true }, "bridge_skin_speed": @@ -6255,10 +6252,23 @@ "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, - "bridge_material_flow": + "bridge_skin_material_flow": { - "label": "Bridge Flow", - "description": "Flow compensation: the amount of material extruded when bridging is multiplied by this value.", + "label": "Bridge Skin Flow", + "description": "Flow compensation: the amount of material extruded when bridging skin is multiplied by this value.", + "unit": "%", + "default_value": 100, + "type": "float", + "minimum_value": "5", + "minimum_value_warning": "50", + "maximum_value_warning": "150", + "enabled": "bridge_settings_enabled", + "settable_per_mesh": true + }, + "bridge_wall_material_flow": + { + "label": "Bridge Wall Flow", + "description": "Flow compensation: the amount of material extruded when bridging walls is multiplied by this value.", "unit": "%", "default_value": 100, "type": "float", From 35600fddd78b5f82e411208c3ac53fa13b0419fb Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Mon, 29 Jan 2018 15:53:52 +0000 Subject: [PATCH 04/85] By default, bridging is disabled when support is enabled but the user can override that. --- resources/definitions/fdmprinter.def.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index d18f22c7b8..0e0857d2f9 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6195,7 +6195,8 @@ "label": "Enable Bridge Settings", "description": "Detect bridges and modify print speed, flow and fan settings while bridges are printed.", "type": "bool", - "default_value": true, + "default_value": false, + "value": "not support_enable and not support_tree_enable", "settable_per_mesh": true, "settable_per_extruder": false, "settable_per_meshgroup": false From 15a0ec1ef17022a4cfdea51e219638d5c99b6292 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Tue, 30 Jan 2018 09:24:23 +0000 Subject: [PATCH 05/85] Make the default value of bridge_wall_max_air_gap 100% so that partial overhangs do not use bridging. Full overhangs (no support at all) will still use the wall bridge settings. --- 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 0e0857d2f9..1467cca252 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6218,7 +6218,7 @@ "label": "Bridge Wall Max Air Gap", "description": "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings.", "unit": "%", - "default_value": 50, + "default_value": 100, "type": "float", "minimum_value": "0", "maximum_value": "100", From 4a499ddfec72aa9d4e0fe4a27d4d6756be47a419 Mon Sep 17 00:00:00 2001 From: Andreea Scorojitu Date: Wed, 14 Feb 2018 10:50:14 +0100 Subject: [PATCH 06/85] CURA-4958 Update_3.2.1_changelog Added the bug fixes done for the 3.2.1 --- plugins/ChangeLogPlugin/ChangeLog.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/ChangeLogPlugin/ChangeLog.txt b/plugins/ChangeLogPlugin/ChangeLog.txt index 6b394f1e2e..8a031c9eae 100755 --- a/plugins/ChangeLogPlugin/ChangeLog.txt +++ b/plugins/ChangeLogPlugin/ChangeLog.txt @@ -1,3 +1,9 @@ +[3.2.1] +*Bug fixes +- Fixed issues where Cura crashes on startup and loading profiles +- Updated translations +- Fixed an issue where the text would not render properly + [3.2.0] *Tree support Experimental tree-like support structure that uses ‘branches’ to support prints. Branches ‘grow’ and multiply towards the model, with fewer contact points than alternative support methods. This results in better surface finishes for organic-shaped prints. From 2f8f42aa64f21b8f4f5a5b9d10a50639147ac51b Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 15 Feb 2018 10:45:24 +0000 Subject: [PATCH 07/85] Reduce default bridge flows to 50%. --- 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 1467cca252..a3a4583b0b 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6258,7 +6258,7 @@ "label": "Bridge Skin Flow", "description": "Flow compensation: the amount of material extruded when bridging skin is multiplied by this value.", "unit": "%", - "default_value": 100, + "default_value": 50, "type": "float", "minimum_value": "5", "minimum_value_warning": "50", @@ -6271,7 +6271,7 @@ "label": "Bridge Wall Flow", "description": "Flow compensation: the amount of material extruded when bridging walls is multiplied by this value.", "unit": "%", - "default_value": 100, + "default_value": 50, "type": "float", "minimum_value": "5", "minimum_value_warning": "50", From ca115d2f4805b139fc8986cfdeaba9615ec3af0b Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Thu, 15 Feb 2018 10:45:40 +0000 Subject: [PATCH 08/85] Added bridge_wall_coast setting. --- resources/definitions/fdmprinter.def.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index a3a4583b0b..57770df26c 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6225,6 +6225,18 @@ "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, + "bridge_wall_coast": + { + "label": "Bridge Wall Coasting", + "description": "This controls the distance the extruder should coast immediately before a bridge wall begins. Coasting before the bridge starts can reduce the pressure in the nozzle and may produce a flatter bridge.", + "unit": "%", + "default_value": 100, + "type": "float", + "minimum_value": "0", + "maximum_value": "500", + "enabled": "bridge_settings_enabled", + "settable_per_mesh": false + }, "bridge_skin_speed": { "label": "Bridge Skin Speed", From 0cf6e3a1cd7f91bda8f1804caf0d52c63a54de29 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 15 Feb 2018 11:38:51 +0100 Subject: [PATCH 09/85] Set rendertype for units in SettingTextField --- resources/qml/Settings/SettingTextField.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/qml/Settings/SettingTextField.qml b/resources/qml/Settings/SettingTextField.qml index 6684ce86cf..f5a15f6f38 100644 --- a/resources/qml/Settings/SettingTextField.qml +++ b/resources/qml/Settings/SettingTextField.qml @@ -83,11 +83,12 @@ SettingItem Label { - anchors.right: parent.right; + anchors.right: parent.right anchors.rightMargin: Math.round(UM.Theme.getSize("setting_unit_margin").width) - anchors.verticalCenter: parent.verticalCenter; + anchors.verticalCenter: parent.verticalCenter - text: definition.unit; + text: definition.unit + renderType: Text.NativeRendering color: UM.Theme.getColor("setting_unit") font: UM.Theme.getFont("default") } From ec8dea927f8d20976953a2388374969e7b493e4d Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 15 Feb 2018 11:34:46 +0100 Subject: [PATCH 10/85] Fix categories too --- resources/qml/Settings/SettingCategory.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/Settings/SettingCategory.qml b/resources/qml/Settings/SettingCategory.qml index 5f22910b00..2266249678 100644 --- a/resources/qml/Settings/SettingCategory.qml +++ b/resources/qml/Settings/SettingCategory.qml @@ -78,6 +78,7 @@ Button verticalCenter: parent.verticalCenter; } text: definition.label + renderType: Text.NativeRendering font: UM.Theme.getFont("setting_category") color: { From 7e4f60cc128f4c6b51c83369af7427c6cacc3a96 Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 15 Feb 2018 11:12:19 +0100 Subject: [PATCH 11/85] Set renderType to Text.NativeRendering for qtquick controls 2 Labels Apparently, the qtquickcontrols 2 Label no longer defaults to using renderType: Text.NativeRendering This causes text to render with QtRendering which looks subtly different on Windows and succinctly broken on some OSX installations. --- resources/qml/Settings/SettingComboBox.qml | 2 ++ resources/qml/Settings/SettingExtruder.qml | 2 ++ resources/qml/Settings/SettingItem.qml | 4 +--- resources/qml/Settings/SettingOptionalExtruder.qml | 2 ++ resources/qml/Sidebar.qml | 5 ++++- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/resources/qml/Settings/SettingComboBox.qml b/resources/qml/Settings/SettingComboBox.qml index 4debf147ae..ad41fc3385 100644 --- a/resources/qml/Settings/SettingComboBox.qml +++ b/resources/qml/Settings/SettingComboBox.qml @@ -80,6 +80,7 @@ SettingItem anchors.right: downArrow.left text: control.currentText + renderType: Text.NativeRendering font: UM.Theme.getFont("default") color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") elide: Text.ElideRight @@ -116,6 +117,7 @@ SettingItem contentItem: Label { text: modelData.value + renderType: Text.NativeRendering color: control.contentItem.color font: UM.Theme.getFont("default") elide: Text.ElideRight diff --git a/resources/qml/Settings/SettingExtruder.qml b/resources/qml/Settings/SettingExtruder.qml index 462512f476..d63f73717a 100644 --- a/resources/qml/Settings/SettingExtruder.qml +++ b/resources/qml/Settings/SettingExtruder.qml @@ -117,6 +117,7 @@ SettingItem rightPadding: swatch.width + UM.Theme.getSize("setting_unit_margin").width text: control.currentText + renderType: Text.NativeRendering font: UM.Theme.getFont("default") color: enabled ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text") @@ -171,6 +172,7 @@ SettingItem contentItem: Label { text: model.name + renderType: Text.NativeRendering color: UM.Theme.getColor("setting_control_text") font: UM.Theme.getFont("default") elide: Text.ElideRight diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index 41fc21b026..8150c1b382 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -112,11 +112,9 @@ Item { anchors.right: settingControls.left; anchors.verticalCenter: parent.verticalCenter - height: UM.Theme.getSize("section").height; - verticalAlignment: Text.AlignVCenter; - text: definition.label elide: Text.ElideMiddle; + renderType: Text.NativeRendering color: UM.Theme.getColor("setting_control_text"); opacity: (definition.visible) ? 1 : 0.5 diff --git a/resources/qml/Settings/SettingOptionalExtruder.qml b/resources/qml/Settings/SettingOptionalExtruder.qml index 2a5220ad5e..bf4cd733d9 100644 --- a/resources/qml/Settings/SettingOptionalExtruder.qml +++ b/resources/qml/Settings/SettingOptionalExtruder.qml @@ -136,6 +136,7 @@ SettingItem rightPadding: swatch.width + UM.Theme.getSize("setting_unit_margin").width text: control.currentText + renderType: Text.NativeRendering font: UM.Theme.getFont("default") color: enabled ? UM.Theme.getColor("setting_control_text") : UM.Theme.getColor("setting_control_disabled_text") @@ -190,6 +191,7 @@ SettingItem contentItem: Label { text: model.name + renderType: Text.NativeRendering color: UM.Theme.getColor("setting_control_text") font: UM.Theme.getFont("default") elide: Text.ElideRight diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index 2da16f3ccb..7b2d6b2247 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -207,12 +207,13 @@ Rectangle color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active") : control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button") } - contentItem: Text + contentItem: Label { text: model.text font: UM.Theme.getFont("default") horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter + renderType: Text.NativeRendering elide: Text.ElideRight color: { @@ -352,6 +353,7 @@ Rectangle font: UM.Theme.getFont("large") color: UM.Theme.getColor("text_subtext") text: (!base.printDuration || !base.printDuration.valid) ? catalog.i18nc("@label Hours and minutes", "00h 00min") : base.printDuration.getDisplayString(UM.DurationFormat.Short) + renderType: Text.NativeRendering MouseArea { @@ -479,6 +481,7 @@ Rectangle anchors.left: parent.left anchors.bottom: parent.bottom font: UM.Theme.getFont("very_small") + renderType: Text.NativeRendering color: UM.Theme.getColor("text_subtext") elide: Text.ElideMiddle width: parent.width From 0fe2806667824669c4ea71976f6624160488b9ca Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 15 Feb 2018 12:53:14 +0100 Subject: [PATCH 12/85] Add native rendering to Print Setup Label CURA-4941 --- resources/qml/Sidebar.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index 7b2d6b2247..faf226d556 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -126,6 +126,7 @@ Rectangle { id: settingsModeLabel text: !hideSettings ? catalog.i18nc("@label:listbox", "Print Setup") : catalog.i18nc("@label:listbox", "Print Setup disabled\nG-code files cannot be modified") + renderType: Text.NativeRendering anchors.left: parent.left anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width anchors.top: hideSettings ? machineSelection.bottom : headerSeparator.bottom From ca3dd511a8193e30321b5d0219ea44384e869f0b Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Thu, 15 Feb 2018 16:39:37 +0100 Subject: [PATCH 13/85] Add a button to open the configuration folder. --- cura/CrashHandler.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 4d8cb6f54d..f0771dade1 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -15,9 +15,11 @@ import urllib.error import shutil import sys -from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR +from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, Qt, QUrl from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton +from PyQt5.QtGui import QDesktopServices +from UM.Resources import Resources from UM.Application import Application from UM.Logger import Logger from UM.View.GL.OpenGL import OpenGL @@ -91,6 +93,7 @@ class CrashHandler: label = QLabel() label.setText(catalog.i18nc("@label crash message", """

A fatal error has occurred.

Unfortunately, Cura encountered an unrecoverable error during start up. It was possibly caused by some incorrect configuration files. We suggest to backup and reset your configuration.

+

Backups can be found in the configuration folder.

Please send us this Crash Report to fix the problem.

""")) label.setWordWrap(True) @@ -104,8 +107,13 @@ class CrashHandler: show_details_button.setMaximumWidth(200) show_details_button.clicked.connect(self._showDetailedReport) + show_configuration_folder_button = QPushButton(catalog.i18nc("@action:button", "Show configuration folder"), dialog) + show_configuration_folder_button.setMaximumWidth(200) + show_configuration_folder_button.clicked.connect(self._showConfigurationFolder) + layout.addWidget(self._send_report_checkbox) layout.addWidget(show_details_button) + layout.addWidget(show_configuration_folder_button) # "backup and start clean" and "close" buttons buttons = QDialogButtonBox() @@ -181,6 +189,10 @@ class CrashHandler: self.early_crash_dialog.close() + def _showConfigurationFolder(self): + path = Resources.getConfigStoragePath(); + QDesktopServices.openUrl(QUrl.fromLocalFile( path )) + def _showDetailedReport(self): self.dialog.exec_() From dae2d9d761c2233f58d4300973253e6d45c300fe Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 27 Feb 2018 13:20:18 +0100 Subject: [PATCH 14/85] Add Dockerfile --- Dockerfile | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..4aa7b4557c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM ultimaker/cura-build-environment:3.2 + +# Environment vars for easy configuration +ENV CURA_BRANCH=master +ENV URANIUM_BRANCH=$CURA_BRANCH +ENV CURA_BENV_GIT_DIR=/srv/cura + +# Setup the repositories +RUN mkdir $CURA_BENV_GIT_DIR +WORKDIR $CURA_BENV_GIT_DIR +RUN git clone https://github.com/Ultimaker/Uranium +WORKDIR $CURA_BENV_GIT_DIR/Uranium +RUN git fetch origin +RUN git checkout $URANIUM_BRANCH +RUN git clone https://github.com/Ultimaker/cura +WORKDIR $CURA_BENV_GIT_DIR/Cura +RUN git fetch origin +RUN git checkout origin $CURA_BRANCH + +# Ensure Uranium is in the python path +RUN export PYTHOHPATH="${PYTHONPATH}:$CURA_BENV_GIT_DIR/Uranium" + +# Run Cura +CMD ["python3", "cura_app.py"] From 4dd01afffbc96363f18fc16773a5a37d42d1324f Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 27 Feb 2018 13:31:07 +0100 Subject: [PATCH 15/85] Cleanup Dockerfile --- Dockerfile | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4aa7b4557c..e71ae35b46 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,20 +5,23 @@ ENV CURA_BRANCH=master ENV URANIUM_BRANCH=$CURA_BRANCH ENV CURA_BENV_GIT_DIR=/srv/cura -# Setup the repositories RUN mkdir $CURA_BENV_GIT_DIR + +# Setup Uranium WORKDIR $CURA_BENV_GIT_DIR RUN git clone https://github.com/Ultimaker/Uranium WORKDIR $CURA_BENV_GIT_DIR/Uranium RUN git fetch origin RUN git checkout $URANIUM_BRANCH +RUN export PYTHOHPATH="${PYTHONPATH}:$CURA_BENV_GIT_DIR/Uranium" + +# Setup Cura +WORKDIR $CURA_BENV_GIT_DIR RUN git clone https://github.com/Ultimaker/cura WORKDIR $CURA_BENV_GIT_DIR/Cura RUN git fetch origin RUN git checkout origin $CURA_BRANCH -# Ensure Uranium is in the python path -RUN export PYTHOHPATH="${PYTHONPATH}:$CURA_BENV_GIT_DIR/Uranium" - # Run Cura +WORKDIR $CURA_BENV_GIT_DIR/Cura CMD ["python3", "cura_app.py"] From 8144ca978b2b67d413c5acd36766da2e6d9f6cf5 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 27 Feb 2018 15:55:24 +0100 Subject: [PATCH 16/85] Add CuraEngine to Dockerfile --- Dockerfile | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e71ae35b46..3f21496e92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,10 @@ FROM ultimaker/cura-build-environment:3.2 # Environment vars for easy configuration +ENV CURA_BENV_BUILD_TYPE=Release ENV CURA_BRANCH=master ENV URANIUM_BRANCH=$CURA_BRANCH +ENV CURA_ENGINE_BRANCH=$CURA_BRANCH ENV CURA_BENV_GIT_DIR=/srv/cura RUN mkdir $CURA_BENV_GIT_DIR @@ -17,11 +19,28 @@ RUN export PYTHOHPATH="${PYTHONPATH}:$CURA_BENV_GIT_DIR/Uranium" # Setup Cura WORKDIR $CURA_BENV_GIT_DIR -RUN git clone https://github.com/Ultimaker/cura +RUN git clone https://github.com/Ultimaker/Cura WORKDIR $CURA_BENV_GIT_DIR/Cura RUN git fetch origin RUN git checkout origin $CURA_BRANCH +# Setup CuraEngine +WORKDIR $CURA_BENV_GIT_DIR +RUN git clone https://github.com/Ultimaker/CuraEngine +WORKDIR $CURA_BENV_GIT_DIR/CuraEngine +RUN git fetch origin +RUN git checkout $URANIUM_BRANCH +RUN mkdir build +WORKDIR $CURA_BENV_GIT_DIR/CuraEngine/build +RUN cmake3 .. \ + -DCMAKE_BUILD_TYPE=$CURA_BENV_BUILD_TYPE \ + -DCMAKE_C_COMPILER=gcc \ + -DCMAKE_CXX_COMPILER=g++ +RUN make + +# Make sure Cura can find CuraEngine +RUN ln -s /usr/local/bin/CuraEngine $CURA_BENV_GIT_DIR/Cura + # Run Cura WORKDIR $CURA_BENV_GIT_DIR/Cura CMD ["python3", "cura_app.py"] From e1a6ab098a5ebf79a0a21fdb16ad55f740335682 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 27 Feb 2018 16:21:07 +0100 Subject: [PATCH 17/85] Rename source paths env variable --- Dockerfile | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f21496e92..b2ecb5e843 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,33 +5,33 @@ ENV CURA_BENV_BUILD_TYPE=Release ENV CURA_BRANCH=master ENV URANIUM_BRANCH=$CURA_BRANCH ENV CURA_ENGINE_BRANCH=$CURA_BRANCH -ENV CURA_BENV_GIT_DIR=/srv/cura +ENV CURA_APP_DIR=/srv/cura -RUN mkdir $CURA_BENV_GIT_DIR +RUN mkdir $CURA_APP_DIR # Setup Uranium -WORKDIR $CURA_BENV_GIT_DIR +WORKDIR $CURA_APP_DIR RUN git clone https://github.com/Ultimaker/Uranium -WORKDIR $CURA_BENV_GIT_DIR/Uranium +WORKDIR $CURA_APP_DIR/Uranium RUN git fetch origin RUN git checkout $URANIUM_BRANCH -RUN export PYTHOHPATH="${PYTHONPATH}:$CURA_BENV_GIT_DIR/Uranium" +RUN export PYTHOHPATH="${PYTHONPATH}:$CURA_APP_DIR/Uranium" # Setup Cura -WORKDIR $CURA_BENV_GIT_DIR +WORKDIR $CURA_APP_DIR RUN git clone https://github.com/Ultimaker/Cura -WORKDIR $CURA_BENV_GIT_DIR/Cura +WORKDIR $CURA_APP_DIR/Cura RUN git fetch origin RUN git checkout origin $CURA_BRANCH # Setup CuraEngine -WORKDIR $CURA_BENV_GIT_DIR +WORKDIR $CURA_APP_DIR RUN git clone https://github.com/Ultimaker/CuraEngine -WORKDIR $CURA_BENV_GIT_DIR/CuraEngine +WORKDIR $CURA_APP_DIR/CuraEngine RUN git fetch origin RUN git checkout $URANIUM_BRANCH RUN mkdir build -WORKDIR $CURA_BENV_GIT_DIR/CuraEngine/build +WORKDIR $CURA_APP_DIR/CuraEngine/build RUN cmake3 .. \ -DCMAKE_BUILD_TYPE=$CURA_BENV_BUILD_TYPE \ -DCMAKE_C_COMPILER=gcc \ @@ -39,8 +39,8 @@ RUN cmake3 .. \ RUN make # Make sure Cura can find CuraEngine -RUN ln -s /usr/local/bin/CuraEngine $CURA_BENV_GIT_DIR/Cura +RUN ln -s /usr/local/bin/CuraEngine $CURA_APP_DIR/Cura # Run Cura -WORKDIR $CURA_BENV_GIT_DIR/Cura +WORKDIR $CURA_APP_DIR/Cura CMD ["python3", "cura_app.py"] From d3f7771e16e00b15efd08214eab146950bfdb6b6 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 27 Feb 2018 16:24:17 +0100 Subject: [PATCH 18/85] Simplify cura engine cmake --- Dockerfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index b2ecb5e843..3f2a3a239e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,10 +32,7 @@ RUN git fetch origin RUN git checkout $URANIUM_BRANCH RUN mkdir build WORKDIR $CURA_APP_DIR/CuraEngine/build -RUN cmake3 .. \ - -DCMAKE_BUILD_TYPE=$CURA_BENV_BUILD_TYPE \ - -DCMAKE_C_COMPILER=gcc \ - -DCMAKE_CXX_COMPILER=g++ +RUN cmake3 .. RUN make # Make sure Cura can find CuraEngine From cee0887d1b146ab88fa22ac61f08bd5cacd66d68 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Tue, 27 Feb 2018 16:24:50 +0100 Subject: [PATCH 19/85] Fix typo in python path --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3f2a3a239e..1a0cbe0fff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN git clone https://github.com/Ultimaker/Uranium WORKDIR $CURA_APP_DIR/Uranium RUN git fetch origin RUN git checkout $URANIUM_BRANCH -RUN export PYTHOHPATH="${PYTHONPATH}:$CURA_APP_DIR/Uranium" +RUN export PYTHONPATH="${PYTHONPATH}:$CURA_APP_DIR/Uranium" # Setup Cura WORKDIR $CURA_APP_DIR From 1c2999551b036755e66d889c620e3a5a3d2ead86 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Wed, 28 Feb 2018 10:30:51 +0100 Subject: [PATCH 20/85] install materials, install cura engine, run headless --- Dockerfile | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1a0cbe0fff..30aacfdb56 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,14 @@ -FROM ultimaker/cura-build-environment:3.2 +FROM ultimaker/cura-build-environment:1 # Environment vars for easy configuration ENV CURA_BENV_BUILD_TYPE=Release -ENV CURA_BRANCH=master +ENV CURA_BRANCH=3.2 ENV URANIUM_BRANCH=$CURA_BRANCH ENV CURA_ENGINE_BRANCH=$CURA_BRANCH +ENV MATERIALS_BRANCH=$CURA_BRANCH ENV CURA_APP_DIR=/srv/cura +# Ensure our sources dir exists RUN mkdir $CURA_APP_DIR # Setup Uranium @@ -15,7 +17,7 @@ RUN git clone https://github.com/Ultimaker/Uranium WORKDIR $CURA_APP_DIR/Uranium RUN git fetch origin RUN git checkout $URANIUM_BRANCH -RUN export PYTHONPATH="${PYTHONPATH}:$CURA_APP_DIR/Uranium" +RUN export PYTHONPATH=${PYTHONPATH}:$CURA_APP_DIR/Uranium # Setup Cura WORKDIR $CURA_APP_DIR @@ -24,6 +26,13 @@ WORKDIR $CURA_APP_DIR/Cura RUN git fetch origin RUN git checkout origin $CURA_BRANCH +# Setup materials +WORKDIR $CURA_APP_DIR/Cura/resources +RUN git clone https://github.com/Ultimaker/fdm_materials materials +WORKDIR $CURA_APP_DIR/Cura/resources/materials +RUN git fetch origin +RUN git checkout origin $MATERIALS_BRANCH + # Setup CuraEngine WORKDIR $CURA_APP_DIR RUN git clone https://github.com/Ultimaker/CuraEngine @@ -34,10 +43,13 @@ RUN mkdir build WORKDIR $CURA_APP_DIR/CuraEngine/build RUN cmake3 .. RUN make +RUN make install + +# TODO: setup libCharon # Make sure Cura can find CuraEngine RUN ln -s /usr/local/bin/CuraEngine $CURA_APP_DIR/Cura # Run Cura WORKDIR $CURA_APP_DIR/Cura -CMD ["python3", "cura_app.py"] +CMD ["python3", "cura_app.py", "--headless"] From 8b1bca6743581d9d31300bd36ff6e0b87d586e8a Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 1 Mar 2018 10:58:18 +0100 Subject: [PATCH 21/85] Add run in docker script --- Dockerfile | 26 ++++++++++++-------------- run_in_docker.sh | 3 +++ 2 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 run_in_docker.sh diff --git a/Dockerfile b/Dockerfile index 30aacfdb56..464aebff53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,32 +13,23 @@ RUN mkdir $CURA_APP_DIR # Setup Uranium WORKDIR $CURA_APP_DIR -RUN git clone https://github.com/Ultimaker/Uranium +RUN git clone -b $URANIUM_BRANCH --depth 1 https://github.com/Ultimaker/Uranium WORKDIR $CURA_APP_DIR/Uranium -RUN git fetch origin -RUN git checkout $URANIUM_BRANCH -RUN export PYTHONPATH=${PYTHONPATH}:$CURA_APP_DIR/Uranium # Setup Cura WORKDIR $CURA_APP_DIR -RUN git clone https://github.com/Ultimaker/Cura +RUN git clone -b $CURA_BRANCH --depth 1 https://github.com/Ultimaker/Cura WORKDIR $CURA_APP_DIR/Cura -RUN git fetch origin -RUN git checkout origin $CURA_BRANCH # Setup materials WORKDIR $CURA_APP_DIR/Cura/resources -RUN git clone https://github.com/Ultimaker/fdm_materials materials +RUN git clone -b $MATERIALS_BRANCH --depth 1 https://github.com/Ultimaker/fdm_materials materials WORKDIR $CURA_APP_DIR/Cura/resources/materials -RUN git fetch origin -RUN git checkout origin $MATERIALS_BRANCH # Setup CuraEngine WORKDIR $CURA_APP_DIR -RUN git clone https://github.com/Ultimaker/CuraEngine +RUN git clone -b $CURA_ENGINE_BRANCH --depth 1 https://github.com/Ultimaker/CuraEngine WORKDIR $CURA_APP_DIR/CuraEngine -RUN git fetch origin -RUN git checkout $URANIUM_BRANCH RUN mkdir build WORKDIR $CURA_APP_DIR/CuraEngine/build RUN cmake3 .. @@ -50,6 +41,13 @@ RUN make install # Make sure Cura can find CuraEngine RUN ln -s /usr/local/bin/CuraEngine $CURA_APP_DIR/Cura +# Tmp cleanup +RUN rm -Rf /var/cache + # Run Cura WORKDIR $CURA_APP_DIR/Cura -CMD ["python3", "cura_app.py", "--headless"] +ENV PYTHONPATH=${PYTHONPATH}:$CURA_APP_DIR/Uranium +ENV DISPLAY=:1.0 +ADD run_in_docker.sh . +RUN chmod +x ./run_in_docker.sh +CMD "./run_in_docker.sh" diff --git a/run_in_docker.sh b/run_in_docker.sh new file mode 100644 index 0000000000..1e939a2739 --- /dev/null +++ b/run_in_docker.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +Xvfb :1 -screen 0 1280x800x16 & +python3 cura_app.py --headless \ No newline at end of file From e8481f55055d0128d4875c315f2c554299994193 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 1 Mar 2018 11:00:49 +0100 Subject: [PATCH 22/85] Cleanup --- Dockerfile | 13 ++++--------- run_in_docker.sh | 1 + 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 464aebff53..d4338c1a4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,10 +2,10 @@ FROM ultimaker/cura-build-environment:1 # Environment vars for easy configuration ENV CURA_BENV_BUILD_TYPE=Release -ENV CURA_BRANCH=3.2 -ENV URANIUM_BRANCH=$CURA_BRANCH -ENV CURA_ENGINE_BRANCH=$CURA_BRANCH -ENV MATERIALS_BRANCH=$CURA_BRANCH +ENV CURA_BRANCH=docker +ENV URANIUM_BRANCH=3.2 +ENV CURA_ENGINE_BRANCH=3.2 +ENV MATERIALS_BRANCH=3.2 ENV CURA_APP_DIR=/srv/cura # Ensure our sources dir exists @@ -41,13 +41,8 @@ RUN make install # Make sure Cura can find CuraEngine RUN ln -s /usr/local/bin/CuraEngine $CURA_APP_DIR/Cura -# Tmp cleanup -RUN rm -Rf /var/cache - # Run Cura WORKDIR $CURA_APP_DIR/Cura ENV PYTHONPATH=${PYTHONPATH}:$CURA_APP_DIR/Uranium -ENV DISPLAY=:1.0 -ADD run_in_docker.sh . RUN chmod +x ./run_in_docker.sh CMD "./run_in_docker.sh" diff --git a/run_in_docker.sh b/run_in_docker.sh index 1e939a2739..eb364fd887 100644 --- a/run_in_docker.sh +++ b/run_in_docker.sh @@ -1,3 +1,4 @@ #!/usr/bin/env bash Xvfb :1 -screen 0 1280x800x16 & +export DISPLAY=:1.0 python3 cura_app.py --headless \ No newline at end of file From 700254ffc910d1e3c52cba6eec93bc7cbf4bb9cf Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 1 Mar 2018 11:40:08 +0100 Subject: [PATCH 23/85] use master for testing --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index d4338c1a4f..65c6851b31 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,9 +3,9 @@ FROM ultimaker/cura-build-environment:1 # Environment vars for easy configuration ENV CURA_BENV_BUILD_TYPE=Release ENV CURA_BRANCH=docker -ENV URANIUM_BRANCH=3.2 -ENV CURA_ENGINE_BRANCH=3.2 -ENV MATERIALS_BRANCH=3.2 +ENV URANIUM_BRANCH=master +ENV CURA_ENGINE_BRANCH=master +ENV MATERIALS_BRANCH=master ENV CURA_APP_DIR=/srv/cura # Ensure our sources dir exists From 2b0211a45f510c81e8a9b2d47ff8c39f4c313cec Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 1 Mar 2018 11:45:40 +0100 Subject: [PATCH 24/85] Move build steps for better caching strategy --- Dockerfile | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index 65c6851b31..3e6adc0f4c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,13 @@ FROM ultimaker/cura-build-environment:1 # Environment vars for easy configuration -ENV CURA_BENV_BUILD_TYPE=Release -ENV CURA_BRANCH=docker -ENV URANIUM_BRANCH=master -ENV CURA_ENGINE_BRANCH=master -ENV MATERIALS_BRANCH=master ENV CURA_APP_DIR=/srv/cura # Ensure our sources dir exists RUN mkdir $CURA_APP_DIR -# Setup Uranium -WORKDIR $CURA_APP_DIR -RUN git clone -b $URANIUM_BRANCH --depth 1 https://github.com/Ultimaker/Uranium -WORKDIR $CURA_APP_DIR/Uranium - -# Setup Cura -WORKDIR $CURA_APP_DIR -RUN git clone -b $CURA_BRANCH --depth 1 https://github.com/Ultimaker/Cura -WORKDIR $CURA_APP_DIR/Cura - -# Setup materials -WORKDIR $CURA_APP_DIR/Cura/resources -RUN git clone -b $MATERIALS_BRANCH --depth 1 https://github.com/Ultimaker/fdm_materials materials -WORKDIR $CURA_APP_DIR/Cura/resources/materials - # Setup CuraEngine +ENV CURA_ENGINE_BRANCH=master WORKDIR $CURA_APP_DIR RUN git clone -b $CURA_ENGINE_BRANCH --depth 1 https://github.com/Ultimaker/CuraEngine WORKDIR $CURA_APP_DIR/CuraEngine @@ -38,6 +19,24 @@ RUN make install # TODO: setup libCharon +# Setup Uranium +ENV URANIUM_BRANCH=master +WORKDIR $CURA_APP_DIR +RUN git clone -b $URANIUM_BRANCH --depth 1 https://github.com/Ultimaker/Uranium +WORKDIR $CURA_APP_DIR/Uranium + +# Setup materials +ENV MATERIALS_BRANCH=master +WORKDIR $CURA_APP_DIR/Cura/resources +RUN git clone -b $MATERIALS_BRANCH --depth 1 https://github.com/Ultimaker/fdm_materials materials +WORKDIR $CURA_APP_DIR/Cura/resources/materials + +# Setup Cura +ENV CURA_BRANCH=docker +WORKDIR $CURA_APP_DIR +RUN git clone -b $CURA_BRANCH --depth 1 https://github.com/Ultimaker/Cura +WORKDIR $CURA_APP_DIR/Cura + # Make sure Cura can find CuraEngine RUN ln -s /usr/local/bin/CuraEngine $CURA_APP_DIR/Cura From a8bf44003f0fd8d362efd41b44cf873cda40b921 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Thu, 1 Mar 2018 11:52:09 +0100 Subject: [PATCH 25/85] Some fixes in Dockerfile --- Dockerfile | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3e6adc0f4c..b2b6243071 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,19 +23,16 @@ RUN make install ENV URANIUM_BRANCH=master WORKDIR $CURA_APP_DIR RUN git clone -b $URANIUM_BRANCH --depth 1 https://github.com/Ultimaker/Uranium -WORKDIR $CURA_APP_DIR/Uranium - -# Setup materials -ENV MATERIALS_BRANCH=master -WORKDIR $CURA_APP_DIR/Cura/resources -RUN git clone -b $MATERIALS_BRANCH --depth 1 https://github.com/Ultimaker/fdm_materials materials -WORKDIR $CURA_APP_DIR/Cura/resources/materials # Setup Cura ENV CURA_BRANCH=docker WORKDIR $CURA_APP_DIR RUN git clone -b $CURA_BRANCH --depth 1 https://github.com/Ultimaker/Cura -WORKDIR $CURA_APP_DIR/Cura + +# Setup materials +ENV MATERIALS_BRANCH=master +WORKDIR $CURA_APP_DIR/Cura/resources +RUN git clone -b $MATERIALS_BRANCH --depth 1 https://github.com/Ultimaker/fdm_materials materials # Make sure Cura can find CuraEngine RUN ln -s /usr/local/bin/CuraEngine $CURA_APP_DIR/Cura From 62169bed2a1781e8c0b187687d87521b2e7aa06b Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Fri, 2 Mar 2018 16:35:21 +0000 Subject: [PATCH 26/85] Rename bridge_wall_max_air_gap to bridge_wall_max_overhang. --- 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 57770df26c..c803f9c084 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6213,9 +6213,9 @@ "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, - "bridge_wall_max_air_gap": + "bridge_wall_max_overhang": { - "label": "Bridge Wall Max Air Gap", + "label": "Bridge Wall Max Overhang", "description": "The maximum allowed width of the region of air below a wall line before the wall is printed using bridge settings. Expressed as a percentage of the wall line width. When the air gap is wider than this, the wall line is printed using the bridge settings. Otherwise, the wall line is printed using the normal settings. The lower the value, the more likely it is that overhung wall lines will be printed using bridge settings.", "unit": "%", "default_value": 100, From 5db636bf9beadaf499a1ae9c953fbb54ffc70ace Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Fri, 2 Mar 2018 16:36:26 +0000 Subject: [PATCH 27/85] Add bridge_wall_min_length setting. It's currently per extruder to make it easy to access within the engine but this should be changed when the settings are refactored. --- resources/definitions/fdmprinter.def.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index c803f9c084..151cb3f45b 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6201,6 +6201,18 @@ "settable_per_extruder": false, "settable_per_meshgroup": false }, + "bridge_wall_min_length": + { + "label": "Minimum Bridge Wall Length", + "description": "Unsupported walls shorter than this will be printed using the normal wall settings. Longer unsupported walls will be printed using the bridge wall settings.", + "unit": "mm", + "type": "float", + "minimum_value": "0", + "default_value": 5, + "enabled": "bridge_settings_enabled", + "settable_per_mesh": false, + "settable_per_extruder": true + }, "bridge_skin_support_threshold": { "label": "Bridge Skin Support Threshold", From 323eac345a086540b2e1faea744abc60b4fa79c2 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Sat, 3 Mar 2018 12:04:58 +0000 Subject: [PATCH 28/85] Added bridge_modify_skins_above. --- resources/definitions/fdmprinter.def.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 151cb3f45b..b7ffcdf240 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6303,6 +6303,15 @@ "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, + "bridge_modify_skins_above": + { + "label": "Modify Skins Above Bridge", + "description": "If enabled, the skin regions present on the 2nd and 3rd layers above the air gap are printed using Bridge Skin Speed. Otherwise, those skins are printed using the normal skin speed.", + "type": "bool", + "default_value": true, + "enabled": "bridge_settings_enabled", + "settable_per_mesh": true + }, "bridge_fan_speed": { "label": "Bridge Fan Speed", From 754e85815a0b881077f2e37cde83568c19d4f099 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Sun, 4 Mar 2018 09:00:41 +0000 Subject: [PATCH 29/85] Additional second bridge skin settings. --- resources/definitions/fdmprinter.def.json | 60 ++++++++++++++++++----- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index b7ffcdf240..5550924c4f 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6303,15 +6303,6 @@ "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, - "bridge_modify_skins_above": - { - "label": "Modify Skins Above Bridge", - "description": "If enabled, the skin regions present on the 2nd and 3rd layers above the air gap are printed using Bridge Skin Speed. Otherwise, those skins are printed using the normal skin speed.", - "type": "bool", - "default_value": true, - "enabled": "bridge_settings_enabled", - "settable_per_mesh": true - }, "bridge_fan_speed": { "label": "Bridge Fan Speed", @@ -6322,8 +6313,55 @@ "default_value": 100, "type": "float", "enabled": "bridge_settings_enabled", - "settable_per_mesh": false, - "settable_per_extruder": true + "settable_per_mesh": true + }, + "bridge_process_second_skin": + { + "label": "Bridges Have Second Skin", + "description": "If enabled, the skin regions on the second layer above the air gap are printed using bridge second skin settings. Otherwise, those skin regions are printed using the normal skin settings.", + "type": "bool", + "default_value": true, + "enabled": "bridge_settings_enabled", + "settable_per_mesh": true + }, + "bridge_skin_speed_2": + { + "label": "Bridge Second Skin Speed", + "description": "Print speed to use when printing the second bridge skin layer.", + "unit": "mm/s", + "type": "float", + "minimum_value": "0.1", + "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", + "maximum_value_warning": "300", + "default_value": 20, + "value": "bridge_skin_speed", + "enabled": "bridge_settings_enabled and bridge_process_second_skin", + "settable_per_mesh": true + }, + "bridge_skin_material_flow_2": + { + "label": "Bridge Second Skin Flow", + "description": "Flow compensation: the amount of material extruded when printing the second bridge skin layer is multiplied by this value.", + "unit": "%", + "default_value": 100, + "type": "float", + "minimum_value": "5", + "minimum_value_warning": "50", + "maximum_value_warning": "150", + "enabled": "bridge_settings_enabled and bridge_process_second_skin", + "settable_per_mesh": true + }, + "bridge_fan_speed_2": + { + "label": "Bridge Second Skin Fan Speed", + "description": "Fan speed to use when printing the second bridge skin layer.", + "unit": "%", + "minimum_value": "0", + "maximum_value": "100", + "default_value": 0, + "type": "float", + "enabled": "bridge_settings_enabled and bridge_process_second_skin", + "settable_per_mesh": true } } }, From 4a498fd622bf38c9e93560d1d4da79fec55b6bfb Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Sun, 4 Mar 2018 21:56:44 +0000 Subject: [PATCH 30/85] Tweak various bridge setting values & descriptions. --- resources/definitions/fdmprinter.def.json | 33 ++++++++++++----------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 5550924c4f..396c45a59e 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6255,11 +6255,11 @@ "description": "The speed at which bridge skin regions are printed.", "unit": "mm/s", "type": "float", - "minimum_value": "0.1", + "minimum_value": "cool_min_speed", "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", "maximum_value_warning": "300", - "default_value": 20, - "value": "speed_topbottom / 2", + "default_value": 15, + "value": "max(cool_min_speed, speed_topbottom / 2)", "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, @@ -6269,18 +6269,18 @@ "description": "The speed at which the bridge walls are printed.", "unit": "mm/s", "type": "float", - "minimum_value": "0.1", + "minimum_value": "cool_min_speed", "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", "maximum_value_warning": "300", - "default_value": 20, - "value": "speed_wall_0 / 2", + "default_value": 15, + "value": "max(cool_min_speed, speed_wall_0 / 2)", "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, "bridge_skin_material_flow": { "label": "Bridge Skin Flow", - "description": "Flow compensation: the amount of material extruded when bridging skin is multiplied by this value.", + "description": "When printing bridge skin regions, the amount of material extruded is multiplied by this value.", "unit": "%", "default_value": 50, "type": "float", @@ -6293,7 +6293,7 @@ "bridge_wall_material_flow": { "label": "Bridge Wall Flow", - "description": "Flow compensation: the amount of material extruded when bridging walls is multiplied by this value.", + "description": "When printing bridge walls, the amount of material extruded is multiplied by this value.", "unit": "%", "default_value": 50, "type": "float", @@ -6306,7 +6306,7 @@ "bridge_fan_speed": { "label": "Bridge Fan Speed", - "description": "Fan speed to use when printing bridge walls and skin.", + "description": "Percentage fan speed to use when printing bridge walls and skin.", "unit": "%", "minimum_value": "0", "maximum_value": "100", @@ -6317,8 +6317,8 @@ }, "bridge_process_second_skin": { - "label": "Bridges Have Second Skin", - "description": "If enabled, the skin regions on the second layer above the air gap are printed using bridge second skin settings. Otherwise, those skin regions are printed using the normal skin settings.", + "label": "Enable Second Bridge Layer", + "description": "If enabled, the skin regions on the second layer above the air are printed using bridge second skin settings. Otherwise, those skin regions are printed using the normal skin settings.", "type": "bool", "default_value": true, "enabled": "bridge_settings_enabled", @@ -6330,10 +6330,10 @@ "description": "Print speed to use when printing the second bridge skin layer.", "unit": "mm/s", "type": "float", - "minimum_value": "0.1", + "minimum_value": "cool_min_speed", "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", "maximum_value_warning": "300", - "default_value": 20, + "default_value": 15, "value": "bridge_skin_speed", "enabled": "bridge_settings_enabled and bridge_process_second_skin", "settable_per_mesh": true @@ -6341,11 +6341,12 @@ "bridge_skin_material_flow_2": { "label": "Bridge Second Skin Flow", - "description": "Flow compensation: the amount of material extruded when printing the second bridge skin layer is multiplied by this value.", + "description": "When printing the second bridge skin layer, the amount of material extruded is multiplied by this value.", "unit": "%", - "default_value": 100, + "default_value": 125, "type": "float", "minimum_value": "5", + "maximum_value": "500", "minimum_value_warning": "50", "maximum_value_warning": "150", "enabled": "bridge_settings_enabled and bridge_process_second_skin", @@ -6354,7 +6355,7 @@ "bridge_fan_speed_2": { "label": "Bridge Second Skin Fan Speed", - "description": "Fan speed to use when printing the second bridge skin layer.", + "description": "Percentage fan speed to use when printing the second bridge skin layer.", "unit": "%", "minimum_value": "0", "maximum_value": "100", From cd01b096b52357c6384e555a141505cebb3c65cf Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Mon, 5 Mar 2018 14:33:13 +0100 Subject: [PATCH 31/85] Emit backend errors so we can process them differently than through a qml notification --- .dockerignore | 4 ++++ Dockerfile | 13 +++++++------ plugins/CuraEngineBackend/CuraEngineBackend.py | 9 +++++++++ 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..d25d71bcc9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.git +.github +resources/materials +CuraEngine \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index b2b6243071..68255c56b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,21 +24,22 @@ ENV URANIUM_BRANCH=master WORKDIR $CURA_APP_DIR RUN git clone -b $URANIUM_BRANCH --depth 1 https://github.com/Ultimaker/Uranium -# Setup Cura -ENV CURA_BRANCH=docker -WORKDIR $CURA_APP_DIR -RUN git clone -b $CURA_BRANCH --depth 1 https://github.com/Ultimaker/Cura - # Setup materials ENV MATERIALS_BRANCH=master -WORKDIR $CURA_APP_DIR/Cura/resources +WORKDIR $CURA_APP_DIR RUN git clone -b $MATERIALS_BRANCH --depth 1 https://github.com/Ultimaker/fdm_materials materials +# Setup Cura +WORKDIR $CURA_APP_DIR/Cura +ADD . . +RUN mv $CURA_APP_DIR/materials resources/materials + # Make sure Cura can find CuraEngine RUN ln -s /usr/local/bin/CuraEngine $CURA_APP_DIR/Cura # Run Cura WORKDIR $CURA_APP_DIR/Cura ENV PYTHONPATH=${PYTHONPATH}:$CURA_APP_DIR/Uranium +RUN chmod +x ./CuraEngine RUN chmod +x ./run_in_docker.sh CMD "./run_in_docker.sh" diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 3982a0ad06..ffeddf21cc 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -33,6 +33,9 @@ from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") class CuraEngineBackend(QObject, Backend): + + backendError = Signal() + ## Starts the back-end plug-in. # # This registers all the signal listeners and prepares for communication @@ -289,6 +292,7 @@ class CuraEngineBackend(QObject, Backend): if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error: self.backendStateChange.emit(BackendState.Error) + self.backendError.emit(job) return if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible: @@ -297,6 +301,7 @@ class CuraEngineBackend(QObject, Backend): "Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) + self.backendError.emit(job) else: self.backendStateChange.emit(BackendState.NotStarted) return @@ -325,6 +330,7 @@ class CuraEngineBackend(QObject, Backend): title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) + self.backendError.emit(job) else: self.backendStateChange.emit(BackendState.NotStarted) return @@ -347,6 +353,7 @@ class CuraEngineBackend(QObject, Backend): title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) + self.backendError.emit(job) return if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError: @@ -355,6 +362,7 @@ class CuraEngineBackend(QObject, Backend): title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) + self.backendError.emit(job) else: self.backendStateChange.emit(BackendState.NotStarted) @@ -364,6 +372,7 @@ class CuraEngineBackend(QObject, Backend): title = catalog.i18nc("@info:title", "Unable to slice")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) + self.backendError.emit(job) else: self.backendStateChange.emit(BackendState.NotStarted) self._invokeSlice() From 5a8f2040d3695a83000aa67ad1a6f038fcc95c02 Mon Sep 17 00:00:00 2001 From: ChrisTerBeke Date: Mon, 5 Mar 2018 17:53:40 +0100 Subject: [PATCH 32/85] Add method to machine manager to get a machine stack by definition id --- cura/Settings/MachineManager.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index e357d778ca..d478321ffc 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -306,6 +306,14 @@ class MachineManager(QObject): self.__emitChangedSignals() + @staticmethod + def getMachine(definition_id: str) -> Optional["GlobalStack"]: + machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine") + for machine in machines: + if machine.definition.getId() == definition_id: + return machine + return None + @pyqtSlot(str, str) def addMachine(self, name: str, definition_id: str) -> None: new_stack = CuraStackBuilder.createMachine(name, definition_id) From 3e8f29d380764766383cd87c006f01c0b27c1523 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 6 Mar 2018 09:37:21 +0100 Subject: [PATCH 33/85] Allow floating point values for moving print head The X, Y, Z coordinates and speed don't necessarily have to be full millimetres or millimetres per minute. Fixes #3271. --- cura/PrinterOutput/PrinterOutputModel.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cura/PrinterOutput/PrinterOutputModel.py b/cura/PrinterOutput/PrinterOutputModel.py index 8234989519..1c23d0e18e 100644 --- a/cura/PrinterOutput/PrinterOutputModel.py +++ b/cura/PrinterOutput/PrinterOutputModel.py @@ -103,32 +103,32 @@ class PrinterOutputModel(QObject): self._head_position = Vector(x, y, z) self.headPositionChanged.emit() - @pyqtProperty("long", "long", "long") - @pyqtProperty("long", "long", "long", "long") + @pyqtProperty(float, float, float) + @pyqtProperty(float, float, float, float) def setHeadPosition(self, x, y, z, speed = 3000): self.updateHeadPosition(x, y, z) self._controller.setHeadPosition(self, x, y, z, speed) - @pyqtProperty("long") - @pyqtProperty("long", "long") + @pyqtProperty(float) + @pyqtProperty(float, float) def setHeadX(self, x, speed = 3000): self.updateHeadPosition(x, self._head_position.y, self._head_position.z) self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed) - @pyqtProperty("long") - @pyqtProperty("long", "long") + @pyqtProperty(float) + @pyqtProperty(float, float) def setHeadY(self, y, speed = 3000): self.updateHeadPosition(self._head_position.x, y, self._head_position.z) self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed) - @pyqtProperty("long") - @pyqtProperty("long", "long") + @pyqtProperty(float) + @pyqtProperty(float, float) def setHeadZ(self, z, speed = 3000): self.updateHeadPosition(self._head_position.x, self._head_position.y, z) self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed) - @pyqtSlot("long", "long", "long") - @pyqtSlot("long", "long", "long", "long") + @pyqtSlot(float, float, float) + @pyqtSlot(float, float, float, float) def moveHead(self, x = 0, y = 0, z = 0, speed = 3000): self._controller.moveHead(self, x, y, z, speed) From 8d5a643c9b0830e381c05b7e97c3483d48b369e3 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Tue, 6 Mar 2018 10:51:39 +0100 Subject: [PATCH 34/85] Removed `print()` statement --- plugins/PerObjectSettingsTool/PerObjectSettingsTool.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index b671db48fb..11e26a033a 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -181,7 +181,6 @@ class PerObjectSettingsTool(Tool): def _checkStackForErrors(self, stack): - print("checking for errors") if stack is None: return False From 27b3a71a98c7c01db88b2b8e38bdbe0770a8c112 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 6 Mar 2018 11:26:14 +0100 Subject: [PATCH 35/85] Sort branded materials for dropdown menu CURA-4606 --- cura/Machines/Models/BrandMaterialsModel.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cura/Machines/Models/BrandMaterialsModel.py b/cura/Machines/Models/BrandMaterialsModel.py index 6628d924f1..2ef1425986 100644 --- a/cura/Machines/Models/BrandMaterialsModel.py +++ b/cura/Machines/Models/BrandMaterialsModel.py @@ -120,12 +120,19 @@ class BrandMaterialsModel(ListModel): material_type_item = {"name": material_type, "colors": BaseMaterialsModel(self)} material_type_item["colors"].clear() + + # Sort materials by name + material_list = sorted(material_list, key = lambda x: x["name"]) material_type_item["colors"].setItems(material_list) material_type_item_list.append(material_type_item) + # Sort material type by name + material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"]) brand_item["materials"].setItems(material_type_item_list) brand_item_list.append(brand_item) + # Sort brand by name + brand_item_list = sorted(brand_item_list, key = lambda x: x["name"]) self.setItems(brand_item_list) From 1ed5a00198e3e3b28de84c60e149427ef2d69004 Mon Sep 17 00:00:00 2001 From: Mark Burton Date: Tue, 6 Mar 2018 10:47:28 +0000 Subject: [PATCH 36/85] Added skin densities and layer 3 settings + tweaked various defaults. --- resources/definitions/fdmprinter.def.json | 149 +++++++++++++++++----- 1 file changed, 114 insertions(+), 35 deletions(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 396c45a59e..a3e50c2b1c 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6249,20 +6249,6 @@ "enabled": "bridge_settings_enabled", "settable_per_mesh": false }, - "bridge_skin_speed": - { - "label": "Bridge Skin Speed", - "description": "The speed at which bridge skin regions are printed.", - "unit": "mm/s", - "type": "float", - "minimum_value": "cool_min_speed", - "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", - "maximum_value_warning": "300", - "default_value": 15, - "value": "max(cool_min_speed, speed_topbottom / 2)", - "enabled": "bridge_settings_enabled", - "settable_per_mesh": true - }, "bridge_wall_speed": { "label": "Bridge Wall Speed", @@ -6277,19 +6263,6 @@ "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, - "bridge_skin_material_flow": - { - "label": "Bridge Skin Flow", - "description": "When printing bridge skin regions, the amount of material extruded is multiplied by this value.", - "unit": "%", - "default_value": 50, - "type": "float", - "minimum_value": "5", - "minimum_value_warning": "50", - "maximum_value_warning": "150", - "enabled": "bridge_settings_enabled", - "settable_per_mesh": true - }, "bridge_wall_material_flow": { "label": "Bridge Wall Flow", @@ -6303,6 +6276,46 @@ "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, + "bridge_skin_speed": + { + "label": "Bridge Skin Speed", + "description": "The speed at which bridge skin regions are printed.", + "unit": "mm/s", + "type": "float", + "minimum_value": "cool_min_speed", + "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", + "maximum_value_warning": "300", + "default_value": 15, + "value": "max(cool_min_speed, speed_topbottom / 2)", + "enabled": "bridge_settings_enabled", + "settable_per_mesh": true + }, + "bridge_skin_material_flow": + { + "label": "Bridge Skin Flow", + "description": "When printing bridge skin regions, the amount of material extruded is multiplied by this value.", + "unit": "%", + "default_value": 60, + "type": "float", + "minimum_value": "5", + "minimum_value_warning": "50", + "maximum_value_warning": "150", + "enabled": "bridge_settings_enabled", + "settable_per_mesh": true + }, + "bridge_skin_density": + { + "label": "Bridge Skin Density", + "description": "The density of the bridge skin layer. Values less than 100 will increase the gaps between the skin lines.", + "unit": "%", + "default_value": 100, + "type": "float", + "minimum_value": "5", + "maximum_value": "100", + "minimum_value_warning": "20", + "enabled": "bridge_settings_enabled", + "settable_per_mesh": true + }, "bridge_fan_speed": { "label": "Bridge Fan Speed", @@ -6315,10 +6328,10 @@ "enabled": "bridge_settings_enabled", "settable_per_mesh": true }, - "bridge_process_second_skin": + "bridge_enable_more_layers": { - "label": "Enable Second Bridge Layer", - "description": "If enabled, the skin regions on the second layer above the air are printed using bridge second skin settings. Otherwise, those skin regions are printed using the normal skin settings.", + "label": "Bridge Has Multiple Layers", + "description": "If enabled, the second and third layers above the air are printed using the following settings. Otherwise, those layers are printed using the normal settings.", "type": "bool", "default_value": true, "enabled": "bridge_settings_enabled", @@ -6333,9 +6346,9 @@ "minimum_value": "cool_min_speed", "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", "maximum_value_warning": "300", - "default_value": 15, + "default_value": 25, "value": "bridge_skin_speed", - "enabled": "bridge_settings_enabled and bridge_process_second_skin", + "enabled": "bridge_settings_enabled and bridge_enable_more_layers", "settable_per_mesh": true }, "bridge_skin_material_flow_2": @@ -6343,13 +6356,26 @@ "label": "Bridge Second Skin Flow", "description": "When printing the second bridge skin layer, the amount of material extruded is multiplied by this value.", "unit": "%", - "default_value": 125, + "default_value": 100, "type": "float", "minimum_value": "5", "maximum_value": "500", "minimum_value_warning": "50", "maximum_value_warning": "150", - "enabled": "bridge_settings_enabled and bridge_process_second_skin", + "enabled": "bridge_settings_enabled and bridge_enable_more_layers", + "settable_per_mesh": true + }, + "bridge_skin_density_2": + { + "label": "Bridge Second Skin Density", + "description": "The density of the second bridge skin layer. Values less than 100 will increase the gaps between the skin lines.", + "unit": "%", + "default_value": 75, + "type": "float", + "minimum_value": "5", + "maximum_value": "100", + "minimum_value_warning": "20", + "enabled": "bridge_settings_enabled and bridge_enable_more_layers", "settable_per_mesh": true }, "bridge_fan_speed_2": @@ -6361,7 +6387,60 @@ "maximum_value": "100", "default_value": 0, "type": "float", - "enabled": "bridge_settings_enabled and bridge_process_second_skin", + "enabled": "bridge_settings_enabled and bridge_enable_more_layers", + "settable_per_mesh": true + }, + "bridge_skin_speed_3": + { + "label": "Bridge Third Skin Speed", + "description": "Print speed to use when printing the third bridge skin layer.", + "unit": "mm/s", + "type": "float", + "minimum_value": "cool_min_speed", + "maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)", + "maximum_value_warning": "300", + "default_value": 15, + "value": "bridge_skin_speed", + "enabled": "bridge_settings_enabled and bridge_enable_more_layers", + "settable_per_mesh": true + }, + "bridge_skin_material_flow_3": + { + "label": "Bridge Third Skin Flow", + "description": "When printing the third bridge skin layer, the amount of material extruded is multiplied by this value.", + "unit": "%", + "default_value": 110, + "type": "float", + "minimum_value": "5", + "maximum_value": "500", + "minimum_value_warning": "50", + "maximum_value_warning": "150", + "enabled": "bridge_settings_enabled and bridge_enable_more_layers", + "settable_per_mesh": true + }, + "bridge_skin_density_3": + { + "label": "Bridge Third Skin Density", + "description": "The density of the third bridge skin layer. Values less than 100 will increase the gaps between the skin lines.", + "unit": "%", + "default_value": 80, + "type": "float", + "minimum_value": "5", + "maximum_value": "100", + "minimum_value_warning": "20", + "enabled": "bridge_settings_enabled and bridge_enable_more_layers", + "settable_per_mesh": true + }, + "bridge_fan_speed_3": + { + "label": "Bridge Third Skin Fan Speed", + "description": "Percentage fan speed to use when printing the third bridge skin layer.", + "unit": "%", + "minimum_value": "0", + "maximum_value": "100", + "default_value": 0, + "type": "float", + "enabled": "bridge_settings_enabled and bridge_enable_more_layers", "settable_per_mesh": true } } From ff0d694e72b41dd860d1ddfdff5b004c68ba67da Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 6 Mar 2018 11:55:20 +0100 Subject: [PATCH 37/85] Select the activated material when material management page shows up CURA-4606 --- resources/qml/Preferences/MaterialsPage.qml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index 4a6d07df81..553cfe0423 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -52,6 +52,24 @@ Item return base.currentItem.root_material_id == root_material_id; } + Component.onCompleted: + { + // Select the activated material when this page shows up + const extruder_position = Cura.ExtruderManager.activeExtruderIndex; + const active_root_material_id = Cura.MachineManager.currentRootMaterialId[extruder_position]; + var itemIndex = -1; + for (var i = 0; i < materialsModel.rowCount(); ++i) + { + var item = materialsModel.getItem(i); + if (item.root_material_id == active_root_material_id) + { + itemIndex = i; + break; + } + } + materialListView.currentIndex = itemIndex; + } + Row // Button Row { id: buttonRow From 1f883f331216c4a39ee2611775d98c39a4562ca8 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Tue, 6 Mar 2018 12:39:54 +0100 Subject: [PATCH 38/85] Remove reply hanlder to prevent crash after canceling a printing job CURA-4960 --- plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index c0d538bb78..73acc02cae 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -194,6 +194,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): # the "reply" should be disconnected if self._latest_reply_handler: self._latest_reply_handler.disconnect() + self._latest_reply_handler = None @pyqtSlot() From f8c129f4c79b563f9355ffd868e08235dc50486b Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 6 Mar 2018 12:46:48 +0100 Subject: [PATCH 39/85] Update ISSUE_TEMPLATE.md Allow for uploading curaproject files in github directly --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 97c849144d..af5b6b6d3c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -6,7 +6,7 @@ Before filing, PLEASE check if the issue already exists (either open or closed) Also, please note the application version in the title of the issue. For example: "[3.2.1] Cannot connect to 3rd-party printer". Please do not write thigns like "Request:" or "[BUG]" in the title; this is what labels are for. It is also helpful to attach a project (.3mf or .curaproject) file and Cura log file so we can debug issues quicker. -Information about how to find the log file can be found at https://github.com/Ultimaker/Cura/wiki/Cura-Preferences-and-Settings-Locations. To upload a project, we recommend http://wetransfer.com, but other file hosts like Google Drive or Dropbox work well too. +Information about how to find the log file can be found at https://github.com/Ultimaker/Cura/wiki/Cura-Preferences-and-Settings-Locations. To upload a project, try changing the extension to e.g. .curaproject.3mf.zip so that github accepts uploading the file. Otherwise we recommend http://wetransfer.com, but other file hosts like Google Drive or Dropbox work well too. Thank you for using Cura! --> From 5950d147ac475abc8edab89da3c5bd8daf90fd39 Mon Sep 17 00:00:00 2001 From: Tim Kuipers Date: Tue, 6 Mar 2018 13:20:15 +0100 Subject: [PATCH 40/85] Update ISSUE_TEMPLATE.md More focus on printer and attaching project file --- .github/ISSUE_TEMPLATE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index af5b6b6d3c..c69cf91433 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -26,6 +26,9 @@ Thank you for using Cura! **Display Driver** +**Printer** + + **Steps to Reproduce** From 31b737468944c6645edfd9779a7562f4a2242259 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 6 Mar 2018 13:39:35 +0100 Subject: [PATCH 41/85] Fix material settings saving upon dialog close CURA-4606 Force to trigger a lose focus on all editing fields so their onEditingFinished callback will get triggered. --- resources/qml/Preferences/MaterialView.qml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index c987880305..d2f653e650 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -47,6 +47,19 @@ TabView return Math.round(diameter); } + // This trick makes sure to make all fields lose focus so their onEditingFinished will be triggered + // and modified values will be saved. This can happen when a user changes a value and then closes the + // dialog directly. + // + // Please note that somehow this callback is ONLY triggered when visible is false. + onVisibleChanged: + { + if (!visible) + { + base.focus = false; + } + } + Tab { title: catalog.i18nc("@title", "Information") From cb7677347d73a5d22f38f90c910faae731ffca13 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 6 Mar 2018 15:26:54 +0100 Subject: [PATCH 42/85] Fix material model update upon variant change CURA-5052 --- cura/Machines/Models/BaseMaterialsModel.py | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index de0c68d60a..5c09a4f060 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -3,6 +3,7 @@ from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty +from UM.Application import Application from UM.Qt.ListModel import ListModel @@ -25,6 +26,8 @@ class BaseMaterialsModel(ListModel): def __init__(self, parent = None): super().__init__(parent) + self._application = Application.getInstance() + self._machine_manager = self._application.getMachineManager() self.addRoleName(self.RootMaterialIdRole, "root_material_id") self.addRoleName(self.IdRole, "id") @@ -35,12 +38,33 @@ class BaseMaterialsModel(ListModel): self.addRoleName(self.ContainerNodeRole, "container_node") self._extruder_position = 0 + self._extruder_stack = None + + self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack) + + def _updateExtruderStack(self): + global_stack = self._machine_manager.activeMachine + if global_stack is None: + return + + if self._extruder_stack is not None: + self._extruder_stack.pyqtContainersChanged.disconnect(self._update) + self._extruder_stack = global_stack.extruders.get(str(self._extruder_position)) + if self._extruder_stack is not None: + self._extruder_stack.pyqtContainersChanged.connect(self._update) def setExtruderPosition(self, position: int): if self._extruder_position != position: self._extruder_position = position + self._updateExtruderStack() self.extruderPositionChanged.emit() @pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged) def extruderPosition(self) -> int: - return self._extruder_positoin + return self._extruder_position + + # + # This is an abstract method that needs to be implemented by + # + def _update(self): + pass From fb798ab7e569436ac4da5494a2b6a780efcb710f Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 6 Mar 2018 15:30:37 +0100 Subject: [PATCH 43/85] Small refactor in MachineManager and add more loggings CURA-4606 - Added more info loggings - Changed some variant names - Use some shortcut variables instead of getInstance()s --- cura/Settings/MachineManager.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index e357d778ca..8cc25b0ccc 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -917,30 +917,38 @@ class MachineManager(QObject): ## Update current quality type and machine after setting material def _updateQualityWithMaterial(self): - current_quality = None + Logger.log("i", "Updating quality/quality_changes due to material change") + current_quality_type = None if self._current_quality_group: - current_quality = self._current_quality_group.quality_type - quality_manager = Application.getInstance()._quality_manager - candidate_quality_groups = quality_manager.getQualityGroups(self._global_container_stack) + current_quality_type = self._current_quality_group.quality_type + candidate_quality_groups = self._quality_manager.getQualityGroups(self._global_container_stack) available_quality_types = {qt for qt, g in candidate_quality_groups.items() if g.is_available} + Logger.log("d", "Current quality type = [%s]", current_quality_type) if not self.activeMaterialsCompatible(): + Logger.log("i", "Active materials are not compatible, setting all qualities to empty (Not Supported).") self._setEmptyQuality() return if not available_quality_types: + Logger.log("i", "No available quality types found, setting all qualities to empty (Not Supported).") self._setEmptyQuality() return - if current_quality in available_quality_types: - self._setQualityGroup(candidate_quality_groups[current_quality], empty_quality_changes = False) + if current_quality_type in available_quality_types: + Logger.log("i", "Current available quality type [%s] is available, applying changes.", current_quality_type) + self._setQualityGroup(candidate_quality_groups[current_quality_type], empty_quality_changes = False) return + # The current quality type is not available so we use the preferred quality type if it's available, + # otherwise use one of the available quality types. quality_type = sorted(list(available_quality_types))[0] preferred_quality_type = self._global_container_stack.getMetaDataEntry("preferred_quality_type") if preferred_quality_type in available_quality_types: quality_type = preferred_quality_type + Logger.log("i", "The current quality type [%s] is not available, switching to [%s] instead", + current_quality_type, quality_type) self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True) def _updateMaterialWithVariant(self, position: Optional[str]): @@ -955,9 +963,8 @@ class MachineManager(QObject): current_material_base_name = extruder.material.getMetaDataEntry("base_file") current_variant_name = extruder.variant.getMetaDataEntry("name") - material_manager = Application.getInstance()._material_manager material_diameter = self._global_container_stack.getProperty("material_diameter", "value") - candidate_materials = material_manager.getAvailableMaterials( + candidate_materials = self._material_manager.getAvailableMaterials( self._global_container_stack.definition.getId(), current_variant_name, material_diameter) @@ -1004,7 +1011,7 @@ class MachineManager(QObject): # See if we need to show the Discard or Keep changes screen if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: - Application.getInstance().discardOrKeepProfileChanges() + self._application.discardOrKeepProfileChanges() @pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged) def activeQualityGroup(self): @@ -1018,7 +1025,7 @@ class MachineManager(QObject): # See if we need to show the Discard or Keep changes screen if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: - Application.getInstance().discardOrKeepProfileChanges() + self._application.discardOrKeepProfileChanges() @pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged) def activeQualityChangesGroup(self): From 6bb42da0566b2d687053152288e2d9234cbf79f1 Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Tue, 6 Mar 2018 15:40:26 +0100 Subject: [PATCH 44/85] Removed related commits to Settins per Object validation and added short validation in StartScliceJob CURA-4972 --- cura/ObjectsModel.py | 10 --- cura/Settings/SettingOverrideDecorator.py | 23 +----- plugins/CuraEngineBackend/StartSliceJob.py | 6 +- .../PerObjectSettingsTool/PerObjectItem.qml | 15 +--- .../PerObjectSettingsPanel.qml | 5 +- .../PerObjectSettingsTool.py | 70 ------------------- 6 files changed, 6 insertions(+), 123 deletions(-) diff --git a/cura/ObjectsModel.py b/cura/ObjectsModel.py index d7077d3d85..f02e8b4db5 100644 --- a/cura/ObjectsModel.py +++ b/cura/ObjectsModel.py @@ -19,8 +19,6 @@ class ObjectsModel(ListModel): self._build_plate_number = -1 - self._stacks_have_errors = None # type:Optional[bool] - def setActiveBuildPlate(self, nr): self._build_plate_number = nr self._update() @@ -69,11 +67,3 @@ class ObjectsModel(ListModel): @staticmethod def createObjectsModel(): return ObjectsModel() - - ## Check if none of the model's stacks contain error states - # The setting applied for the settings per model - def stacksHaveErrors(self) -> bool: - return bool(self._stacks_have_errors) - - def setStacksHaveErrors(self, value): - self._stacks_have_errors = value \ No newline at end of file diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index 6e98f014dc..24d94e4955 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -9,8 +9,7 @@ from UM.Signal import Signal, signalemitter from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Logger import Logger -from UM.Settings.Validator import ValidatorState -from PyQt5.QtCore import QTimer + from UM.Application import Application from cura.Settings.PerObjectContainerStack import PerObjectContainerStack @@ -40,10 +39,6 @@ class SettingOverrideDecorator(SceneNodeDecorator): self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId() self._is_non_printing_mesh = False - self._error_check_timer = QTimer() - self._error_check_timer.setInterval(250) - self._error_check_timer.setSingleShot(True) - self._error_check_timer.timeout.connect(self._checkStackForErrors) self._stack.propertyChanged.connect(self._onSettingChanged) @@ -99,21 +94,9 @@ class SettingOverrideDecorator(SceneNodeDecorator): # Trigger slice/need slicing if the value has changed. if property_name == "value": self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings) - if not self._is_non_printing_mesh: - # self._error_check_timer.start() - self._checkStackForErrors() - Application.getInstance().getBackend().needsSlicing() - Application.getInstance().getBackend().tickle() - def _checkStackForErrors(self): - hasErrors = False; - for key in self._stack.getAllKeys(): - validation_state = self._stack.getProperty(key, "validationState") - if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError): - Logger.log("w", "Setting Per Object %s is not valid.", key) - hasErrors = True - break - Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors) + Application.getInstance().getBackend().needsSlicing() + Application.getInstance().getBackend().tickle() ## Makes sure that the stack upon which the container stack is placed is # kept up to date. diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 8b7205f8b2..afbc816cc5 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -136,14 +136,10 @@ class StartSliceJob(Job): self.setResult(StartJobResult.MaterialIncompatible) return - # Validate settings per selectable model - if Application.getInstance().getObjectsModel().stacksHaveErrors(): - self.setResult(StartJobResult.ObjectSettingError) - return # Don't slice if there is a per object setting with an error value. for node in DepthFirstIterator(self._scene.getRoot()): - if node.isSelectable(): + if type(node) is not CuraSceneNode or not node.isSelectable(): continue if self._checkStackForErrors(node.callDecoration("getStack")): diff --git a/plugins/PerObjectSettingsTool/PerObjectItem.qml b/plugins/PerObjectSettingsTool/PerObjectItem.qml index 1317c00b19..559ad2bf81 100644 --- a/plugins/PerObjectSettingsTool/PerObjectItem.qml +++ b/plugins/PerObjectSettingsTool/PerObjectItem.qml @@ -25,20 +25,7 @@ UM.TooltipArea onClicked: { - // Important first set visible and then subscribe - // otherwise the setting is not yet in list - // For unsubscribe is important first remove the subscription and then - // set as invisible - if(checked) - { - addedSettingsModel.setVisible(model.key, checked); - UM.ActiveTool.triggerActionWithData("subscribeForSettingValidation", model.key) - } - else - { - UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key) - addedSettingsModel.setVisible(model.key, checked); - } + addedSettingsModel.setVisible(model.key, checked); UM.ActiveTool.forceUpdate(); } } diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index e72e1224df..03a2ce1bf4 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -240,10 +240,7 @@ Item { width: Math.round(UM.Theme.getSize("setting").height / 2) height: UM.Theme.getSize("setting").height - onClicked: { - addedSettingsModel.setVisible(model.key, false) - UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key) - } + onClicked: addedSettingsModel.setVisible(model.key, false) style: ButtonStyle { diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index 11e26a033a..d2db5ff420 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -10,10 +10,7 @@ from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator from cura.Settings.ExtruderManager import ExtruderManager from UM.Settings.SettingInstance import SettingInstance from UM.Event import Event -from UM.Settings.Validator import ValidatorState -from UM.Logger import Logger -from PyQt5.QtCore import QTimer ## This tool allows the user to add & change settings per node in the scene. # The settings per object are kept in a ContainerStack, which is linked to a node by decorator. @@ -37,12 +34,6 @@ class PerObjectSettingsTool(Tool): self._onGlobalContainerChanged() Selection.selectionChanged.connect(self._updateEnabled) - self._scene = Application.getInstance().getController().getScene() - - self._error_check_timer = QTimer() - self._error_check_timer.setInterval(250) - self._error_check_timer.setSingleShot(True) - self._error_check_timer.timeout.connect(self._updateStacksHaveErrors) def event(self, event): super().event(event) @@ -151,64 +142,3 @@ class PerObjectSettingsTool(Tool): else: self._single_model_selected = True Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected) - - - def _onPropertyChanged(self, key: str, property_name: str) -> None: - if property_name == "validationState": - # self._error_check_timer.start() - return - - def _updateStacksHaveErrors(self) -> None: - return - # self._checkStacksHaveErrors() - - - def _checkStacksHaveErrors(self): - - for node in DepthFirstIterator(self._scene.getRoot()): - - # valdiate only objects which can be selected because the settings per object - # can be applied only for them - if not node.isSelectable(): - continue - - hasErrors = self._checkStackForErrors(node.callDecoration("getStack")) - Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors) - - #If any of models has an error then no reason check next objects on the build plate - if hasErrors: - break - - - def _checkStackForErrors(self, stack): - if stack is None: - return False - - for key in stack.getAllKeys(): - validation_state = stack.getProperty(key, "validationState") - if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError): - Logger.log("w", "Setting Per Object %s is not valid.", key) - return True - return False - - def subscribeForSettingValidation(self, setting_name): - selected_object = Selection.getSelectedObject(0) - stack = selected_object.callDecoration("getStack") # Don't try to get the active extruder since it may be None anyway. - if not stack: - return "" - - settings = stack.getTop() - setting_instance = settings.getInstance(setting_name) - if setting_instance: - setting_instance.propertyChanged.connect(self._onPropertyChanged) - - def unsubscribeForSettingValidation(self, setting_name): - selected_object = Selection.getSelectedObject(0) - stack = selected_object.callDecoration("getStack") # Don't try to get the active extruder since it may be None anyway. - if not stack: - return "" - - settings = stack.getTop() - setting_instance = settings.getInstance(setting_name) - if setting_instance: - setting_instance.propertyChanged.disconnect(self._onPropertyChanged) From eb84e6aa3ef13cfa40fcb81a1b8bbd62994e049b Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 6 Mar 2018 15:47:01 +0100 Subject: [PATCH 45/85] Disable not supported custom profiles CURA-5051 --- resources/qml/Menus/ProfileMenu.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/Menus/ProfileMenu.qml b/resources/qml/Menus/ProfileMenu.qml index 72cda13ca9..5b9a5a3b73 100644 --- a/resources/qml/Menus/ProfileMenu.qml +++ b/resources/qml/Menus/ProfileMenu.qml @@ -52,6 +52,7 @@ Menu { text: model.name checkable: model.available + enabled: model.available checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name exclusiveGroup: group onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group) From b179edf60e31a57e42c84cf88ce82f84187cd60f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 6 Mar 2018 15:41:14 +0100 Subject: [PATCH 46/85] Document which layer is which pass Contributes to issue CURA-5040. --- plugins/XRayView/xray_composite.shader | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/XRayView/xray_composite.shader b/plugins/XRayView/xray_composite.shader index 82dca52cf9..0a8f6364d7 100644 --- a/plugins/XRayView/xray_composite.shader +++ b/plugins/XRayView/xray_composite.shader @@ -13,9 +13,9 @@ vertex = } fragment = - uniform sampler2D u_layer0; - uniform sampler2D u_layer1; - uniform sampler2D u_layer2; + uniform sampler2D u_layer0; //Default pass. + uniform sampler2D u_layer1; //Selection pass. + uniform sampler2D u_layer2; //X-ray pass. uniform vec2 u_offset[9]; @@ -83,9 +83,9 @@ vertex41core = fragment41core = #version 410 - uniform sampler2D u_layer0; - uniform sampler2D u_layer1; - uniform sampler2D u_layer2; + uniform sampler2D u_layer0; //Default pass. + uniform sampler2D u_layer1; //Selection pass. + uniform sampler2D u_layer2; //X-ray pass. uniform vec2 u_offset[9]; From 3b0a9bf16c88d0f8ae9179d5cdbd855f7b64204d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 6 Mar 2018 15:50:35 +0100 Subject: [PATCH 47/85] Fix checking whether to render objects in X-ray pass Otherwise nothing gets drawn there. Contributes to issue CURA-5040. --- plugins/XRayView/XRayPass.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/XRayView/XRayPass.py b/plugins/XRayView/XRayPass.py index 38c88a256e..a75d393b35 100644 --- a/plugins/XRayView/XRayPass.py +++ b/plugins/XRayView/XRayPass.py @@ -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 os.path @@ -10,7 +10,7 @@ from UM.View.RenderPass import RenderPass from UM.View.RenderBatch import RenderBatch from UM.View.GL.OpenGL import OpenGL -from UM.Scene.SceneNode import SceneNode +from cura.Scene.CuraSceneNode import CuraSceneNode from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator class XRayPass(RenderPass): @@ -27,7 +27,7 @@ class XRayPass(RenderPass): batch = RenderBatch(self._shader, type = RenderBatch.RenderType.NoType, backface_cull = False, blend_mode = RenderBatch.BlendMode.Additive) for node in DepthFirstIterator(self._scene.getRoot()): - if type(node) is SceneNode and node.getMeshData() and node.isVisible(): + if isinstance(node, CuraSceneNode) and node.getMeshData() and node.isVisible(): batch.addItem(node.getWorldTransformation(), node.getMeshData()) self.bind() From 75d9297c7d664347fb579ae0d01f44bcbaf04832 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 6 Mar 2018 17:05:21 +0100 Subject: [PATCH 48/85] Optimize 3MF writer and XML material serialization CURA-5049 --- plugins/3MFWriter/ThreeMFWorkspaceWriter.py | 35 +++++++++++-------- .../XmlMaterialProfile/XmlMaterialProfile.py | 23 ++++++++---- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index 507274d355..3f5e69317e 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -1,14 +1,15 @@ -# 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.Workspace.WorkspaceWriter import WorkspaceWriter +import configparser +from io import StringIO +import zipfile + from UM.Application import Application +from UM.Logger import Logger from UM.Preferences import Preferences from UM.Settings.ContainerRegistry import ContainerRegistry -from cura.Settings.ExtruderManager import ExtruderManager -import zipfile -from io import StringIO -import configparser +from UM.Workspace.WorkspaceWriter import WorkspaceWriter class ThreeMFWorkspaceWriter(WorkspaceWriter): @@ -16,7 +17,10 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): super().__init__() def write(self, stream, nodes, mode=WorkspaceWriter.OutputMode.BinaryMode): - mesh_writer = Application.getInstance().getMeshFileHandler().getWriter("3MFWriter") + application = Application.getInstance() + machine_manager = application.getMachineManager() + + mesh_writer = application.getMeshFileHandler().getWriter("3MFWriter") if not mesh_writer: # We need to have the 3mf mesh writer, otherwise we can't save the entire workspace return False @@ -29,17 +33,17 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): if archive is None: # This happens if there was no mesh data to write. archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED) - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_stack = machine_manager.activeMachine # Add global container stack data to the archive. - self._writeContainerToArchive(global_container_stack, archive) + self._writeContainerToArchive(global_stack, archive) # Also write all containers in the stack to the file - for container in global_container_stack.getContainers(): + for container in global_stack.getContainers(): self._writeContainerToArchive(container, archive) # Check if the machine has extruders and save all that data as well. - for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()): + for extruder_stack in global_stack.extruders.values(): self._writeContainerToArchive(extruder_stack, archive) for container in extruder_stack.getContainers(): self._writeContainerToArchive(container, archive) @@ -59,9 +63,9 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): version_file = zipfile.ZipInfo("Cura/version.ini") version_config_parser = configparser.ConfigParser(interpolation = None) version_config_parser.add_section("versions") - version_config_parser.set("versions", "cura_version", Application.getInstance().getVersion()) - version_config_parser.set("versions", "build_type", Application.getInstance().getBuildType()) - version_config_parser.set("versions", "is_debug_mode", str(Application.getInstance().getIsDebugMode())) + version_config_parser.set("versions", "cura_version", application.getVersion()) + version_config_parser.set("versions", "build_type", application.getBuildType()) + version_config_parser.set("versions", "is_debug_mode", str(application.getIsDebugMode())) version_file_string = StringIO() version_config_parser.write(version_file_string) @@ -85,7 +89,8 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): # Some containers have a base file, which should then be the file to use. if "base_file" in container.getMetaData(): base_file = container.getMetaDataEntry("base_file") - container = ContainerRegistry.getInstance().findContainers(id = base_file)[0] + if base_file != container.getId(): + container = ContainerRegistry.getInstance().findContainers(id = base_file)[0] file_name = "Cura/%s.%s" % (container.getId(), file_suffix) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index edc782cc9e..a7ba423153 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -200,18 +200,25 @@ class XmlMaterialProfile(InstanceContainer): ## Begin Settings Block builder.start("settings") - if self.getDefinition().getId() == "fdmprinter": + if self.getMetaDataEntry("definition") == "fdmprinter": for instance in self.findInstances(): self._addSettingElement(builder, instance) machine_container_map = {} machine_nozzle_map = {} - variant_manager = CuraApplication.getInstance()._variant_manager + variant_manager = CuraApplication.getInstance().getVariantManager() + material_manager = CuraApplication.getInstance().getMaterialManager() + + root_material_id = self.getMetaDataEntry("base_file") # if basefile is self.getId, this is a basefile. + material_group = material_manager.getMaterialGroup(root_material_id) + + all_containers = [] + for node in [material_group.root_material_node] + material_group.derived_material_node_list: + all_containers.append(node.getContainer()) - all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self.getId()) for container in all_containers: - definition_id = container.getDefinition().getId() + definition_id = container.getMetaDataEntry("definition") if definition_id == "fdmprinter": continue @@ -233,7 +240,8 @@ class XmlMaterialProfile(InstanceContainer): product_id_map = self.getProductIdMap() for definition_id, container in machine_container_map.items(): - definition = container.getDefinition() + definition_id = container.getMetaDataEntry("definition") + definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = definition_id)[0] product = definition_id for product_name, product_id_list in product_id_map.items(): @@ -243,13 +251,14 @@ class XmlMaterialProfile(InstanceContainer): builder.start("machine") builder.start("machine_identifier", { - "manufacturer": container.getMetaDataEntry("machine_manufacturer", definition.getMetaDataEntry("manufacturer", "Unknown")), + "manufacturer": container.getMetaDataEntry("machine_manufacturer", + definition_metadata.get("manufacturer", "Unknown")), "product": product }) builder.end("machine_identifier") for instance in container.findInstances(): - if self.getDefinition().getId() == "fdmprinter" and self.getInstance(instance.definition.key) and self.getProperty(instance.definition.key, "value") == instance.value: + if self.getMetaDataEntry("definition") == "fdmprinter" and self.getInstance(instance.definition.key) and self.getProperty(instance.definition.key, "value") == instance.value: # If the settings match that of the base profile, just skip since we inherit the base profile. continue From 6cae5c2e35956dccd2e0e64ff222e561c9edac1b Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 7 Mar 2018 10:39:20 +0100 Subject: [PATCH 49/85] Remove unnecessary signal connection in MaterialsModel CURA-5052 --- cura/Machines/Models/BaseMaterialsModel.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cura/Machines/Models/BaseMaterialsModel.py b/cura/Machines/Models/BaseMaterialsModel.py index 5c09a4f060..0a1337feeb 100644 --- a/cura/Machines/Models/BaseMaterialsModel.py +++ b/cura/Machines/Models/BaseMaterialsModel.py @@ -40,8 +40,6 @@ class BaseMaterialsModel(ListModel): self._extruder_position = 0 self._extruder_stack = None - self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack) - def _updateExtruderStack(self): global_stack = self._machine_manager.activeMachine if global_stack is None: From ecfb62b69ec6e7726150cb7c61e27a0f0e5d6122 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 7 Mar 2018 11:00:42 +0100 Subject: [PATCH 50/85] Rename ColorChange to FilamentChange I think this is more clear to the users what this should actually do. --- .../{ColorChange.py => FilamentChange.py} | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) rename plugins/PostProcessingPlugin/scripts/{ColorChange.py => FilamentChange.py} (79%) diff --git a/plugins/PostProcessingPlugin/scripts/ColorChange.py b/plugins/PostProcessingPlugin/scripts/FilamentChange.py similarity index 79% rename from plugins/PostProcessingPlugin/scripts/ColorChange.py rename to plugins/PostProcessingPlugin/scripts/FilamentChange.py index 8db45f4033..2bb7891634 100644 --- a/plugins/PostProcessingPlugin/scripts/ColorChange.py +++ b/plugins/PostProcessingPlugin/scripts/FilamentChange.py @@ -2,17 +2,15 @@ # under the terms of the AGPLv3 or higher from ..Script import Script -#from UM.Logger import Logger -# from cura.Settings.ExtruderManager import ExtruderManager -class ColorChange(Script): +class FilamentChange(Script): def __init__(self): super().__init__() def getSettingDataString(self): return """{ - "name":"Color Change", - "key": "ColorChange", + "name":"Filament Change", + "key": "FilamentChange", "metadata": {}, "version": 2, "settings": @@ -60,17 +58,17 @@ class ColorChange(Script): if later_retract is not None and later_retract > 0.: color_change = color_change + (" L%.2f" % later_retract) - color_change = color_change + " ; Generated by ColorChange plugin" + color_change = color_change + " ; Generated by FilamentChange plugin" - layer_targets = layer_nums.split(',') + layer_targets = layer_nums.split(",") if len(layer_targets) > 0: for layer_num in layer_targets: - layer_num = int( layer_num.strip() ) + layer_num = int(layer_num.strip()) if layer_num < len(data): - layer = data[ layer_num - 1 ] + layer = data[layer_num - 1] lines = layer.split("\n") - lines.insert(2, color_change ) - final_line = "\n".join( lines ) - data[ layer_num - 1 ] = final_line + lines.insert(2, color_change) + final_line = "\n".join(lines) + data[layer_num - 1] = final_line return data From 507de9c22c0e957a2e7dcaa9e8c91939f6b94c92 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 7 Mar 2018 11:30:36 +0100 Subject: [PATCH 51/85] Catch timeout exceptions when printing via USB If the firmware froze or something... --- plugins/USBPrinting/USBPrinterOutputDevice.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 6e2b5153db..11cc7bf472 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from UM.Logger import Logger @@ -17,7 +17,7 @@ from .avr_isp import stk500v2, intelHex from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty -from serial import Serial, SerialException +from serial import Serial, SerialException, SerialTimeoutException from threading import Thread from time import time, sleep from queue import Queue @@ -266,8 +266,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice): command = (command + "\n").encode() if not command.endswith(b"\n"): command += b"\n" - self._serial.write(b"\n") - self._serial.write(command) + try: + self._serial.write(b"\n") + self._serial.write(command) + except SerialTimeoutException: + Logger.log("w", "Timeout when sending command to printer via USB.") def _update(self): while self._connection_state == ConnectionState.connected and self._serial is not None: From 23653d73577039c25bfc5586deacd2986a2bd3fc Mon Sep 17 00:00:00 2001 From: Aleksei S Date: Wed, 7 Mar 2018 11:34:10 +0100 Subject: [PATCH 52/85] Fix: Pressing 'cancel' button during compressing the print job should show "Prepare" page CURA-4960 --- plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index 73acc02cae..7a4c590acc 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -93,13 +93,15 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._gcode = gcode_list + is_job_sent = True if len(self._printers) > 1: self._spawnPrinterSelectionDialog() else: - self.sendPrintJob() + is_job_sent = self.sendPrintJob() # Notify the UI that a switch to the print monitor should happen - Application.getInstance().getController().setActiveStage("MonitorStage") + if is_job_sent: + Application.getInstance().getController().setActiveStage("MonitorStage") def _spawnPrinterSelectionDialog(self): if self._printer_selection_dialog is None: @@ -121,7 +123,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): i18n_catalog.i18nc("@info:status", "Sending new jobs (temporarily) blocked, still sending the previous print job.")) self._error_message.show() - return + return False self._sending_gcode = True @@ -134,7 +136,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): compressed_gcode = self._compressGCode() if compressed_gcode is None: # Abort was called. - return + return False parts = [] @@ -152,6 +154,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress) + return True + @pyqtProperty(QObject, notify=activePrinterChanged) def activePrinter(self) -> Optional["PrinterOutputModel"]: return self._active_printer From c0d55ac2a8c7ab36debb0f1dee912cacb6748b11 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 7 Mar 2018 11:15:26 +0100 Subject: [PATCH 53/85] Postpone signals in project loading to avoid incomplete data update CURA-5056 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index cbe882f253..6b56ec89f5 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -6,6 +6,7 @@ from UM.Application import Application from UM.Logger import Logger from UM.i18n import i18nCatalog +from UM.Signal import postponeSignals, CompressTechnique from UM.Settings.ContainerStack import ContainerStack from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.InstanceContainer import InstanceContainer @@ -434,6 +435,24 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # \param file_name @call_on_qt_thread def read(self, file_name): + container_registry = ContainerRegistry.getInstance() + signals = [container_registry.containerAdded, + container_registry.containerRemoved, + container_registry.containerMetaDataChanged] + # + # We now have different managers updating their lookup tables upon container changes. It is critical to make + # sure that the managers have a complete set of data when they update. + # + # In project loading, lots of the container-related signals are loosely emitted, which can create timing gaps + # for incomplete data update or other kinds of issues to happen. + # + # To avoid this, we postpone all signals so they don't get emitted immediately. But, please also be aware that, + # because of this, do not expect to have the latest data in the lookup tables in project loading. + # + with postponeSignals(*signals, compress = CompressTechnique.CompressSingle): + return self._read(file_name) + + def _read(self, file_name): archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] From 27cce564677afb836259de72779ad71d499ad5cc Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 7 Mar 2018 11:42:57 +0100 Subject: [PATCH 54/85] Add error message for possible bugs in MaterialManager CURA-5056 --- cura/Machines/MaterialManager.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 98e4f67f82..56577b5655 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -102,6 +102,13 @@ class MaterialManager(QObject): # GUID -> material group list self._guid_material_groups_map = defaultdict(list) for root_material_id, material_group in self._material_group_map.items(): + # This can happen when we are updating with incomplete data. + if material_group.root_material_node is None: + Logger.log("e", "Missing root material node for [%s]. Probably caused by update using incomplete data." + " Check all related signals for further debugging.", + material_group.name) + # Do nothing here, we wait for a next signal to trigger an update. + return guid = material_group.root_material_node.metadata["GUID"] self._guid_material_groups_map[guid].append(material_group) From 29792bbdd82ff62a4cc782e515a76058d4ce954f Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 7 Mar 2018 11:42:57 +0100 Subject: [PATCH 55/85] Reschedule update upon incomplete data in MaterialManager CURA-5056 --- cura/Machines/MaterialManager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 56577b5655..92bdbd0e86 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -107,7 +107,7 @@ class MaterialManager(QObject): Logger.log("e", "Missing root material node for [%s]. Probably caused by update using incomplete data." " Check all related signals for further debugging.", material_group.name) - # Do nothing here, we wait for a next signal to trigger an update. + self._update_timer.start() return guid = material_group.root_material_node.metadata["GUID"] self._guid_material_groups_map[guid].append(material_group) @@ -217,6 +217,7 @@ class MaterialManager(QObject): self.materialsUpdated.emit() def _updateMaps(self): + Logger.log("i", "Updating material lookup data ...") self.initialize() def _onContainerMetadataChanged(self, container): From 77e3be68b3de78924088aaf236fbc78eaaa3031d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 7 Mar 2018 13:23:25 +0100 Subject: [PATCH 56/85] Add removeMaterialByRootId() in MaterialManager CURA-5056 --- cura/Machines/MaterialManager.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 92bdbd0e86..89b3a76f9a 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -371,6 +371,16 @@ class MaterialManager(QObject): material_diameter, root_material_id) return node + def removeMaterialByRootId(self, root_material_id: str): + material_group = self.getMaterialGroup(root_material_id) + if not material_group: + Logger.log("i", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id) + return + + nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list + for node in nodes_to_remove: + self._container_registry.removeContainer(node.metadata["id"]) + # # Methods for GUI # @@ -394,14 +404,7 @@ class MaterialManager(QObject): @pyqtSlot("QVariant") def removeMaterial(self, material_node: "MaterialNode"): root_material_id = material_node.metadata["base_file"] - material_group = self.getMaterialGroup(root_material_id) - if not material_group: - Logger.log("d", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id) - return - - nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list - for node in nodes_to_remove: - self._container_registry.removeContainer(node.metadata["id"]) + self.removeMaterialByRootId(root_material_id) # # Creates a duplicate of a material, which has the same GUID and base_file metadata. From 618bcebd8251beb7b74298442b4504a9bfbec77a Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Wed, 7 Mar 2018 13:24:22 +0100 Subject: [PATCH 57/85] Fix create new for conflicting materials in project loading CURA-5056 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 74 +++++++++++++++++---- 1 file changed, 60 insertions(+), 14 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 6b56ec89f5..01ca136bcf 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -97,6 +97,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # In Cura 2.5 and 2.6, the empty profiles used to have those long names self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]} + self._old_new_materials = {} + self._materials_to_select = {} + + def _clearState(self): + self._id_mapping = {} + self._old_new_materials = {} + self._materials_to_select = {} + ## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results. # This has nothing to do with speed, but with getting consistent new naming for instances & objects. def getNewId(self, old_id): @@ -449,10 +457,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # To avoid this, we postpone all signals so they don't get emitted immediately. But, please also be aware that, # because of this, do not expect to have the latest data in the lookup tables in project loading. # - with postponeSignals(*signals, compress = CompressTechnique.CompressSingle): + with postponeSignals(*signals, compress = CompressTechnique.NoCompression): return self._read(file_name) def _read(self, file_name): + application = CuraApplication.getInstance() + material_manager = application.getMaterialManager() + + self._clearState() + archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] @@ -545,31 +558,41 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0] if xml_material_profile: + to_deserialize_material = False material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)] for material_container_file in material_container_files: container_id = self._stripFileToId(material_container_file) + need_new_name = False materials = self._container_registry.findInstanceContainers(id = container_id) if not materials: - material_container = xml_material_profile(container_id) - material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"), - file_name = material_container_file) - containers_to_add.append(material_container) + # No material found, deserialize this material later and add it + to_deserialize_material = True else: material_container = materials[0] + old_material_root_id = material_container.getMetaDataEntry("base_file") if not self._container_registry.isReadOnly(container_id): # Only create new materials if they are not read only. + to_deserialize_material = True + if self._resolve_strategies["material"] == "override": - material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"), - file_name = material_container_file) + # Remove the old materials and then deserialize the one from the project + root_material_id = material_container.getMetaDataEntry("base_file") + material_manager.removeMaterialByRootId(root_material_id) elif self._resolve_strategies["material"] == "new": # Note that we *must* deserialize it with a new ID, as multiple containers will be # auto created & added. - material_container = xml_material_profile(self.getNewId(container_id)) - material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"), - file_name = material_container_file) - containers_to_add.append(material_container) + container_id = self.getNewId(container_id) + self._old_new_materials[old_material_root_id] = container_id + need_new_name = True - material_containers.append(material_container) + if to_deserialize_material: + material_container = xml_material_profile(container_id) + material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"), + file_name = container_id + "." + self._material_container_suffix) + if need_new_name: + new_name = ContainerRegistry.getInstance().uniqueName(material_container.getName()) + material_container.setName(new_name) + containers_to_add.append(material_container) Job.yieldThread() Logger.log("d", "Workspace loading is checking instance containers...") @@ -1081,11 +1104,18 @@ class ThreeMFWorkspaceReader(WorkspaceReader): old_to_new_material_dict[old_id] = each_material break + global_stack.quality = empty_quality_container + # replace old material in global and extruder stacks with new self._replaceStackMaterialWithNew(global_stack, old_to_new_material_dict) if extruder_stacks: - for each_extruder_stack in extruder_stacks: - self._replaceStackMaterialWithNew(each_extruder_stack, old_to_new_material_dict) + for extruder_stack in extruder_stacks: + if extruder_stack.material.getId() in ("empty", "empty_material"): + continue + old_root_material_id = extruder_stack.material.getMetaDataEntry("base_file") + if old_root_material_id in self._old_new_materials: + new_root_material_id = self._old_new_materials[old_root_material_id] + self._materials_to_select[extruder_stack.getMetaDataEntry("position")] = new_root_material_id if extruder_stacks: for stack in extruder_stacks: @@ -1123,6 +1153,22 @@ class ThreeMFWorkspaceReader(WorkspaceReader): def _updateActiveMachine(self, global_stack): # Actually change the active machine. machine_manager = Application.getInstance().getMachineManager() + material_manager = Application.getInstance().getMaterialManager() + + # Switch materials if new materials are created due to conflicts + # We do it here because MaterialManager hasn't been updated in _read() yet. + for position, root_material_id in self._materials_to_select.items(): + extruder_stack = global_stack.extruders[position] + material_diameter = extruder_stack.materialDiameter + material_node = material_manager.getMaterialNode(global_stack.getMetaDataEntry("definition"), + extruder_stack.variant.getName(), + material_diameter, root_material_id) + if material_node is None: + Application.getInstance().callLater(self._updateActiveMachine, global_stack) + return + extruder_stack.material = material_node.getContainer() + Logger.log("d", "Changed extruder [%s] to material [%s]", position, root_material_id) + machine_manager.setActiveMachine(global_stack.getId()) # Notify everything/one that is to notify about changes. From 2a10c9a2d72dd4672032847e9856b222a63f7a55 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 8 Mar 2018 09:10:43 +0100 Subject: [PATCH 58/85] Move assertion in _performMerge() CURA-5070 --- cura/Settings/ContainerManager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index f910f0c0e2..345c25ded9 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -389,8 +389,6 @@ class ContainerManager(QObject): return ContainerManager.getInstance() def _performMerge(self, merge_into, merge, clear_settings = True): - assert isinstance(merge, type(merge_into)) - if merge == merge_into: return From 375770818b762a9d75b159e260b5c879fa15e7c6 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 8 Mar 2018 14:23:14 +0100 Subject: [PATCH 59/85] Add typing for _getPrinterOutputDevices Then my IDE will give hints on where a printer of the wrong type is being added. Contributes to issue CURA-5061. --- plugins/UltimakerMachineActions/BedLevelMachineAction.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/UltimakerMachineActions/BedLevelMachineAction.py b/plugins/UltimakerMachineActions/BedLevelMachineAction.py index 04b6cf1acc..738fd81c63 100644 --- a/plugins/UltimakerMachineActions/BedLevelMachineAction.py +++ b/plugins/UltimakerMachineActions/BedLevelMachineAction.py @@ -1,3 +1,8 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import List + from cura.MachineAction import MachineAction from cura.PrinterOutputDevice import PrinterOutputDevice @@ -32,7 +37,7 @@ class BedLevelMachineAction(MachineAction): printer_output_devices[0].moveHead(0, 0, 3) printer_output_devices[0].homeHead() - def _getPrinterOutputDevices(self): + def _getPrinterOutputDevices(self) -> List[PrinterOutputDevice]: return [printer_output_device for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices() if isinstance(printer_output_device, PrinterOutputDevice)] @pyqtSlot() From 676f9b84747337eaf80262c5d2e632c60d91d272 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 8 Mar 2018 15:18:32 +0100 Subject: [PATCH 60/85] Switch if-statement around for fail checking This reduces indent and makes the control flow a bit easier to read in my opinion. Contributes to issue CURA-5061. --- .../BedLevelMachineAction.py | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/plugins/UltimakerMachineActions/BedLevelMachineAction.py b/plugins/UltimakerMachineActions/BedLevelMachineAction.py index 738fd81c63..b6ecdeec34 100644 --- a/plugins/UltimakerMachineActions/BedLevelMachineAction.py +++ b/plugins/UltimakerMachineActions/BedLevelMachineAction.py @@ -10,6 +10,7 @@ from UM.FlameProfiler import pyqtSlot from UM.Application import Application from UM.i18n import i18nCatalog +from UM.Logger import Logger catalog = i18nCatalog("cura") @@ -32,10 +33,13 @@ class BedLevelMachineAction(MachineAction): def startBedLeveling(self): self._bed_level_position = 0 printer_output_devices = self._getPrinterOutputDevices() - if printer_output_devices: - printer_output_devices[0].homeBed() - printer_output_devices[0].moveHead(0, 0, 3) - printer_output_devices[0].homeHead() + if not printer_output_devices: + Logger.log("e", "Can't start bed levelling. The printer connection seems to have been lost.") + return + + printer_output_devices[0].homeBed() + printer_output_devices[0].moveHead(0, 0, 3) + printer_output_devices[0].homeHead() def _getPrinterOutputDevices(self) -> List[PrinterOutputDevice]: return [printer_output_device for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices() if isinstance(printer_output_device, PrinterOutputDevice)] @@ -43,26 +47,29 @@ class BedLevelMachineAction(MachineAction): @pyqtSlot() def moveToNextLevelPosition(self): output_devices = self._getPrinterOutputDevices() - if output_devices: # We found at least one output device - output_device = output_devices[0] + if not output_devices: #No output devices. Can't move. + Logger.log("e", "Can't move to the next position. The printer connection seems to have been lost.") + return - if self._bed_level_position == 0: - output_device.moveHead(0, 0, 3) - output_device.homeHead() - output_device.moveHead(0, 0, 3) - output_device.moveHead(Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") - 10, 0, 0) - output_device.moveHead(0, 0, -3) - self._bed_level_position += 1 - elif self._bed_level_position == 1: - output_device.moveHead(0, 0, 3) - output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value" ) / 2, Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") - 10, 0) - output_device.moveHead(0, 0, -3) - self._bed_level_position += 1 - elif self._bed_level_position == 2: - output_device.moveHead(0, 0, 3) - output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") / 2 + 10, -(Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") + 10), 0) - output_device.moveHead(0, 0, -3) - self._bed_level_position += 1 - elif self._bed_level_position >= 3: - output_device.sendCommand("M18") # Turn off all motors so the user can move the axes - self.setFinished() \ No newline at end of file + output_device = output_devices[0] + + if self._bed_level_position == 0: + output_device.moveHead(0, 0, 3) + output_device.homeHead() + output_device.moveHead(0, 0, 3) + output_device.moveHead(Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") - 10, 0, 0) + output_device.moveHead(0, 0, -3) + self._bed_level_position += 1 + elif self._bed_level_position == 1: + output_device.moveHead(0, 0, 3) + output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value" ) / 2, Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") - 10, 0) + output_device.moveHead(0, 0, -3) + self._bed_level_position += 1 + elif self._bed_level_position == 2: + output_device.moveHead(0, 0, 3) + output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") / 2 + 10, -(Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") + 10), 0) + output_device.moveHead(0, 0, -3) + self._bed_level_position += 1 + elif self._bed_level_position >= 3: + output_device.sendCommand("M18") # Turn off all motors so the user can move the axes + self.setFinished() \ No newline at end of file From b4cf25cb7f9b7653fc7b6bfe531d7348a111419d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 8 Mar 2018 15:29:48 +0100 Subject: [PATCH 61/85] Call moveHead and moveBed on the printer model Instead of on the output device. This function was moved, which caused Cura to crash when the bed levelling procedure was called. Contributes to issue CURA-5061. --- .../BedLevelMachineAction.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/plugins/UltimakerMachineActions/BedLevelMachineAction.py b/plugins/UltimakerMachineActions/BedLevelMachineAction.py index b6ecdeec34..6a8a337d8c 100644 --- a/plugins/UltimakerMachineActions/BedLevelMachineAction.py +++ b/plugins/UltimakerMachineActions/BedLevelMachineAction.py @@ -32,14 +32,16 @@ class BedLevelMachineAction(MachineAction): @pyqtSlot() def startBedLeveling(self): self._bed_level_position = 0 + printer_output_devices = self._getPrinterOutputDevices() if not printer_output_devices: Logger.log("e", "Can't start bed levelling. The printer connection seems to have been lost.") return + printer = printer_output_devices[0].activePrinter - printer_output_devices[0].homeBed() - printer_output_devices[0].moveHead(0, 0, 3) - printer_output_devices[0].homeHead() + printer.homeBed() + printer.moveHead(0, 0, 3) + printer.homeHead() def _getPrinterOutputDevices(self) -> List[PrinterOutputDevice]: return [printer_output_device for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices() if isinstance(printer_output_device, PrinterOutputDevice)] @@ -50,26 +52,25 @@ class BedLevelMachineAction(MachineAction): if not output_devices: #No output devices. Can't move. Logger.log("e", "Can't move to the next position. The printer connection seems to have been lost.") return - - output_device = output_devices[0] + printer = output_devices[0].activePrinter if self._bed_level_position == 0: - output_device.moveHead(0, 0, 3) - output_device.homeHead() - output_device.moveHead(0, 0, 3) - output_device.moveHead(Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") - 10, 0, 0) - output_device.moveHead(0, 0, -3) + printer.moveHead(0, 0, 3) + printer.homeHead() + printer.moveHead(0, 0, 3) + printer.moveHead(Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") - 10, 0, 0) + printer.moveHead(0, 0, -3) self._bed_level_position += 1 elif self._bed_level_position == 1: - output_device.moveHead(0, 0, 3) - output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value" ) / 2, Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") - 10, 0) - output_device.moveHead(0, 0, -3) + printer.moveHead(0, 0, 3) + printer.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value" ) / 2, Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") - 10, 0) + printer.moveHead(0, 0, -3) self._bed_level_position += 1 elif self._bed_level_position == 2: - output_device.moveHead(0, 0, 3) - output_device.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") / 2 + 10, -(Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") + 10), 0) - output_device.moveHead(0, 0, -3) + printer.moveHead(0, 0, 3) + printer.moveHead(-Application.getInstance().getGlobalContainerStack().getProperty("machine_width", "value") / 2 + 10, -(Application.getInstance().getGlobalContainerStack().getProperty("machine_depth", "value") + 10), 0) + printer.moveHead(0, 0, -3) self._bed_level_position += 1 elif self._bed_level_position >= 3: - output_device.sendCommand("M18") # Turn off all motors so the user can move the axes + output_devices[0].sendCommand("M18") # Turn off all motors so the user can move the axes self.setFinished() \ No newline at end of file From d1beae46034c8d181fdc5bc7d16abe39de2c4822 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 8 Mar 2018 15:42:27 +0100 Subject: [PATCH 62/85] Better logging for checkIsValidProjectFile() CURA-4966 --- cura/CuraApplication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 2ca321e4cc..438e8ffed7 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1689,7 +1689,7 @@ class CuraApplication(QtApplication): result = workspace_reader.preRead(file_path, show_dialog=False) return result == WorkspaceReader.PreReadResult.accepted except Exception as e: - Logger.log("e", "Could not check file %s: %s", file_url, e) + Logger.logException("e", "Could not check file %s: %s", file_url) return False def _onContextMenuRequested(self, x: float, y: float) -> None: From 6aefb2215ddabc00b3708acc8909315cda1c9d9d Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 8 Mar 2018 16:50:57 +0100 Subject: [PATCH 63/85] Fix _setQualityChangesGroup() CURA-4966 --- 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 f7483ae38b..f8692da0e3 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -881,7 +881,7 @@ class MachineManager(QObject): quality_node = quality_group.nodes_for_extruders.get(position) quality_changes_container = self._empty_quality_changes_container - quality_container = self._empty_quality_changes_container + quality_container = self._empty_quality_container if quality_changes_node: quality_changes_container = quality_changes_node.getContainer() if quality_node: From b8d3cbfe169fe83c38b958b8ebd110292811bd91 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 8 Mar 2018 15:43:10 +0100 Subject: [PATCH 64/85] Refactor project loading CURA-4966 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 1193 ++++++++----------- plugins/3MFReader/WorkspaceDialog.py | 12 - 2 files changed, 482 insertions(+), 723 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 01ca136bcf..4698d498d2 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -1,6 +1,13 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +from configparser import ConfigParser +import zipfile +import os +import threading + +import xml.etree.ElementTree as ET + from UM.Workspace.WorkspaceReader import WorkspaceReader from UM.Application import Application @@ -14,24 +21,14 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from UM.MimeTypeDatabase import MimeTypeDatabase from UM.Job import Job from UM.Preferences import Preferences -from UM.Util import parseBool -from .WorkspaceDialog import WorkspaceDialog - -import xml.etree.ElementTree as ET from cura.Settings.CuraStackBuilder import CuraStackBuilder -from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderStack import ExtruderStack from cura.Settings.GlobalStack import GlobalStack from cura.Settings.CuraContainerStack import _ContainerIndexes from cura.CuraApplication import CuraApplication -from configparser import ConfigParser -import zipfile -import io -import configparser -import os -import threading +from .WorkspaceDialog import WorkspaceDialog i18n_catalog = i18nCatalog("cura") @@ -67,6 +64,48 @@ def call_on_qt_thread(func): return _call_on_qt_thread_wrapper +class ContainerInfo: + def __init__(self, file_name: str, serialized: str, parser: ConfigParser): + self.file_name = file_name + self.serialized = serialized + self.parser = parser + self.container = None + self.definition_id = None + + +class QualityChangesInfo: + def __init__(self): + self.name = None + self.global_info = None + self.extruder_info_dict = {} + + +class MachineInfo: + def __init__(self): + self.container_id = None + self.name = None + self.definition_id = None + self.quality_type = None + self.custom_quality_name = None + self.quality_changes_info = None + self.variant_info = None + + self.definition_changes_info = None + self.user_changes_info = None + + self.extruder_info_dict = {} + + +class ExtruderInfo: + def __init__(self): + self.position = None + self.variant_info = None + self.root_material_id = None + + self.definition_changes_info = None + self.user_changes_info = None + + ## Base implementation for reading 3MF workspace files. class ThreeMFWorkspaceReader(WorkspaceReader): def __init__(self): @@ -97,13 +136,17 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # In Cura 2.5 and 2.6, the empty profiles used to have those long names self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]} + self._is_same_machine_type = False self._old_new_materials = {} self._materials_to_select = {} + self._machine_info = None def _clearState(self): + self._is_same_machine_type = False self._id_mapping = {} self._old_new_materials = {} self._materials_to_select = {} + self._machine_info = None ## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results. # This has nothing to do with speed, but with getting consistent new naming for instances & objects. @@ -156,6 +199,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # \param file_name # \param show_dialog In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog. def preRead(self, file_name, show_dialog=True, *args, **kwargs): + self._clearState() + self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name) if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted: pass @@ -163,6 +208,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace") return WorkspaceReader.PreReadResult.failed + self._machine_info = MachineInfo() machine_type = "" variant_type_name = i18n_catalog.i18nc("@label", "Nozzle") @@ -182,23 +228,23 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # # Read definition containers # + machine_definition_id = None machine_definition_container_count = 0 extruder_definition_container_count = 0 definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] - for each_definition_container_file in definition_container_files: - container_id = self._stripFileToId(each_definition_container_file) + for definition_container_file in definition_container_files: + container_id = self._stripFileToId(definition_container_file) definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id) + serialized = archive.open(definition_container_file).read().decode("utf-8") if not definitions: - definition_container = DefinitionContainer(container_id) - definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"), file_name = each_definition_container_file) - definition_container = definition_container.getMetaData() - + definition_container = DefinitionContainer.deserializeMetadata(serialized, container_id)[0] else: definition_container = definitions[0] definition_container_type = definition_container.get("type") if definition_container_type == "machine": + machine_definition_id = container_id machine_type = definition_container["name"] variant_type_name = definition_container.get("variants_name", variant_type_name) @@ -207,22 +253,33 @@ class ThreeMFWorkspaceReader(WorkspaceReader): extruder_definition_container_count += 1 else: Logger.log("w", "Unknown definition container type %s for %s", - definition_container_type, each_definition_container_file) + definition_container_type, definition_container_file) Job.yieldThread() if machine_definition_container_count != 1: - return WorkspaceReader.PreReadResult.failed #Not a workspace file but ordinary 3MF. + return WorkspaceReader.PreReadResult.failed # Not a workspace file but ordinary 3MF. material_labels = [] material_conflict = False xml_material_profile = self._getXmlProfileClass() + reverse_material_id_dict = {} if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).preferredSuffix if xml_material_profile: material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)] for material_container_file in material_container_files: container_id = self._stripFileToId(material_container_file) - material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8"))) + + from hashlib import sha1 + hex_container_id = sha1(container_id.encode('utf-8')).hexdigest() + + serialized = archive.open(material_container_file).read().decode("utf-8") + metadata_list = xml_material_profile.deserializeMetadata(serialized, hex_container_id) + reverse_map = {metadata["id"].replace(hex_container_id, container_id): container_id.replace(hex_container_id, container_id) + for metadata in metadata_list} + reverse_material_id_dict.update(reverse_map) + + material_labels.append(self._getMaterialLabelFromSerialized(serialized)) if self._container_registry.findContainersMetadata(id = container_id): #This material already exists. containers_found_dict["material"] = True if not self._container_registry.isReadOnly(container_id): # Only non readonly materials can be in conflict @@ -232,24 +289,38 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Check if any quality_changes instance container is in conflict. instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] quality_name = "" - quality_type = "" num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes - num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes num_user_settings = 0 quality_changes_conflict = False - definition_changes_conflict = False - for each_instance_container_file in instance_container_files: - container_id = self._stripFileToId(each_instance_container_file) + self._machine_info.quality_changes_info = QualityChangesInfo() + + quality_changes_info_list = [] + instance_container_info_dict = {} # id -> parser + for instance_container_file_name in instance_container_files: + container_id = self._stripFileToId(instance_container_file_name) + + serialized = archive.open(instance_container_file_name).read().decode("utf-8") + serialized = InstanceContainer._updateSerialized(serialized, instance_container_file_name) + parser = ConfigParser(interpolation = None) + parser.read_string(serialized) + container_info = ContainerInfo(instance_container_file_name, serialized, parser) + instance_container_info_dict[container_id] = container_info + instance_container = InstanceContainer(container_id) # Deserialize InstanceContainer by converting read data from bytes to string - instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"), - file_name = each_instance_container_file) + instance_container.deserialize(serialized, file_name = instance_container_file_name) instance_container_list.append(instance_container) container_type = instance_container.getMetaDataEntry("type") if container_type == "quality_changes": + quality_changes_info_list.append(container_info) + + if not parser.has_option("metadata", "extruder"): + self._machine_info.quality_changes_info.name = parser["general"]["name"] + self._machine_info.quality_changes_info.global_info = container_info + quality_name = instance_container.getName() num_settings_overriden_by_quality_changes += len(instance_container._instances) # Check if quality changes already exists. @@ -259,17 +330,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Check if there really is a conflict by comparing the values if quality_changes[0] != instance_container: quality_changes_conflict = True - elif container_type == "definition_changes": - definition_name = instance_container.getName() - num_settings_overriden_by_definition_changes += len(instance_container._instances) - # Check if definition changes already exists. - definition_changes = self._container_registry.findInstanceContainers(id = container_id) - # Check if there is any difference the loaded settings from the project file and the settings in Cura. - if definition_changes: - containers_found_dict["definition_changes"] = True - # Check if there really is a conflict by comparing the values - if definition_changes[0] != instance_container: - definition_changes_conflict = True elif container_type == "quality": if not quality_name: quality_name = instance_container.getName() @@ -282,6 +342,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Job.yieldThread() + if self._machine_info.quality_changes_info.global_info is None: + self._machine_info.quality_changes_info = None + # Load ContainerStack files and ExtruderStack files global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles( file_name, cura_file_names) @@ -290,10 +353,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # - the global stack exists but some/all of the extruder stacks DON'T exist # - the global stack DOESN'T exist but some/all of the extruder stacks exist # To simplify this, only check if the global stack exists or not - container_id = self._stripFileToId(global_stack_file) + global_stack_id = self._stripFileToId(global_stack_file) serialized = archive.open(global_stack_file).read().decode("utf-8") machine_name = self._getMachineNameFromSerializedStack(serialized) - stacks = self._container_registry.findContainerStacks(id = container_id) + stacks = self._container_registry.findContainerStacks(name = machine_name, type = "machine") + self._is_same_machine_type = True if stacks: global_stack = stacks[0] containers_found_dict["machine"] = True @@ -305,30 +369,79 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if global_stack.getContainer(index).getId() != container_id: machine_conflict = True break + self._is_same_machine_type = global_stack.definition.getId() == machine_definition_id + + # Get quality type + parser = ConfigParser(interpolation = None) + parser.read_string(serialized) + quality_container_id = parser["containers"][str(_ContainerIndexes.Quality)] + quality_type = instance_container_info_dict[quality_container_id].parser["metadata"]["quality_type"] + + # Get machine info + serialized = archive.open(global_stack_file).read().decode("utf-8") + serialized = GlobalStack._updateSerialized(serialized, global_stack_file) + parser = ConfigParser(interpolation = None) + parser.read_string(serialized) + definition_changes_id = parser["containers"][str(_ContainerIndexes.DefinitionChanges)] + if definition_changes_id not in ("empty", "empty_definition_changes"): + self._machine_info.definition_changes_info = instance_container_info_dict[definition_changes_id] + user_changes_id = parser["containers"][str(_ContainerIndexes.UserChanges)] + if user_changes_id not in ("empty", "empty_user_changes"): + self._machine_info.user_changes_info = instance_container_info_dict[user_changes_id] + + # Also check variant and material in case it doesn't have extruder stacks + if not extruder_stack_files: + position = "0" + + extruder_info = ExtruderInfo() + extruder_info.position = position + variant_id = parser["containers"][str(_ContainerIndexes.Variant)] + material_id = parser["containers"][str(_ContainerIndexes.Material)] + if variant_id not in ("empty", "empty_variant"): + extruder_info.variant_info = instance_container_info_dict[variant_id] + if material_id not in ("empty", "empty_material"): + root_material_id = reverse_material_id_dict[material_id] + extruder_info.root_material_id = root_material_id + self._machine_info.extruder_info_dict[position] = extruder_info + else: + variant_id = parser["containers"][str(_ContainerIndexes.Variant)] + if variant_id not in ("empty", "empty_variant"): + self._machine_info.variant_info = instance_container_info_dict[variant_id] + Job.yieldThread() # if the global stack is found, we check if there are conflicts in the extruder stacks - if containers_found_dict["machine"] and not machine_conflict: - for extruder_stack_file in extruder_stack_files: - serialized = archive.open(extruder_stack_file).read().decode("utf-8") - parser = configparser.ConfigParser(interpolation = None) - parser.read_string(serialized) + for extruder_stack_file in extruder_stack_files: + serialized = archive.open(extruder_stack_file).read().decode("utf-8") + serialized = ExtruderStack._updateSerialized(serialized, extruder_stack_file) + parser = ConfigParser(interpolation = None) + parser.read_string(serialized) - # The check should be done for the extruder stack that's associated with the existing global stack, - # and those extruder stacks may have different IDs. - # So we check according to the positions + # The check should be done for the extruder stack that's associated with the existing global stack, + # and those extruder stacks may have different IDs. + # So we check according to the positions + position = parser["metadata"]["position"] + variant_id = parser["containers"][str(_ContainerIndexes.Variant)] + material_id = parser["containers"][str(_ContainerIndexes.Material)] - position = str(parser["metadata"]["position"]) + extruder_info = ExtruderInfo() + extruder_info.position = position + if variant_id not in ("empty", "empty_variant"): + extruder_info.variant_info = instance_container_info_dict[variant_id] + if material_id not in ("empty", "empty_material"): + root_material_id = reverse_material_id_dict[material_id] + extruder_info.root_material_id = root_material_id + definition_changes_id = parser["containers"][str(_ContainerIndexes.DefinitionChanges)] + if definition_changes_id not in ("empty", "empty_definition_changes"): + extruder_info.definition_changes_info = instance_container_info_dict[definition_changes_id] + user_changes_id = parser["containers"][str(_ContainerIndexes.UserChanges)] + if user_changes_id not in ("empty", "empty_user_changes"): + extruder_info.user_changes_info = instance_container_info_dict[user_changes_id] + self._machine_info.extruder_info_dict[position] = extruder_info + + if not machine_conflict and containers_found_dict["machine"]: if position not in global_stack.extruders: - # The extruder position defined in the project doesn't exist in this global stack. - # We can say that it is a machine conflict, but it is very hard to override the machine in this - # case because we need to override the existing extruders and add the non-existing extruders. - # - # HACK: - # To make this simple, we simply say that there is no machine conflict and create a new machine - # by default. - machine_conflict = False - break + continue existing_extruder_stack = global_stack.extruders[position] # check if there are any changes at all in any of the container stacks. @@ -340,6 +453,15 @@ class ThreeMFWorkspaceReader(WorkspaceReader): machine_conflict = True break + if self._machine_info.quality_changes_info is not None: + for quality_changes_info in quality_changes_info_list: + if not quality_changes_info.parser.has_option("metadata", "extruder"): + continue + extruder_definition_id = quality_changes_info.parser["metadata"]["extruder"] + extruder_definition_metadata = self._container_registry.findDefinitionContainersMetadata(id = extruder_definition_id)[0] + position = extruder_definition_metadata["position"] + self._machine_info.quality_changes_info.extruder_info_dict[position] = quality_changes_info + num_visible_settings = 0 try: temp_preferences = Preferences() @@ -369,10 +491,18 @@ class ThreeMFWorkspaceReader(WorkspaceReader): extruders = num_extruders * [""] + self._machine_info.container_id = global_stack_id + self._machine_info.name = machine_name + self._machine_info.definition_id = machine_definition_id + self._machine_info.quality_type = quality_type + self._machine_info.custom_quality_name = quality_name + + if machine_conflict and not self._is_same_machine_type: + machine_conflict = False + # Show the dialog, informing the user what is about to happen. self._dialog.setMachineConflict(machine_conflict) self._dialog.setQualityChangesConflict(quality_changes_conflict) - self._dialog.setDefinitionChangesConflict(definition_changes_conflict) self._dialog.setMaterialConflict(material_conflict) self._dialog.setHasVisibleSettingsField(has_visible_settings_string) self._dialog.setNumVisibleSettings(num_visible_settings) @@ -415,7 +545,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): ## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack. def _overrideExtruderStack(self, global_stack, extruder_file_content, extruder_stack_file): # Get extruder position first - extruder_config = configparser.ConfigParser(interpolation = None) + extruder_config = ConfigParser(interpolation = None) extruder_config.read_string(extruder_file_content) if not extruder_config.has_option("metadata", "position"): msg = "Could not find 'metadata/position' in extruder stack file" @@ -464,8 +594,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): application = CuraApplication.getInstance() material_manager = application.getMaterialManager() - self._clearState() - archive = zipfile.ZipFile(file_name, "r") cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")] @@ -492,52 +620,24 @@ class ThreeMFWorkspaceReader(WorkspaceReader): else: global_preferences.setValue("cura/categories_expanded", categories_expanded) - Application.getInstance().expandedCategoriesChanged.emit() # Notify the GUI of the change + application.expandedCategoriesChanged.emit() # Notify the GUI of the change - self._id_mapping = {} + # If a machine with the same name is of a different type, always create a new one. + if not self._is_same_machine_type or self._resolve_strategies["machine"] != "override": + # We need to create a new machine + machine_name = self._container_registry.uniqueName(self._machine_info.name) - # We don't add containers right away, but wait right until right before the stack serialization. - # We do this so that if something goes wrong, it's easier to clean up. - containers_to_add = [] + global_stack = CuraStackBuilder.createMachine(machine_name, self._machine_info.definition_id) + extruder_stack_dict = global_stack.extruders - global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names) + self._container_registry.addContainer(global_stack) + else: + # Find the machine + global_stack = self._container_registry.findContainerStacks(name = self._machine_info.name, type = "machine")[0] + extruder_stacks = self._container_registry.findContainerStacks(machine = global_stack.getId(), + type = "extruder_train") + extruder_stack_dict = {stack.getMetaDataEntry("position"): stack for stack in extruder_stacks} - global_stack = None - extruder_stacks = [] - extruder_stacks_added = [] - container_stacks_added = [] - machine_extruder_count = None - - containers_added = [] - - global_stack_id_original = self._stripFileToId(global_stack_file) - global_stack_id_new = global_stack_id_original - global_stack_name_original = self._getMachineNameFromSerializedStack(archive.open(global_stack_file).read().decode("utf-8")) - global_stack_name_new = global_stack_name_original - global_stack_need_rename = False - - extruder_stack_id_map = {} # new and old ExtruderStack IDs map - if self._resolve_strategies["machine"] == "new": - # We need a new id if the id already exists - if self._container_registry.findContainerStacksMetadata(id = global_stack_id_original): - global_stack_id_new = self.getNewId(global_stack_id_original) - global_stack_need_rename = True - - if self._container_registry.findContainerStacksMetadata(name = global_stack_id_original): - global_stack_name_new = self._container_registry.uniqueName(global_stack_name_original) - - for each_extruder_stack_file in extruder_stack_files: - old_container_id = self._stripFileToId(each_extruder_stack_file) - new_container_id = old_container_id - if self._container_registry.findContainerStacksMetadata(id = old_container_id): - # get a new name for this extruder - new_container_id = self.getNewId(old_container_id) - - extruder_stack_id_map[old_container_id] = new_container_id - - # TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few - # TODO: cases that the container loaded is the same (most notable in materials & definitions). - # TODO: It might be possible that we need to add smarter checking in the future. 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)] @@ -552,15 +652,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Job.yieldThread() Logger.log("d", "Workspace loading is checking materials...") - material_containers = [] # Get all the material files and check if they exist. If not, add them. xml_material_profile = self._getXmlProfileClass() if self._material_container_suffix is None: self._material_container_suffix = ContainerRegistry.getMimeTypeForContainer(xml_material_profile).suffixes[0] if xml_material_profile: - to_deserialize_material = False material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)] for material_container_file in material_container_files: + to_deserialize_material = False container_id = self._stripFileToId(material_container_file) need_new_name = False materials = self._container_registry.findInstanceContainers(id = container_id) @@ -571,7 +670,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): else: material_container = materials[0] old_material_root_id = material_container.getMetaDataEntry("base_file") - if not self._container_registry.isReadOnly(container_id): # Only create new materials if they are not read only. + if not self._container_registry.isReadOnly(old_material_root_id): # Only create new materials if they are not read only. to_deserialize_material = True if self._resolve_strategies["material"] == "override": @@ -592,544 +691,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if need_new_name: new_name = ContainerRegistry.getInstance().uniqueName(material_container.getName()) material_container.setName(new_name) - containers_to_add.append(material_container) + self._container_registry.addContainer(material_container) Job.yieldThread() - Logger.log("d", "Workspace loading is checking instance containers...") - # Get quality_changes and user profiles saved in the workspace - instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)] - user_instance_containers = [] - quality_and_definition_changes_instance_containers = [] - quality_changes_instance_containers = [] - for instance_container_file in instance_container_files: - container_id = self._stripFileToId(instance_container_file) - serialized = archive.open(instance_container_file).read().decode("utf-8") + # Handle quality changes if any + self._processQualityChanges(global_stack) - # HACK! we ignore "quality" and "variant" instance containers! - parser = configparser.ConfigParser(interpolation = None) - parser.read_string(serialized) - if not parser.has_option("metadata", "type"): - Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file) - continue - if parser.get("metadata", "type") in self._ignored_instance_container_types: - continue - - instance_container = InstanceContainer(container_id) - - # Deserialize InstanceContainer by converting read data from bytes to string - instance_container.deserialize(serialized, file_name = instance_container_file) - container_type = instance_container.getMetaDataEntry("type") - Job.yieldThread() - - # - # IMPORTANT: - # If an instance container (or maybe other type of container) exists, and user chooses "Create New", - # we need to rename this container and all references to it, and changing those references are VERY - # HARD. - # - if container_type in self._ignored_instance_container_types: - # Ignore certain instance container types - Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type) - continue - elif container_type == "user": - # Check if quality changes already exists. - user_containers = self._container_registry.findInstanceContainers(id = container_id) - if not user_containers: - containers_to_add.append(instance_container) - else: - if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None: - instance_container = user_containers[0] - instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"), - file_name = instance_container_file) - instance_container.setDirty(True) - elif self._resolve_strategies["machine"] == "new": - # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. - old_extruder_id = instance_container.getMetaDataEntry("extruder", None) - if old_extruder_id: - new_extruder_id = extruder_stack_id_map[old_extruder_id] - new_id = new_extruder_id + "_current_settings" - instance_container.setMetaDataEntry("id", new_id) - instance_container.setName(new_id) - instance_container.setMetaDataEntry("extruder", new_extruder_id) - containers_to_add.append(instance_container) - - machine_id = instance_container.getMetaDataEntry("machine", None) - if machine_id: - new_machine_id = self.getNewId(machine_id) - new_id = new_machine_id + "_current_settings" - instance_container.setMetaDataEntry("id", new_id) - instance_container.setName(new_id) - instance_container.setMetaDataEntry("machine", new_machine_id) - containers_to_add.append(instance_container) - user_instance_containers.append(instance_container) - elif container_type in ("quality_changes", "definition_changes"): - # Check if quality changes already exists. - changes_containers = self._container_registry.findInstanceContainers(id = container_id) - if not changes_containers: - # no existing containers with the same ID, so we can safely add the new one - containers_to_add.append(instance_container) - else: - # we have found existing container with the same ID, so we need to resolve according to the - # selected strategy. - if self._resolve_strategies[container_type] == "override": - instance_container = changes_containers[0] - instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"), - file_name = instance_container_file) - instance_container.setDirty(True) - - elif self._resolve_strategies[container_type] == "new": - # TODO: how should we handle the case "new" for quality_changes and definition_changes? - - instance_container.setName(self._container_registry.uniqueName(instance_container.getName())) - new_changes_container_id = self.getNewId(instance_container.getId()) - instance_container.setMetaDataEntry("id", new_changes_container_id) - - # TODO: we don't know the following is correct or not, need to verify - # AND REFACTOR!!! - if self._resolve_strategies["machine"] == "new": - # The machine is going to get a spiffy new name, so ensure that the id's of user settings match. - old_extruder_id = instance_container.getMetaDataEntry("extruder", None) - # Note that in case of a quality_changes extruder means the definition id of the extruder stack - # For the user settings, it means the actual extruder stack id it's assigned to. - if old_extruder_id and old_extruder_id in extruder_stack_id_map: - new_extruder_id = extruder_stack_id_map[old_extruder_id] - instance_container.setMetaDataEntry("extruder", new_extruder_id) - - machine_id = instance_container.getMetaDataEntry("machine", None) - if machine_id: - new_machine_id = self.getNewId(machine_id) - instance_container.setMetaDataEntry("machine", new_machine_id) - - containers_to_add.append(instance_container) - - elif self._resolve_strategies[container_type] is None: - # The ID already exists, but nothing in the values changed, so do nothing. - pass - quality_and_definition_changes_instance_containers.append(instance_container) - if container_type == "quality_changes": - quality_changes_instance_containers.append(instance_container) - - if container_type == "definition_changes": - definition_changes_extruder_count = instance_container.getProperty("machine_extruder_count", "value") - if definition_changes_extruder_count is not None: - machine_extruder_count = definition_changes_extruder_count - - else: - existing_container = self._container_registry.findInstanceContainersMetadata(id = container_id) - if not existing_container: - containers_to_add.append(instance_container) - if global_stack_need_rename: - if instance_container.getMetaDataEntry("machine"): - instance_container.setMetaDataEntry("machine", global_stack_id_new) - - # Add all the containers right before we try to add / serialize the stack - for container in containers_to_add: - self._container_registry.addContainer(container) - container.setDirty(True) - containers_added.append(container) - - # Get the stack(s) saved in the workspace. - Logger.log("d", "Workspace loading is checking stacks containers...") - - # load global stack file - try: - stack = None - - if self._resolve_strategies["machine"] == "override": - container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original) - stack = container_stacks[0] - - # HACK - # There is a machine, check if it has authentication data. If so, keep that data. - network_authentication_id = stack.getMetaDataEntry("network_authentication_id") - network_authentication_key = stack.getMetaDataEntry("network_authentication_key") - stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"), file_name = global_stack_file) - if network_authentication_id: - stack.addMetaDataEntry("network_authentication_id", network_authentication_id) - if network_authentication_key: - stack.addMetaDataEntry("network_authentication_key", network_authentication_key) - - elif self._resolve_strategies["machine"] == "new": - # create a new global stack - stack = GlobalStack(global_stack_id_new) - # Deserialize stack by converting read data from bytes to string - stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"), - file_name = global_stack_file) - - # Ensure a unique ID and name - stack.setMetaDataEntry("id", global_stack_id_new) - - # Only machines need a new name, stacks may be non-unique - stack.setName(global_stack_name_new) - - container_stacks_added.append(stack) - # self._container_registry.addContainer(stack) - containers_added.append(stack) - else: - Logger.log("e", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"]) - - # Create a new definition_changes container if it was empty - if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer(): - stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings")) - global_stack = stack - Job.yieldThread() - except: - Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") - # Something went really wrong. Try to remove any data that we added. - for container in containers_added: - self._container_registry.removeContainer(container.getId()) - return - - # load extruder stack files - has_extruder_stack_files = len(extruder_stack_files) > 0 - empty_quality_container = self._container_registry.findInstanceContainers(id = "empty_quality")[0] - empty_quality_changes_container = self._container_registry.findInstanceContainers(id = "empty_quality_changes")[0] - try: - for extruder_stack_file in extruder_stack_files: - container_id = self._stripFileToId(extruder_stack_file) - extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8") - - if self._resolve_strategies["machine"] == "override": - # deserialize new extruder stack over the current ones (if any) - stack = self._overrideExtruderStack(global_stack, extruder_file_content, extruder_stack_file) - if stack is None: - continue - - elif self._resolve_strategies["machine"] == "new": - new_id = extruder_stack_id_map[container_id] - stack = ExtruderStack(new_id) - - # HACK: the global stack can have a new name, so we need to make sure that this extruder stack - # references to the new name instead of the old one. Normally, this can be done after - # deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize() - # also does addExtruder() to its machine stack, so we have to make sure that it's pointing - # to the right machine BEFORE deserialization. - extruder_config = configparser.ConfigParser(interpolation = None) - extruder_config.read_string(extruder_file_content) - extruder_config.set("metadata", "machine", global_stack_id_new) - tmp_string_io = io.StringIO() - extruder_config.write(tmp_string_io) - extruder_file_content = tmp_string_io.getvalue() - - stack.deserialize(extruder_file_content, file_name = extruder_stack_file) - - # Ensure a unique ID and name - stack.setMetaDataEntry("id", new_id) - - self._container_registry.addContainer(stack) - extruder_stacks_added.append(stack) - containers_added.append(stack) - else: - Logger.log("w", "Unknown resolve strategy: %s", self._resolve_strategies["machine"]) - - # Create a new definition_changes container if it was empty - if stack.definitionChanges == self._container_registry.getEmptyInstanceContainer(): - stack.setDefinitionChanges(CuraStackBuilder.createDefinitionChangesContainer(stack, stack.getId() + "_settings")) - - if stack.getMetaDataEntry("type") == "extruder_train": - extruder_stacks.append(stack) - - # If not extruder stacks were saved in the project file (pre 3.1) create one manually - # We re-use the container registry's addExtruderStackForSingleExtrusionMachine method for this - if not extruder_stacks: - # If we choose to override a machine but to create a new custom quality profile, the custom quality - # profile is not immediately applied to the global_stack, so this fix for single extrusion machines - # will use the current custom quality profile on the existing machine. The extra optional argument - # in that function is used in this case to specify a new global stack quality_changes container so - # the fix can correctly create and copy over the custom quality settings to the newly created extruder. - new_global_quality_changes = None - if self._resolve_strategies["quality_changes"] == "new" and len(quality_changes_instance_containers) > 0: - new_global_quality_changes = quality_changes_instance_containers[0] - - # Depending if the strategy is to create a new or override, the ids must be or not be unique - stack = self._container_registry.addExtruderStackForSingleExtrusionMachine(global_stack, "fdmextruder", - new_global_quality_changes, - create_new_ids = self._resolve_strategies["machine"] == "new") - if new_global_quality_changes is not None: - quality_changes_instance_containers.append(stack.qualityChanges) - quality_and_definition_changes_instance_containers.append(stack.qualityChanges) - if global_stack.quality.getId() in ("empty", "empty_quality"): - stack.quality = empty_quality_container - if self._resolve_strategies["machine"] == "override": - # in case the extruder is newly created (for a single-extrusion machine), we need to override - # the existing extruder stack. - existing_extruder_stack = global_stack.extruders[stack.getMetaDataEntry("position")] - for idx in range(len(_ContainerIndexes.IndexTypeMap)): - existing_extruder_stack.replaceContainer(idx, stack._containers[idx], postpone_emit = True) - extruder_stacks.append(existing_extruder_stack) - else: - extruder_stacks.append(stack) - - except: - Logger.logException("w", "We failed to serialize the stack. Trying to clean up.") - # Something went really wrong. Try to remove any data that we added. - for container in containers_added: - self._container_registry.removeContainer(container.getId()) - return - - ## In case there is a new machine and once the extruders are created, the global stack is added to the registry, - # otherwise the addContainers function in CuraContainerRegistry will create an extruder stack and then creating - # useless files - if self._resolve_strategies["machine"] == "new": - self._container_registry.addContainer(global_stack) - - # Check quality profiles to make sure that if one stack has the "not supported" quality profile, - # all others should have the same. - # - # This block code tries to fix the following problems in Cura 3.0 and earlier: - # 1. The upgrade script can rename all "Not Supported" quality profiles to "empty_quality", but it cannot fix - # the problem that the global stack the extruder stacks may have different quality profiles. The code - # below loops over all stacks and make sure that if there is one stack with "Not Supported" profile, the - # rest should also use the "Not Supported" profile. - # 2. In earlier versions (at least 2.7 and 3.0), a wrong quality profile could be assigned to a stack. For - # example, a UM3 can have a BB 0.8 variant with "aa04_pla_fast" quality profile enabled. To fix this, - # in the code below we also check the actual available quality profiles for the machine. - # - has_not_supported = False - for stack in [global_stack] + extruder_stacks: - if stack.quality.getId() in ("empty", "empty_quality"): - has_not_supported = True - break - - # We filter out extruder stacks that are not actually used, for example the UM3 and custom FDM printer extruder count setting. - extruder_stacks_in_use = extruder_stacks - if machine_extruder_count is not None: - extruder_stacks_in_use = extruder_stacks[:machine_extruder_count] - - quality_manager = CuraApplication.getInstance()._quality_manager - all_quality_groups = quality_manager.getQualityGroups(global_stack) - available_quality_types = [qt for qt, qg in all_quality_groups.items() if qg.is_available] - if not has_not_supported: - has_not_supported = not available_quality_types - - quality_has_been_changed = False - - if has_not_supported: - for stack in [global_stack] + extruder_stacks_in_use: - stack.replaceContainer(_ContainerIndexes.Quality, empty_quality_container) - stack.replaceContainer(_ContainerIndexes.QualityChanges, empty_quality_changes_container) - quality_has_been_changed = True - - else: - # The machine in the project has non-empty quality and there are usable qualities for this machine. - # We need to check if the current quality_type is still usable for this machine, if not, then the quality - # will be reset to the "preferred quality" if present, otherwise "normal". - if global_stack.quality.getMetaDataEntry("quality_type") not in available_quality_types: - # We are here because the quality_type specified in the project is not supported any more, - # so we need to switch it to the "preferred quality" if present, otherwise "normal". - quality_has_been_changed = True - - # find the preferred quality - preferred_quality_id = global_stack.getMetaDataEntry("preferred_quality", None) - if preferred_quality_id is not None: - definition_id = global_stack.definition.getId() - definition_id = global_stack.definition.getMetaDataEntry("quality_definition", definition_id) - if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")): - definition_id = "fdmprinter" - - containers = self._container_registry.findInstanceContainers(id = preferred_quality_id, - type = "quality", - definition = definition_id) - containers = [c for c in containers if not c.getMetaDataEntry("material", "")] - if containers: - global_stack.quality = containers[0] - global_stack.qualityChanges = empty_quality_changes_container - # also find the quality containers for the extruders - for extruder_stack in extruder_stacks_in_use: - search_criteria = {"id": preferred_quality_id, - "type": "quality", - "definition": definition_id} - if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"): - search_criteria["material"] = extruder_stack.material.getId() - containers = self._container_registry.findInstanceContainers(**search_criteria) - if containers: - extruder_stack.quality = containers[0] - extruder_stack.qualityChanges = empty_quality_changes_container - else: - Logger.log("e", "Cannot find preferred quality for extruder [%s].", extruder_stack.getId()) - - else: - # we cannot find the preferred quality. THIS SHOULD NOT HAPPEN - Logger.log("e", "Cannot find the preferred quality for machine [%s]", global_stack.getId()) - else: - # The quality_type specified in the project file is usable, but the quality container itself may not - # be correct. For example, for UM2, the quality container can be "draft" while it should be "um2_draft" - # instead. - # In this code branch, we try to fix those incorrect quality containers. - # - # ***IMPORTANT***: We only do this fix for single-extrusion machines. - # We will first find the correct quality profile for the extruder, then apply the same - # quality profile for the global stack. - # - if len(extruder_stacks) == 1: - extruder_stack = extruder_stacks[0] - - search_criteria = {"type": "quality", "quality_type": global_stack.quality.getMetaDataEntry("quality_type")} - search_criteria["definition"] = global_stack.definition.getId() - if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")): - search_criteria["definition"] = "fdmprinter" - - if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"): - search_criteria["material"] = extruder_stack.material.getId() - containers = self._container_registry.findInstanceContainers(**search_criteria) - if containers: - new_quality_container = containers[0] - extruder_stack.quality = new_quality_container - global_stack.quality = new_quality_container - - # Now we are checking if the quality in the extruder stacks is the same as in the global. In other case, - # the quality is set to be the same. - definition_id = global_stack.definition.getId() - definition_id = global_stack.definition.getMetaDataEntry("quality_definition", definition_id) - if not parseBool(global_stack.getMetaDataEntry("has_machine_quality", "False")): - definition_id = "fdmprinter" - - for extruder_stack in extruder_stacks_in_use: - - # If the quality is different in the stacks, then the quality in the global stack is trusted - if extruder_stack.quality.getMetaDataEntry("quality_type") != global_stack.quality.getMetaDataEntry("quality_type"): - search_criteria = {"id": global_stack.quality.getId(), - "type": "quality", - "definition": definition_id} - if global_stack.getMetaDataEntry("has_machine_materials") and extruder_stack.material.getId() not in ("empty", "empty_material"): - search_criteria["material"] = extruder_stack.material.getId() - containers = self._container_registry.findInstanceContainers(**search_criteria) - if containers: - extruder_stack.quality = containers[0] - extruder_stack.qualityChanges = empty_quality_changes_container - else: - Logger.log("e", "Cannot find a suitable quality for extruder [%s].", extruder_stack.getId()) - - quality_has_been_changed = True - - else: - Logger.log("i", "The quality is the same for the global and the extruder stack [%s]", global_stack.quality.getId()) - - # Replacing the old containers if resolve is "new". - # When resolve is "new", some containers will get renamed, so all the other containers that reference to those - # MUST get updated too. - # - if self._resolve_strategies["machine"] == "new": - # A new machine was made, but it was serialized with the wrong user container. Fix that now. - for container in user_instance_containers: - # replacing the container ID for user instance containers for the extruders - extruder_id = container.getMetaDataEntry("extruder", None) - if extruder_id: - for extruder in extruder_stacks: - if extruder.getId() == extruder_id: - extruder.userChanges = container - continue - - # replacing the container ID for user instance containers for the machine - machine_id = container.getMetaDataEntry("machine", None) - if machine_id: - if global_stack.getId() == machine_id: - global_stack.userChanges = container - continue - - changes_container_types = ("quality_changes", "definition_changes") - if quality_has_been_changed: - # DO NOT replace quality_changes if the current quality_type is not supported - changes_container_types = ("definition_changes",) - for changes_container_type in changes_container_types: - if self._resolve_strategies[changes_container_type] == "new": - # Quality changes needs to get a new ID, added to registry and to the right stacks - for each_changes_container in quality_and_definition_changes_instance_containers: - # NOTE: The renaming and giving new IDs are possibly redundant because they are done in the - # instance container loading part. - new_id = each_changes_container.getId() - - # Find the old (current) changes container in the global stack - if changes_container_type == "quality_changes": - old_container = global_stack.qualityChanges - elif changes_container_type == "definition_changes": - old_container = global_stack.definitionChanges - - # sanity checks - # NOTE: The following cases SHOULD NOT happen!!!! - if old_container.getId() in ("empty_quality_changes", "empty_definition_changes", "empty"): - Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!", - changes_container_type, global_stack.getId()) - continue - - # Replace the quality/definition changes container if it's in the GlobalStack - # NOTE: we can get an empty container here, but the IDs will not match, - # so this comparison is fine. - if self._id_mapping.get(old_container.getId()) == new_id: - if changes_container_type == "quality_changes": - global_stack.qualityChanges = each_changes_container - elif changes_container_type == "definition_changes": - global_stack.definitionChanges = each_changes_container - continue - - # Replace the quality/definition changes container if it's in one of the ExtruderStacks - # Only apply the change if we have loaded extruder stacks from the project - if has_extruder_stack_files: - for each_extruder_stack in extruder_stacks: - changes_container = None - if changes_container_type == "quality_changes": - changes_container = each_extruder_stack.qualityChanges - elif changes_container_type == "definition_changes": - changes_container = each_extruder_stack.definitionChanges - - # sanity checks - # NOTE: The following cases SHOULD NOT happen!!!! - if changes_container.getId() in ("empty_quality_changes", "empty_definition_changes", "empty"): - Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!", - changes_container_type, each_extruder_stack.getId()) - continue - - # NOTE: we can get an empty container here, but the IDs will not match, - # so this comparison is fine. - if self._id_mapping.get(changes_container.getId()) == new_id: - if changes_container_type == "quality_changes": - each_extruder_stack.qualityChanges = each_changes_container - elif changes_container_type == "definition_changes": - each_extruder_stack.definitionChanges = each_changes_container - - if self._resolve_strategies["material"] == "new": - # the actual material instance container can have an ID such as - # __ - # which cannot be determined immediately, so here we use a HACK to find the right new material - # instance ID: - # - get the old material IDs for all material - # - find the old material with the longest common prefix in ID, that's the old material - # - update the name by replacing the old prefix with the new - # - find the new material container and set it to the stack - old_to_new_material_dict = {} - for each_material in material_containers: - # find the material's old name - for old_id, new_id in self._id_mapping.items(): - if each_material.getId() == new_id: - old_to_new_material_dict[old_id] = each_material - break - - global_stack.quality = empty_quality_container - - # replace old material in global and extruder stacks with new - self._replaceStackMaterialWithNew(global_stack, old_to_new_material_dict) - if extruder_stacks: - for extruder_stack in extruder_stacks: - if extruder_stack.material.getId() in ("empty", "empty_material"): - continue - old_root_material_id = extruder_stack.material.getMetaDataEntry("base_file") - if old_root_material_id in self._old_new_materials: - new_root_material_id = self._old_new_materials[old_root_material_id] - self._materials_to_select[extruder_stack.getMetaDataEntry("position")] = new_root_material_id - - if extruder_stacks: - for stack in extruder_stacks: - ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId()) + # Prepare the machine + self._applyChangesToMachine(global_stack, extruder_stack_dict) Logger.log("d", "Workspace loading is notifying rest of the code of changes...") - - if self._resolve_strategies["machine"] == "new": - for stack in extruder_stacks: - stack.setNextStack(global_stack) - stack.containersChanged.emit(stack.getTop()) - else: - CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit() - # Actually change the active machine. # # This is scheduled for later is because it depends on the Variant/Material/Qualitiy Managers to have the latest @@ -1137,7 +708,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # function is running on the main thread (Qt thread), although those "changed" signals have been emitted, but # they won't take effect until this function is done. # To solve this, we schedule _updateActiveMachine() for later so it will have the latest data. - CuraApplication.getInstance().callLater(self._updateActiveMachine, global_stack) + Application.getInstance().setGlobalContainerStack(global_stack) + self._updateActiveMachine(global_stack) # Load all the nodes / meshdata of the workspace nodes = self._3mf_mesh_reader.read(file_name) @@ -1150,85 +722,284 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self.setWorkspaceName(base_file_name) return nodes + def _processQualityChanges(self, global_stack): + if self._machine_info.quality_changes_info is None: + return + + application = CuraApplication.getInstance() + quality_manager = application.getQualityManager() + + # If we have custom profiles, load them + quality_changes_name = self._machine_info.quality_changes_info.name + if self._machine_info.quality_changes_info is not None: + Logger.log("i", "Loading custom profile [%s] from project file", + self._machine_info.quality_changes_info.name) + + # Get the correct extruder definition IDs for quality changes + from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch + machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(global_stack) + machine_definition_for_quality = self._container_registry.findDefinitionContainers(id = machine_definition_id_for_quality)[0] + extruder_dict_for_quality = machine_definition_for_quality.getMetaDataEntry("machine_extruder_trains") + + quality_changes_info = self._machine_info.quality_changes_info + quality_changes_quality_type = quality_changes_info.global_info.parser["metadata"]["quality_type"] + + quality_changes_name = quality_changes_info.name + create_new = self._resolve_strategies.get("quality_changes") != "override" + if create_new: + container_info_dict = {None: self._machine_info.quality_changes_info.global_info} + container_info_dict.update(quality_changes_info.extruder_info_dict) + + quality_changes_name = self._container_registry.uniqueName(quality_changes_name) + for position, container_info in container_info_dict.items(): + extruder_definition_id = None + if position is not None: + extruder_definition_id = extruder_dict_for_quality[position] + + container = quality_manager._createQualityChanges(quality_changes_quality_type, + quality_changes_name, + global_stack, extruder_definition_id) + container_info.container = container + container.setDirty(True) + self._container_registry.addContainer(container) + + Logger.log("d", "Created new quality changes container [%s]", container.getId()) + + else: + # Find the existing containers + quality_changes_containers = self._container_registry.findInstanceContainers(name = quality_changes_name, + type = "quality_changes") + for container in quality_changes_containers: + extruder_definition_id = container.getMetaDataEntry("extruder") + if not extruder_definition_id: + quality_changes_info.global_info.container = container + else: + extruder_definition_metadata = self._container_registry.findDefinitionContainersMetadata(id = extruder_definition_id)[0] + position = extruder_definition_metadata["position"] + if position not in quality_changes_info.extruder_info_dict: + quality_changes_info.extruder_info_dict[position] = ContainerInfo(None, None, None) + container_info = quality_changes_info.extruder_info_dict[position] + container_info.container = container + + for position, container_info in quality_changes_info.extruder_info_dict.items(): + container_info.definition_id = extruder_dict_for_quality[position] + + # If there is no quality changes for any extruder, create one. + if not quality_changes_info.extruder_info_dict: + container_info = ContainerInfo(None, None, None) + quality_changes_info.extruder_info_dict["0"] = container_info + extruder_definition_id = extruder_dict_for_quality["0"] + container_info.definition_id = extruder_definition_id + + container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name, + global_stack, extruder_definition_id) + container_info.container = container + container.setDirty(True) + self._container_registry.addContainer(container) + + Logger.log("d", "Created new quality changes container [%s]", container.getId()) + + # Clear all existing containers + quality_changes_info.global_info.container.clear() + for container_info in quality_changes_info.extruder_info_dict.values(): + container_info.container.clear() + + # Loop over everything and override the existing containers + global_info = quality_changes_info.global_info + global_info.container.clear() # Clear all + for key, value in global_info.parser["values"].items(): + if not machine_definition_for_quality.getProperty(key, "settable_per_extruder"): + global_info.container.setProperty(key, "value", value) + else: + quality_changes_info.extruder_info_dict["0"].container.setProperty(key, "value", value) + + for position, container_info in quality_changes_info.extruder_info_dict.items(): + if container_info.parser is None: + continue + + if container_info.container is None: + extruder_definition_id = extruder_dict_for_quality[position] + container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name, + global_stack, extruder_definition_id) + container_info.container = container + + for key, value in container_info.parser["values"].items(): + container_info.container.setProperty(key, "value", value) + + self._machine_info.quality_changes_info.name = quality_changes_name + + def _clearStack(self, stack): + application = CuraApplication.getInstance() + + stack.definitionChanges.clear() + stack.variant = application.empty_variant_container + stack.material = application.empty_material_container + stack.quality = application.empty_quality_container + stack.qualityChanges = application.empty_quality_changes_container + stack.userChanges.clear() + + def _applyDefinitionChanges(self, global_stack, extruder_stack_dict): + values_to_set_for_extruders = {} + if self._machine_info.definition_changes_info is not None: + parser = self._machine_info.definition_changes_info.parser + for key, value in parser["values"].items(): + if global_stack.getProperty(key, "settable_per_extruder"): + values_to_set_for_extruders[key] = value + else: + global_stack.definitionChanges.setProperty(key, "value", value) + + for position, extruder_stack in extruder_stack_dict.items(): + if position not in self._machine_info.extruder_info_dict: + continue + + extruder_info = self._machine_info.extruder_info_dict[position] + if extruder_info.definition_changes_info is None: + continue + parser = extruder_info.definition_changes_info.parser + for key, value in values_to_set_for_extruders.items(): + extruder_stack.definitionChanges.setProperty(key, "value", value) + if parser is not None: + for key, value in parser["values"].items(): + extruder_stack.definitionChanges.setProperty(key, "value", value) + + def _applyUserChanges(self, global_stack, extruder_stack_dict): + values_to_set_for_extruder_0 = {} + if self._machine_info.user_changes_info is not None: + parser = self._machine_info.user_changes_info.parser + for key, value in parser["values"].items(): + if global_stack.getProperty(key, "settable_per_extruder"): + values_to_set_for_extruder_0[key] = value + else: + global_stack.userChanges.setProperty(key, "value", value) + + for position, extruder_stack in extruder_stack_dict.items(): + if position not in self._machine_info.extruder_info_dict: + continue + + extruder_info = self._machine_info.extruder_info_dict[position] + if extruder_info.user_changes_info is not None: + parser = self._machine_info.extruder_info_dict[position].user_changes_info.parser + if position == "0": + for key, value in values_to_set_for_extruder_0.items(): + extruder_stack.userChanges.setProperty(key, "value", value) + if parser is not None: + for key, value in parser["values"].items(): + extruder_stack.userChanges.setProperty(key, "value", value) + + def _applyVariants(self, global_stack, extruder_stack_dict): + application = CuraApplication.getInstance() + variant_manager = application.getVariantManager() + + if self._machine_info.variant_info is not None: + 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) + if node is not None: + global_stack.variant = node.getContainer() + + for position, extruder_stack in extruder_stack_dict.items(): + if position not in self._machine_info.extruder_info_dict: + continue + extruder_info = self._machine_info.extruder_info_dict[position] + if extruder_info.variant_info is None: + continue + 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) + if node is not None: + extruder_stack.variant = node.getContainer() + + def _applyMaterials(self, global_stack, extruder_stack_dict): + application = CuraApplication.getInstance() + material_manager = application.getMaterialManager() + + # Force update lookup tables first + material_manager.initialize() + + for position, extruder_stack in extruder_stack_dict.items(): + if position not in self._machine_info.extruder_info_dict: + continue + extruder_info = self._machine_info.extruder_info_dict[position] + if extruder_info.root_material_id is None: + continue + + root_material_id = extruder_info.root_material_id + root_material_id = self._old_new_materials.get(root_material_id, root_material_id) + + # 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(), + machine_material_diameter, + root_material_id) + if material_node is not None: + extruder_stack.material = material_node.getContainer() + + def _applyChangesToMachine(self, global_stack, extruder_stack_dict): + # Clear all first + self._clearStack(global_stack) + for extruder_stack in extruder_stack_dict.values(): + self._clearStack(extruder_stack) + + self._applyDefinitionChanges(global_stack, extruder_stack_dict) + self._applyUserChanges(global_stack, extruder_stack_dict) + self._applyVariants(global_stack, extruder_stack_dict) + self._applyMaterials(global_stack, extruder_stack_dict) + + # prepare the quality to select + self._quality_changes_to_apply = None + self._quality_type_to_apply = None + if self._machine_info.quality_changes_info is not None: + self._quality_changes_to_apply = self._machine_info.quality_changes_info.name + else: + self._quality_type_to_apply = self._machine_info.quality_type + def _updateActiveMachine(self, global_stack): # Actually change the active machine. machine_manager = Application.getInstance().getMachineManager() material_manager = Application.getInstance().getMaterialManager() + quality_manager = Application.getInstance().getQualityManager() - # Switch materials if new materials are created due to conflicts - # We do it here because MaterialManager hasn't been updated in _read() yet. - for position, root_material_id in self._materials_to_select.items(): - extruder_stack = global_stack.extruders[position] - material_diameter = extruder_stack.materialDiameter - material_node = material_manager.getMaterialNode(global_stack.getMetaDataEntry("definition"), - extruder_stack.variant.getName(), - material_diameter, root_material_id) - if material_node is None: - Application.getInstance().callLater(self._updateActiveMachine, global_stack) - return - extruder_stack.material = material_node.getContainer() - Logger.log("d", "Changed extruder [%s] to material [%s]", position, root_material_id) + # Force update the lookup maps first + material_manager.initialize() + quality_manager.initialize() machine_manager.setActiveMachine(global_stack.getId()) + if self._quality_changes_to_apply: + quality_changes_group_dict = quality_manager.getQualityChangesGroups(global_stack) + if self._quality_changes_to_apply not in quality_changes_group_dict: + Logger.log("e", "Could not find quality_changes [%s]", self._quality_changes_to_apply) + return + quality_changes_group = quality_changes_group_dict[self._quality_changes_to_apply] + machine_manager.setQualityChangesGroup(quality_changes_group) + else: + self._quality_type_to_apply = self._quality_type_to_apply.lower() + quality_group_dict = quality_manager.getQualityGroups(global_stack) + if self._quality_type_to_apply in quality_group_dict: + quality_group = quality_group_dict[self._quality_type_to_apply] + else: + Logger.log("i", "Could not find quality type [%s], switch to default", self._quality_type_to_apply) + preferred_quality_type = global_stack.getMetaDataEntry("preferred_quality_type") + quality_group_dict = quality_manager.getQualityGroups(global_stack) + quality_group = quality_group_dict.get(preferred_quality_type) + if quality_group is None: + Logger.log("e", "Could not get preferred quality type [%s]", preferred_quality_type) + + if quality_group is not None: + machine_manager.setQualityGroup(quality_group) + # Notify everything/one that is to notify about changes. global_stack.containersChanged.emit(global_stack.getTop()) - ## HACK: Replaces the material container in the given stack with a newly created material container. - # This function is used when the user chooses to resolve material conflicts by creating new ones. - def _replaceStackMaterialWithNew(self, stack, old_new_material_dict): - # The material containers in the project file are 'parent' material such as "generic_pla", - # but a material container used in a global/extruder stack is a 'child' material, - # such as "generic_pla_ultimaker3_AA_0.4", which can be formalised as the following: - # - # __ - # - # In the project loading, when a user chooses to resolve material conflicts by creating new ones, - # the old 'parent' material ID and the new 'parent' material ID are known, but not the child material IDs. - # In this case, the global stack and the extruder stacks need to use the newly created material, but the - # material containers they use are 'child' material. So, here, we need to find the right 'child' material for - # the stacks. - # - # This hack approach works as follows: - # - No matter there is a child material or not, the actual material we are looking for has the prefix - # "", which is the old material name. For the material in a stack, we know that the new - # material's ID will be "_blabla..", so we just need to replace the old material ID - # with the new one to get the new 'child' material. - # - Because the material containers have IDs such as "m #nn", if we use simple prefix matching, there can - # be a problem in the following scenario: - # - there are two materials in the project file, namely "m #1" and "m #11" - # - the child materials in use are for example: "m #1_um3_aa04", "m #11_um3_aa04" - # - if we only check for a simple prefix match, then "m #11_um3_aa04" will match with "m #1", but they - # are not the same material - # To avoid this, when doing the prefix matching, we use the result with the longest mactching prefix. - - # find the old material ID - old_material_id_in_stack = stack.material.getId() - best_matching_old_material_id = None - best_matching_old_material_prefix_length = -1 - for old_parent_material_id in old_new_material_dict: - if len(old_parent_material_id) < best_matching_old_material_prefix_length: - continue - if len(old_parent_material_id) <= len(old_material_id_in_stack): - if old_parent_material_id == old_material_id_in_stack[0:len(old_parent_material_id)]: - best_matching_old_material_prefix_length = len(old_parent_material_id) - best_matching_old_material_id = old_parent_material_id - - if best_matching_old_material_id is None: - Logger.log("w", "Cannot find any matching old material ID for stack [%s] material [%s]. Something can go wrong", - stack.getId(), old_material_id_in_stack) - return - - # find the new material container - new_material_id = old_new_material_dict[best_matching_old_material_id].getId() + old_material_id_in_stack[len(best_matching_old_material_id):] - new_material_containers = self._container_registry.findInstanceContainers(id = new_material_id, type = "material") - if not new_material_containers: - Logger.log("e", "Cannot find new material container [%s]", new_material_id) - return - - # replace the material in the given stack - stack.material = new_material_containers[0] - def _stripFileToId(self, file): mime_type = MimeTypeDatabase.getMimeTypeForFile(file) file = mime_type.stripExtension(file) @@ -1239,7 +1010,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): ## Get the list of ID's of all containers in a container stack by partially parsing it's serialized data. def _getContainerIdListFromSerialized(self, serialized): - parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False) + parser = ConfigParser(interpolation=None, empty_lines_in_values=False) parser.read_string(serialized) container_ids = [] @@ -1260,7 +1031,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): return container_ids def _getMachineNameFromSerializedStack(self, serialized): - parser = configparser.ConfigParser(interpolation=None, empty_lines_in_values=False) + parser = ConfigParser(interpolation=None, empty_lines_in_values=False) parser.read_string(serialized) return parser["general"].get("name", "") diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index 5b474843d5..bb31afd40b 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -52,7 +52,6 @@ class WorkspaceDialog(QObject): machineConflictChanged = pyqtSignal() qualityChangesConflictChanged = pyqtSignal() - definitionChangesConflictChanged = pyqtSignal() materialConflictChanged = pyqtSignal() numVisibleSettingsChanged = pyqtSignal() activeModeChanged = pyqtSignal() @@ -196,10 +195,6 @@ class WorkspaceDialog(QObject): def qualityChangesConflict(self): return self._has_quality_changes_conflict - @pyqtProperty(bool, notify=definitionChangesConflictChanged) - def definitionChangesConflict(self): - return self._has_definition_changes_conflict - @pyqtProperty(bool, notify=materialConflictChanged) def materialConflict(self): return self._has_material_conflict @@ -229,18 +224,11 @@ class WorkspaceDialog(QObject): self._has_quality_changes_conflict = quality_changes_conflict self.qualityChangesConflictChanged.emit() - def setDefinitionChangesConflict(self, definition_changes_conflict): - if self._has_definition_changes_conflict != definition_changes_conflict: - self._has_definition_changes_conflict = definition_changes_conflict - self.definitionChangesConflictChanged.emit() - def getResult(self): if "machine" in self._result and not self._has_machine_conflict: self._result["machine"] = None if "quality_changes" in self._result and not self._has_quality_changes_conflict: self._result["quality_changes"] = None - if "definition_changes" in self._result and not self._has_definition_changes_conflict: - self._result["definition_changes"] = None if "material" in self._result and not self._has_material_conflict: self._result["material"] = None From ef8cd304dc847a61fd45f3b69aa49d30774faac8 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 8 Mar 2018 20:00:19 +0100 Subject: [PATCH 65/85] No keep/discard setting dialog in project loading CURA-4966 --- cura/Settings/MachineManager.py | 8 ++++---- plugins/3MFReader/ThreeMFWorkspaceReader.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index f8692da0e3..1b3f01170e 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1012,13 +1012,13 @@ class MachineManager(QObject): self._updateQualityWithMaterial() @pyqtSlot(QObject) - def setQualityGroup(self, quality_group): + def setQualityGroup(self, quality_group, no_dialog = False): self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setQualityGroup(quality_group) # See if we need to show the Discard or Keep changes screen - if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: + if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: self._application.discardOrKeepProfileChanges() @pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged) @@ -1026,13 +1026,13 @@ class MachineManager(QObject): return self._current_quality_group @pyqtSlot(QObject) - def setQualityChangesGroup(self, quality_changes_group): + def setQualityChangesGroup(self, quality_changes_group, no_dialog = False): self.blurSettings.emit() with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): self._setQualityChangesGroup(quality_changes_group) # See if we need to show the Discard or Keep changes screen - if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: + if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: self._application.discardOrKeepProfileChanges() @pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 4698d498d2..b02dda4b83 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -980,7 +980,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Logger.log("e", "Could not find quality_changes [%s]", self._quality_changes_to_apply) return quality_changes_group = quality_changes_group_dict[self._quality_changes_to_apply] - machine_manager.setQualityChangesGroup(quality_changes_group) + machine_manager.setQualityChangesGroup(quality_changes_group, no_dialog = True) else: self._quality_type_to_apply = self._quality_type_to_apply.lower() quality_group_dict = quality_manager.getQualityGroups(global_stack) @@ -995,7 +995,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Logger.log("e", "Could not get preferred quality type [%s]", preferred_quality_type) if quality_group is not None: - machine_manager.setQualityGroup(quality_group) + machine_manager.setQualityGroup(quality_group, no_dialog = True) # Notify everything/one that is to notify about changes. global_stack.containersChanged.emit(global_stack.getTop()) From a4f9c91d9526d62112f869bcedd6bbd348a22506 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 9 Mar 2018 09:30:32 +0100 Subject: [PATCH 66/85] Add acceleration/jerk data for TEVO Tarantula Thanks to @SuperBoppy in ticket #3323. --- resources/definitions/tevo_tarantula.def.json | 82 +++++++------------ 1 file changed, 30 insertions(+), 52 deletions(-) diff --git a/resources/definitions/tevo_tarantula.def.json b/resources/definitions/tevo_tarantula.def.json index a9f9cefff2..c3bfb38192 100644 --- a/resources/definitions/tevo_tarantula.def.json +++ b/resources/definitions/tevo_tarantula.def.json @@ -2,7 +2,8 @@ "version": 2, "name": "Tevo Tarantula", "inherits": "fdmprinter", - "metadata": { + "metadata": + { "visible": true, "author": "TheAssassin", "manufacturer": "Tevo", @@ -11,62 +12,39 @@ "platform": "prusai3_platform.stl" }, - "overrides": { - "machine_name": { - "default_value": "Tevo Tarantula" - }, - "machine_heated_bed": { - "default_value": true - }, - "machine_width": { - "default_value": 200 - }, - "machine_height": { - "default_value": 200 - }, - "machine_depth": { - "default_value": 200 - }, - "machine_center_is_zero": { - "default_value": false - }, - "machine_nozzle_size": { - "default_value": 0.4 - }, - "material_diameter": { - "default_value": 1.75 - }, - "machine_head_polygon": { - "default_value": [ + "overrides": + { + "machine_name": { "default_value": "Tevo Tarantula" }, + "machine_heated_bed": { "default_value": true }, + "machine_width": { "default_value": 200 }, + "machine_height": { "default_value": 200 }, + "machine_depth": { "default_value": 200 }, + "machine_center_is_zero": { "default_value": false }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 }, + "machine_head_polygon": + { + "default_value": + [ [-75, -18], [-75, 35], [18, 35], [18, -18] ] }, - "gantry_height": { - "default_value": 55 - }, - "machine_gcode_flavor": { - "default_value": "RepRap (Marlin/Sprinter)" - }, - "machine_acceleration": { - "default_value": 500 - }, - "machine_max_jerk_xy": { - "default_value": 4.0 - }, - "machine_max_jerk_z": { - "default_value": 0.2 - }, - "machine_max_jerk_e": { - "default_value": 2.5 - }, - "machine_start_gcode": { - "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..." - }, - "machine_end_gcode": { - "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG1 X0 Y200 F3600 ;move extruder out of the way by moving the baseplate to the front for easier access to printed object\nM84 ;steppers off" - } + "gantry_height": { "default_value": 55 }, + "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, + "machine_acceleration": { "default_value": 2650 }, + "machine_max_jerk_xy": { "default_value": 15.0 }, + "machine_max_jerk_z": { "default_value": 0.4 }, + "machine_max_jerk_e": { "default_value": 5 }, + "machine_max_feedrate_x": { "default_value": 255 }, + "machine_max_feedrate_y": { "default_value": 225 }, + "machine_max_feedrate_z": { "default_value": 3 }, + "machine_max_acceleration_x": { "default_value": 2620 }, + "machine_max_acceleration_y": { "default_value": 2650 }, + "acceleration_print": { "default_value": 2650 }, + "machine_start_gcode": { "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F9000 ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F9000\n;Put printing message on LCD screen\nM117 Printing..." }, + "machine_end_gcode": { "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG1 X0 Y200 F3600 ;move extruder out of the way by moving the baseplate to the front for easier access to printed object\nM84 ;steppers off" } } } From 15f017b404576ae588013a74481b2231bc262c0a Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 9 Mar 2018 10:18:47 +0100 Subject: [PATCH 67/85] Set dirty for containers created in project loading CURA-5056 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index b02dda4b83..ac61016de7 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -691,6 +691,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): if need_new_name: new_name = ContainerRegistry.getInstance().uniqueName(material_container.getName()) material_container.setName(new_name) + material_container.setDirty(True) self._container_registry.addContainer(material_container) Job.yieldThread() From c278942cecf980ff26d53ba29268e29eed06416e Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 9 Mar 2018 10:26:48 +0100 Subject: [PATCH 68/85] CURA-67 Don't enable by default `value` is set to !support and !tree support, both of which are typically disabled by default, making this setting's default value effectively `true` even though the line above says the default should be `false`. --- resources/definitions/fdmprinter.def.json | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index f7c014ad61..12ba59b8a1 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -6236,7 +6236,6 @@ "description": "Detect bridges and modify print speed, flow and fan settings while bridges are printed.", "type": "bool", "default_value": false, - "value": "not support_enable and not support_tree_enable", "settable_per_mesh": true, "settable_per_extruder": false, "settable_per_meshgroup": false From 6bee5bf1b034a0306ed54374153e2acb965f8466 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Fri, 9 Mar 2018 10:36:34 +0100 Subject: [PATCH 69/85] CURA-4972 Tweak per review comments --- 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 afbc816cc5..31ea999161 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -139,7 +139,7 @@ class StartSliceJob(Job): # Don't slice if there is a per object setting with an error value. for node in DepthFirstIterator(self._scene.getRoot()): - if type(node) is not CuraSceneNode or not node.isSelectable(): + if not isinstance(node, CuraSceneNode) or not node.isSelectable(): continue if self._checkStackForErrors(node.callDecoration("getStack")): From 3550ef80e0d6734320b46dd344b55e3a97462fb6 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 9 Mar 2018 10:59:33 +0100 Subject: [PATCH 70/85] Fix quality management page QML CURA-5063 --- cura/Machines/Models/QualitySettingsModel.py | 2 +- resources/qml/Preferences/ProfileTab.qml | 17 +++++++++++++---- resources/qml/Preferences/ProfilesPage.qml | 11 +++++++---- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/cura/Machines/Models/QualitySettingsModel.py b/cura/Machines/Models/QualitySettingsModel.py index 38b7ec28e4..e0b29d77a5 100644 --- a/cura/Machines/Models/QualitySettingsModel.py +++ b/cura/Machines/Models/QualitySettingsModel.py @@ -69,7 +69,7 @@ class QualitySettingsModel(ListModel): return self._selected_quality_item def _update(self): - if self._selected_quality_item is None: + if not self._selected_quality_item: self.setItems([]) return diff --git a/resources/qml/Preferences/ProfileTab.qml b/resources/qml/Preferences/ProfileTab.qml index 40f725e092..e202e933f3 100644 --- a/resources/qml/Preferences/ProfileTab.qml +++ b/resources/qml/Preferences/ProfileTab.qml @@ -14,6 +14,15 @@ Tab property string extruderPosition: "" property var qualityItem: null + property bool isQualityItemCurrentlyActivated: + { + if (qualityItem == null) + { + return false; + } + return qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName; + } + TableView { anchors.fill: parent @@ -36,8 +45,8 @@ Tab anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.right: parent.right text: (styleData.value.substr(0,1) == "=") ? catalog.i18nc("@info:status", "Calculated") : styleData.value - font.strikeout: styleData.column == 1 && setting.user_value != "" && qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName - font.italic: setting.profile_value_source == "quality_changes" || (setting.user_value != "" && qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName) + font.strikeout: styleData.column == 1 && setting.user_value != "" && base.isQualityItemCurrentlyActivated + font.italic: setting.profile_value_source == "quality_changes" || (setting.user_value != "" && base.isQualityItemCurrentlyActivated) opacity: font.strikeout ? 0.5 : 1 color: styleData.textColor elide: Text.ElideRight @@ -63,7 +72,7 @@ Tab { role: "user_value" title: catalog.i18nc("@title:column", "Current"); - visible: qualityItem.name == Cura.MachineManager.activeQualityOrQualityChangesName + visible: base.isQualityItemCurrentlyActivated width: (parent.width * 0.18) | 0 delegate: itemDelegate } @@ -86,7 +95,7 @@ Tab { id: qualitySettings selectedPosition: base.extruderPosition - selectedQualityItem: base.qualityItem + selectedQualityItem: base.qualityItem == null ? {} : base.qualityItem } SystemPalette { id: palette } diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index 3e10aca000..ff35e27eeb 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -36,13 +36,15 @@ Item text: catalog.i18nc("@title:tab", "Profiles") } - property var hasCurrentItem: qualityListView.currentItem != null + property var hasCurrentItem: base.currentItem != null property var currentItem: { var current_index = qualityListView.currentIndex; - return qualitiesModel.getItem(current_index); + return (current_index == -1) ? null : qualitiesModel.getItem(current_index); } + property var currentItemName: hasCurrentItem ? base.currentItem.name : "" + property var isCurrentItemActivated: { if (!base.currentItem) { return false; @@ -235,7 +237,7 @@ Item icon: StandardIcon.Question; title: catalog.i18nc("@title:window", "Confirm Remove") - text: catalog.i18nc("@label (%1 is object name)", "Are you sure you wish to remove %1? This cannot be undone!").arg(base.currentItem.name) + text: catalog.i18nc("@label (%1 is object name)", "Are you sure you wish to remove %1? This cannot be undone!").arg(base.currentItemName) standardButtons: StandardButton.Yes | StandardButton.No modality: Qt.ApplicationModal @@ -437,6 +439,7 @@ Item Item { anchors.fill: parent + visible: base.currentItem != null Item // Profile title Label { @@ -446,7 +449,7 @@ Item height: childrenRect.height Label { - text: base.currentItem.name + text: base.currentItemName font: UM.Theme.getFont("large") } } From d8853b8a98618ba18d050a9b893c84df987c8845 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 9 Mar 2018 11:39:40 +0100 Subject: [PATCH 71/85] Sort profile models case-insensitively Cast every sorting key to uppercase before doing this. Don't cast to lowercase or there will be problems with characters that don't have lowercase and with Turkish dotted i vs. undotted i. Fixes #3460. --- cura/Machines/Models/BrandMaterialsModel.py | 6 +++--- .../Models/CustomQualityProfilesDropDownMenuModel.py | 2 +- cura/Machines/Models/GenericMaterialsModel.py | 2 +- cura/Machines/Models/MaterialManagementModel.py | 2 +- cura/Machines/Models/NozzleModel.py | 2 +- cura/Machines/Models/QualityManagementModel.py | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cura/Machines/Models/BrandMaterialsModel.py b/cura/Machines/Models/BrandMaterialsModel.py index 2ef1425986..1146a8acee 100644 --- a/cura/Machines/Models/BrandMaterialsModel.py +++ b/cura/Machines/Models/BrandMaterialsModel.py @@ -122,17 +122,17 @@ class BrandMaterialsModel(ListModel): material_type_item["colors"].clear() # Sort materials by name - material_list = sorted(material_list, key = lambda x: x["name"]) + material_list = sorted(material_list, key = lambda x: x["name"].upper()) material_type_item["colors"].setItems(material_list) material_type_item_list.append(material_type_item) # Sort material type by name - material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"]) + material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"].upper()) brand_item["materials"].setItems(material_type_item_list) brand_item_list.append(brand_item) # Sort brand by name - brand_item_list = sorted(brand_item_list, key = lambda x: x["name"]) + brand_item_list = sorted(brand_item_list, key = lambda x: x["name"].upper()) self.setItems(brand_item_list) diff --git a/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py b/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py index 0d297379cd..a188a43e72 100644 --- a/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py +++ b/cura/Machines/Models/CustomQualityProfilesDropDownMenuModel.py @@ -23,7 +23,7 @@ class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel): quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(active_global_stack) item_list = [] - for key in sorted(quality_changes_group_dict): + for key in sorted(quality_changes_group_dict, key = lambda name: name.upper()): quality_changes_group = quality_changes_group_dict[key] item = {"name": quality_changes_group.name, diff --git a/cura/Machines/Models/GenericMaterialsModel.py b/cura/Machines/Models/GenericMaterialsModel.py index d20fc05b6e..47240ebffd 100644 --- a/cura/Machines/Models/GenericMaterialsModel.py +++ b/cura/Machines/Models/GenericMaterialsModel.py @@ -55,6 +55,6 @@ class GenericMaterialsModel(BaseMaterialsModel): item_list.append(item) # Sort the item list by material name alphabetically - item_list = sorted(item_list, key = lambda d: d["name"]) + item_list = sorted(item_list, key = lambda d: d["name"].upper()) self.setItems(item_list) diff --git a/cura/Machines/Models/MaterialManagementModel.py b/cura/Machines/Models/MaterialManagementModel.py index b250232282..1ea0fd9cf7 100644 --- a/cura/Machines/Models/MaterialManagementModel.py +++ b/cura/Machines/Models/MaterialManagementModel.py @@ -97,5 +97,5 @@ class MaterialManagementModel(ListModel): material_list.append(item) - material_list = sorted(material_list, key = lambda k: (k["brand"].lower(), k["name"])) + material_list = sorted(material_list, key = lambda k: (k["brand"].upper(), k["name"].upper())) self.setItems(material_list) diff --git a/cura/Machines/Models/NozzleModel.py b/cura/Machines/Models/NozzleModel.py index 19d4a800c8..27d190962b 100644 --- a/cura/Machines/Models/NozzleModel.py +++ b/cura/Machines/Models/NozzleModel.py @@ -45,7 +45,7 @@ class NozzleModel(ListModel): return item_list = [] - for hotend_name, container_node in sorted(variant_node_dict.items(), key = lambda i: i[0]): + for hotend_name, container_node in sorted(variant_node_dict.items(), key = lambda i: i[0].upper()): item = {"id": hotend_name, "hotend_name": hotend_name, "container_node": container_node diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index e089f92329..542016ab68 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -59,7 +59,7 @@ class QualityManagementModel(ListModel): "quality_changes_group": None} item_list.append(item) # Sort by quality names - item_list = sorted(item_list, key = lambda x: x["name"]) + item_list = sorted(item_list, key = lambda x: x["name"].upper()) # Create quality_changes group items quality_changes_item_list = [] @@ -74,7 +74,7 @@ class QualityManagementModel(ListModel): quality_changes_item_list.append(item) # Sort quality_changes items by names and append to the item list - quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"]) + quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"].upper()) item_list += quality_changes_item_list self.setItems(item_list) From 970f1da810aee98673652b15f4df008824f11dd7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 9 Mar 2018 13:47:19 +0100 Subject: [PATCH 72/85] Add missing getVersion and location for quality It worked out because older plug-ins still defined this, but let's properly put it in here too. Contributes to issue CURA-5054. --- plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py b/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py index b4b75dddf7..c853e2b93b 100644 --- a/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade30to31/__init__.py @@ -33,6 +33,10 @@ def getMetaData(): "get_version": upgrade.getCfgVersion, "location": {"./extruders"} }, + "quality": { + "get_version": upgrade.getCfgVersion, + "location": {"./quality"} + }, "quality_changes": { "get_version": upgrade.getCfgVersion, "location": {"./quality"} From 60de2aff657347553d011a6697958f97b43ad0ac Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 9 Mar 2018 13:59:31 +0100 Subject: [PATCH 73/85] Basic upgrade module from 3.2 to 3.3 implementation 'Basic' meaning no implementation at all. Contributes to issue CURA-5054. --- .../VersionUpgrade32to33.py | 18 +++++++++++++++ .../VersionUpgrade32to33/__init__.py | 23 +++++++++++++++++++ .../VersionUpgrade32to33/plugin.json | 8 +++++++ 3 files changed, 49 insertions(+) create mode 100644 plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py create mode 100644 plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py create mode 100644 plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py new file mode 100644 index 0000000000..64eded8b2e --- /dev/null +++ b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py @@ -0,0 +1,18 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import configparser #To parse preference files. +import io #To serialise the preference files afterwards. + +from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this. + +## Upgrades configurations from the state they were in at version 3.2 to the +# state they should be in at version 3.3. +class VersionUpgrade32to33(VersionUpgrade): + ## Gets the version number from a CFG file. + def getCfgVersion(self, serialized): + raise NotImplementedError("This has not yet been implemented.") + + ## Upgrades a quality container to the new format. + def upgradeQuality(self, serialized, filename): + raise NotImplementedError("This has not yet been implemented.") \ No newline at end of file diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py new file mode 100644 index 0000000000..dbcad86ac4 --- /dev/null +++ b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py @@ -0,0 +1,23 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from . import VersionUpgrade32to33 + +upgrade = VersionUpgrade32to33.VersionUpgrade32to33() + +def getMetaData(): + return { + "version_upgrade": { + # From To Upgrade function + ("quality", 2000004): ("quality", 3000004, upgrade.upgradeQuality), + }, + "sources": { + "quality": { + "get_version": upgrade.getCfgVersion, + "location": {"./quality"} + } + } + } + +def register(app): + return { "version_upgrade": upgrade } \ No newline at end of file diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json b/plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json new file mode 100644 index 0000000000..fbce09c807 --- /dev/null +++ b/plugins/VersionUpgrade/VersionUpgrade32to33/plugin.json @@ -0,0 +1,8 @@ + { + "name": "Version Upgrade 3.2 to 3.3", + "author": "Ultimaker B.V.", + "version": "1.0.0", + "description": "Upgrades configurations from Cura 3.2 to Cura 3.3.", + "api": 4, + "i18n-catalog": "cura" +} From af89209cdeb31eec4abf9ebcb30266239ac35676 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 9 Mar 2018 14:02:04 +0100 Subject: [PATCH 74/85] Changed rounding of prettyTime by accident --- resources/qml/PrintMonitor.qml | 2 +- resources/qml/Sidebar.qml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml index 0841144043..ae74170004 100644 --- a/resources/qml/PrintMonitor.qml +++ b/resources/qml/PrintMonitor.qml @@ -122,7 +122,7 @@ Column { label: catalog.i18nc("@label", "Printing Time") value: activePrintJob != null ? getPrettyTime(activePrintJob.timeTotal) : "" - width:base.width + width: base.width visible: activePrinter != null } diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index e04d8607da..e3107ea7f8 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -64,11 +64,11 @@ Rectangle function getPrettyTime(time) { - var hours = Math.round(time / 3600) + var hours = Math.floor(time / 3600) time -= hours * 3600 - var minutes = Math.round(time / 60); + var minutes = Math.floor(time / 60); time -= minutes * 60 - var seconds = Math.round(time); + var seconds = Math.floor(time); var finalTime = strPadLeft(hours, "0", 2) + ':' + strPadLeft(minutes,'0',2)+ ':' + strPadLeft(seconds,'0',2); return finalTime; From e30d15ab666087f6f8411196060a056b592a24ec Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 9 Mar 2018 14:02:15 +0100 Subject: [PATCH 75/85] Implement getCfgVersion Mostly copied from the implementation in the 3.0 to 3.1 upgrade. Contributes to issue CURA-5054. --- .../VersionUpgrade32to33.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py index 64eded8b2e..6f7dbeccef 100644 --- a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py +++ b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py @@ -9,9 +9,23 @@ from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this. ## Upgrades configurations from the state they were in at version 3.2 to the # state they should be in at version 3.3. class VersionUpgrade32to33(VersionUpgrade): - ## Gets the version number from a CFG file. - def getCfgVersion(self, serialized): - raise NotImplementedError("This has not yet been implemented.") + ## Gets the version number from a CFG file in Uranium's 3.2 format. + # + # Since the format may change, this is implemented for the 3.2 format only + # and needs to be included in the version upgrade system rather than + # globally in Uranium. + # + # \param serialised The serialised form of a CFG file. + # \return The version number stored in the CFG file. + # \raises ValueError The format of the version number in the file is + # incorrect. + # \raises KeyError The format of the file is incorrect. + def getCfgVersion(self, serialised): + parser = configparser.ConfigParser(interpolation = None) + parser.read_string(serialised) + format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised. + setting_version = int(parser.get("metadata", "setting_version", fallback = 0)) + return format_version * 1000000 + setting_version ## Upgrades a quality container to the new format. def upgradeQuality(self, serialized, filename): From a40be0c64a919707d4c3b723d09560e9d8acc16f Mon Sep 17 00:00:00 2001 From: Andreea Scorojitu Date: Fri, 9 Mar 2018 14:25:35 +0100 Subject: [PATCH 76/85] Add default_build_plate to expert view, CURA-5032 The default build plate settings is now added in Expert view in SettingsVisibility --- resources/preset_setting_visibility_groups/expert.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/preset_setting_visibility_groups/expert.cfg b/resources/preset_setting_visibility_groups/expert.cfg index e180b831d8..d6989f8b26 100644 --- a/resources/preset_setting_visibility_groups/expert.cfg +++ b/resources/preset_setting_visibility_groups/expert.cfg @@ -103,6 +103,7 @@ material_print_temperature_layer_0 material_initial_print_temperature material_final_print_temperature material_extrusion_cool_down_speed +default_material_bed_temperature material_bed_temperature material_bed_temperature_layer_0 material_diameter From de72dd3455218a240acd0bbf4c25b423c6cc77ff Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 9 Mar 2018 14:26:14 +0100 Subject: [PATCH 77/85] Fix material update upon gcode flavour change CURA-5060 --- cura/Machines/MaterialManager.py | 4 +- cura/Settings/MachineManager.py | 6 ++- .../MachineSettingsAction.py | 43 ++++++++++--------- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/cura/Machines/MaterialManager.py b/cura/Machines/MaterialManager.py index 89b3a76f9a..7c205c0084 100644 --- a/cura/Machines/MaterialManager.py +++ b/cura/Machines/MaterialManager.py @@ -357,10 +357,10 @@ class MaterialManager(QObject): else: return None - def getDefaultMaterial(self, global_stack: "GlobalStack", extruder_variant_name: str) -> Optional["MaterialNode"]: + def getDefaultMaterial(self, global_stack: "GlobalStack", extruder_variant_name: Optional[str]) -> Optional["MaterialNode"]: node = None machine_definition = global_stack.definition - if parseBool(machine_definition.getMetaDataEntry("has_materials", False)): + if parseBool(global_stack.getMetaDataEntry("has_materials", False)): material_diameter = machine_definition.getProperty("material_diameter", "value") if isinstance(material_diameter, SettingFunction): material_diameter = material_diameter(global_stack) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 1b3f01170e..144f495997 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -905,11 +905,13 @@ class MachineManager(QObject): def _setMaterial(self, position, container_node = None): if container_node: self._global_container_stack.extruders[position].material = container_node.getContainer() + root_material_id = container_node.metadata["base_file"] + root_material_name = container_node.getContainer().getName() else: self._global_container_stack.extruders[position].material = self._empty_material_container + root_material_id = None + root_material_name = None # The _current_root_material_id is used in the MaterialMenu to see which material is selected - root_material_id = container_node.metadata["base_file"] - root_material_name = container_node.getContainer().getName() if root_material_id != self._current_root_material_id[position]: self._current_root_material_id[position] = root_material_id self._current_root_material_name[position] = root_material_name diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index baa0639d3f..bac4af69cc 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -26,6 +26,8 @@ class MachineSettingsAction(MachineAction): super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings")) self._qml_url = "MachineSettingsAction.qml" + self._application = Application.getInstance() + self._global_container_stack = None from cura.Settings.CuraContainerStack import _ContainerIndexes @@ -34,16 +36,16 @@ class MachineSettingsAction(MachineAction): self._container_registry = ContainerRegistry.getInstance() self._container_registry.containerAdded.connect(self._onContainerAdded) self._container_registry.containerRemoved.connect(self._onContainerRemoved) - Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) + self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged) self._empty_container = self._container_registry.getEmptyInstanceContainer() - self._backend = Application.getInstance().getBackend() + self._backend = self._application.getBackend() def _onContainerAdded(self, container): # Add this action as a supported action to all machine definitions if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": - Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) + self._application.getMachineActionManager().addSupportedAction(container.getId(), self.getKey()) def _onContainerRemoved(self, container): # Remove definition_changes containers when a stack is removed @@ -61,11 +63,11 @@ class MachineSettingsAction(MachineAction): # Make sure there is a definition_changes container to store the machine settings definition_changes_container = self._global_container_stack.definitionChanges if definition_changes_container == self._empty_container: - definition_changes_container = CuraStackBuilder.createDefinitionChangesContainer( - self._global_container_stack, self._global_container_stack.getName() + "_settings") + CuraStackBuilder.createDefinitionChangesContainer(self._global_container_stack, + self._global_container_stack.getName() + "_settings") # Notify the UI in which container to store the machine settings data - from cura.Settings.CuraContainerStack import CuraContainerStack, _ContainerIndexes + from cura.Settings.CuraContainerStack import _ContainerIndexes container_index = _ContainerIndexes.DefinitionChanges if container_index != self._container_index: @@ -107,13 +109,13 @@ class MachineSettingsAction(MachineAction): def setMachineExtruderCount(self, extruder_count): # Note: this method was in this class before, but since it's quite generic and other plugins also need it # it was moved to the machine manager instead. Now this method just calls the machine manager. - Application.getInstance().getMachineManager().setActiveMachineExtruderCount(extruder_count) + self._application.getMachineManager().setActiveMachineExtruderCount(extruder_count) @pyqtSlot() def forceUpdate(self): # Force rebuilding the build volume by reloading the global container stack. # This is a bit of a hack, but it seems quick enough. - Application.getInstance().globalContainerStackChanged.emit() + self._application.globalContainerStackChanged.emit() @pyqtSlot() def updateHasMaterialsMetadata(self): @@ -126,9 +128,11 @@ class MachineSettingsAction(MachineAction): # In other words: only continue for the UM2 (extended), but not for the UM2+ return - stacks = ExtruderManager.getInstance().getExtruderStacks() + machine_manager = self._application.getMachineManager() + extruder_positions = list(self._global_container_stack.extruders.keys()) has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode" + material_node = None if has_materials: if "has_materials" in self._global_container_stack.getMetaData(): self._global_container_stack.setMetaDataEntry("has_materials", True) @@ -136,26 +140,23 @@ class MachineSettingsAction(MachineAction): self._global_container_stack.addMetaDataEntry("has_materials", True) # Set the material container for each extruder to a sane default - for stack in stacks: - material_container = stack.material - if material_container == self._empty_container: - machine_approximate_diameter = str(round(self._global_container_stack.getProperty("material_diameter", "value"))) - search_criteria = { "type": "material", "definition": "fdmprinter", "id": self._global_container_stack.getMetaDataEntry("preferred_material"), "approximate_diameter": machine_approximate_diameter} - materials = self._container_registry.findInstanceContainers(**search_criteria) - if materials: - stack.material = materials[0] + material_manager = self._application.getMaterialManager() + #material_manager.initialize() + material_node = material_manager.getDefaultMaterial(self._global_container_stack, None) + else: # The metadata entry is stored in an ini, and ini files are parsed as strings only. # Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False. if "has_materials" in self._global_container_stack.getMetaData(): self._global_container_stack.removeMetaDataEntry("has_materials") - for stack in stacks: - stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer() + # set materials + for position in extruder_positions: + machine_manager.setMaterial(position, material_node) - Application.getInstance().globalContainerStackChanged.emit() + self._application.globalContainerStackChanged.emit() @pyqtSlot(int) def updateMaterialForDiameter(self, extruder_position: int): # Updates the material container to a material that matches the material diameter set for the printer - Application.getInstance().getExtruderManager().updateMaterialForDiameter(extruder_position) + self._application.getExtruderManager().updateMaterialForDiameter(extruder_position) From 48dae7b6c79dc426692cb7e2ffb4c40a1d907f09 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 9 Mar 2018 14:35:37 +0100 Subject: [PATCH 78/85] Remove commented code CURA-5060 --- plugins/MachineSettingsAction/MachineSettingsAction.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index bac4af69cc..a97f3b463f 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -141,7 +141,6 @@ class MachineSettingsAction(MachineAction): # Set the material container for each extruder to a sane default material_manager = self._application.getMaterialManager() - #material_manager.initialize() material_node = material_manager.getDefaultMaterial(self._global_container_stack, None) else: From 8c7e413038c0f1af6612b8e8d33b47d489e2ba3a Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 9 Mar 2018 14:37:41 +0100 Subject: [PATCH 79/85] Remove unnecessary setGlobalContainerStack() in project loading CURA-5073 --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index ac61016de7..7201df65e4 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -709,7 +709,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # function is running on the main thread (Qt thread), although those "changed" signals have been emitted, but # they won't take effect until this function is done. # To solve this, we schedule _updateActiveMachine() for later so it will have the latest data. - Application.getInstance().setGlobalContainerStack(global_stack) self._updateActiveMachine(global_stack) # Load all the nodes / meshdata of the workspace From fb814b6519764c6f94cfe9c330b81db884449368 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 9 Mar 2018 14:54:08 +0100 Subject: [PATCH 80/85] Implement upgrade for quality changes extruder metadata The metadata used to be the ID of the extruder. Now it's the position of the extruder stack. Contributes to issue CURA-5054. --- .../VersionUpgrade32to33.py | 71 ++++++++++++++++++- .../VersionUpgrade32to33/__init__.py | 6 +- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py index 6f7dbeccef..2b8818177f 100644 --- a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py +++ b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py @@ -6,6 +6,53 @@ import io #To serialise the preference files afterwards. from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this. +## Mapping extruder definition IDs to the positions that they are in. +_EXTRUDER_TO_POSITION = { + "builder_premium_large_front": 1, + "builder_premium_large_rear": 0, + "builder_premium_medium_front": 1, + "builder_premium_medium_rear": 0, + "builder_premium_small_front": 1, + "builder_premium_small_rear": 0, + "cartesio_extruder_0": 0, + "cartesio_extruder_1": 1, + "cartesio_extruder_2": 2, + "cartesio_extruder_3": 3, + "custom_extruder_1": 0, #Warning, non-programmers are attempting to count here. + "custom_extruder_2": 1, + "custom_extruder_3": 2, + "custom_extruder_4": 3, + "custom_extruder_5": 4, + "custom_extruder_6": 5, + "custom_extruder_7": 6, + "custom_extruder_8": 7, + "hBp_extruder_left": 0, + "hBp_extruder_right": 1, + "makeit_dual_1st": 0, + "makeit_dual_2nd": 1, + "makeit_l_dual_1st": 0, + "makeit_l_dual_2nd": 1, + "ord_extruder_0": 0, + "ord_extruder_1": 1, + "ord_extruder_2": 2, + "ord_extruder_3": 3, + "ord_extruder_4": 4, + "punchtec_connect_xl_extruder_left": 0, + "punchtec_connect_xl_extruder_right": 1, + "raise3D_N2_dual_extruder_0": 0, + "raise3D_N2_dual_extruder_1": 1, + "raise3D_N2_plus_dual_extruder_0": 0, + "raise3D_N2_plus_dual_extruder_1": 1, + "ultimaker3_extended_extruder_left": 0, + "ultimaker3_extended_extruder_right": 1, + "ultimaker3_extruder_left": 0, + "ultimaker3_extruder_right": 1, + "ultimaker_original_dual_1st": 0, + "ultimaker_original_dual_2nd": 1, + "vertex_k8400_dual_1st": 0, + "vertex_k8400_dual_2nd": 1 +} + ## Upgrades configurations from the state they were in at version 3.2 to the # state they should be in at version 3.3. class VersionUpgrade32to33(VersionUpgrade): @@ -27,6 +74,24 @@ class VersionUpgrade32to33(VersionUpgrade): setting_version = int(parser.get("metadata", "setting_version", fallback = 0)) return format_version * 1000000 + setting_version - ## Upgrades a quality container to the new format. - def upgradeQuality(self, serialized, filename): - raise NotImplementedError("This has not yet been implemented.") \ No newline at end of file + ## Upgrades a quality changes container to the new format. + def upgradeQualityChanges(self, serialized, filename): + parser = configparser.ConfigParser(interpolation = None) + parser.read_string(serialized) + + #Extruder quality changes profiles have the extruder position instead of the ID of the extruder definition. + if "metadata" in parser and "extruder" in parser["metadata"]: #Only do this for extruder profiles. + extruder_id = parser["metadata"]["extruder"] + if extruder_id in _EXTRUDER_TO_POSITION: + extruder_position = _EXTRUDER_TO_POSITION[extruder_id] + else: + extruder_position = 0 #The user was using custom extruder definitions. He's on his own then. + + parser["metadata"]["extruder"] = str(extruder_position) + + #Update version number. + parser["general"]["version"] = "3" + + result = io.StringIO() + parser.write(result) + return [filename], [result.getvalue()] \ No newline at end of file diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py index dbcad86ac4..7465d79eb0 100644 --- a/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py @@ -8,11 +8,11 @@ upgrade = VersionUpgrade32to33.VersionUpgrade32to33() def getMetaData(): return { "version_upgrade": { - # From To Upgrade function - ("quality", 2000004): ("quality", 3000004, upgrade.upgradeQuality), + # From To Upgrade function + ("quality_changes", 2000004): ("quality", 3000004, upgrade.upgradeQualityChanges), }, "sources": { - "quality": { + "quality_changes": { "get_version": upgrade.getCfgVersion, "location": {"./quality"} } From c9905449ebbb306e84217d6066e65e4c90bb2c81 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 9 Mar 2018 14:54:01 +0100 Subject: [PATCH 81/85] Clean up MachineSettingsAction imports CURA-5060 --- plugins/MachineSettingsAction/MachineSettingsAction.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index a97f3b463f..26280a1f84 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -2,20 +2,16 @@ # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import pyqtProperty, pyqtSignal + +import UM.i18n from UM.FlameProfiler import pyqtSlot - -from cura.MachineAction import MachineAction - from UM.Application import Application from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.DefinitionContainer import DefinitionContainer -from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator -from UM.Logger import Logger -from cura.Settings.ExtruderManager import ExtruderManager +from cura.MachineAction import MachineAction from cura.Settings.CuraStackBuilder import CuraStackBuilder -import UM.i18n catalog = UM.i18n.i18nCatalog("cura") From 206d20c440af28afe62e188b372c2ae45fa5c541 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Fri, 9 Mar 2018 15:04:53 +0100 Subject: [PATCH 82/85] Fix empty definition_changes check in MachineSettingsAction CURA-5060 Should check for both "empty" and "empty_definition_changes". --- .../MachineSettingsAction.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index 26280a1f84..ded59bf934 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -34,10 +34,16 @@ class MachineSettingsAction(MachineAction): self._container_registry.containerRemoved.connect(self._onContainerRemoved) self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged) - self._empty_container = self._container_registry.getEmptyInstanceContainer() - self._backend = self._application.getBackend() + self._empty_definition_container_id_list = [] + + def _isEmptyDefinitionChanges(self, container_id: str): + if not self._empty_definition_container_id_list: + self._empty_definition_container_id_list = [self._application.empty_container.getId(), + self._application.empty_definition_changes_container.getId()] + return container_id in self._empty_definition_container_id_list + def _onContainerAdded(self, container): # Add this action as a supported action to all machine definitions if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine": @@ -46,19 +52,19 @@ class MachineSettingsAction(MachineAction): def _onContainerRemoved(self, container): # Remove definition_changes containers when a stack is removed if container.getMetaDataEntry("type") in ["machine", "extruder_train"]: - definition_changes_container = container.definitionChanges - if definition_changes_container == self._empty_container: + definition_changes_id = container.definitionChanges.getId() + if self._isEmptyDefinitionChanges(definition_changes_id): return - self._container_registry.removeContainer(definition_changes_container.getId()) + self._container_registry.removeContainer(definition_changes_id) def _reset(self): if not self._global_container_stack: return # Make sure there is a definition_changes container to store the machine settings - definition_changes_container = self._global_container_stack.definitionChanges - if definition_changes_container == self._empty_container: + definition_changes_id = self._global_container_stack.definitionChanges.getId() + if self._isEmptyDefinitionChanges(definition_changes_id): CuraStackBuilder.createDefinitionChangesContainer(self._global_container_stack, self._global_container_stack.getName() + "_settings") From 2962e0e282705ba3440f5aea6fc2e11b779d39e2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 9 Mar 2018 15:20:20 +0100 Subject: [PATCH 83/85] Upgrade to quality_changes rather than quality Oops. Contributes to issue CURA-5054. --- plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py index 7465d79eb0..146ebbe95b 100644 --- a/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py +++ b/plugins/VersionUpgrade/VersionUpgrade32to33/__init__.py @@ -9,7 +9,7 @@ def getMetaData(): return { "version_upgrade": { # From To Upgrade function - ("quality_changes", 2000004): ("quality", 3000004, upgrade.upgradeQualityChanges), + ("quality_changes", 2000004): ("quality_changes", 3000004, upgrade.upgradeQualityChanges), }, "sources": { "quality_changes": { From 5caa92cf7a38493544797ebe3faee9bf2c290e7b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 9 Mar 2018 15:21:45 +0100 Subject: [PATCH 84/85] Update current version for quality changes We now need to upgrade up to version 3000004. Contributes to issue CURA-5054. --- cura/CuraApplication.py | 12 +++++++----- cura/Machines/QualityChangesGroup.py | 2 ++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 438e8ffed7..9f37f09681 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1,5 +1,6 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. + #Type hinting. from typing import Dict @@ -55,6 +56,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.SettingFunction import SettingFunction from cura.Settings.MachineNameValidator import MachineNameValidator +from cura.Machines.QualityChangesGroup import QualityChangesGroup from cura.Machines.Models.BuildPlateModel import BuildPlateModel from cura.Machines.Models.NozzleModel import NozzleModel from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel @@ -207,11 +209,11 @@ class CuraApplication(QtApplication): UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions( { - ("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"), - ("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"), - ("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"), - ("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"), - ("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"), + ("quality_changes", QualityChangesGroup.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"), + ("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"), + ("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"), + ("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"), + ("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"), ("definition_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.DefinitionChangesContainer, "application/x-uranium-instancecontainer"), } ) diff --git a/cura/Machines/QualityChangesGroup.py b/cura/Machines/QualityChangesGroup.py index f8de3d2011..c24a547e8c 100644 --- a/cura/Machines/QualityChangesGroup.py +++ b/cura/Machines/QualityChangesGroup.py @@ -7,6 +7,8 @@ from .QualityGroup import QualityGroup class QualityChangesGroup(QualityGroup): + ## The file format version of quality changes. + Version = 3 def __init__(self, name: str, quality_type: str, parent = None): super().__init__(name, quality_type, parent) From e18b1cde55b23615faaff6cc0c4546fe59da2eab Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Fri, 9 Mar 2018 15:37:30 +0100 Subject: [PATCH 85/85] Store extruder position under 'position' instead of 'extruder' Otherwise it thinks it is still an extruder ID. Contributes to issue CURA-5054. --- .../VersionUpgrade32to33/VersionUpgrade32to33.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py index 2b8818177f..df22d6bf32 100644 --- a/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py +++ b/plugins/VersionUpgrade/VersionUpgrade32to33/VersionUpgrade32to33.py @@ -87,7 +87,8 @@ class VersionUpgrade32to33(VersionUpgrade): else: extruder_position = 0 #The user was using custom extruder definitions. He's on his own then. - parser["metadata"]["extruder"] = str(extruder_position) + parser["metadata"]["position"] = str(extruder_position) + del parser["metadata"]["extruder"] #Update version number. parser["general"]["version"] = "3"