mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 22:47:29 -06:00
Merge 3.2 into master
This commit is contained in:
commit
ffa4df6a06
12 changed files with 277 additions and 55 deletions
|
@ -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.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
|
@ -74,6 +74,11 @@ class BuildVolume(SceneNode):
|
||||||
self._adhesion_type = None
|
self._adhesion_type = None
|
||||||
self._platform = Platform(self)
|
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
|
self._global_container_stack = None
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._onStackChanged)
|
Application.getInstance().globalContainerStackChanged.connect(self._onStackChanged)
|
||||||
self._onStackChanged()
|
self._onStackChanged()
|
||||||
|
@ -97,11 +102,6 @@ class BuildVolume(SceneNode):
|
||||||
self._setting_change_timer.setSingleShot(True)
|
self._setting_change_timer.setSingleShot(True)
|
||||||
self._setting_change_timer.timeout.connect(self._onSettingChangeTimerFinished)
|
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.
|
# Must be after setting _build_volume_message, apparently that is used in getMachineManager.
|
||||||
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
|
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
|
||||||
# Therefore this works.
|
# Therefore this works.
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import sys
|
|
||||||
import platform
|
import platform
|
||||||
import traceback
|
import traceback
|
||||||
import faulthandler
|
import faulthandler
|
||||||
|
@ -13,9 +12,11 @@ import json
|
||||||
import ssl
|
import ssl
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.error
|
import urllib.error
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
|
||||||
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 PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit, QGroupBox, QCheckBox, QPushButton
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -49,10 +50,11 @@ fatal_exception_types = [
|
||||||
class CrashHandler:
|
class CrashHandler:
|
||||||
crash_url = "https://stats.ultimaker.com/api/cura"
|
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.exception_type = exception_type
|
||||||
self.value = value
|
self.value = value
|
||||||
self.traceback = tb
|
self.traceback = tb
|
||||||
|
self.has_started = has_started
|
||||||
self.dialog = None # Don't create a QDialog before there is a QApplication
|
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
|
# While we create the GUI, the information will be stored for sending afterwards
|
||||||
|
@ -64,21 +66,130 @@ class CrashHandler:
|
||||||
for part in line.rstrip("\n").split("\n"):
|
for part in line.rstrip("\n").split("\n"):
|
||||||
Logger.log("c", part)
|
Logger.log("c", part)
|
||||||
|
|
||||||
if not CuraDebugMode and 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
|
return
|
||||||
|
|
||||||
application = QCoreApplication.instance()
|
if not has_started:
|
||||||
if not application:
|
self._send_report_checkbox = None
|
||||||
sys.exit(1)
|
self.early_crash_dialog = self._createEarlyCrashDialog()
|
||||||
|
|
||||||
self.dialog = QDialog()
|
self.dialog = QDialog()
|
||||||
self._createDialog()
|
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", """<p><b>A fatal error has occurred.</p></b>
|
||||||
|
<p>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.</p>
|
||||||
|
<p>Please send us this Crash Report to fix the problem.</p>
|
||||||
|
"""))
|
||||||
|
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(200)
|
||||||
|
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):
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
date_now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
idx = 0
|
||||||
|
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 + "_" + date_now + "_" + 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, ignore_errors = True)
|
||||||
|
# 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.
|
## Creates a modal dialog.
|
||||||
def _createDialog(self):
|
def _createDialog(self):
|
||||||
self.dialog.setMinimumWidth(640)
|
self.dialog.setMinimumWidth(640)
|
||||||
self.dialog.setMinimumHeight(640)
|
self.dialog.setMinimumHeight(640)
|
||||||
self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))
|
self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))
|
||||||
|
# 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)
|
layout = QVBoxLayout(self.dialog)
|
||||||
|
|
||||||
|
@ -89,6 +200,9 @@ class CrashHandler:
|
||||||
layout.addWidget(self._userDescriptionWidget())
|
layout.addWidget(self._userDescriptionWidget())
|
||||||
layout.addWidget(self._buttonsWidget())
|
layout.addWidget(self._buttonsWidget())
|
||||||
|
|
||||||
|
def _close(self):
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
def _messageWidget(self):
|
def _messageWidget(self):
|
||||||
label = QLabel()
|
label = QLabel()
|
||||||
label.setText(catalog.i18nc("@label crash message", """<p><b>A fatal error has occurred. Please send us this Crash Report to fix the problem</p></b>
|
label.setText(catalog.i18nc("@label crash message", """<p><b>A fatal error has occurred. Please send us this Crash Report to fix the problem</p></b>
|
||||||
|
@ -148,8 +262,8 @@ class CrashHandler:
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
|
||||||
text_area = QTextEdit()
|
text_area = QTextEdit()
|
||||||
trace_dict = traceback.format_exception(self.exception_type, self.value, self.traceback)
|
trace_list = traceback.format_exception(self.exception_type, self.value, self.traceback)
|
||||||
trace = "".join(trace_dict)
|
trace = "".join(trace_list)
|
||||||
text_area.setText(trace)
|
text_area.setText(trace)
|
||||||
text_area.setReadOnly(True)
|
text_area.setReadOnly(True)
|
||||||
|
|
||||||
|
@ -157,14 +271,28 @@ class CrashHandler:
|
||||||
group.setLayout(layout)
|
group.setLayout(layout)
|
||||||
|
|
||||||
# Parsing all the information to fill the dictionary
|
# Parsing all the information to fill the dictionary
|
||||||
summary = trace_dict[len(trace_dict)-1].rstrip("\n")
|
summary = ""
|
||||||
module = trace_dict[len(trace_dict)-2].rstrip("\n").split("\n")
|
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(", ")
|
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)
|
directory, filename = os.path.split(filepath)
|
||||||
line = int(module_split[1].lstrip("line "))
|
line = ""
|
||||||
function = module_split[2].lstrip("in ")
|
if len(module_split) > 1:
|
||||||
code = module[1].lstrip(" ")
|
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
|
# Using this workaround for a cross-platform path splitting
|
||||||
split_path = []
|
split_path = []
|
||||||
|
@ -249,9 +377,13 @@ class CrashHandler:
|
||||||
def _buttonsWidget(self):
|
def _buttonsWidget(self):
|
||||||
buttons = QDialogButtonBox()
|
buttons = QDialogButtonBox()
|
||||||
buttons.addButton(QDialogButtonBox.Close)
|
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.rejected.connect(self.dialog.close)
|
||||||
buttons.accepted.connect(self._sendCrashReport)
|
|
||||||
|
|
||||||
return buttons
|
return buttons
|
||||||
|
|
||||||
|
@ -269,15 +401,23 @@ class CrashHandler:
|
||||||
kwoptions["context"] = ssl._create_unverified_context()
|
kwoptions["context"] = ssl._create_unverified_context()
|
||||||
|
|
||||||
Logger.log("i", "Sending crash report info to [%s]...", self.crash_url)
|
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:
|
try:
|
||||||
f = urllib.request.urlopen(self.crash_url, **kwoptions)
|
f = urllib.request.urlopen(self.crash_url, **kwoptions)
|
||||||
Logger.log("i", "Sent crash report info.")
|
Logger.log("i", "Sent crash report info.")
|
||||||
|
if not self.has_started:
|
||||||
|
print("Sent crash report info.\n")
|
||||||
f.close()
|
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")
|
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")
|
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)
|
os._exit(1)
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,8 @@ class CuraApplication(QtApplication):
|
||||||
# changes of the settings.
|
# changes of the settings.
|
||||||
SettingVersion = 4
|
SettingVersion = 4
|
||||||
|
|
||||||
|
Created = False
|
||||||
|
|
||||||
class ResourceTypes:
|
class ResourceTypes:
|
||||||
QmlFiles = Resources.UserType + 1
|
QmlFiles = Resources.UserType + 1
|
||||||
Firmware = Resources.UserType + 2
|
Firmware = Resources.UserType + 2
|
||||||
|
@ -136,7 +138,6 @@ class CuraApplication(QtApplication):
|
||||||
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
|
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
|
||||||
# this list of dir names will be used by UM to detect an old cura directory
|
# 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"]:
|
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
|
||||||
Resources.addExpectedDirNameInData(dir_name)
|
Resources.addExpectedDirNameInData(dir_name)
|
||||||
|
@ -227,6 +228,10 @@ class CuraApplication(QtApplication):
|
||||||
tray_icon_name = "cura-icon-32.png",
|
tray_icon_name = "cura-icon-32.png",
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
|
# FOR TESTING ONLY
|
||||||
|
if kwargs["parsed_command_line"].get("trigger_early_crash", False):
|
||||||
|
assert not "This crash is triggered by the trigger_early_crash command line argument."
|
||||||
|
|
||||||
self.default_theme = "cura-light"
|
self.default_theme = "cura-light"
|
||||||
|
|
||||||
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
|
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
|
||||||
|
@ -260,7 +265,7 @@ class CuraApplication(QtApplication):
|
||||||
self._center_after_select = False
|
self._center_after_select = False
|
||||||
self._camera_animation = None
|
self._camera_animation = None
|
||||||
self._cura_actions = None
|
self._cura_actions = None
|
||||||
self._started = False
|
self.started = False
|
||||||
|
|
||||||
self._message_box_callback = None
|
self._message_box_callback = None
|
||||||
self._message_box_callback_arguments = []
|
self._message_box_callback_arguments = []
|
||||||
|
@ -375,6 +380,8 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
|
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
|
||||||
|
|
||||||
|
CuraApplication.Created = True
|
||||||
|
|
||||||
@pyqtSlot(str, result = str)
|
@pyqtSlot(str, result = str)
|
||||||
def getVisibilitySettingPreset(self, settings_preset_name) -> str:
|
def getVisibilitySettingPreset(self, settings_preset_name) -> str:
|
||||||
result = self._loadPresetSettingVisibilityGroup(settings_preset_name)
|
result = self._loadPresetSettingVisibilityGroup(settings_preset_name)
|
||||||
|
@ -461,7 +468,6 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _onEngineCreated(self):
|
def _onEngineCreated(self):
|
||||||
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||||
|
|
||||||
|
@ -556,7 +562,7 @@ class CuraApplication(QtApplication):
|
||||||
#
|
#
|
||||||
# Note that the AutoSave plugin also calls this method.
|
# Note that the AutoSave plugin also calls this method.
|
||||||
def saveSettings(self):
|
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
|
return
|
||||||
|
|
||||||
ContainerRegistry.getInstance().saveDirtyContainers()
|
ContainerRegistry.getInstance().saveDirtyContainers()
|
||||||
|
@ -734,7 +740,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.
|
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._openFile(file_name)
|
||||||
|
|
||||||
self._started = True
|
self.started = True
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
## Run Cura without GUI elements and interaction (server mode).
|
## Run Cura without GUI elements and interaction (server mode).
|
||||||
|
|
|
@ -606,6 +606,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName())
|
extruder_quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine_quality_changes.getName())
|
||||||
if extruder_quality_changes_container:
|
if extruder_quality_changes_container:
|
||||||
quality_changes_id = extruder_quality_changes_container.getId()
|
quality_changes_id = extruder_quality_changes_container.getId()
|
||||||
|
extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId())
|
||||||
extruder_stack.setQualityChangesById(quality_changes_id)
|
extruder_stack.setQualityChangesById(quality_changes_id)
|
||||||
else:
|
else:
|
||||||
# if we still cannot find a quality changes container for the extruder, create a new one
|
# if we still cannot find a quality changes container for the extruder, create a new one
|
||||||
|
@ -711,7 +712,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
parser = configparser.ConfigParser()
|
parser = configparser.ConfigParser(interpolation=None)
|
||||||
try:
|
try:
|
||||||
parser.read([file_path])
|
parser.read([file_path])
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -519,13 +519,19 @@ class ExtruderManager(QObject):
|
||||||
material_diameter = 0
|
material_diameter = 0
|
||||||
|
|
||||||
material_approximate_diameter = str(round(material_diameter))
|
material_approximate_diameter = str(round(material_diameter))
|
||||||
machine_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value")
|
material_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value")
|
||||||
if not machine_diameter:
|
setting_provider = extruder_stack
|
||||||
|
if not material_diameter:
|
||||||
if extruder_stack.definition.hasProperty("material_diameter", "value"):
|
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:
|
else:
|
||||||
machine_diameter = global_stack.definition.getProperty("material_diameter", "value")
|
material_diameter = global_stack.definition.getProperty("material_diameter", "value")
|
||||||
machine_approximate_diameter = str(round(machine_diameter))
|
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:
|
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.")
|
Logger.log("i", "The the currently active material(s) do not match the diameter set for the printer. Finding alternatives.")
|
||||||
|
|
49
cura_app.py
49
cura_app.py
|
@ -16,6 +16,12 @@ parser.add_argument('--debug',
|
||||||
default = False,
|
default = False,
|
||||||
help = "Turn on the debug mode by setting this option."
|
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])
|
known_args = vars(parser.parse_known_args()[0])
|
||||||
|
|
||||||
if not known_args["debug"]:
|
if not known_args["debug"]:
|
||||||
|
@ -26,7 +32,7 @@ if not known_args["debug"]:
|
||||||
return os.path.expanduser("~/.local/share/cura")
|
return os.path.expanduser("~/.local/share/cura")
|
||||||
elif Platform.isOSX():
|
elif Platform.isOSX():
|
||||||
return os.path.expanduser("~/Library/Logs/cura")
|
return os.path.expanduser("~/Library/Logs/cura")
|
||||||
|
|
||||||
if hasattr(sys, "frozen"):
|
if hasattr(sys, "frozen"):
|
||||||
dirpath = get_cura_dir_path()
|
dirpath = get_cura_dir_path()
|
||||||
os.makedirs(dirpath, exist_ok = True)
|
os.makedirs(dirpath, exist_ok = True)
|
||||||
|
@ -71,8 +77,45 @@ if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is u
|
||||||
|
|
||||||
def exceptHook(hook_type, value, traceback):
|
def exceptHook(hook_type, value, traceback):
|
||||||
from cura.CrashHandler import CrashHandler
|
from cura.CrashHandler import CrashHandler
|
||||||
_crash_handler = CrashHandler(hook_type, value, traceback)
|
from cura.CuraApplication import CuraApplication
|
||||||
_crash_handler.show()
|
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 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()
|
||||||
|
else:
|
||||||
|
application = QApplication(sys.argv)
|
||||||
|
application.removePostedEvents(None)
|
||||||
|
_crash_handler = CrashHandler(hook_type, value, traceback, has_started)
|
||||||
|
# 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_())
|
||||||
|
|
||||||
if not known_args["debug"]:
|
if not known_args["debug"]:
|
||||||
sys.excepthook = exceptHook
|
sys.excepthook = exceptHook
|
||||||
|
|
|
@ -122,7 +122,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
# The default ContainerStack.deserialize() will connect signals, which is not desired in this case.
|
# 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.
|
# 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")
|
serialized = archive.open(file_name).read().decode("utf-8")
|
||||||
stack_config = ConfigParser()
|
stack_config = ConfigParser(interpolation = None)
|
||||||
stack_config.read_string(serialized)
|
stack_config.read_string(serialized)
|
||||||
|
|
||||||
# sanity check
|
# sanity check
|
||||||
|
@ -303,7 +303,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
if containers_found_dict["machine"] and not machine_conflict:
|
if containers_found_dict["machine"] and not machine_conflict:
|
||||||
for extruder_stack_file in extruder_stack_files:
|
for extruder_stack_file in extruder_stack_files:
|
||||||
serialized = archive.open(extruder_stack_file).read().decode("utf-8")
|
serialized = archive.open(extruder_stack_file).read().decode("utf-8")
|
||||||
parser = configparser.ConfigParser()
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
parser.read_string(serialized)
|
parser.read_string(serialized)
|
||||||
|
|
||||||
# The check should be done for the extruder stack that's associated with the existing global stack,
|
# 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.
|
## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack.
|
||||||
def _overrideExtruderStack(self, global_stack, extruder_file_content, extruder_stack_file):
|
def _overrideExtruderStack(self, global_stack, extruder_file_content, extruder_stack_file):
|
||||||
# Get extruder position first
|
# Get extruder position first
|
||||||
extruder_config = configparser.ConfigParser()
|
extruder_config = configparser.ConfigParser(interpolation = None)
|
||||||
extruder_config.read_string(extruder_file_content)
|
extruder_config.read_string(extruder_file_content)
|
||||||
if not extruder_config.has_option("metadata", "position"):
|
if not extruder_config.has_option("metadata", "position"):
|
||||||
msg = "Could not find 'metadata/position' in extruder stack file"
|
msg = "Could not find 'metadata/position' in extruder stack file"
|
||||||
|
@ -565,7 +565,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
serialized = archive.open(instance_container_file).read().decode("utf-8")
|
serialized = archive.open(instance_container_file).read().decode("utf-8")
|
||||||
|
|
||||||
# HACK! we ignore "quality" and "variant" instance containers!
|
# HACK! we ignore "quality" and "variant" instance containers!
|
||||||
parser = configparser.ConfigParser()
|
parser = configparser.ConfigParser(interpolation = None)
|
||||||
parser.read_string(serialized)
|
parser.read_string(serialized)
|
||||||
if not parser.has_option("metadata", "type"):
|
if not parser.has_option("metadata", "type"):
|
||||||
Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
|
Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
|
||||||
|
@ -763,7 +763,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
# deserialize() by setting the metadata, but in the case of ExtruderStack, deserialize()
|
# 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
|
# also does addExtruder() to its machine stack, so we have to make sure that it's pointing
|
||||||
# to the right machine BEFORE deserialization.
|
# 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.read_string(extruder_file_content)
|
||||||
extruder_config.set("metadata", "machine", global_stack_id_new)
|
extruder_config.set("metadata", "machine", global_stack_id_new)
|
||||||
tmp_string_io = io.StringIO()
|
tmp_string_io = io.StringIO()
|
||||||
|
|
|
@ -57,7 +57,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
||||||
|
|
||||||
# Save Cura version
|
# Save Cura version
|
||||||
version_file = zipfile.ZipInfo("Cura/version.ini")
|
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.add_section("versions")
|
||||||
version_config_parser.set("versions", "cura_version", Application.getInstance().getVersion())
|
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", "build_type", Application.getInstance().getBuildType())
|
||||||
|
|
|
@ -9,6 +9,7 @@ from UM.Application import Application
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
|
||||||
class AutoSave(Extension):
|
class AutoSave(Extension):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
@ -16,18 +17,40 @@ class AutoSave(Extension):
|
||||||
Preferences.getInstance().preferenceChanged.connect(self._triggerTimer)
|
Preferences.getInstance().preferenceChanged.connect(self._triggerTimer)
|
||||||
|
|
||||||
self._global_stack = None
|
self._global_stack = None
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
|
||||||
self._onGlobalStackChanged()
|
|
||||||
|
|
||||||
Preferences.getInstance().addPreference("cura/autosave_delay", 1000 * 10)
|
Preferences.getInstance().addPreference("cura/autosave_delay", 1000 * 10)
|
||||||
|
|
||||||
self._change_timer = QTimer()
|
self._change_timer = QTimer()
|
||||||
self._change_timer.setInterval(Preferences.getInstance().getValue("cura/autosave_delay"))
|
self._change_timer.setInterval(Preferences.getInstance().getValue("cura/autosave_delay"))
|
||||||
self._change_timer.setSingleShot(True)
|
self._change_timer.setSingleShot(True)
|
||||||
self._change_timer.timeout.connect(self._onTimeout)
|
|
||||||
|
|
||||||
self._saving = False
|
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.
|
||||||
|
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):
|
||||||
|
# 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
|
||||||
|
|
||||||
|
self._change_timer.timeout.connect(self._onTimeout)
|
||||||
|
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||||
|
self._onGlobalStackChanged()
|
||||||
|
|
||||||
|
self._triggerTimer()
|
||||||
|
|
||||||
def _triggerTimer(self, *args):
|
def _triggerTimer(self, *args):
|
||||||
if not self._saving:
|
if not self._saving:
|
||||||
self._change_timer.start()
|
self._change_timer.start()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import numpy
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
import time
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
@ -343,10 +344,12 @@ class StartSliceJob(Job):
|
||||||
|
|
||||||
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
|
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
|
||||||
start_gcode = settings["machine_start_gcode"]
|
start_gcode = settings["machine_start_gcode"]
|
||||||
bed_temperature_settings = {"material_bed_temperature", "material_bed_temperature_layer_0"}
|
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))
|
pattern = r"\{(%s)(,\s?\w+)?\}" % "|".join(bed_temperature_settings) # match {setting} as well as {setting, extruder_nr}
|
||||||
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_bed_temp_prepend"] = re.search(pattern, start_gcode) == None
|
||||||
settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_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"]
|
||||||
|
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.
|
# 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
|
# Use values from the first used extruder by default so we get the expected temperatures
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
"machine_height": { "default_value": 120 },
|
"machine_height": { "default_value": 120 },
|
||||||
"machine_heated_bed": { "default_value": true },
|
"machine_heated_bed": { "default_value": true },
|
||||||
"machine_center_is_zero": { "default_value": false },
|
"machine_center_is_zero": { "default_value": false },
|
||||||
"material_diameter": { "value": 1.75 },
|
"material_diameter": { "default_value": 1.75 },
|
||||||
"machine_nozzle_size": {
|
"machine_nozzle_size": {
|
||||||
"default_value": 0.4,
|
"default_value": 0.4,
|
||||||
"minimum_value": 0.15
|
"minimum_value": 0.15
|
||||||
|
|
|
@ -1343,7 +1343,7 @@ msgstr "인터페이스로드 중 ..."
|
||||||
#, python-format
|
#, python-format
|
||||||
msgctxt "@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm."
|
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"
|
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
|
#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1417
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue