|
@@ -1,9 +1,7 @@
|
|
|
# Copyright (c) 2018 Ultimaker B.V.
|
|
|
# Cura is released under the terms of the LGPLv3 or higher.
|
|
|
-#Type hinting.
|
|
|
-from typing import Dict
|
|
|
|
|
|
-from PyQt5.QtCore import QObject
|
|
|
+from PyQt5.QtCore import QObject, QTimer
|
|
|
from PyQt5.QtNetwork import QLocalServer
|
|
|
from PyQt5.QtNetwork import QLocalSocket
|
|
|
|
|
@@ -59,18 +57,22 @@ from cura.Machines.Models.BuildPlateModel import BuildPlateModel
|
|
|
from cura.Machines.Models.NozzleModel import NozzleModel
|
|
|
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
|
|
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
|
|
|
-
|
|
|
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
|
|
-
|
|
|
from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
|
|
|
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
|
|
|
from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
|
|
|
+from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
|
|
+from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
|
|
|
+from cura.Machines.Models.MachineManagementModel import MachineManagementModel
|
|
|
+
|
|
|
+from cura.Machines.Models.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
|
|
|
+
|
|
|
+from cura.Machines.MachineErrorChecker import MachineErrorChecker
|
|
|
|
|
|
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
|
|
|
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
|
|
|
|
|
|
from cura.Machines.VariantManager import VariantManager
|
|
|
-from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
|
|
|
|
|
from . import PlatformPhysics
|
|
|
from . import BuildVolume
|
|
@@ -87,7 +89,6 @@ from cura.Settings.ExtruderManager import ExtruderManager
|
|
|
from cura.Settings.UserChangesModel import UserChangesModel
|
|
|
from cura.Settings.ExtrudersModel import ExtrudersModel
|
|
|
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
|
|
-from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
|
|
|
from cura.Settings.ContainerManager import ContainerManager
|
|
|
|
|
|
from cura.ObjectsModel import ObjectsModel
|
|
@@ -98,7 +99,6 @@ from PyQt5.QtGui import QColor, QIcon
|
|
|
from PyQt5.QtWidgets import QMessageBox
|
|
|
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
|
|
|
|
|
-from configparser import ConfigParser
|
|
|
import sys
|
|
|
import os.path
|
|
|
import numpy
|
|
@@ -138,15 +138,10 @@ class CuraApplication(QtApplication):
|
|
|
MachineStack = Resources.UserType + 7
|
|
|
ExtruderStack = Resources.UserType + 8
|
|
|
DefinitionChangesContainer = Resources.UserType + 9
|
|
|
+ SettingVisibilityPreset = Resources.UserType + 10
|
|
|
|
|
|
Q_ENUMS(ResourceTypes)
|
|
|
|
|
|
- # FIXME: This signal belongs to the MachineManager, but the CuraEngineBackend plugin requires on it.
|
|
|
- # Because plugins are initialized before the ContainerRegistry, putting this signal in MachineManager
|
|
|
- # will make it initialized before ContainerRegistry does, and it won't find the active machine, thus
|
|
|
- # Cura will always show the Add Machine Dialog upon start.
|
|
|
- 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"]:
|
|
@@ -191,6 +186,7 @@ class CuraApplication(QtApplication):
|
|
|
Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
|
|
|
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
|
|
|
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
|
|
|
+ Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility")
|
|
|
|
|
|
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
|
|
|
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes")
|
|
@@ -208,10 +204,10 @@ class CuraApplication(QtApplication):
|
|
|
UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
|
|
|
{
|
|
|
("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
|
|
|
- ("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"),
|
|
|
- ("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"),
|
|
|
- ("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"),
|
|
|
- ("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"),
|
|
|
+ ("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"),
|
|
|
+ ("extruder_train", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.ExtruderStack, "application/x-cura-extruderstack"),
|
|
|
+ ("preferences", Preferences.Version * 1000000 + self.SettingVersion): (Resources.Preferences, "application/x-uranium-preferences"),
|
|
|
+ ("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer"),
|
|
|
("definition_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.DefinitionChangesContainer, "application/x-uranium-instancecontainer"),
|
|
|
}
|
|
|
)
|
|
@@ -223,12 +219,15 @@ class CuraApplication(QtApplication):
|
|
|
self._machine_manager = None # This is initialized on demand.
|
|
|
self._extruder_manager = None
|
|
|
self._material_manager = None
|
|
|
+ self._quality_manager = None
|
|
|
self._object_manager = None
|
|
|
self._build_plate_model = None
|
|
|
self._multi_build_plate_model = None
|
|
|
+ self._setting_visibility_presets_model = None
|
|
|
self._setting_inheritance_manager = None
|
|
|
self._simple_mode_settings_manager = None
|
|
|
self._cura_scene_controller = None
|
|
|
+ self._machine_error_checker = None
|
|
|
|
|
|
self._additional_components = {} # Components to add to certain areas in the interface
|
|
|
|
|
@@ -285,10 +284,15 @@ class CuraApplication(QtApplication):
|
|
|
self._preferred_mimetype = ""
|
|
|
self._i18n_catalog = i18nCatalog("cura")
|
|
|
|
|
|
- self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
|
|
|
+ self._update_platform_activity_timer = QTimer()
|
|
|
+ self._update_platform_activity_timer.setInterval(500)
|
|
|
+ self._update_platform_activity_timer.setSingleShot(True)
|
|
|
+ self._update_platform_activity_timer.timeout.connect(self.updatePlatformActivity)
|
|
|
+
|
|
|
+ self.getController().getScene().sceneChanged.connect(self.updatePlatformActivityDelayed)
|
|
|
self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
|
|
|
self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
|
|
|
- self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivity)
|
|
|
+ self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivityDelayed)
|
|
|
|
|
|
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
|
|
|
Resources.addType(self.ResourceTypes.Firmware, "firmware")
|
|
@@ -375,20 +379,6 @@ class CuraApplication(QtApplication):
|
|
|
|
|
|
preferences.setDefault("local_file/last_used_type", "text/x-gcode")
|
|
|
|
|
|
- setting_visibily_preset_names = self.getVisibilitySettingPresetTypes()
|
|
|
- preferences.setDefault("general/visible_settings_preset", setting_visibily_preset_names)
|
|
|
-
|
|
|
- preset_setting_visibility_choice = Preferences.getInstance().getValue("general/preset_setting_visibility_choice")
|
|
|
-
|
|
|
- default_preset_visibility_group_name = "Basic"
|
|
|
- if preset_setting_visibility_choice == "" or preset_setting_visibility_choice is None:
|
|
|
- if preset_setting_visibility_choice not in setting_visibily_preset_names:
|
|
|
- preset_setting_visibility_choice = default_preset_visibility_group_name
|
|
|
-
|
|
|
- visible_settings = self.getVisibilitySettingPreset(settings_preset_name = preset_setting_visibility_choice)
|
|
|
- preferences.setDefault("general/visible_settings", visible_settings)
|
|
|
- preferences.setDefault("general/preset_setting_visibility_choice", preset_setting_visibility_choice)
|
|
|
-
|
|
|
self.applicationShuttingDown.connect(self.saveSettings)
|
|
|
self.engineCreatedSignal.connect(self._onEngineCreated)
|
|
|
|
|
@@ -404,91 +394,6 @@ class CuraApplication(QtApplication):
|
|
|
|
|
|
CuraApplication.Created = True
|
|
|
|
|
|
- @pyqtSlot(str, result = str)
|
|
|
- def getVisibilitySettingPreset(self, settings_preset_name) -> str:
|
|
|
- result = self._loadPresetSettingVisibilityGroup(settings_preset_name)
|
|
|
- formatted_preset_settings = self._serializePresetSettingVisibilityData(result)
|
|
|
-
|
|
|
- return formatted_preset_settings
|
|
|
-
|
|
|
- ## Serialise the given preset setting visibitlity group dictionary into a string which is concatenated by ";"
|
|
|
- #
|
|
|
- def _serializePresetSettingVisibilityData(self, settings_data: dict) -> str:
|
|
|
- result_string = ""
|
|
|
-
|
|
|
- for key in settings_data:
|
|
|
- result_string += key + ";"
|
|
|
- for value in settings_data[key]:
|
|
|
- result_string += value + ";"
|
|
|
-
|
|
|
- return result_string
|
|
|
-
|
|
|
- ## Load the preset setting visibility group with the given name
|
|
|
- #
|
|
|
- def _loadPresetSettingVisibilityGroup(self, visibility_preset_name) -> Dict[str, str]:
|
|
|
- preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups)
|
|
|
-
|
|
|
- result = {}
|
|
|
- right_preset_found = False
|
|
|
-
|
|
|
- for item in os.listdir(preset_dir):
|
|
|
- file_path = os.path.join(preset_dir, item)
|
|
|
- if not os.path.isfile(file_path):
|
|
|
- continue
|
|
|
-
|
|
|
- parser = ConfigParser(allow_no_value = True) # accept options without any value,
|
|
|
-
|
|
|
- try:
|
|
|
- parser.read([file_path])
|
|
|
-
|
|
|
- if not parser.has_option("general", "name"):
|
|
|
- continue
|
|
|
-
|
|
|
- if parser["general"]["name"] == visibility_preset_name:
|
|
|
- right_preset_found = True
|
|
|
- for section in parser.sections():
|
|
|
- if section == 'general':
|
|
|
- continue
|
|
|
- else:
|
|
|
- section_settings = []
|
|
|
- for option in parser[section].keys():
|
|
|
- section_settings.append(option)
|
|
|
-
|
|
|
- result[section] = section_settings
|
|
|
-
|
|
|
- if right_preset_found:
|
|
|
- break
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- Logger.log("e", "Failed to load setting visibility preset %s: %s", file_path, str(e))
|
|
|
-
|
|
|
- return result
|
|
|
-
|
|
|
- ## Check visibility setting preset folder and returns available types
|
|
|
- #
|
|
|
- def getVisibilitySettingPresetTypes(self):
|
|
|
- preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups)
|
|
|
- result = {}
|
|
|
-
|
|
|
- for item in os.listdir(preset_dir):
|
|
|
- file_path = os.path.join(preset_dir, item)
|
|
|
- if not os.path.isfile(file_path):
|
|
|
- continue
|
|
|
-
|
|
|
- parser = ConfigParser(allow_no_value=True) # accept options without any value,
|
|
|
-
|
|
|
- try:
|
|
|
- parser.read([file_path])
|
|
|
-
|
|
|
- if not parser.has_option("general", "name") and not parser.has_option("general", "weight"):
|
|
|
- continue
|
|
|
-
|
|
|
- result[parser["general"]["weight"]] = parser["general"]["name"]
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- Logger.log("e", "Failed to load setting preset %s: %s", file_path, str(e))
|
|
|
-
|
|
|
- return result
|
|
|
|
|
|
def _onEngineCreated(self):
|
|
|
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
|
@@ -546,27 +451,18 @@ class CuraApplication(QtApplication):
|
|
|
|
|
|
@pyqtSlot(str)
|
|
|
def discardOrKeepProfileChangesClosed(self, option):
|
|
|
+ global_stack = self.getGlobalContainerStack()
|
|
|
if option == "discard":
|
|
|
- global_stack = self.getGlobalContainerStack()
|
|
|
- for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
|
|
|
- extruder.getTop().clear()
|
|
|
- global_stack.getTop().clear()
|
|
|
+ for extruder in global_stack.extruders.values():
|
|
|
+ extruder.userChanges.clear()
|
|
|
+ global_stack.userChanges.clear()
|
|
|
|
|
|
# if the user decided to keep settings then the user settings should be re-calculated and validated for errors
|
|
|
# before slicing. To ensure that slicer uses right settings values
|
|
|
elif option == "keep":
|
|
|
- global_stack = self.getGlobalContainerStack()
|
|
|
- for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
|
|
|
- user_extruder_container = extruder.getTop()
|
|
|
- if user_extruder_container:
|
|
|
- user_extruder_container.update()
|
|
|
-
|
|
|
- user_global_container = global_stack.getTop()
|
|
|
- if user_global_container:
|
|
|
- user_global_container.update()
|
|
|
-
|
|
|
- # notify listeners that quality has changed (after user selected discard or keep)
|
|
|
- self.getMachineManager().activeQualityChanged.emit()
|
|
|
+ for extruder in global_stack.extruders.values():
|
|
|
+ extruder.userChanges.update()
|
|
|
+ global_stack.userChanges.update()
|
|
|
|
|
|
@pyqtSlot(int)
|
|
|
def messageBoxClosed(self, button):
|
|
@@ -602,8 +498,13 @@ class CuraApplication(QtApplication):
|
|
|
def getStaticVersion(cls):
|
|
|
return CuraVersion
|
|
|
|
|
|
+ ## Handle removing the unneeded plugins
|
|
|
+ # \sa PluginRegistry
|
|
|
+ def _removePlugins(self):
|
|
|
+ self._plugin_registry.removePlugins()
|
|
|
+
|
|
|
## Handle loading of all plugin types (and the backend explicitly)
|
|
|
- # \sa PluginRegistery
|
|
|
+ # \sa PluginRegistry
|
|
|
def _loadPlugins(self):
|
|
|
self._plugin_registry.addType("profile_reader", self._addProfileReader)
|
|
|
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
|
|
@@ -742,19 +643,28 @@ class CuraApplication(QtApplication):
|
|
|
self.preRun()
|
|
|
|
|
|
container_registry = ContainerRegistry.getInstance()
|
|
|
+
|
|
|
+ Logger.log("i", "Initializing variant manager")
|
|
|
self._variant_manager = VariantManager(container_registry)
|
|
|
self._variant_manager.initialize()
|
|
|
|
|
|
+ Logger.log("i", "Initializing material manager")
|
|
|
from cura.Machines.MaterialManager import MaterialManager
|
|
|
self._material_manager = MaterialManager(container_registry, parent = self)
|
|
|
self._material_manager.initialize()
|
|
|
|
|
|
+ Logger.log("i", "Initializing quality manager")
|
|
|
from cura.Machines.QualityManager import QualityManager
|
|
|
self._quality_manager = QualityManager(container_registry, parent = self)
|
|
|
self._quality_manager.initialize()
|
|
|
|
|
|
+ Logger.log("i", "Initializing machine manager")
|
|
|
self._machine_manager = MachineManager(self)
|
|
|
|
|
|
+ Logger.log("i", "Initializing machine error checker")
|
|
|
+ self._machine_error_checker = MachineErrorChecker(self)
|
|
|
+ self._machine_error_checker.initialize()
|
|
|
+
|
|
|
# Check if we should run as single instance or not
|
|
|
self._setUpSingleInstanceServer()
|
|
|
|
|
@@ -767,6 +677,11 @@ class CuraApplication(QtApplication):
|
|
|
self._print_information = PrintInformation.PrintInformation()
|
|
|
self._cura_actions = CuraActions.CuraActions(self)
|
|
|
|
|
|
+ # Initialize setting visibility presets model
|
|
|
+ self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self)
|
|
|
+ default_visibility_profile = self._setting_visibility_presets_model.getItem(0)
|
|
|
+ Preferences.getInstance().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
|
|
|
+
|
|
|
# Detect in which mode to run and execute that mode
|
|
|
if self.getCommandLineOption("headless", False):
|
|
|
self.runWithoutGUI()
|
|
@@ -780,8 +695,11 @@ class CuraApplication(QtApplication):
|
|
|
self._openFile(file_name)
|
|
|
|
|
|
self.started = True
|
|
|
+ self.initializationFinished.emit()
|
|
|
self.exec_()
|
|
|
|
|
|
+ initializationFinished = pyqtSignal()
|
|
|
+
|
|
|
## Run Cura without GUI elements and interaction (server mode).
|
|
|
def runWithoutGUI(self):
|
|
|
self._use_gui = False
|
|
@@ -846,6 +764,13 @@ class CuraApplication(QtApplication):
|
|
|
def hasGui(self):
|
|
|
return self._use_gui
|
|
|
|
|
|
+ @pyqtSlot(result = QObject)
|
|
|
+ def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel:
|
|
|
+ return self._setting_visibility_presets_model
|
|
|
+
|
|
|
+ def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
|
|
|
+ return self._machine_error_checker
|
|
|
+
|
|
|
def getMachineManager(self, *args) -> MachineManager:
|
|
|
if self._machine_manager is None:
|
|
|
self._machine_manager = MachineManager(self)
|
|
@@ -960,6 +885,7 @@ class CuraApplication(QtApplication):
|
|
|
qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
|
|
|
qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
|
|
|
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
|
|
|
+ qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
|
|
|
|
|
|
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
|
|
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
|
|
@@ -968,6 +894,7 @@ class CuraApplication(QtApplication):
|
|
|
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
|
|
|
|
|
|
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
|
|
+ qmlRegisterType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel")
|
|
|
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
|
|
|
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
|
|
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
|
|
@@ -1047,6 +974,10 @@ class CuraApplication(QtApplication):
|
|
|
def getSceneBoundingBoxString(self):
|
|
|
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
|
|
|
|
|
|
+ def updatePlatformActivityDelayed(self, node = None):
|
|
|
+ if node is not None and node.getMeshData() is not None:
|
|
|
+ self._update_platform_activity_timer.start()
|
|
|
+
|
|
|
## Update scene bounding box for current build plate
|
|
|
def updatePlatformActivity(self, node = None):
|
|
|
count = 0
|
|
@@ -1179,7 +1110,7 @@ class CuraApplication(QtApplication):
|
|
|
continue
|
|
|
if not node.getMeshData() and not node.callDecoration("isGroup"):
|
|
|
continue # Node that doesnt have a mesh and is not a group.
|
|
|
- if node.getParent() and node.getParent().callDecoration("isGroup"):
|
|
|
+ if node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent().callDecoration("isSliceable"):
|
|
|
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
|
|
if not node.isSelectable():
|
|
|
continue # i.e. node with layer data
|
|
@@ -1354,8 +1285,11 @@ class CuraApplication(QtApplication):
|
|
|
def reloadAll(self):
|
|
|
Logger.log("i", "Reloading all loaded mesh data.")
|
|
|
nodes = []
|
|
|
+ has_merged_nodes = False
|
|
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
|
|
- if not isinstance(node, CuraSceneNode) or not node.getMeshData():
|
|
|
+ if not isinstance(node, CuraSceneNode) or not node.getMeshData() :
|
|
|
+ if node.getName() == 'MergedMesh':
|
|
|
+ has_merged_nodes = True
|
|
|
continue
|
|
|
|
|
|
nodes.append(node)
|
|
@@ -1369,10 +1303,14 @@ class CuraApplication(QtApplication):
|
|
|
job = ReadMeshJob(file_name)
|
|
|
job._node = node
|
|
|
job.finished.connect(self._reloadMeshFinished)
|
|
|
+ if has_merged_nodes:
|
|
|
+ job.finished.connect(self.updateOriginOfMergedMeshes)
|
|
|
+
|
|
|
job.start()
|
|
|
else:
|
|
|
Logger.log("w", "Unable to reload data because we don't have a filename.")
|
|
|
|
|
|
+
|
|
|
## Get logging data of the backend engine
|
|
|
# \returns \type{string} Logging data
|
|
|
@pyqtSlot(result = str)
|
|
@@ -1442,6 +1380,58 @@ class CuraApplication(QtApplication):
|
|
|
|
|
|
# Use the previously found center of the group bounding box as the new location of the group
|
|
|
group_node.setPosition(group_node.getBoundingBox().center)
|
|
|
+ group_node.setName("MergedMesh") # add a specific name to destinguis this node
|
|
|
+
|
|
|
+
|
|
|
+ ## Updates origin position of all merged meshes
|
|
|
+ # \param jobNode \type{Job} empty object which passed which is required by JobQueue
|
|
|
+ def updateOriginOfMergedMeshes(self, jobNode):
|
|
|
+ group_nodes = []
|
|
|
+ for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
|
|
+ if isinstance(node, CuraSceneNode) and node.getName() == "MergedMesh":
|
|
|
+
|
|
|
+ #checking by name might be not enough, the merged mesh should has "GroupDecorator" decorator
|
|
|
+ for decorator in node.getDecorators():
|
|
|
+ if isinstance(decorator, GroupDecorator):
|
|
|
+ group_nodes.append(node)
|
|
|
+ break
|
|
|
+
|
|
|
+ for group_node in group_nodes:
|
|
|
+ meshes = [node.getMeshData() for node in group_node.getAllChildren() if node.getMeshData()]
|
|
|
+
|
|
|
+ # Compute the center of the objects
|
|
|
+ object_centers = []
|
|
|
+ # Forget about the translation that the original objects have
|
|
|
+ zero_translation = Matrix(data=numpy.zeros(3))
|
|
|
+ for mesh, node in zip(meshes, group_node.getChildren()):
|
|
|
+ transformation = node.getLocalTransformation()
|
|
|
+ transformation.setTranslation(zero_translation)
|
|
|
+ transformed_mesh = mesh.getTransformed(transformation)
|
|
|
+ center = transformed_mesh.getCenterPosition()
|
|
|
+ if center is not None:
|
|
|
+ object_centers.append(center)
|
|
|
+
|
|
|
+ if object_centers and len(object_centers) > 0:
|
|
|
+ middle_x = sum([v.x for v in object_centers]) / len(object_centers)
|
|
|
+ middle_y = sum([v.y for v in object_centers]) / len(object_centers)
|
|
|
+ middle_z = sum([v.z for v in object_centers]) / len(object_centers)
|
|
|
+ offset = Vector(middle_x, middle_y, middle_z)
|
|
|
+ else:
|
|
|
+ offset = Vector(0, 0, 0)
|
|
|
+
|
|
|
+ # Move each node to the same position.
|
|
|
+ for mesh, node in zip(meshes, group_node.getChildren()):
|
|
|
+ transformation = node.getLocalTransformation()
|
|
|
+ transformation.setTranslation(zero_translation)
|
|
|
+ transformed_mesh = mesh.getTransformed(transformation)
|
|
|
+
|
|
|
+ # Align the object around its zero position
|
|
|
+ # and also apply the offset to center it inside the group.
|
|
|
+ node.setPosition(-transformed_mesh.getZeroPosition() - offset)
|
|
|
+
|
|
|
+ # Use the previously found center of the group bounding box as the new location of the group
|
|
|
+ group_node.setPosition(group_node.getBoundingBox().center)
|
|
|
+
|
|
|
|
|
|
@pyqtSlot()
|
|
|
def groupSelected(self):
|
|
@@ -1457,6 +1447,12 @@ class CuraApplication(QtApplication):
|
|
|
group_node.setPosition(center)
|
|
|
group_node.setCenterPosition(center)
|
|
|
|
|
|
+ # Remove nodes that are directly parented to another selected node from the selection so they remain parented
|
|
|
+ selected_nodes = Selection.getAllSelectedObjects().copy()
|
|
|
+ for node in selected_nodes:
|
|
|
+ if node.getParent() in selected_nodes and not node.getParent().callDecoration("isGroup"):
|
|
|
+ Selection.remove(node)
|
|
|
+
|
|
|
# Move selected nodes into the group-node
|
|
|
Selection.applyOperation(SetParentOperation, group_node)
|
|
|
|
|
@@ -1475,6 +1471,10 @@ class CuraApplication(QtApplication):
|
|
|
group_parent = node.getParent()
|
|
|
children = node.getChildren().copy()
|
|
|
for child in children:
|
|
|
+ # Ungroup only 1 level deep
|
|
|
+ if child.getParent() != node:
|
|
|
+ continue
|
|
|
+
|
|
|
# Set the parent of the children to the parent of the group-node
|
|
|
op.addOperation(SetParentOperation(child, group_parent))
|
|
|
|
|
@@ -1604,6 +1604,8 @@ class CuraApplication(QtApplication):
|
|
|
fixed_nodes.append(node_)
|
|
|
arranger = Arrange.create(fixed_nodes = fixed_nodes)
|
|
|
min_offset = 8
|
|
|
+ default_extruder_position = self.getMachineManager().defaultExtruderPosition
|
|
|
+ default_extruder_id = self._global_container_stack.extruders[default_extruder_position].getId()
|
|
|
|
|
|
for original_node in nodes:
|
|
|
|
|
@@ -1669,6 +1671,8 @@ class CuraApplication(QtApplication):
|
|
|
|
|
|
op = AddSceneNodeOperation(node, scene.getRoot())
|
|
|
op.push()
|
|
|
+
|
|
|
+ node.callDecoration("setActiveExtruder", default_extruder_id)
|
|
|
scene.sceneChanged.emit(node)
|
|
|
|
|
|
self.fileCompleted.emit(filename)
|
|
@@ -1689,7 +1693,7 @@ class CuraApplication(QtApplication):
|
|
|
result = workspace_reader.preRead(file_path, show_dialog=False)
|
|
|
return result == WorkspaceReader.PreReadResult.accepted
|
|
|
except Exception as e:
|
|
|
- Logger.log("e", "Could not check file %s: %s", file_url, e)
|
|
|
+ Logger.logException("e", "Could not check file %s: %s", file_url)
|
|
|
return False
|
|
|
|
|
|
def _onContextMenuRequested(self, x: float, y: float) -> None:
|