From 25eb1ff342da66bc6c177c472138321629c16abf Mon Sep 17 00:00:00 2001 From: fieldOfView Date: Sat, 3 Feb 2018 22:09:40 +0100 Subject: [PATCH 01/23] Fix the detection of multiextruder replacement patterns Since #3068, replacement patterns can take the form of {setting_nr, extruder_nr}. This form was not being recognised when determining if CuraEngine should prepend its own preheat sequence. --- plugins/CuraEngineBackend/StartSliceJob.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 9e3621c782..0ece5e202a 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -5,6 +5,7 @@ import numpy from string import Formatter from enum import IntEnum import time +import re from UM.Job import Job from UM.Application import Application @@ -337,10 +338,12 @@ class StartSliceJob(Job): # Pre-compute material material_bed_temp_prepend and material_print_temp_prepend start_gcode = settings["machine_start_gcode"] - bed_temperature_settings = {"material_bed_temperature", "material_bed_temperature_layer_0"} - settings["material_bed_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in bed_temperature_settings)) - print_temperature_settings = {"material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"} - settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_temperature_settings)) + bed_temperature_settings = ["material_bed_temperature", "material_bed_temperature_layer_0"] + pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(bed_temperature_settings) # match {setting} as well as {setting, extruder_nr} + settings["material_bed_temp_prepend"] = re.search(pattern, start_gcode) == None + print_temperature_settings = ["material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"] + pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(print_temperature_settings) # match {setting} as well as {setting, extruder_nr} + settings["material_print_temp_prepend"] = re.search(pattern, start_gcode) == None # Replace the setting tokens in start and end g-code. # Use values from the first used extruder by default so we get the expected temperatures From 1d946085d375d28299297836673c06051daf6372 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Jan 2018 13:40:47 +0100 Subject: [PATCH 02/23] Make crash dialog available before Application starts CURA-4895 --- cura/CrashHandler.py | 14 +++++++------- cura/CuraApplication.py | 11 +++++++---- cura_app.py | 36 ++++++++++++++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 0c6740f740..8709461da3 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -1,7 +1,6 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import sys import platform import traceback import faulthandler @@ -14,7 +13,7 @@ import ssl import urllib.request import urllib.error -from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QCoreApplication +from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox from UM.Application import Application @@ -49,10 +48,11 @@ fatal_exception_types = [ class CrashHandler: crash_url = "https://stats.ultimaker.com/api/cura" - def __init__(self, exception_type, value, tb): + def __init__(self, exception_type, value, tb, has_started = True): self.exception_type = exception_type self.value = value self.traceback = tb + self.has_started = has_started self.dialog = None # Don't create a QDialog before there is a QApplication # While we create the GUI, the information will be stored for sending afterwards @@ -67,10 +67,6 @@ class CrashHandler: if not CuraDebugMode and exception_type not in fatal_exception_types: return - application = QCoreApplication.instance() - if not application: - sys.exit(1) - self.dialog = QDialog() self._createDialog() @@ -79,6 +75,7 @@ class CrashHandler: self.dialog.setMinimumWidth(640) self.dialog.setMinimumHeight(640) self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report")) + self.dialog.finished.connect(self._close) layout = QVBoxLayout(self.dialog) @@ -89,6 +86,9 @@ class CrashHandler: layout.addWidget(self._userDescriptionWidget()) layout.addWidget(self._buttonsWidget()) + def _close(self): + os._exit(1) + def _messageWidget(self): label = QLabel() label.setText(catalog.i18nc("@label crash message", """

A fatal error has occurred. Please send us this Crash Report to fix the problem

diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 8df9f01e91..301215005e 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1,5 +1,4 @@ # Copyright (c) 2017 Ultimaker B.V. -# Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtNetwork import QLocalServer from PyQt5.QtNetwork import QLocalSocket @@ -113,6 +112,8 @@ class CuraApplication(QtApplication): # changes of the settings. SettingVersion = 4 + Created = False + class ResourceTypes: QmlFiles = Resources.UserType + 1 Firmware = Resources.UserType + 2 @@ -256,7 +257,7 @@ class CuraApplication(QtApplication): self._center_after_select = False self._camera_animation = None self._cura_actions = None - self._started = False + self.started = False self._message_box_callback = None self._message_box_callback_arguments = [] @@ -409,6 +410,8 @@ class CuraApplication(QtApplication): self.getCuraSceneController().setActiveBuildPlate(0) # Initialize + CuraApplication.Created = True + def _onEngineCreated(self): self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) @@ -503,7 +506,7 @@ class CuraApplication(QtApplication): # # Note that the AutoSave plugin also calls this method. def saveSettings(self): - if not self._started: # Do not do saving during application start + if not self.started: # Do not do saving during application start return ContainerRegistry.getInstance().saveDirtyContainers() @@ -732,7 +735,7 @@ class CuraApplication(QtApplication): for file_name in self._open_file_queue: #Open all the files that were queued up while plug-ins were loading. self._openFile(file_name) - self._started = True + self.started = True self.exec_() diff --git a/cura_app.py b/cura_app.py index b9afb9bbcc..624228b715 100755 --- a/cura_app.py +++ b/cura_app.py @@ -71,8 +71,40 @@ if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is u def exceptHook(hook_type, value, traceback): from cura.CrashHandler import CrashHandler - _crash_handler = CrashHandler(hook_type, value, traceback) - _crash_handler.show() + from cura.CuraApplication import CuraApplication + has_started = False + if CuraApplication.Created: + has_started = CuraApplication.getInstance().started + + # + # When the exception hook is triggered, the QApplication may not have been initialized yet. In this case, we don't + # have an QApplication to handle the event loop, which is required by the Crash Dialog. + # The flag "CuraApplication.Created" is set to True when CuraApplication finishes its constructor call. + # + # Before the "started" flag is set to True, the Qt event loop has not started yet. The event loop is a blocking + # call to the QApplication.exec_(). In this case, we need to: + # 1. Remove all scheduled events so no more unnecessary events will be processed, such as loading the main dialog, + # loading the machine, etc. + # 2. Start the Qt event loop with exec_() and show the Crash Dialog. + # + # If the application has finished its initialization and was running fine, and then something causes a crash, + # we run the old routine to show the Crash Dialog. + # + from PyQt5.Qt import QApplication + if CuraApplication.Created: + _crash_handler = CrashHandler(hook_type, value, traceback, has_started) + if not has_started: + CuraApplication.getInstance().removePostedEvents(None) + _crash_handler.show() + sys.exit(CuraApplication.getInstance().exec_()) + else: + _crash_handler.show() + sys.exit(1) + else: + application = QApplication(sys.argv) + _crash_handler = CrashHandler(hook_type, value, traceback, has_started) + _crash_handler.dialog.show() + sys.exit(application.exec_()) if not known_args["debug"]: sys.excepthook = exceptHook From 6bbc18d51e788dad525934963a8765f9758ff810 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Jan 2018 15:52:20 +0100 Subject: [PATCH 03/23] Make an extra early crash dialog CURA-4895 An early crash dialog showing options to backup the current user data and reset the configuration files. --- cura/CrashHandler.py | 130 ++++++++++++++++++++++++++++++++++++++-- cura/CuraApplication.py | 1 - cura_app.py | 2 +- 3 files changed, 125 insertions(+), 8 deletions(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 8709461da3..58f172e5a9 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -12,9 +12,10 @@ import json import ssl import urllib.request import urllib.error +import shutil from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR -from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton from UM.Application import Application from UM.Logger import Logger @@ -67,15 +68,120 @@ class CrashHandler: if not CuraDebugMode and exception_type not in fatal_exception_types: return + self._send_report_checkbox = None + self.early_crash_dialog = self._createEarlyCrashDialog() self.dialog = QDialog() self._createDialog() + def _createEarlyCrashDialog(self): + dialog = QDialog() + dialog.setMinimumWidth(500) + dialog.setMinimumHeight(170) + dialog.setWindowTitle(catalog.i18nc("@title:window", "Cura Crashed")) + dialog.finished.connect(self._closeEarlyCrashDialog) + + layout = QVBoxLayout(dialog) + + 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.

+

Please send us this Crash Report to fix the problem.

+ """)) + label.setWordWrap(True) + layout.addWidget(label) + + # "send report" check box and show details + self._send_report_checkbox = QCheckBox(catalog.i18nc("@action:button", "Send crash report to Ultimaker"), dialog) + self._send_report_checkbox.setChecked(True) + + show_details_button = QPushButton(catalog.i18nc("@action:button", "Show detailed crash report"), dialog) + show_details_button.setMaximumWidth(180) + show_details_button.clicked.connect(self._showDetailedReport) + + layout.addWidget(self._send_report_checkbox) + layout.addWidget(show_details_button) + + # "backup and start clean" and "close" buttons + buttons = QDialogButtonBox() + buttons.addButton(QDialogButtonBox.Close) + buttons.addButton(catalog.i18nc("@action:button", "Backup and Reset Configuration"), QDialogButtonBox.AcceptRole) + buttons.rejected.connect(self._closeEarlyCrashDialog) + buttons.accepted.connect(self._backupAndStartClean) + + layout.addWidget(buttons) + + return dialog + + def _closeEarlyCrashDialog(self): + if self._send_report_checkbox.isChecked(): + self._sendCrashReport() + os._exit(1) + + def _backupAndStartClean(self): + # TODO: backup the current cura directories and create clean ones + from cura.CuraVersion import CuraVersion + from UM.Resources import Resources + # The early crash may happen before those information is set in Resources, so we need to set them here to + # make sure that Resources can find the correct place. + Resources.ApplicationIdentifier = "cura" + Resources.ApplicationVersion = CuraVersion + config_path = Resources.getConfigStoragePath() + data_path = Resources.getDataStoragePath() + cache_path = Resources.getCacheStoragePath() + + folders_to_backup = [] + folders_to_remove = [] # only cache folder needs to be removed + + folders_to_backup.append(config_path) + if data_path != config_path: + folders_to_backup.append(data_path) + + # Only remove the cache folder if it's not the same as data or config + if cache_path not in (config_path, data_path): + folders_to_remove.append(cache_path) + + for folder in folders_to_remove: + shutil.rmtree(folder, ignore_errors = True) + for folder in folders_to_backup: + base_name = os.path.basename(folder) + root_dir = os.path.dirname(folder) + + idx = 0 + file_name = base_name + "_" + str(time.time()) + zip_file_path = os.path.join(root_dir, file_name + ".zip") + while os.path.exists(zip_file_path): + idx += 1 + file_name = base_name + "_" + str(time.time()) + "_" + idx + zip_file_path = os.path.join(root_dir, file_name + ".zip") + try: + # remove the .zip extension because make_archive() adds it + zip_file_path = zip_file_path[:-4] + shutil.make_archive(zip_file_path, "zip", root_dir = root_dir, base_dir = base_name) + + # remove the folder only when the backup is successful + shutil.rmtree(folder) + # create an empty folder so Resources will not try to copy the old ones + os.makedirs(folder, 0o0755, exist_ok=True) + + except Exception as e: + Logger.logException("e", "Failed to backup [%s] to file [%s]", folder, zip_file_path) + if not self.has_started: + print("Failed to backup [%s] to file [%s]: %s", folder, zip_file_path, e) + + self.early_crash_dialog.close() + + def _showDetailedReport(self): + self.dialog.exec_() + ## Creates a modal dialog. def _createDialog(self): self.dialog.setMinimumWidth(640) self.dialog.setMinimumHeight(640) self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report")) - self.dialog.finished.connect(self._close) + # if the application has not fully started, this will be a detailed report dialog which should not + # close the application when it's closed. + if self.has_started: + self.dialog.finished.connect(self._close) layout = QVBoxLayout(self.dialog) @@ -249,9 +355,13 @@ class CrashHandler: def _buttonsWidget(self): buttons = QDialogButtonBox() buttons.addButton(QDialogButtonBox.Close) - buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.AcceptRole) + # Like above, this will be served as a separate detailed report dialog if the application has not yet been + # fully loaded. In this case, "send report" will be a check box in the early crash dialog, so there is no + # need for this extra button. + if self.has_started: + buttons.addButton(catalog.i18nc("@action:button", "Send report"), QDialogButtonBox.AcceptRole) + buttons.accepted.connect(self._sendCrashReport) buttons.rejected.connect(self.dialog.close) - buttons.accepted.connect(self._sendCrashReport) return buttons @@ -269,15 +379,23 @@ class CrashHandler: kwoptions["context"] = ssl._create_unverified_context() Logger.log("i", "Sending crash report info to [%s]...", self.crash_url) + if not self.has_started: + print("Sending crash report info to [%s]...\n" % self.crash_url) try: f = urllib.request.urlopen(self.crash_url, **kwoptions) Logger.log("i", "Sent crash report info.") + if not self.has_started: + print("Sent crash report info.\n") f.close() - except urllib.error.HTTPError: + except urllib.error.HTTPError as e: Logger.logException("e", "An HTTP error occurred while trying to send crash report") - except Exception: # We don't want any exception to cause problems + if not self.has_started: + print("An HTTP error occurred while trying to send crash report: %s" % e) + except Exception as e: # We don't want any exception to cause problems Logger.logException("e", "An exception occurred while trying to send crash report") + if not self.has_started: + print("An exception occurred while trying to send crash report: %s" % e) os._exit(1) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 301215005e..81956c04f1 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -134,7 +134,6 @@ class CuraApplication(QtApplication): stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished def __init__(self, **kwargs): - # this list of dir names will be used by UM to detect an old cura directory for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]: Resources.addExpectedDirNameInData(dir_name) diff --git a/cura_app.py b/cura_app.py index 624228b715..a4099881d1 100755 --- a/cura_app.py +++ b/cura_app.py @@ -103,7 +103,7 @@ def exceptHook(hook_type, value, traceback): else: application = QApplication(sys.argv) _crash_handler = CrashHandler(hook_type, value, traceback, has_started) - _crash_handler.dialog.show() + _crash_handler.early_crash_dialog.show() sys.exit(application.exec_()) if not known_args["debug"]: From aefe24222f05a183f88473648eca55f060bd54e3 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Jan 2018 16:04:11 +0100 Subject: [PATCH 04/23] Add a CLI flag to test early crash CURA-4895 --- cura/CuraApplication.py | 4 ++++ cura_app.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 81956c04f1..c39ce1d079 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -223,6 +223,10 @@ class CuraApplication(QtApplication): tray_icon_name = "cura-icon-32.png", **kwargs) + # FOR TESTING ONLY + if kwargs["parsed_command_line"].get("trigger_early_crash", False): + 1/0 + self.default_theme = "cura-light" self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png"))) diff --git a/cura_app.py b/cura_app.py index a4099881d1..79ef170ac9 100755 --- a/cura_app.py +++ b/cura_app.py @@ -16,6 +16,12 @@ parser.add_argument('--debug', default = False, help = "Turn on the debug mode by setting this option." ) +parser.add_argument('--trigger-early-crash', + dest = 'trigger_early_crash', + action = 'store_true', + default = False, + help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog." + ) known_args = vars(parser.parse_known_args()[0]) if not known_args["debug"]: From 30b1e74881bcfe85ffe035b24157f42918904861 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 30 Jan 2018 16:41:33 +0100 Subject: [PATCH 05/23] Fixes for early crash dialog CURA-4895 --- cura/CrashHandler.py | 8 +++++++- cura_app.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 58f172e5a9..3ddc878b40 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -13,6 +13,7 @@ import ssl import urllib.request import urllib.error import shutil +import sys from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton @@ -140,6 +141,11 @@ class CrashHandler: if cache_path not in (config_path, data_path): folders_to_remove.append(cache_path) + # need to close the redirected stdout and stderr files, otherwise, on Windows, those opened files will prevent + # the directory removal calls below. + sys.stdout.close() + sys.stderr.close() + for folder in folders_to_remove: shutil.rmtree(folder, ignore_errors = True) for folder in folders_to_backup: @@ -159,7 +165,7 @@ class CrashHandler: shutil.make_archive(zip_file_path, "zip", root_dir = root_dir, base_dir = base_name) # remove the folder only when the backup is successful - shutil.rmtree(folder) + shutil.rmtree(folder, ignore_errors = True) # create an empty folder so Resources will not try to copy the old ones os.makedirs(folder, 0o0755, exist_ok=True) diff --git a/cura_app.py b/cura_app.py index 79ef170ac9..4bab1d5a7b 100755 --- a/cura_app.py +++ b/cura_app.py @@ -101,7 +101,7 @@ def exceptHook(hook_type, value, traceback): _crash_handler = CrashHandler(hook_type, value, traceback, has_started) if not has_started: CuraApplication.getInstance().removePostedEvents(None) - _crash_handler.show() + _crash_handler.early_crash_dialog.show() sys.exit(CuraApplication.getInstance().exec_()) else: _crash_handler.show() From c2aa5fc2a2756404f8ded96218aeeea566d8cdfb Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 1 Feb 2018 13:12:23 +0100 Subject: [PATCH 06/23] Only initialise AutoSave after Application finishes its start up CURA-4895 --- cura/CuraApplication.py | 8 ++++++++ plugins/AutoSave/AutoSave.py | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c39ce1d079..e2efaeb517 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -106,6 +106,14 @@ if not MYPY: CuraDebugMode = False +# +# A global signal which is triggered when CuraApplication has finished its start up. +# This is used to initialise some plugins such as AutoSave which should only be started after the application passed +# the start up successfully. +# +applicationStarted = pyqtSignal() + + class CuraApplication(QtApplication): # SettingVersion represents the set of settings available in the machine/extruder definitions. # You need to make sure that this version number needs to be increased if there is any non-backwards-compatible diff --git a/plugins/AutoSave/AutoSave.py b/plugins/AutoSave/AutoSave.py index 331f328f2d..30c12b5424 100644 --- a/plugins/AutoSave/AutoSave.py +++ b/plugins/AutoSave/AutoSave.py @@ -9,6 +9,7 @@ from UM.Application import Application from UM.Resources import Resources from UM.Logger import Logger + class AutoSave(Extension): def __init__(self): super().__init__() @@ -16,8 +17,6 @@ class AutoSave(Extension): Preferences.getInstance().preferenceChanged.connect(self._triggerTimer) self._global_stack = None - Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) - self._onGlobalStackChanged() Preferences.getInstance().addPreference("cura/autosave_delay", 1000 * 10) @@ -28,6 +27,21 @@ class AutoSave(Extension): self._saving = False + # At this point, the Application instance has not finished its constructor call yet, so directly using something + # like Application.getInstance() is not correct. The initialisation now will only gets triggered after the + # application finishes its start up successfully. + from cura.CuraApplication import applicationStarted + applicationStarted.connect(self.initialize) + + def initialize(self): + from cura.CuraApplication import applicationStarted + applicationStarted.disconnect(self.initialize) + + Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) + self._onGlobalStackChanged() + + self._triggerTimer() + def _triggerTimer(self, *args): if not self._saving: self._change_timer.start() From 547baff239524301b134d1378db5bc125f55c67c Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 1 Feb 2018 13:17:37 +0100 Subject: [PATCH 07/23] Always show crash dialog CURA-4895 --- cura/CrashHandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 3ddc878b40..f49983143f 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -66,7 +66,7 @@ class CrashHandler: for part in line.rstrip("\n").split("\n"): Logger.log("c", part) - if not CuraDebugMode and exception_type not in fatal_exception_types: + if exception_type not in fatal_exception_types: return self._send_report_checkbox = None From b59eadce1c6b90984999aeae7c437e09607f33be Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 1 Feb 2018 16:39:56 +0100 Subject: [PATCH 08/23] Fix AutoSave crashing the early crash dialog CURA-4895 --- cura/CrashHandler.py | 5 ++++- cura/CuraApplication.py | 8 -------- cura_app.py | 1 + plugins/AutoSave/AutoSave.py | 17 +++++++++++++---- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index f49983143f..e5975b9b2b 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -66,7 +66,10 @@ class CrashHandler: for part in line.rstrip("\n").split("\n"): Logger.log("c", part) - if exception_type not in fatal_exception_types: + # If Cura has fully started, we only show fatal errors. + # If Cura has not fully started yet, we always show the early crash dialog. Otherwise, Cura will just crash + # without any information. + if has_started and exception_type not in fatal_exception_types: return self._send_report_checkbox = None diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index e2efaeb517..c39ce1d079 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -106,14 +106,6 @@ if not MYPY: CuraDebugMode = False -# -# A global signal which is triggered when CuraApplication has finished its start up. -# This is used to initialise some plugins such as AutoSave which should only be started after the application passed -# the start up successfully. -# -applicationStarted = pyqtSignal() - - class CuraApplication(QtApplication): # SettingVersion represents the set of settings available in the machine/extruder definitions. # You need to make sure that this version number needs to be increased if there is any non-backwards-compatible diff --git a/cura_app.py b/cura_app.py index 4bab1d5a7b..d1cf215fc5 100755 --- a/cura_app.py +++ b/cura_app.py @@ -108,6 +108,7 @@ def exceptHook(hook_type, value, traceback): sys.exit(1) else: application = QApplication(sys.argv) + application.removePostedEvents(None) _crash_handler = CrashHandler(hook_type, value, traceback, has_started) _crash_handler.early_crash_dialog.show() sys.exit(application.exec_()) diff --git a/plugins/AutoSave/AutoSave.py b/plugins/AutoSave/AutoSave.py index 30c12b5424..a549e6ff0a 100644 --- a/plugins/AutoSave/AutoSave.py +++ b/plugins/AutoSave/AutoSave.py @@ -30,12 +30,21 @@ class AutoSave(Extension): # At this point, the Application instance has not finished its constructor call yet, so directly using something # like Application.getInstance() is not correct. The initialisation now will only gets triggered after the # application finishes its start up successfully. - from cura.CuraApplication import applicationStarted - applicationStarted.connect(self.initialize) + self._init_timer = QTimer() + self._init_timer.setInterval(1000) + self._init_timer.setSingleShot(True) + self._init_timer.timeout.connect(self.initialize) + self._init_timer.start() def initialize(self): - from cura.CuraApplication import applicationStarted - applicationStarted.disconnect(self.initialize) + # only initialise if the application is created and has started + from cura.CuraApplication import CuraApplication + if not CuraApplication.Created: + self._init_timer.start() + return + if not CuraApplication.getInstance().started: + self._init_timer.start() + return Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() From e1bca1ca5d2c646b057cd2d1f24c2a5d94a43f33 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 5 Feb 2018 10:57:12 +0100 Subject: [PATCH 09/23] CURA-4895 Don't run autosave settings if application has not been created or if not started --- plugins/AutoSave/AutoSave.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/AutoSave/AutoSave.py b/plugins/AutoSave/AutoSave.py index a549e6ff0a..50b66885d4 100644 --- a/plugins/AutoSave/AutoSave.py +++ b/plugins/AutoSave/AutoSave.py @@ -67,6 +67,15 @@ class AutoSave(Extension): self._global_stack.containersChanged.connect(self._triggerTimer) def _onTimeout(self): + # only initialise if the application is created and has started + from cura.CuraApplication import CuraApplication + if not CuraApplication.Created: + self._change_timer.start() + return + if not CuraApplication.getInstance().started: + self._change_timer.start() + return + self._saving = True # To prevent the save process from triggering another autosave. Logger.log("d", "Autosaving preferences, instances and profiles") From 915bb2e450ad2a710c4cdb6815c663904dbb91f5 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 5 Feb 2018 11:16:30 +0100 Subject: [PATCH 10/23] CURA-4895 Connect timer signal just if CuraApplication has been previously created and started --- plugins/AutoSave/AutoSave.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/plugins/AutoSave/AutoSave.py b/plugins/AutoSave/AutoSave.py index 50b66885d4..5fdac502b5 100644 --- a/plugins/AutoSave/AutoSave.py +++ b/plugins/AutoSave/AutoSave.py @@ -23,7 +23,6 @@ class AutoSave(Extension): self._change_timer = QTimer() self._change_timer.setInterval(Preferences.getInstance().getValue("cura/autosave_delay")) self._change_timer.setSingleShot(True) - self._change_timer.timeout.connect(self._onTimeout) self._saving = False @@ -46,6 +45,7 @@ class AutoSave(Extension): self._init_timer.start() return + self._change_timer.timeout.connect(self._onTimeout) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() @@ -67,15 +67,6 @@ class AutoSave(Extension): self._global_stack.containersChanged.connect(self._triggerTimer) def _onTimeout(self): - # only initialise if the application is created and has started - from cura.CuraApplication import CuraApplication - if not CuraApplication.Created: - self._change_timer.start() - return - if not CuraApplication.getInstance().started: - self._change_timer.start() - return - self._saving = True # To prevent the save process from triggering another autosave. Logger.log("d", "Autosaving preferences, instances and profiles") From 383319d631c644d29c9b7a1e98ad1ed3b574f48c Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 5 Feb 2018 11:17:17 +0100 Subject: [PATCH 11/23] CURA-4895 Correctly format the date for the backup file --- cura/CrashHandler.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index e5975b9b2b..e90619aee5 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -144,23 +144,20 @@ class CrashHandler: if cache_path not in (config_path, data_path): folders_to_remove.append(cache_path) - # need to close the redirected stdout and stderr files, otherwise, on Windows, those opened files will prevent - # the directory removal calls below. - sys.stdout.close() - sys.stderr.close() - for folder in folders_to_remove: shutil.rmtree(folder, ignore_errors = True) for folder in folders_to_backup: base_name = os.path.basename(folder) root_dir = os.path.dirname(folder) + import datetime + date_now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") idx = 0 - file_name = base_name + "_" + str(time.time()) + file_name = base_name + "_" + date_now zip_file_path = os.path.join(root_dir, file_name + ".zip") while os.path.exists(zip_file_path): idx += 1 - file_name = base_name + "_" + str(time.time()) + "_" + idx + file_name = base_name + "_" + date_now + "_" + idx zip_file_path = os.path.join(root_dir, file_name + ".zip") try: # remove the .zip extension because make_archive() adds it From e64fb9c7622d149ad3fdcaf2fb55469fdf361bf1 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Mon, 5 Feb 2018 14:47:11 +0100 Subject: [PATCH 12/23] Splash closed on early crash --- cura_app.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cura_app.py b/cura_app.py index d1cf215fc5..d0170e5972 100755 --- a/cura_app.py +++ b/cura_app.py @@ -32,7 +32,7 @@ if not known_args["debug"]: return os.path.expanduser("~/.local/share/cura") elif Platform.isOSX(): return os.path.expanduser("~/Library/Logs/cura") - + if hasattr(sys, "frozen"): dirpath = get_cura_dir_path() os.makedirs(dirpath, exist_ok = True) @@ -110,6 +110,7 @@ def exceptHook(hook_type, value, traceback): application = QApplication(sys.argv) application.removePostedEvents(None) _crash_handler = CrashHandler(hook_type, value, traceback, has_started) + CuraApplication.getInstance().closeSplash() _crash_handler.early_crash_dialog.show() sys.exit(application.exec_()) From a85a720184ab3d7f8cd7097375d00ff73dd4a805 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Mon, 5 Feb 2018 17:17:46 +0100 Subject: [PATCH 13/23] CURA-4895 Close the splash screen when the early crash dialog appears. Increase the size of 'show detailed crash report button' --- cura/CrashHandler.py | 8 +++++--- cura_app.py | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index e90619aee5..a6b2373031 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -72,8 +72,10 @@ class CrashHandler: if has_started and exception_type not in fatal_exception_types: return - self._send_report_checkbox = None - self.early_crash_dialog = self._createEarlyCrashDialog() + if not has_started: + self._send_report_checkbox = None + self.early_crash_dialog = self._createEarlyCrashDialog() + self.dialog = QDialog() self._createDialog() @@ -99,7 +101,7 @@ class CrashHandler: self._send_report_checkbox.setChecked(True) show_details_button = QPushButton(catalog.i18nc("@action:button", "Show detailed crash report"), dialog) - show_details_button.setMaximumWidth(180) + show_details_button.setMaximumWidth(200) show_details_button.clicked.connect(self._showDetailedReport) layout.addWidget(self._send_report_checkbox) diff --git a/cura_app.py b/cura_app.py index d0170e5972..6c2d1c2937 100755 --- a/cura_app.py +++ b/cura_app.py @@ -99,18 +99,21 @@ def exceptHook(hook_type, value, traceback): from PyQt5.Qt import QApplication if CuraApplication.Created: _crash_handler = CrashHandler(hook_type, value, traceback, has_started) + if CuraApplication.splash is not None: + CuraApplication.splash.close() if not has_started: CuraApplication.getInstance().removePostedEvents(None) _crash_handler.early_crash_dialog.show() sys.exit(CuraApplication.getInstance().exec_()) else: _crash_handler.show() - sys.exit(1) else: application = QApplication(sys.argv) application.removePostedEvents(None) _crash_handler = CrashHandler(hook_type, value, traceback, has_started) - CuraApplication.getInstance().closeSplash() + # This means the QtApplication could be created and so the splash screen. Then Cura closes it + if CuraApplication.splash is not None: + CuraApplication.splash.close() _crash_handler.early_crash_dialog.show() sys.exit(application.exec_()) From 4438e064160c67ce4696d005353ed7be330eb5f3 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 5 Feb 2018 17:21:51 +0100 Subject: [PATCH 14/23] Remove TODO CURA-4895 --- cura/CrashHandler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index a6b2373031..5796375897 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -124,7 +124,7 @@ class CrashHandler: os._exit(1) def _backupAndStartClean(self): - # TODO: backup the current cura directories and create clean ones + # backup the current cura directories and create clean ones from cura.CuraVersion import CuraVersion from UM.Resources import Resources # The early crash may happen before those information is set in Resources, so we need to set them here to From 8ffd5442e7caf0bf873676cd913d57c7f4785490 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Tue, 6 Feb 2018 10:53:12 +0100 Subject: [PATCH 15/23] Fix indexing error in CrashHandler CURA-4895 --- cura/CrashHandler.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 5796375897..fa09db3e50 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -262,8 +262,8 @@ class CrashHandler: layout = QVBoxLayout() text_area = QTextEdit() - trace_dict = traceback.format_exception(self.exception_type, self.value, self.traceback) - trace = "".join(trace_dict) + trace_list = traceback.format_exception(self.exception_type, self.value, self.traceback) + trace = "".join(trace_list) text_area.setText(trace) text_area.setReadOnly(True) @@ -271,14 +271,28 @@ class CrashHandler: group.setLayout(layout) # Parsing all the information to fill the dictionary - summary = trace_dict[len(trace_dict)-1].rstrip("\n") - module = trace_dict[len(trace_dict)-2].rstrip("\n").split("\n") + summary = "" + if len(trace_list) >= 1: + summary = trace_list[len(trace_list)-1].rstrip("\n") + module = [""] + if len(trace_list) >= 2: + module = trace_list[len(trace_list)-2].rstrip("\n").split("\n") module_split = module[0].split(", ") - filepath = module_split[0].split("\"")[1] + + filepath_directory_split = module_split[0].split("\"") + filepath = "" + if len(filepath_directory_split) > 1: + filepath = filepath_directory_split[1] directory, filename = os.path.split(filepath) - line = int(module_split[1].lstrip("line ")) - function = module_split[2].lstrip("in ") - code = module[1].lstrip(" ") + line = "" + if len(module_split) > 1: + line = int(module_split[1].lstrip("line ")) + function = "" + if len(module_split) > 2: + function = module_split[2].lstrip("in ") + code = "" + if len(module) > 1: + code = module[1].lstrip(" ") # Using this workaround for a cross-platform path splitting split_path = [] From 399a63912e063a9c18517bf552da7e381ef4d175 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 6 Feb 2018 16:10:58 +0100 Subject: [PATCH 16/23] Improve error message with the trigger early crash option This way our logs are a bit more clear. --- cura/CuraApplication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index c39ce1d079..06bcdbfe05 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -225,7 +225,7 @@ class CuraApplication(QtApplication): # FOR TESTING ONLY if kwargs["parsed_command_line"].get("trigger_early_crash", False): - 1/0 + assert not "This crash is triggered by the trigger_early_crash command line argument." self.default_theme = "cura-light" From 45a4c569155494214d6353ce91b582f1bf15434a Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 6 Feb 2018 16:25:15 +0100 Subject: [PATCH 17/23] Fix brackets in Korean translation So that it won't crash. --- resources/i18n/ko_KR/cura.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/i18n/ko_KR/cura.po b/resources/i18n/ko_KR/cura.po index 1cabac3628..f63d7ba64f 100644 --- a/resources/i18n/ko_KR/cura.po +++ b/resources/i18n/ko_KR/cura.po @@ -1343,7 +1343,7 @@ msgstr "인터페이스로드 중 ..." #, python-format msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm." msgid "%(width).1f x %(depth).1f x %(height).1f mm" -msgstr "% (너비) .1f x % (깊이) .1f x % (높이) .1f mm" +msgstr "%(width).1f x %(depth).1f x %(height).1f mm" #: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417 #, python-brace-format From 1ab7eb64a1b022c978e4cc77585e51dd964cc80f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 6 Feb 2018 16:50:03 +0100 Subject: [PATCH 18/23] Create message before connecting to stack changed signal Because if the stack changed signal gets fired while the __init__ function is still running then you get a critical error. --- cura/BuildVolume.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 2567641cc9..672e01360a 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from cura.Settings.ExtruderManager import ExtruderManager @@ -73,6 +73,11 @@ class BuildVolume(SceneNode): self._adhesion_type = None self._platform = Platform(self) + self._build_volume_message = Message(catalog.i18nc("@info:status", + "The build volume height has been reduced due to the value of the" + " \"Print Sequence\" setting to prevent the gantry from colliding" + " with printed models."), title = catalog.i18nc("@info:title", "Build Volume")) + self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onStackChanged) self._onStackChanged() @@ -96,11 +101,6 @@ class BuildVolume(SceneNode): self._setting_change_timer.setSingleShot(True) self._setting_change_timer.timeout.connect(self._onSettingChangeTimerFinished) - self._build_volume_message = Message(catalog.i18nc("@info:status", - "The build volume height has been reduced due to the value of the" - " \"Print Sequence\" setting to prevent the gantry from colliding" - " with printed models."), title = catalog.i18nc("@info:title","Build Volume")) - # Must be after setting _build_volume_message, apparently that is used in getMachineManager. # activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality. # Therefore this works. From cb6b465ce7a1b4f3744aaa204a37746fa2a9947b Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 7 Feb 2018 09:55:22 +0100 Subject: [PATCH 19/23] CURA-4924 fix loading profiles with % in it --- cura/Settings/CuraContainerRegistry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 0b328cebda..dda78fc52f 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -711,7 +711,7 @@ class CuraContainerRegistry(ContainerRegistry): if not os.path.isfile(file_path): continue - parser = configparser.ConfigParser() + parser = configparser.ConfigParser(interpolation=None) try: parser.read([file_path]) except: From 98f925d50cc6e804470bb8c93a2a17940d9a3ce7 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 7 Feb 2018 10:00:02 +0100 Subject: [PATCH 20/23] CURA-4924 now creating the extruder container with metadata extruder --- cura/Settings/CuraContainerRegistry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index dda78fc52f..148ed6fe59 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -606,6 +606,7 @@ class CuraContainerRegistry(ContainerRegistry): extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName()) if extruder_quality_changes_container: quality_changes_id = extruder_quality_changes_container.getId() + extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId()) extruder_stack.setQualityChangesById(quality_changes_id) else: # if we still cannot find a quality changes container for the extruder, create a new one From a460804bbe4d59fdae192d13fba052ee01adeb96 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 7 Feb 2018 10:21:40 +0100 Subject: [PATCH 21/23] CURA-4924 always use interpolation = None for configparser, preventive --- plugins/3MFReader/ThreeMFWorkspaceReader.py | 10 +++++----- plugins/3MFWriter/ThreeMFWorkspaceWriter.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 01de4cc2c5..589ac6b845 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -122,7 +122,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # The default ContainerStack.deserialize() will connect signals, which is not desired in this case. # Since we know that the stack files are INI files, so we directly use the ConfigParser to parse them. serialized = archive.open(file_name).read().decode("utf-8") - stack_config = ConfigParser() + stack_config = ConfigParser(interpolation = None) stack_config.read_string(serialized) # sanity check @@ -303,7 +303,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): 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() + parser = configparser.ConfigParser(interpolation = None) parser.read_string(serialized) # The check should be done for the extruder stack that's associated with the existing global stack, @@ -407,7 +407,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() + extruder_config = configparser.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" @@ -564,7 +564,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): serialized = archive.open(instance_container_file).read().decode("utf-8") # HACK! we ignore "quality" and "variant" instance containers! - parser = configparser.ConfigParser() + 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) @@ -762,7 +762,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # 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() + 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() diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index 825259ad58..507274d355 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -57,7 +57,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): # Save Cura version version_file = zipfile.ZipInfo("Cura/version.ini") - version_config_parser = configparser.ConfigParser() + 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()) From 3a88c1ee887271dc29026ea2520203b3b57867f6 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 7 Feb 2018 11:27:02 +0100 Subject: [PATCH 22/23] CURA-4923 fix handle setting function in material_diameter --- cura/Settings/ExtruderManager.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index f9f0fbb401..35b5b1320b 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -519,13 +519,19 @@ class ExtruderManager(QObject): material_diameter = 0 material_approximate_diameter = str(round(material_diameter)) - machine_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value") - if not machine_diameter: + material_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value") + setting_provider = extruder_stack + if not material_diameter: if extruder_stack.definition.hasProperty("material_diameter", "value"): - machine_diameter = extruder_stack.definition.getProperty("material_diameter", "value") + material_diameter = extruder_stack.definition.getProperty("material_diameter", "value") else: - machine_diameter = global_stack.definition.getProperty("material_diameter", "value") - machine_approximate_diameter = str(round(machine_diameter)) + material_diameter = global_stack.definition.getProperty("material_diameter", "value") + setting_provider = global_stack + + if isinstance(material_diameter, SettingFunction): + material_diameter = material_diameter(setting_provider) + + machine_approximate_diameter = str(round(material_diameter)) if material_approximate_diameter != machine_approximate_diameter: Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.") From f29d07ce40c0b15b0b07630cb02dc1f832135bd3 Mon Sep 17 00:00:00 2001 From: Jack Ha Date: Wed, 7 Feb 2018 11:42:15 +0100 Subject: [PATCH 23/23] CURA-4923 corrected the material diameter of malyan_m200 --- resources/definitions/malyan_m200.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/malyan_m200.def.json b/resources/definitions/malyan_m200.def.json index 9aae3a5244..365b031c43 100644 --- a/resources/definitions/malyan_m200.def.json +++ b/resources/definitions/malyan_m200.def.json @@ -51,7 +51,7 @@ "machine_height": { "default_value": 120 }, "machine_heated_bed": { "default_value": true }, "machine_center_is_zero": { "default_value": false }, - "material_diameter": { "value": 1.75 }, + "material_diameter": { "default_value": 1.75 }, "machine_nozzle_size": { "default_value": 0.4, "minimum_value": 0.15