|
@@ -3,7 +3,9 @@
|
|
|
|
|
|
import configparser
|
|
|
from io import StringIO
|
|
|
+from threading import Lock
|
|
|
import zipfile
|
|
|
+from typing import Dict, Any
|
|
|
|
|
|
from UM.Application import Application
|
|
|
from UM.Logger import Logger
|
|
@@ -13,15 +15,50 @@ from UM.Workspace.WorkspaceWriter import WorkspaceWriter
|
|
|
from UM.i18n import i18nCatalog
|
|
|
catalog = i18nCatalog("cura")
|
|
|
|
|
|
-from cura.Utils.Threading import call_on_qt_thread
|
|
|
+from .PCBDialog import PCBDialog
|
|
|
+from .ThreeMFWriter import ThreeMFWriter
|
|
|
+from .SettingsExportModel import SettingsExportModel
|
|
|
+from .SettingsExportGroup import SettingsExportGroup
|
|
|
+
|
|
|
+USER_SETTINGS_PATH = "Cura/user-settings.json"
|
|
|
|
|
|
|
|
|
class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
|
|
def __init__(self):
|
|
|
super().__init__()
|
|
|
+ self._main_thread_lock = Lock()
|
|
|
+ self._success = False
|
|
|
+ self._export_model = None
|
|
|
+ self._stream = None
|
|
|
+ self._nodes = None
|
|
|
+ self._mode = None
|
|
|
+ self._config_dialog = None
|
|
|
|
|
|
- @call_on_qt_thread
|
|
|
- def write(self, stream, nodes, mode=WorkspaceWriter.OutputMode.BinaryMode):
|
|
|
+ def _preWrite(self):
|
|
|
+ is_pcb = False
|
|
|
+ if hasattr(self._stream, 'name'):
|
|
|
+ # This only works with local file, but we don't want remote PCB files yet
|
|
|
+ is_pcb = self._stream.name.endswith('.pcb')
|
|
|
+
|
|
|
+ if is_pcb:
|
|
|
+ self._config_dialog = PCBDialog()
|
|
|
+ self._config_dialog.finished.connect(self._onPCBConfigFinished)
|
|
|
+ self._config_dialog.show()
|
|
|
+ else:
|
|
|
+ self._doWrite()
|
|
|
+
|
|
|
+ def _onPCBConfigFinished(self, accepted: bool):
|
|
|
+ if accepted:
|
|
|
+ self._export_model = self._config_dialog.getModel()
|
|
|
+ self._doWrite()
|
|
|
+ else:
|
|
|
+ self._main_thread_lock.release()
|
|
|
+
|
|
|
+ def _doWrite(self):
|
|
|
+ self._write()
|
|
|
+ self._main_thread_lock.release()
|
|
|
+
|
|
|
+ def _write(self):
|
|
|
application = Application.getInstance()
|
|
|
machine_manager = application.getMachineManager()
|
|
|
|
|
@@ -30,24 +67,24 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
|
|
if not mesh_writer: # We need to have the 3mf mesh writer, otherwise we can't save the entire workspace
|
|
|
self.setInformation(catalog.i18nc("@error:zip", "3MF Writer plug-in is corrupt."))
|
|
|
Logger.error("3MF Writer class is unavailable. Can't write workspace.")
|
|
|
- return False
|
|
|
+ return
|
|
|
|
|
|
global_stack = machine_manager.activeMachine
|
|
|
if global_stack is None:
|
|
|
- self.setInformation(catalog.i18nc("@error", "There is no workspace yet to write. Please add a printer first."))
|
|
|
+ self.setInformation(
|
|
|
+ catalog.i18nc("@error", "There is no workspace yet to write. Please add a printer first."))
|
|
|
Logger.error("Tried to write a 3MF workspace before there was a global stack.")
|
|
|
- return False
|
|
|
+ return
|
|
|
|
|
|
# Indicate that the 3mf mesh writer should not close the archive just yet (we still need to add stuff to it).
|
|
|
mesh_writer.setStoreArchive(True)
|
|
|
- if not mesh_writer.write(stream, nodes, mode):
|
|
|
+ if not mesh_writer.write(self._stream, self._nodes, self._mode, self._export_model):
|
|
|
self.setInformation(mesh_writer.getInformation())
|
|
|
- return False
|
|
|
+ return
|
|
|
|
|
|
archive = mesh_writer.getArchive()
|
|
|
if archive is None: # This happens if there was no mesh data to write.
|
|
|
- archive = zipfile.ZipFile(stream, "w", compression = zipfile.ZIP_DEFLATED)
|
|
|
-
|
|
|
+ archive = zipfile.ZipFile(self._stream, "w", compression=zipfile.ZIP_DEFLATED)
|
|
|
|
|
|
try:
|
|
|
# Add global container stack data to the archive.
|
|
@@ -62,15 +99,21 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
|
|
self._writeContainerToArchive(extruder_stack, archive)
|
|
|
for container in extruder_stack.getContainers():
|
|
|
self._writeContainerToArchive(container, archive)
|
|
|
+
|
|
|
+ # Write user settings data
|
|
|
+ if self._export_model is not None:
|
|
|
+ user_settings_data = self._getUserSettings(self._export_model)
|
|
|
+ ThreeMFWriter._storeMetadataJson(user_settings_data, archive, USER_SETTINGS_PATH)
|
|
|
except PermissionError:
|
|
|
self.setInformation(catalog.i18nc("@error:zip", "No permission to write the workspace here."))
|
|
|
Logger.error("No permission to write workspace to this stream.")
|
|
|
- return False
|
|
|
+ return
|
|
|
|
|
|
# Write preferences to archive
|
|
|
- original_preferences = Application.getInstance().getPreferences() #Copy only the preferences that we use to the workspace.
|
|
|
+ original_preferences = Application.getInstance().getPreferences() # Copy only the preferences that we use to the workspace.
|
|
|
temp_preferences = Preferences()
|
|
|
- for preference in {"general/visible_settings", "cura/active_mode", "cura/categories_expanded", "metadata/setting_version"}:
|
|
|
+ for preference in {"general/visible_settings", "cura/active_mode", "cura/categories_expanded",
|
|
|
+ "metadata/setting_version"}:
|
|
|
temp_preferences.addPreference(preference, None)
|
|
|
temp_preferences.setValue(preference, original_preferences.getValue(preference))
|
|
|
preferences_string = StringIO()
|
|
@@ -81,7 +124,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
|
|
|
|
|
# Save Cura version
|
|
|
version_file = zipfile.ZipInfo("Cura/version.ini")
|
|
|
- version_config_parser = configparser.ConfigParser(interpolation = None)
|
|
|
+ version_config_parser = configparser.ConfigParser(interpolation=None)
|
|
|
version_config_parser.add_section("versions")
|
|
|
version_config_parser.set("versions", "cura_version", application.getVersion())
|
|
|
version_config_parser.set("versions", "build_type", application.getBuildType())
|
|
@@ -98,13 +141,37 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
|
|
except PermissionError:
|
|
|
self.setInformation(catalog.i18nc("@error:zip", "No permission to write the workspace here."))
|
|
|
Logger.error("No permission to write workspace to this stream.")
|
|
|
- return False
|
|
|
+ return
|
|
|
except EnvironmentError as e:
|
|
|
self.setInformation(catalog.i18nc("@error:zip", str(e)))
|
|
|
- Logger.error("EnvironmentError when writing workspace to this stream: {err}".format(err = str(e)))
|
|
|
- return False
|
|
|
+ Logger.error("EnvironmentError when writing workspace to this stream: {err}".format(err=str(e)))
|
|
|
+ return
|
|
|
mesh_writer.setStoreArchive(False)
|
|
|
- return True
|
|
|
+
|
|
|
+ self._success = True
|
|
|
+
|
|
|
+ def write(self, stream, nodes, mode=WorkspaceWriter.OutputMode.BinaryMode):
|
|
|
+ self._success = False
|
|
|
+ self._export_model = None
|
|
|
+ self._stream = stream
|
|
|
+ self._nodes = nodes
|
|
|
+ self._mode = mode
|
|
|
+ self._config_dialog = None
|
|
|
+
|
|
|
+ self._main_thread_lock.acquire()
|
|
|
+ # Export is done in main thread because it may require a few asynchronous configuration steps
|
|
|
+ Application.getInstance().callLater(self._preWrite)
|
|
|
+ self._main_thread_lock.acquire() # Block until lock has been released, meaning the config+write is over
|
|
|
+
|
|
|
+ self._main_thread_lock.release()
|
|
|
+
|
|
|
+ self._export_model = None
|
|
|
+ self._stream = None
|
|
|
+ self._nodes = None
|
|
|
+ self._mode = None
|
|
|
+ self._config_dialog = None
|
|
|
+
|
|
|
+ return self._success
|
|
|
|
|
|
@staticmethod
|
|
|
def _writePluginMetadataToArchive(archive: zipfile.ZipFile) -> None:
|
|
@@ -165,4 +232,27 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
|
|
|
archive.writestr(file_in_archive, serialized_data)
|
|
|
except (FileNotFoundError, EnvironmentError):
|
|
|
Logger.error("File became inaccessible while writing to it: {archive_filename}".format(archive_filename = archive.fp.name))
|
|
|
- return
|
|
|
+ return
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def _getUserSettings(model: SettingsExportModel) -> Dict[str, Dict[str, Any]]:
|
|
|
+ user_settings = {}
|
|
|
+
|
|
|
+ for group in model.settingsGroups:
|
|
|
+ category = ''
|
|
|
+ if group.category == SettingsExportGroup.Category.Global:
|
|
|
+ category = 'global'
|
|
|
+ elif group.category == SettingsExportGroup.Category.Extruder:
|
|
|
+ category = f"extruder_{group.extruder_index}"
|
|
|
+
|
|
|
+ if len(category) > 0:
|
|
|
+ settings_values = {}
|
|
|
+ stack = group.stack
|
|
|
+
|
|
|
+ for setting in group.settings:
|
|
|
+ if setting.selected:
|
|
|
+ settings_values[setting.id] = stack.getProperty(setting.id, "value")
|
|
|
+
|
|
|
+ user_settings[category] = settings_values
|
|
|
+
|
|
|
+ return user_settings
|