mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-15 02:37:49 -06:00
Merge branch 'master' into feature_curaversion_appname
# Conflicts: # cura/CuraApplication.py
This commit is contained in:
commit
056655e584
1147 changed files with 62054 additions and 12466 deletions
|
@ -4,7 +4,7 @@
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import cast, TYPE_CHECKING, Optional, Callable
|
||||
from typing import cast, TYPE_CHECKING, Optional, Callable, List
|
||||
|
||||
import numpy
|
||||
|
||||
|
@ -51,6 +51,7 @@ from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
|
|||
from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
|
||||
from cura.Arranging.ShapeArray import ShapeArray
|
||||
from cura.MultiplyObjectsJob import MultiplyObjectsJob
|
||||
from cura.GlobalStacksModel import GlobalStacksModel
|
||||
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
|
||||
from cura.Operations.SetParentOperation import SetParentOperation
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||
|
@ -113,8 +114,11 @@ from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions
|
|||
|
||||
from cura.ObjectsModel import ObjectsModel
|
||||
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
|
||||
|
||||
from cura import ApplicationMetadata, UltimakerCloudAuthentication
|
||||
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
from UM.Decorators import override
|
||||
|
||||
|
@ -127,22 +131,12 @@ if TYPE_CHECKING:
|
|||
|
||||
numpy.seterr(all = "ignore")
|
||||
|
||||
try:
|
||||
from cura.CuraVersion import CuraAppName, CuraAppDisplayName, CuraVersion, CuraBuildType, CuraDebugMode, CuraSDKVersion # type: ignore
|
||||
except ImportError:
|
||||
CuraAppName = "cura"
|
||||
CuraAppDisplayName = "Ultimaker Cura"
|
||||
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
|
||||
CuraBuildType = ""
|
||||
CuraDebugMode = False
|
||||
CuraSDKVersion = "5.0.0"
|
||||
|
||||
|
||||
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
|
||||
# changes of the settings.
|
||||
SettingVersion = 5
|
||||
SettingVersion = 7
|
||||
|
||||
Created = False
|
||||
|
||||
|
@ -162,12 +156,12 @@ class CuraApplication(QtApplication):
|
|||
Q_ENUMS(ResourceTypes)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(name = CuraAppName,
|
||||
app_display_name = CuraAppDisplayName,
|
||||
version = CuraVersion,
|
||||
api_version = CuraSDKVersion,
|
||||
buildtype = CuraBuildType,
|
||||
is_debug_mode = CuraDebugMode,
|
||||
super().__init__(name = ApplicationMetadata.CuraAppName,
|
||||
app_display_name = ApplicationMetadata.CuraAppDisplayName,
|
||||
version = ApplicationMetadata.CuraVersion,
|
||||
api_version = ApplicationMetadata.CuraSDKVersion,
|
||||
buildtype = ApplicationMetadata.CuraBuildType,
|
||||
is_debug_mode = ApplicationMetadata.CuraDebugMode,
|
||||
tray_icon_name = "cura-icon-32.png",
|
||||
**kwargs)
|
||||
|
||||
|
@ -182,7 +176,6 @@ class CuraApplication(QtApplication):
|
|||
# Variables set from CLI
|
||||
self._files_to_open = []
|
||||
self._use_single_instance = False
|
||||
self._trigger_early_crash = False # For debug only
|
||||
|
||||
self._single_instance = None
|
||||
|
||||
|
@ -207,6 +200,8 @@ class CuraApplication(QtApplication):
|
|||
self._container_manager = None
|
||||
|
||||
self._object_manager = None
|
||||
self._extruders_model = None
|
||||
self._extruders_model_with_optional = None
|
||||
self._build_plate_model = None
|
||||
self._multi_build_plate_model = None
|
||||
self._setting_visibility_presets_model = None
|
||||
|
@ -261,6 +256,14 @@ class CuraApplication(QtApplication):
|
|||
from cura.CuraPackageManager import CuraPackageManager
|
||||
self._package_manager_class = CuraPackageManager
|
||||
|
||||
@pyqtProperty(str, constant=True)
|
||||
def ultimakerCloudApiRootUrl(self) -> str:
|
||||
return UltimakerCloudAuthentication.CuraCloudAPIRoot
|
||||
|
||||
@pyqtProperty(str, constant = True)
|
||||
def ultimakerCloudAccountRootUrl(self) -> str:
|
||||
return UltimakerCloudAuthentication.CuraCloudAccountAPIRoot
|
||||
|
||||
# Adds command line options to the command line parser. This should be called after the application is created and
|
||||
# before the pre-start.
|
||||
def addCommandLineOptions(self):
|
||||
|
@ -293,7 +296,10 @@ class CuraApplication(QtApplication):
|
|||
sys.exit(0)
|
||||
|
||||
self._use_single_instance = self._cli_args.single_instance
|
||||
self._trigger_early_crash = self._cli_args.trigger_early_crash
|
||||
# FOR TESTING ONLY
|
||||
if self._cli_args.trigger_early_crash:
|
||||
assert not "This crash is triggered by the trigger_early_crash command line argument."
|
||||
|
||||
for filename in self._cli_args.file:
|
||||
self._files_to_open.append(os.path.abspath(filename))
|
||||
|
||||
|
@ -429,7 +435,8 @@ class CuraApplication(QtApplication):
|
|||
def startSplashWindowPhase(self) -> None:
|
||||
super().startSplashWindowPhase()
|
||||
|
||||
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
|
||||
if not self.getIsHeadLess():
|
||||
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
|
||||
|
||||
self.setRequiredPlugins([
|
||||
# Misc.:
|
||||
|
@ -440,6 +447,7 @@ class CuraApplication(QtApplication):
|
|||
"XmlMaterialProfile", #Cura crashes without this one.
|
||||
"Toolbox", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back.
|
||||
"PrepareStage", #Cura is useless without this one since you can't load models.
|
||||
"PreviewStage", #This shows the list of the plugin views that are installed in Cura.
|
||||
"MonitorStage", #Major part of Cura's functionality.
|
||||
"LocalFileOutputDevice", #Major part of Cura's functionality.
|
||||
"LocalContainerProvider", #Cura is useless without any profiles or setting definitions.
|
||||
|
@ -493,7 +501,8 @@ class CuraApplication(QtApplication):
|
|||
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
|
||||
preferences.addPreference("cura/choice_on_open_project", "always_ask")
|
||||
preferences.addPreference("cura/use_multi_build_plate", False)
|
||||
|
||||
preferences.addPreference("view/settings_list_height", 400)
|
||||
preferences.addPreference("view/settings_visible", False)
|
||||
preferences.addPreference("cura/currency", "€")
|
||||
preferences.addPreference("cura/material_settings", "{}")
|
||||
|
||||
|
@ -632,9 +641,7 @@ class CuraApplication(QtApplication):
|
|||
self._message_box_callback(button, *self._message_box_callback_arguments)
|
||||
self._message_box_callback = None
|
||||
self._message_box_callback_arguments = []
|
||||
|
||||
showPrintMonitor = pyqtSignal(bool, arguments = ["show"])
|
||||
|
||||
|
||||
def setSaveDataEnabled(self, enabled: bool) -> None:
|
||||
self._save_data_enabled = enabled
|
||||
|
||||
|
@ -660,12 +667,12 @@ class CuraApplication(QtApplication):
|
|||
|
||||
## Handle loading of all plugin types (and the backend explicitly)
|
||||
# \sa PluginRegistry
|
||||
def _loadPlugins(self):
|
||||
def _loadPlugins(self) -> None:
|
||||
self._plugin_registry.addType("profile_reader", self._addProfileReader)
|
||||
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
|
||||
|
||||
if Platform.isLinux():
|
||||
lib_suffixes = {"", "64", "32", "x32"} #A few common ones on different distributions.
|
||||
lib_suffixes = {"", "64", "32", "x32"} # A few common ones on different distributions.
|
||||
else:
|
||||
lib_suffixes = {""}
|
||||
for suffix in lib_suffixes:
|
||||
|
@ -862,6 +869,19 @@ class CuraApplication(QtApplication):
|
|||
self._object_manager = ObjectsModel.createObjectsModel()
|
||||
return self._object_manager
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getExtrudersModel(self, *args) -> "ExtrudersModel":
|
||||
if self._extruders_model is None:
|
||||
self._extruders_model = ExtrudersModel(self)
|
||||
return self._extruders_model
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getExtrudersModelWithOptional(self, *args) -> "ExtrudersModel":
|
||||
if self._extruders_model_with_optional is None:
|
||||
self._extruders_model_with_optional = ExtrudersModel(self)
|
||||
self._extruders_model_with_optional.setAddOptionalExtruder(True)
|
||||
return self._extruders_model_with_optional
|
||||
|
||||
@pyqtSlot(result = QObject)
|
||||
def getMultiBuildPlateModel(self, *args) -> MultiBuildPlateModel:
|
||||
if self._multi_build_plate_model is None:
|
||||
|
@ -936,7 +956,7 @@ class CuraApplication(QtApplication):
|
|||
engine.rootContext().setContextProperty("CuraApplication", self)
|
||||
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
|
||||
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
|
||||
engine.rootContext().setContextProperty("CuraSDKVersion", CuraSDKVersion)
|
||||
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
|
||||
|
||||
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
||||
|
||||
|
@ -954,6 +974,7 @@ class CuraApplication(QtApplication):
|
|||
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
|
||||
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
||||
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
||||
qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel")
|
||||
|
||||
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
|
||||
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
||||
|
@ -975,6 +996,8 @@ class CuraApplication(QtApplication):
|
|||
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance)
|
||||
qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel")
|
||||
|
||||
qmlRegisterType(PrinterOutputDevice, "Cura", 1, 0, "PrinterOutputDevice")
|
||||
|
||||
from cura.API import CuraAPI
|
||||
qmlRegisterSingletonType(CuraAPI, "Cura", 1, 1, "API", self.getCuraAPI)
|
||||
|
||||
|
@ -1085,88 +1108,6 @@ class CuraApplication(QtApplication):
|
|||
self._platform_activity = True if count > 0 else False
|
||||
self.activityChanged.emit()
|
||||
|
||||
# Remove all selected objects from the scene.
|
||||
@pyqtSlot()
|
||||
@deprecated("Moved to CuraActions", "2.6")
|
||||
def deleteSelection(self):
|
||||
if not self.getController().getToolsEnabled():
|
||||
return
|
||||
removed_group_nodes = []
|
||||
op = GroupedOperation()
|
||||
nodes = Selection.getAllSelectedObjects()
|
||||
for node in nodes:
|
||||
op.addOperation(RemoveSceneNodeOperation(node))
|
||||
group_node = node.getParent()
|
||||
if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes:
|
||||
remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes))
|
||||
if len(remaining_nodes_in_group) == 1:
|
||||
removed_group_nodes.append(group_node)
|
||||
op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent()))
|
||||
op.addOperation(RemoveSceneNodeOperation(group_node))
|
||||
op.push()
|
||||
|
||||
## Remove an object from the scene.
|
||||
# Note that this only removes an object if it is selected.
|
||||
@pyqtSlot("quint64")
|
||||
@deprecated("Use deleteSelection instead", "2.6")
|
||||
def deleteObject(self, object_id):
|
||||
if not self.getController().getToolsEnabled():
|
||||
return
|
||||
|
||||
node = self.getController().getScene().findObject(object_id)
|
||||
|
||||
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
|
||||
node = Selection.getSelectedObject(0)
|
||||
|
||||
if node:
|
||||
op = GroupedOperation()
|
||||
op.addOperation(RemoveSceneNodeOperation(node))
|
||||
|
||||
group_node = node.getParent()
|
||||
if group_node:
|
||||
# Note that at this point the node has not yet been deleted
|
||||
if len(group_node.getChildren()) <= 2 and group_node.callDecoration("isGroup"):
|
||||
op.addOperation(SetParentOperation(group_node.getChildren()[0], group_node.getParent()))
|
||||
op.addOperation(RemoveSceneNodeOperation(group_node))
|
||||
|
||||
op.push()
|
||||
|
||||
## Create a number of copies of existing object.
|
||||
# \param object_id
|
||||
# \param count number of copies
|
||||
# \param min_offset minimum offset to other objects.
|
||||
@pyqtSlot("quint64", int)
|
||||
@deprecated("Use CuraActions::multiplySelection", "2.6")
|
||||
def multiplyObject(self, object_id, count, min_offset = 8):
|
||||
node = self.getController().getScene().findObject(object_id)
|
||||
if not node:
|
||||
node = Selection.getSelectedObject(0)
|
||||
|
||||
while node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||
node = node.getParent()
|
||||
|
||||
job = MultiplyObjectsJob([node], count, min_offset)
|
||||
job.start()
|
||||
return
|
||||
|
||||
## Center object on platform.
|
||||
@pyqtSlot("quint64")
|
||||
@deprecated("Use CuraActions::centerSelection", "2.6")
|
||||
def centerObject(self, object_id):
|
||||
node = self.getController().getScene().findObject(object_id)
|
||||
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
|
||||
node = Selection.getSelectedObject(0)
|
||||
|
||||
if not node:
|
||||
return
|
||||
|
||||
if node.getParent() and node.getParent().callDecoration("isGroup"):
|
||||
node = node.getParent()
|
||||
|
||||
if node:
|
||||
op = SetTransformOperation(node, Vector())
|
||||
op.push()
|
||||
|
||||
## Select all nodes containing mesh data in the scene.
|
||||
@pyqtSlot()
|
||||
def selectAll(self):
|
||||
|
@ -1246,62 +1187,75 @@ class CuraApplication(QtApplication):
|
|||
|
||||
## Arrange all objects.
|
||||
@pyqtSlot()
|
||||
def arrangeObjectsToAllBuildPlates(self):
|
||||
nodes = []
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
def arrangeObjectsToAllBuildPlates(self) -> None:
|
||||
nodes_to_arrange = []
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
|
||||
if not isinstance(node, SceneNode):
|
||||
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"):
|
||||
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
|
||||
|
||||
parent_node = node.getParent()
|
||||
if parent_node and parent_node.callDecoration("isGroup"):
|
||||
continue # Grouped nodes don't need resetting as their parent (the group) is reset)
|
||||
|
||||
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
|
||||
continue # i.e. node with layer data
|
||||
|
||||
bounding_box = node.getBoundingBox()
|
||||
# Skip nodes that are too big
|
||||
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
|
||||
nodes.append(node)
|
||||
job = ArrangeObjectsAllBuildPlatesJob(nodes)
|
||||
if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
|
||||
nodes_to_arrange.append(node)
|
||||
job = ArrangeObjectsAllBuildPlatesJob(nodes_to_arrange)
|
||||
job.start()
|
||||
self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate
|
||||
|
||||
# Single build plate
|
||||
@pyqtSlot()
|
||||
def arrangeAll(self):
|
||||
nodes = []
|
||||
def arrangeAll(self) -> None:
|
||||
nodes_to_arrange = []
|
||||
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
|
||||
if not isinstance(node, SceneNode):
|
||||
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"):
|
||||
|
||||
parent_node = node.getParent()
|
||||
if parent_node and parent_node.callDecoration("isGroup"):
|
||||
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
|
||||
|
||||
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
|
||||
continue # i.e. node with layer data
|
||||
|
||||
if node.callDecoration("getBuildPlateNumber") == active_build_plate:
|
||||
# Skip nodes that are too big
|
||||
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
|
||||
nodes.append(node)
|
||||
self.arrange(nodes, fixed_nodes = [])
|
||||
bounding_box = node.getBoundingBox()
|
||||
if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
|
||||
nodes_to_arrange.append(node)
|
||||
self.arrange(nodes_to_arrange, fixed_nodes = [])
|
||||
|
||||
## Arrange a set of nodes given a set of fixed nodes
|
||||
# \param nodes nodes that we have to place
|
||||
# \param fixed_nodes nodes that are placed in the arranger before finding spots for nodes
|
||||
def arrange(self, nodes, fixed_nodes):
|
||||
def arrange(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode]) -> None:
|
||||
min_offset = self.getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
|
||||
job = ArrangeObjectsJob(nodes, fixed_nodes, min_offset = max(min_offset, 8))
|
||||
job.start()
|
||||
|
||||
## Reload all mesh data on the screen from file.
|
||||
@pyqtSlot()
|
||||
def reloadAll(self):
|
||||
def reloadAll(self) -> None:
|
||||
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() :
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
|
||||
if not isinstance(node, CuraSceneNode) or not node.getMeshData():
|
||||
if node.getName() == "MergedMesh":
|
||||
has_merged_nodes = True
|
||||
continue
|
||||
|
@ -1315,7 +1269,7 @@ class CuraApplication(QtApplication):
|
|||
file_name = node.getMeshData().getFileName()
|
||||
if file_name:
|
||||
job = ReadMeshJob(file_name)
|
||||
job._node = node
|
||||
job._node = node # type: ignore
|
||||
job.finished.connect(self._reloadMeshFinished)
|
||||
if has_merged_nodes:
|
||||
job.finished.connect(self.updateOriginOfMergedMeshes)
|
||||
|
@ -1324,20 +1278,8 @@ class CuraApplication(QtApplication):
|
|||
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)
|
||||
def getEngineLog(self):
|
||||
log = ""
|
||||
|
||||
for entry in self.getBackend().getLog():
|
||||
log += entry.decode()
|
||||
|
||||
return log
|
||||
|
||||
@pyqtSlot("QStringList")
|
||||
def setExpandedCategories(self, categories):
|
||||
def setExpandedCategories(self, categories: List[str]) -> None:
|
||||
categories = list(set(categories))
|
||||
categories.sort()
|
||||
joined = ";".join(categories)
|
||||
|
@ -1348,7 +1290,7 @@ class CuraApplication(QtApplication):
|
|||
expandedCategoriesChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty("QStringList", notify = expandedCategoriesChanged)
|
||||
def expandedCategories(self):
|
||||
def expandedCategories(self) -> List[str]:
|
||||
return self.getPreferences().getValue("cura/categories_expanded").split(";")
|
||||
|
||||
@pyqtSlot()
|
||||
|
@ -1398,13 +1340,12 @@ class CuraApplication(QtApplication):
|
|||
|
||||
|
||||
## Updates origin position of all merged meshes
|
||||
# \param jobNode \type{Job} empty object which passed which is required by JobQueue
|
||||
def updateOriginOfMergedMeshes(self, jobNode):
|
||||
def updateOriginOfMergedMeshes(self, _):
|
||||
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
|
||||
# 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)
|
||||
|
@ -1448,7 +1389,7 @@ class CuraApplication(QtApplication):
|
|||
|
||||
|
||||
@pyqtSlot()
|
||||
def groupSelected(self):
|
||||
def groupSelected(self) -> None:
|
||||
# Create a group-node
|
||||
group_node = CuraSceneNode()
|
||||
group_decorator = GroupDecorator()
|
||||
|
@ -1464,7 +1405,8 @@ class CuraApplication(QtApplication):
|
|||
# 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"):
|
||||
parent = node.getParent()
|
||||
if parent is not None and parent in selected_nodes and not parent.callDecoration("isGroup"):
|
||||
Selection.remove(node)
|
||||
|
||||
# Move selected nodes into the group-node
|
||||
|
@ -1476,7 +1418,7 @@ class CuraApplication(QtApplication):
|
|||
Selection.add(group_node)
|
||||
|
||||
@pyqtSlot()
|
||||
def ungroupSelected(self):
|
||||
def ungroupSelected(self) -> None:
|
||||
selected_objects = Selection.getAllSelectedObjects().copy()
|
||||
for node in selected_objects:
|
||||
if node.callDecoration("isGroup"):
|
||||
|
@ -1499,7 +1441,7 @@ class CuraApplication(QtApplication):
|
|||
# Note: The group removes itself from the scene once all its children have left it,
|
||||
# see GroupDecorator._onChildrenChanged
|
||||
|
||||
def _createSplashScreen(self):
|
||||
def _createSplashScreen(self) -> Optional[CuraSplashScreen.CuraSplashScreen]:
|
||||
if self._is_headless:
|
||||
return None
|
||||
return CuraSplashScreen.CuraSplashScreen()
|
||||
|
@ -1665,7 +1607,9 @@ class CuraApplication(QtApplication):
|
|||
is_non_sliceable = "." + file_extension in self._non_sliceable_extensions
|
||||
|
||||
if is_non_sliceable:
|
||||
self.callLater(lambda: self.getController().setActiveView("SimulationView"))
|
||||
# Need to switch first to the preview stage and then to layer view
|
||||
self.callLater(lambda: (self.getController().setActiveStage("PreviewStage"),
|
||||
self.getController().setActiveView("SimulationView")))
|
||||
|
||||
block_slicing_decorator = BlockSlicingDecorator()
|
||||
node.addDecorator(block_slicing_decorator)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue