mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-22 06:03:57 -06:00
commit
09d30c5a2c
108 changed files with 5670 additions and 1324 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -35,6 +35,7 @@ plugins/GodMode
|
||||||
plugins/PostProcessingPlugin
|
plugins/PostProcessingPlugin
|
||||||
plugins/X3GWriter
|
plugins/X3GWriter
|
||||||
plugins/FlatProfileExporter
|
plugins/FlatProfileExporter
|
||||||
|
plugins/ProfileFlattener
|
||||||
plugins/cura-god-mode-plugin
|
plugins/cura-god-mode-plugin
|
||||||
|
|
||||||
#Build stuff
|
#Build stuff
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Version=1
|
|
||||||
Name=Cura
|
Name=Cura
|
||||||
Name[de]=Cura
|
Name[de]=Cura
|
||||||
GenericName=3D Printing Software
|
GenericName=3D Printing Software
|
||||||
|
|
|
@ -600,20 +600,21 @@ class BuildVolume(SceneNode):
|
||||||
result_areas[extruder_id].append(polygon) #Don't perform the offset on these.
|
result_areas[extruder_id].append(polygon) #Don't perform the offset on these.
|
||||||
|
|
||||||
# Add prime tower location as disallowed area.
|
# Add prime tower location as disallowed area.
|
||||||
prime_tower_collision = False
|
if len(used_extruders) > 1: #No prime tower in single-extrusion.
|
||||||
prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
|
prime_tower_collision = False
|
||||||
for extruder_id in prime_tower_areas:
|
prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
|
||||||
for prime_tower_area in prime_tower_areas[extruder_id]:
|
for extruder_id in prime_tower_areas:
|
||||||
for area in result_areas[extruder_id]:
|
for prime_tower_area in prime_tower_areas[extruder_id]:
|
||||||
if prime_tower_area.intersectsPolygon(area) is not None:
|
for area in result_areas[extruder_id]:
|
||||||
prime_tower_collision = True
|
if prime_tower_area.intersectsPolygon(area) is not None:
|
||||||
|
prime_tower_collision = True
|
||||||
|
break
|
||||||
|
if prime_tower_collision: #Already found a collision.
|
||||||
break
|
break
|
||||||
if prime_tower_collision: #Already found a collision.
|
if not prime_tower_collision:
|
||||||
break
|
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
|
||||||
if not prime_tower_collision:
|
else:
|
||||||
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
|
self._error_areas.extend(prime_tower_areas[extruder_id])
|
||||||
else:
|
|
||||||
self._error_areas.extend(prime_tower_areas[extruder_id])
|
|
||||||
|
|
||||||
self._has_errors = len(self._error_areas) > 0
|
self._has_errors = len(self._error_areas) > 0
|
||||||
|
|
||||||
|
@ -955,4 +956,4 @@ class BuildVolume(SceneNode):
|
||||||
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
|
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
|
||||||
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
|
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
|
||||||
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts"]
|
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts"]
|
||||||
_extruder_settings = ["support_enable", "support_interface_enable", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_interface_extruder_nr", "brim_line_count", "adhesion_extruder_nr", "adhesion_type"] #Settings that can affect which extruders are used.
|
_extruder_settings = ["support_enable", "support_bottom_enable", "support_roof_enable", "support_infill_extruder_nr", "support_extruder_nr_layer_0", "support_bottom_extruder_nr", "support_roof_extruder_nr", "brim_line_count", "adhesion_extruder_nr", "adhesion_type"] #Settings that can affect which extruders are used.
|
||||||
|
|
|
@ -48,35 +48,32 @@ def show(exception_type, value, tb):
|
||||||
dialog = QDialog()
|
dialog = QDialog()
|
||||||
dialog.setMinimumWidth(640)
|
dialog.setMinimumWidth(640)
|
||||||
dialog.setMinimumHeight(640)
|
dialog.setMinimumHeight(640)
|
||||||
dialog.setWindowTitle(catalog.i18nc("@title:window", "Oops!"))
|
dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))
|
||||||
|
|
||||||
layout = QVBoxLayout(dialog)
|
layout = QVBoxLayout(dialog)
|
||||||
|
|
||||||
label = QLabel(dialog)
|
#label = QLabel(dialog)
|
||||||
pixmap = QPixmap()
|
#pixmap = QPixmap()
|
||||||
|
#try:
|
||||||
try:
|
# data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read()
|
||||||
data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read()
|
# pixmap.loadFromData(data)
|
||||||
pixmap.loadFromData(data)
|
#except:
|
||||||
except:
|
# try:
|
||||||
try:
|
# from UM.Resources import Resources
|
||||||
from UM.Resources import Resources
|
# path = Resources.getPath(Resources.Images, "kitten.jpg")
|
||||||
path = Resources.getPath(Resources.Images, "kitten.jpg")
|
# pixmap.load(path)
|
||||||
pixmap.load(path)
|
# except:
|
||||||
except:
|
# pass
|
||||||
pass
|
#pixmap = pixmap.scaled(150, 150)
|
||||||
|
#label.setPixmap(pixmap)
|
||||||
pixmap = pixmap.scaled(150, 150)
|
#label.setAlignment(Qt.AlignCenter)
|
||||||
label.setPixmap(pixmap)
|
#layout.addWidget(label)
|
||||||
label.setAlignment(Qt.AlignCenter)
|
|
||||||
layout.addWidget(label)
|
|
||||||
|
|
||||||
label = QLabel(dialog)
|
label = QLabel(dialog)
|
||||||
layout.addWidget(label)
|
layout.addWidget(label)
|
||||||
|
|
||||||
#label.setScaledContents(True)
|
#label.setScaledContents(True)
|
||||||
label.setText(catalog.i18nc("@label", """<p>A fatal exception has occurred that we could not recover from!</p>
|
label.setText(catalog.i18nc("@label", """<p>A fatal exception has occurred that we could not recover from!</p>
|
||||||
<p>We hope this picture of a kitten helps you recover from the shock.</p>
|
|
||||||
<p>Please use the information below to post a bug report at <a href=\"http://github.com/Ultimaker/Cura/issues\">http://github.com/Ultimaker/Cura/issues</a></p>
|
<p>Please use the information below to post a bug report at <a href=\"http://github.com/Ultimaker/Cura/issues\">http://github.com/Ultimaker/Cura/issues</a></p>
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QUrl
|
from PyQt5.QtCore import QObject, QUrl
|
||||||
from PyQt5.QtGui import QDesktopServices
|
from PyQt5.QtGui import QDesktopServices
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
|
|
||||||
from UM.Event import CallFunctionEvent
|
from UM.Event import CallFunctionEvent
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
from UM.Math.Vector import Vector
|
||||||
|
from UM.Scene.Selection import Selection
|
||||||
|
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||||
|
from UM.Operations.GroupedOperation import GroupedOperation
|
||||||
|
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||||
|
from UM.Operations.SetTransformOperation import SetTransformOperation
|
||||||
|
|
||||||
|
from cura.SetParentOperation import SetParentOperation
|
||||||
|
from cura.MultiplyObjectsJob import MultiplyObjectsJob
|
||||||
|
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
|
||||||
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
class CuraActions(QObject):
|
class CuraActions(QObject):
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
|
@ -23,5 +36,84 @@ class CuraActions(QObject):
|
||||||
event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {})
|
event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {})
|
||||||
Application.getInstance().functionEvent(event)
|
Application.getInstance().functionEvent(event)
|
||||||
|
|
||||||
|
## Center all objects in the selection
|
||||||
|
@pyqtSlot()
|
||||||
|
def centerSelection(self) -> None:
|
||||||
|
operation = GroupedOperation()
|
||||||
|
for node in Selection.getAllSelectedObjects():
|
||||||
|
current_node = node
|
||||||
|
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
|
||||||
|
current_node = current_node.getParent()
|
||||||
|
|
||||||
|
center_operation = SetTransformOperation(current_node, Vector())
|
||||||
|
operation.addOperation(center_operation)
|
||||||
|
operation.push()
|
||||||
|
|
||||||
|
## Multiply all objects in the selection
|
||||||
|
#
|
||||||
|
# \param count The number of times to multiply the selection.
|
||||||
|
@pyqtSlot(int)
|
||||||
|
def multiplySelection(self, count: int) -> None:
|
||||||
|
job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, 8)
|
||||||
|
job.start()
|
||||||
|
|
||||||
|
## Delete all selected objects.
|
||||||
|
@pyqtSlot()
|
||||||
|
def deleteSelection(self) -> None:
|
||||||
|
if not Application.getInstance().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()
|
||||||
|
|
||||||
|
## Set the extruder that should be used to print the selection.
|
||||||
|
#
|
||||||
|
# \param extruder_id The ID of the extruder stack to use for the selected objects.
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def setExtruderForSelection(self, extruder_id: str) -> None:
|
||||||
|
operation = GroupedOperation()
|
||||||
|
|
||||||
|
nodes_to_change = []
|
||||||
|
for node in Selection.getAllSelectedObjects():
|
||||||
|
# Do not change any nodes that already have the right extruder set.
|
||||||
|
if node.callDecoration("getActiveExtruder") == extruder_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If the node is a group, apply the active extruder to all children of the group.
|
||||||
|
if node.callDecoration("isGroup"):
|
||||||
|
for grouped_node in BreadthFirstIterator(node):
|
||||||
|
if grouped_node.callDecoration("getActiveExtruder") == extruder_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if grouped_node.callDecoration("isGroup"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
nodes_to_change.append(grouped_node)
|
||||||
|
continue
|
||||||
|
|
||||||
|
nodes_to_change.append(node)
|
||||||
|
|
||||||
|
if not nodes_to_change:
|
||||||
|
# If there are no changes to make, we still need to reset the selected extruders.
|
||||||
|
# This is a workaround for checked menu items being deselected while still being
|
||||||
|
# selected.
|
||||||
|
ExtruderManager.getInstance().resetSelectedObjectExtruders()
|
||||||
|
return
|
||||||
|
|
||||||
|
for node in nodes_to_change:
|
||||||
|
operation.addOperation(SetObjectExtruderOperation(node, extruder_id))
|
||||||
|
operation.push()
|
||||||
|
|
||||||
def _openUrl(self, url):
|
def _openUrl(self, url):
|
||||||
QDesktopServices.openUrl(url)
|
QDesktopServices.openUrl(url)
|
|
@ -26,6 +26,7 @@ from UM.Message import Message
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
from UM.Workspace.WorkspaceReader import WorkspaceReader
|
||||||
from UM.Platform import Platform
|
from UM.Platform import Platform
|
||||||
|
from UM.Decorators import deprecated
|
||||||
|
|
||||||
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||||
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
||||||
|
@ -68,6 +69,8 @@ from cura.Settings.ContainerSettingsModel import ContainerSettingsModel
|
||||||
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
||||||
from cura.Settings.QualitySettingsModel import QualitySettingsModel
|
from cura.Settings.QualitySettingsModel import QualitySettingsModel
|
||||||
from cura.Settings.ContainerManager import ContainerManager
|
from cura.Settings.ContainerManager import ContainerManager
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
|
@ -105,10 +108,15 @@ class CuraApplication(QtApplication):
|
||||||
UserInstanceContainer = Resources.UserType + 6
|
UserInstanceContainer = Resources.UserType + 6
|
||||||
MachineStack = Resources.UserType + 7
|
MachineStack = Resources.UserType + 7
|
||||||
ExtruderStack = Resources.UserType + 8
|
ExtruderStack = Resources.UserType + 8
|
||||||
|
DefinitionChangesContainer = Resources.UserType + 9
|
||||||
|
|
||||||
Q_ENUMS(ResourceTypes)
|
Q_ENUMS(ResourceTypes)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
# this list of dir names will be used by UM to detect an old cura directory
|
||||||
|
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
|
||||||
|
Resources.addExpectedDirNameInData(dir_name)
|
||||||
|
|
||||||
Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources"))
|
Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources"))
|
||||||
if not hasattr(sys, "frozen"):
|
if not hasattr(sys, "frozen"):
|
||||||
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
|
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
|
||||||
|
@ -146,6 +154,7 @@ class CuraApplication(QtApplication):
|
||||||
Resources.addStorageType(self.ResourceTypes.UserInstanceContainer, "user")
|
Resources.addStorageType(self.ResourceTypes.UserInstanceContainer, "user")
|
||||||
Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
|
Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
|
||||||
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
|
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
|
||||||
|
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
|
||||||
|
|
||||||
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer)
|
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer)
|
||||||
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer)
|
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer)
|
||||||
|
@ -153,6 +162,7 @@ class CuraApplication(QtApplication):
|
||||||
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer)
|
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer)
|
||||||
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack)
|
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack)
|
||||||
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack)
|
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack)
|
||||||
|
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer)
|
||||||
|
|
||||||
## Initialise the version upgrade manager with Cura's storage paths.
|
## Initialise the version upgrade manager with Cura's storage paths.
|
||||||
import UM.VersionUpgradeManager #Needs to be here to prevent circular dependencies.
|
import UM.VersionUpgradeManager #Needs to be here to prevent circular dependencies.
|
||||||
|
@ -214,6 +224,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
|
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
|
||||||
self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
|
self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
|
||||||
|
self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
|
||||||
|
|
||||||
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
|
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
|
||||||
Resources.addType(self.ResourceTypes.Firmware, "firmware")
|
Resources.addType(self.ResourceTypes.Firmware, "firmware")
|
||||||
|
@ -407,7 +418,7 @@ class CuraApplication(QtApplication):
|
||||||
elif instance_type == "variant":
|
elif instance_type == "variant":
|
||||||
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
|
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
|
||||||
elif instance_type == "definition_changes":
|
elif instance_type == "definition_changes":
|
||||||
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
|
path = Resources.getStoragePath(self.ResourceTypes.DefinitionChangesContainer, file_name)
|
||||||
|
|
||||||
if path:
|
if path:
|
||||||
instance.setPath(path)
|
instance.setPath(path)
|
||||||
|
@ -430,16 +441,18 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack))
|
mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack))
|
||||||
file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix
|
file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix
|
||||||
stack_type = stack.getMetaDataEntry("type", None)
|
|
||||||
path = None
|
path = None
|
||||||
if not stack_type or stack_type == "machine":
|
if isinstance(stack, GlobalStack):
|
||||||
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
|
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
|
||||||
elif stack_type == "extruder_train":
|
elif isinstance(stack, ExtruderStack):
|
||||||
path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name)
|
path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name)
|
||||||
if path:
|
else:
|
||||||
stack.setPath(path)
|
path = Resources.getStoragePath(Resources.ContainerStacks, file_name)
|
||||||
with SaveFile(path, "wt") as f:
|
|
||||||
f.write(data)
|
stack.setPath(path)
|
||||||
|
with SaveFile(path, "wt") as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot(str, result = QUrl)
|
@pyqtSlot(str, result = QUrl)
|
||||||
|
@ -803,6 +816,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
# Remove all selected objects from the scene.
|
# Remove all selected objects from the scene.
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
|
@deprecated("Moved to CuraActions", "2.6")
|
||||||
def deleteSelection(self):
|
def deleteSelection(self):
|
||||||
if not self.getController().getToolsEnabled():
|
if not self.getController().getToolsEnabled():
|
||||||
return
|
return
|
||||||
|
@ -823,6 +837,7 @@ class CuraApplication(QtApplication):
|
||||||
## Remove an object from the scene.
|
## Remove an object from the scene.
|
||||||
# Note that this only removes an object if it is selected.
|
# Note that this only removes an object if it is selected.
|
||||||
@pyqtSlot("quint64")
|
@pyqtSlot("quint64")
|
||||||
|
@deprecated("Use deleteSelection instead", "2.6")
|
||||||
def deleteObject(self, object_id):
|
def deleteObject(self, object_id):
|
||||||
if not self.getController().getToolsEnabled():
|
if not self.getController().getToolsEnabled():
|
||||||
return
|
return
|
||||||
|
@ -850,13 +865,22 @@ class CuraApplication(QtApplication):
|
||||||
# \param count number of copies
|
# \param count number of copies
|
||||||
# \param min_offset minimum offset to other objects.
|
# \param min_offset minimum offset to other objects.
|
||||||
@pyqtSlot("quint64", int)
|
@pyqtSlot("quint64", int)
|
||||||
|
@deprecated("Use CuraActions::multiplySelection", "2.6")
|
||||||
def multiplyObject(self, object_id, count, min_offset = 8):
|
def multiplyObject(self, object_id, count, min_offset = 8):
|
||||||
job = MultiplyObjectsJob(object_id, count, min_offset)
|
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()
|
job.start()
|
||||||
return
|
return
|
||||||
|
|
||||||
## Center object on platform.
|
## Center object on platform.
|
||||||
@pyqtSlot("quint64")
|
@pyqtSlot("quint64")
|
||||||
|
@deprecated("Use CuraActions::centerSelection", "2.6")
|
||||||
def centerObject(self, object_id):
|
def centerObject(self, object_id):
|
||||||
node = self.getController().getScene().findObject(object_id)
|
node = self.getController().getScene().findObject(object_id)
|
||||||
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
|
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
|
||||||
|
@ -1257,6 +1281,8 @@ class CuraApplication(QtApplication):
|
||||||
arranger = Arrange.create(scene_root = root)
|
arranger = Arrange.create(scene_root = root)
|
||||||
min_offset = 8
|
min_offset = 8
|
||||||
|
|
||||||
|
self.fileLoaded.emit(filename)
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
node.setSelectable(True)
|
node.setSelectable(True)
|
||||||
node.setName(os.path.basename(filename))
|
node.setName(os.path.basename(filename))
|
||||||
|
@ -1316,3 +1342,13 @@ class CuraApplication(QtApplication):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.log("e", "Could not check file %s: %s", file_url, e)
|
Logger.log("e", "Could not check file %s: %s", file_url, e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _onContextMenuRequested(self, x: float, y: float) -> None:
|
||||||
|
# Ensure we select the object if we request a context menu over an object without having a selection.
|
||||||
|
if not Selection.hasSelection():
|
||||||
|
node = self.getController().getScene().findObject(self.getRenderer().getRenderPass("selection").getIdAtPosition(x, y))
|
||||||
|
if node:
|
||||||
|
while(node.getParent() and node.getParent().callDecoration("isGroup")):
|
||||||
|
node = node.getParent()
|
||||||
|
|
||||||
|
Selection.add(node)
|
||||||
|
|
|
@ -24,9 +24,9 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
||||||
|
|
||||||
|
|
||||||
class MultiplyObjectsJob(Job):
|
class MultiplyObjectsJob(Job):
|
||||||
def __init__(self, object_id, count, min_offset = 8):
|
def __init__(self, objects, count, min_offset = 8):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._object_id = object_id
|
self._objects = objects
|
||||||
self._count = count
|
self._count = count
|
||||||
self._min_offset = min_offset
|
self._min_offset = min_offset
|
||||||
|
|
||||||
|
@ -35,38 +35,42 @@ class MultiplyObjectsJob(Job):
|
||||||
dismissable=False, progress=0)
|
dismissable=False, progress=0)
|
||||||
status_message.show()
|
status_message.show()
|
||||||
scene = Application.getInstance().getController().getScene()
|
scene = Application.getInstance().getController().getScene()
|
||||||
node = scene.findObject(self._object_id)
|
|
||||||
|
|
||||||
if not node and self._object_id != 0: # Workaround for tool handles overlapping the selected object
|
total_progress = len(self._objects) * self._count
|
||||||
node = Selection.getSelectedObject(0)
|
current_progress = 0
|
||||||
|
|
||||||
# If object is part of a group, multiply group
|
|
||||||
current_node = node
|
|
||||||
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
|
|
||||||
current_node = current_node.getParent()
|
|
||||||
|
|
||||||
root = scene.getRoot()
|
root = scene.getRoot()
|
||||||
arranger = Arrange.create(scene_root=root)
|
arranger = Arrange.create(scene_root=root)
|
||||||
node_too_big = False
|
|
||||||
if node.getBoundingBox().width < 300 or node.getBoundingBox().depth < 300:
|
|
||||||
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset)
|
|
||||||
else:
|
|
||||||
node_too_big = True
|
|
||||||
nodes = []
|
nodes = []
|
||||||
found_solution_for_all = True
|
for node in self._objects:
|
||||||
for i in range(self._count):
|
# If object is part of a group, multiply group
|
||||||
# We do place the nodes one by one, as we want to yield in between.
|
current_node = node
|
||||||
if not node_too_big:
|
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
|
||||||
node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
|
current_node = current_node.getParent()
|
||||||
if node_too_big or not solution_found:
|
|
||||||
found_solution_for_all = False
|
node_too_big = False
|
||||||
new_location = node.getPosition()
|
if node.getBoundingBox().width < 300 or node.getBoundingBox().depth < 300:
|
||||||
new_location = new_location.set(z = 100 - i * 20)
|
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset)
|
||||||
node.setPosition(new_location)
|
else:
|
||||||
|
node_too_big = True
|
||||||
|
|
||||||
|
found_solution_for_all = True
|
||||||
|
for i in range(self._count):
|
||||||
|
# We do place the nodes one by one, as we want to yield in between.
|
||||||
|
if not node_too_big:
|
||||||
|
node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
|
||||||
|
if node_too_big or not solution_found:
|
||||||
|
found_solution_for_all = False
|
||||||
|
new_location = node.getPosition()
|
||||||
|
new_location = new_location.set(z = 100 - i * 20)
|
||||||
|
node.setPosition(new_location)
|
||||||
|
|
||||||
|
nodes.append(node)
|
||||||
|
current_progress += 1
|
||||||
|
status_message.setProgress((current_progress / total_progress) * 100)
|
||||||
|
Job.yieldThread()
|
||||||
|
|
||||||
nodes.append(node)
|
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
status_message.setProgress((i + 1) / self._count * 100)
|
|
||||||
|
|
||||||
if nodes:
|
if nodes:
|
||||||
op = GroupedOperation()
|
op = GroupedOperation()
|
||||||
|
|
|
@ -183,7 +183,10 @@ class PrintInformation(QObject):
|
||||||
|
|
||||||
def _onActiveMaterialChanged(self):
|
def _onActiveMaterialChanged(self):
|
||||||
if self._active_material_container:
|
if self._active_material_container:
|
||||||
self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged)
|
try:
|
||||||
|
self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged)
|
||||||
|
except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that is already disconnected.
|
||||||
|
pass
|
||||||
|
|
||||||
active_material_id = Application.getInstance().getMachineManager().activeMaterialId
|
active_material_id = Application.getInstance().getMachineManager().activeMaterialId
|
||||||
active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id=active_material_id)
|
active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id=active_material_id)
|
||||||
|
|
|
@ -429,7 +429,7 @@ class ContainerManager(QObject):
|
||||||
|
|
||||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||||
# Find the quality_changes container for this stack and merge the contents of the top container into it.
|
# Find the quality_changes container for this stack and merge the contents of the top container into it.
|
||||||
quality_changes = stack.findContainer(type = "quality_changes")
|
quality_changes = stack.qualityChanges
|
||||||
if not quality_changes or quality_changes.isReadOnly():
|
if not quality_changes or quality_changes.isReadOnly():
|
||||||
Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
|
Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
|
||||||
continue
|
continue
|
||||||
|
@ -482,8 +482,8 @@ class ContainerManager(QObject):
|
||||||
# Go through the active stacks and create quality_changes containers from the user containers.
|
# Go through the active stacks and create quality_changes containers from the user containers.
|
||||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||||
user_container = stack.getTop()
|
user_container = stack.getTop()
|
||||||
quality_container = stack.findContainer(type = "quality")
|
quality_container = stack.quality
|
||||||
quality_changes_container = stack.findContainer(type = "quality_changes")
|
quality_changes_container = stack.qualityChanges
|
||||||
if not quality_container or not quality_changes_container:
|
if not quality_container or not quality_changes_container:
|
||||||
Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
|
Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
|
||||||
continue
|
continue
|
||||||
|
@ -604,7 +604,7 @@ class ContainerManager(QObject):
|
||||||
machine_definition = global_stack.getBottom()
|
machine_definition = global_stack.getBottom()
|
||||||
|
|
||||||
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
||||||
material_containers = [stack.findContainer(type="material") for stack in active_stacks]
|
material_containers = [stack.material for stack in active_stacks]
|
||||||
|
|
||||||
result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name,
|
result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name,
|
||||||
QualityManager.getInstance().getParentMachineDefinition(machine_definition),
|
QualityManager.getInstance().getParentMachineDefinition(machine_definition),
|
||||||
|
|
|
@ -6,6 +6,7 @@ import os.path
|
||||||
import re
|
import re
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
|
from UM.Decorators import override
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
@ -16,8 +17,10 @@ from UM.Platform import Platform
|
||||||
from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with.
|
from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with.
|
||||||
from UM.Util import parseBool
|
from UM.Util import parseBool
|
||||||
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from . import ExtruderStack
|
||||||
from cura.Settings.ContainerManager import ContainerManager
|
from . import GlobalStack
|
||||||
|
from .ContainerManager import ContainerManager
|
||||||
|
from .ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
@ -26,6 +29,20 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
## Overridden from ContainerRegistry
|
||||||
|
#
|
||||||
|
# Adds a container to the registry.
|
||||||
|
#
|
||||||
|
# This will also try to convert a ContainerStack to either Extruder or
|
||||||
|
# Global stack based on metadata information.
|
||||||
|
@override(ContainerRegistry)
|
||||||
|
def addContainer(self, container):
|
||||||
|
# Note: Intentional check with type() because we want to ignore subclasses
|
||||||
|
if type(container) == ContainerStack:
|
||||||
|
container = self._convertContainerStack(container)
|
||||||
|
|
||||||
|
super().addContainer(container)
|
||||||
|
|
||||||
## Create a name that is not empty and unique
|
## Create a name that is not empty and unique
|
||||||
# \param container_type \type{string} Type of the container (machine, quality, ...)
|
# \param container_type \type{string} Type of the container (machine, quality, ...)
|
||||||
# \param current_name \type{} Current name of the container, which may be an acceptable option
|
# \param current_name \type{} Current name of the container, which may be an acceptable option
|
||||||
|
@ -212,6 +229,11 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
# If it hasn't returned by now, none of the plugins loaded the profile successfully.
|
# If it hasn't returned by now, none of the plugins loaded the profile successfully.
|
||||||
return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)}
|
return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)}
|
||||||
|
|
||||||
|
@override(ContainerRegistry)
|
||||||
|
def load(self):
|
||||||
|
super().load()
|
||||||
|
self._fixupExtruders()
|
||||||
|
|
||||||
def _configureProfile(self, profile, id_seed, new_name):
|
def _configureProfile(self, profile, id_seed, new_name):
|
||||||
profile.setReadOnly(False)
|
profile.setReadOnly(False)
|
||||||
profile.setDirty(True) # Ensure the profiles are correctly saved
|
profile.setDirty(True) # Ensure the profiles are correctly saved
|
||||||
|
@ -284,3 +306,47 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False))
|
return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
## Convert an "old-style" pure ContainerStack to either an Extruder or Global stack.
|
||||||
|
def _convertContainerStack(self, container):
|
||||||
|
assert type(container) == ContainerStack
|
||||||
|
|
||||||
|
container_type = container.getMetaDataEntry("type")
|
||||||
|
if container_type not in ("extruder_train", "machine"):
|
||||||
|
# It is not an extruder or machine, so do nothing with the stack
|
||||||
|
return container
|
||||||
|
|
||||||
|
Logger.log("d", "Converting ContainerStack {stack} to {type}", stack = container.getId(), type = container_type)
|
||||||
|
|
||||||
|
new_stack = None
|
||||||
|
if container_type == "extruder_train":
|
||||||
|
new_stack = ExtruderStack.ExtruderStack(container.getId())
|
||||||
|
else:
|
||||||
|
new_stack = GlobalStack.GlobalStack(container.getId())
|
||||||
|
|
||||||
|
container_contents = container.serialize()
|
||||||
|
new_stack.deserialize(container_contents)
|
||||||
|
|
||||||
|
# Delete the old configuration file so we do not get double stacks
|
||||||
|
if os.path.isfile(container.getPath()):
|
||||||
|
os.remove(container.getPath())
|
||||||
|
|
||||||
|
return new_stack
|
||||||
|
|
||||||
|
# Fix the extruders that were upgraded to ExtruderStack instances during addContainer.
|
||||||
|
# The stacks are now responsible for setting the next stack on deserialize. However,
|
||||||
|
# due to problems with loading order, some stacks may not have the proper next stack
|
||||||
|
# set after upgrading, because the proper global stack was not yet loaded. This method
|
||||||
|
# makes sure those extruders also get the right stack set.
|
||||||
|
def _fixupExtruders(self):
|
||||||
|
extruder_stacks = self.findContainers(ExtruderStack.ExtruderStack)
|
||||||
|
for extruder_stack in extruder_stacks:
|
||||||
|
if extruder_stack.getNextStack():
|
||||||
|
# Has the right next stack, so ignore it.
|
||||||
|
continue
|
||||||
|
|
||||||
|
machines = ContainerRegistry.getInstance().findContainerStacks(id=extruder_stack.getMetaDataEntry("machine", ""))
|
||||||
|
if machines:
|
||||||
|
extruder_stack.setNextStack(machines[0])
|
||||||
|
else:
|
||||||
|
Logger.log("w", "Could not find machine {machine} for extruder {extruder}", machine = extruder_stack.getMetaDataEntry("machine"), extruder = extruder_stack.getId())
|
||||||
|
|
607
cura/Settings/CuraContainerStack.py
Executable file
607
cura/Settings/CuraContainerStack.py
Executable file
|
@ -0,0 +1,607 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
|
||||||
|
|
||||||
|
from UM.Decorators import override
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
|
||||||
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
|
|
||||||
|
from . import Exceptions
|
||||||
|
|
||||||
|
|
||||||
|
## Base class for Cura related stacks that want to enforce certain containers are available.
|
||||||
|
#
|
||||||
|
# This class makes sure that the stack has the following containers set: user changes, quality
|
||||||
|
# changes, quality, material, variant, definition changes and finally definition. Initially,
|
||||||
|
# these will be equal to the empty instance container.
|
||||||
|
#
|
||||||
|
# The container types are determined based on the following criteria:
|
||||||
|
# - user: An InstanceContainer with the metadata entry "type" set to "user".
|
||||||
|
# - quality changes: An InstanceContainer with the metadata entry "type" set to "quality_changes".
|
||||||
|
# - quality: An InstanceContainer with the metadata entry "type" set to "quality".
|
||||||
|
# - material: An InstanceContainer with the metadata entry "type" set to "material".
|
||||||
|
# - variant: An InstanceContainer with the metadata entry "type" set to "variant".
|
||||||
|
# - definition changes: An InstanceContainer with the metadata entry "type" set to "definition_changes".
|
||||||
|
# - definition: A DefinitionContainer.
|
||||||
|
#
|
||||||
|
# Internally, this class ensures the mentioned containers are always there and kept in a specific order.
|
||||||
|
# This also means that operations on the stack that modifies the container ordering is prohibited and
|
||||||
|
# will raise an exception.
|
||||||
|
class CuraContainerStack(ContainerStack):
|
||||||
|
def __init__(self, container_id: str, *args, **kwargs):
|
||||||
|
super().__init__(container_id, *args, **kwargs)
|
||||||
|
|
||||||
|
self._empty_instance_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
|
|
||||||
|
self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))]
|
||||||
|
|
||||||
|
self.containersChanged.connect(self._onContainersChanged)
|
||||||
|
|
||||||
|
# This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted.
|
||||||
|
pyqtContainersChanged = pyqtSignal()
|
||||||
|
|
||||||
|
## Set the user changes container.
|
||||||
|
#
|
||||||
|
# \param new_user_changes The new user changes container. It is expected to have a "type" metadata entry with the value "user".
|
||||||
|
def setUserChanges(self, new_user_changes: InstanceContainer) -> None:
|
||||||
|
self.replaceContainer(_ContainerIndexes.UserChanges, new_user_changes)
|
||||||
|
|
||||||
|
## Get the user changes container.
|
||||||
|
#
|
||||||
|
# \return The user changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged)
|
||||||
|
def userChanges(self) -> InstanceContainer:
|
||||||
|
return self._containers[_ContainerIndexes.UserChanges]
|
||||||
|
|
||||||
|
## Set the quality changes container.
|
||||||
|
#
|
||||||
|
# \param new_quality_changes The new quality changes container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
||||||
|
def setQualityChanges(self, new_quality_changes: InstanceContainer) -> None:
|
||||||
|
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes)
|
||||||
|
|
||||||
|
## Set the quality changes container by an ID.
|
||||||
|
#
|
||||||
|
# This will search for the specified container and set it. If no container was found, an error will be raised.
|
||||||
|
#
|
||||||
|
# \param new_quality_changes_id The ID of the new quality changes container.
|
||||||
|
#
|
||||||
|
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
||||||
|
def setQualityChangesById(self, new_quality_changes_id: str) -> None:
|
||||||
|
quality_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_changes_id)
|
||||||
|
if quality_changes:
|
||||||
|
self.setQualityChanges(quality_changes[0])
|
||||||
|
else:
|
||||||
|
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_changes_id))
|
||||||
|
|
||||||
|
## Get the quality changes container.
|
||||||
|
#
|
||||||
|
# \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged)
|
||||||
|
def qualityChanges(self) -> InstanceContainer:
|
||||||
|
return self._containers[_ContainerIndexes.QualityChanges]
|
||||||
|
|
||||||
|
## Set the quality container.
|
||||||
|
#
|
||||||
|
# \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality".
|
||||||
|
def setQuality(self, new_quality: InstanceContainer) -> None:
|
||||||
|
self.replaceContainer(_ContainerIndexes.Quality, new_quality)
|
||||||
|
|
||||||
|
## Set the quality container by an ID.
|
||||||
|
#
|
||||||
|
# This will search for the specified container and set it. If no container was found, an error will be raised.
|
||||||
|
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
|
||||||
|
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultQuality
|
||||||
|
# for details.
|
||||||
|
#
|
||||||
|
# \param new_quality_id The ID of the new quality container.
|
||||||
|
#
|
||||||
|
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
||||||
|
def setQualityById(self, new_quality_id: str) -> None:
|
||||||
|
quality = self._empty_instance_container
|
||||||
|
if new_quality_id == "default":
|
||||||
|
new_quality = self.findDefaultQuality()
|
||||||
|
if new_quality:
|
||||||
|
quality = new_quality
|
||||||
|
else:
|
||||||
|
qualities = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_id)
|
||||||
|
if qualities:
|
||||||
|
quality = qualities[0]
|
||||||
|
else:
|
||||||
|
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_id))
|
||||||
|
|
||||||
|
self.setQuality(quality)
|
||||||
|
|
||||||
|
## Get the quality container.
|
||||||
|
#
|
||||||
|
# \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged)
|
||||||
|
def quality(self) -> InstanceContainer:
|
||||||
|
return self._containers[_ContainerIndexes.Quality]
|
||||||
|
|
||||||
|
## Set the material container.
|
||||||
|
#
|
||||||
|
# \param new_quality_changes The new material container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
||||||
|
def setMaterial(self, new_material: InstanceContainer) -> None:
|
||||||
|
self.replaceContainer(_ContainerIndexes.Material, new_material)
|
||||||
|
|
||||||
|
## Set the material container by an ID.
|
||||||
|
#
|
||||||
|
# This will search for the specified container and set it. If no container was found, an error will be raised.
|
||||||
|
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
|
||||||
|
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultMaterial
|
||||||
|
# for details.
|
||||||
|
#
|
||||||
|
# \param new_quality_changes_id The ID of the new material container.
|
||||||
|
#
|
||||||
|
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
||||||
|
def setMaterialById(self, new_material_id: str) -> None:
|
||||||
|
material = self._empty_instance_container
|
||||||
|
if new_material_id == "default":
|
||||||
|
new_material = self.findDefaultMaterial()
|
||||||
|
if new_material:
|
||||||
|
material = new_material
|
||||||
|
else:
|
||||||
|
materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id)
|
||||||
|
if materials:
|
||||||
|
material = materials[0]
|
||||||
|
else:
|
||||||
|
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_material_id))
|
||||||
|
|
||||||
|
self.setMaterial(material)
|
||||||
|
|
||||||
|
## Get the material container.
|
||||||
|
#
|
||||||
|
# \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged)
|
||||||
|
def material(self) -> InstanceContainer:
|
||||||
|
return self._containers[_ContainerIndexes.Material]
|
||||||
|
|
||||||
|
## Set the variant container.
|
||||||
|
#
|
||||||
|
# \param new_quality_changes The new variant container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
||||||
|
def setVariant(self, new_variant: InstanceContainer) -> None:
|
||||||
|
self.replaceContainer(_ContainerIndexes.Variant, new_variant)
|
||||||
|
|
||||||
|
## Set the variant container by an ID.
|
||||||
|
#
|
||||||
|
# This will search for the specified container and set it. If no container was found, an error will be raised.
|
||||||
|
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
|
||||||
|
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultVariant
|
||||||
|
# for details.
|
||||||
|
#
|
||||||
|
# \param new_quality_changes_id The ID of the new variant container.
|
||||||
|
#
|
||||||
|
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
||||||
|
def setVariantById(self, new_variant_id: str) -> None:
|
||||||
|
variant = self._empty_instance_container
|
||||||
|
if new_variant_id == "default":
|
||||||
|
new_variant = self.findDefaultVariant()
|
||||||
|
if new_variant:
|
||||||
|
variant = new_variant
|
||||||
|
else:
|
||||||
|
variants = ContainerRegistry.getInstance().findInstanceContainers(id = new_variant_id)
|
||||||
|
if variants:
|
||||||
|
variant = variants[0]
|
||||||
|
else:
|
||||||
|
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_variant_id))
|
||||||
|
|
||||||
|
self.setVariant(variant)
|
||||||
|
|
||||||
|
## Get the variant container.
|
||||||
|
#
|
||||||
|
# \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged)
|
||||||
|
def variant(self) -> InstanceContainer:
|
||||||
|
return self._containers[_ContainerIndexes.Variant]
|
||||||
|
|
||||||
|
## Set the definition changes container.
|
||||||
|
#
|
||||||
|
# \param new_quality_changes The new definition changes container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
||||||
|
def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None:
|
||||||
|
self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)
|
||||||
|
|
||||||
|
## Set the definition changes container by an ID.
|
||||||
|
#
|
||||||
|
# \param new_quality_changes_id The ID of the new definition changes container.
|
||||||
|
#
|
||||||
|
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
||||||
|
def setDefinitionChangesById(self, new_definition_changes_id: str) -> None:
|
||||||
|
new_definition_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_definition_changes_id)
|
||||||
|
if new_definition_changes:
|
||||||
|
self.setDefinitionChanges(new_definition_changes[0])
|
||||||
|
else:
|
||||||
|
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_changes_id))
|
||||||
|
|
||||||
|
## Get the definition changes container.
|
||||||
|
#
|
||||||
|
# \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged)
|
||||||
|
def definitionChanges(self) -> InstanceContainer:
|
||||||
|
return self._containers[_ContainerIndexes.DefinitionChanges]
|
||||||
|
|
||||||
|
## Set the definition container.
|
||||||
|
#
|
||||||
|
# \param new_quality_changes The new definition container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
||||||
|
def setDefinition(self, new_definition: DefinitionContainer) -> None:
|
||||||
|
self.replaceContainer(_ContainerIndexes.Definition, new_definition)
|
||||||
|
|
||||||
|
## Set the definition container by an ID.
|
||||||
|
#
|
||||||
|
# \param new_quality_changes_id The ID of the new definition container.
|
||||||
|
#
|
||||||
|
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
||||||
|
def setDefinitionById(self, new_definition_id: str) -> None:
|
||||||
|
new_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = new_definition_id)
|
||||||
|
if new_definition:
|
||||||
|
self.setDefinition(new_definition[0])
|
||||||
|
else:
|
||||||
|
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_id))
|
||||||
|
|
||||||
|
## Get the definition container.
|
||||||
|
#
|
||||||
|
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@pyqtProperty(DefinitionContainer, fset = setDefinition, notify = pyqtContainersChanged)
|
||||||
|
def definition(self) -> DefinitionContainer:
|
||||||
|
return self._containers[_ContainerIndexes.Definition]
|
||||||
|
|
||||||
|
## Check whether the specified setting has a 'user' value.
|
||||||
|
#
|
||||||
|
# A user value here is defined as the setting having a value in either
|
||||||
|
# the UserChanges or QualityChanges container.
|
||||||
|
#
|
||||||
|
# \return True if the setting has a user value, False if not.
|
||||||
|
@pyqtSlot(str, result = bool)
|
||||||
|
def hasUserValue(self, key: str) -> bool:
|
||||||
|
if self._containers[_ContainerIndexes.UserChanges].hasProperty(key, "value"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self._containers[_ContainerIndexes.QualityChanges].hasProperty(key, "value"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
## Set a property of a setting.
|
||||||
|
#
|
||||||
|
# This will set a property of a specified setting. Since the container stack does not contain
|
||||||
|
# any settings itself, it is required to specify a container to set the property on. The target
|
||||||
|
# container is matched by container type.
|
||||||
|
#
|
||||||
|
# \param key The key of the setting to set.
|
||||||
|
# \param property_name The name of the property to set.
|
||||||
|
# \param new_value The new value to set the property to.
|
||||||
|
# \param target_container The type of the container to set the property of. Defaults to "user".
|
||||||
|
def setProperty(self, key: str, property_name: str, new_value: Any, target_container: str = "user") -> None:
|
||||||
|
container_index = _ContainerIndexes.TypeIndexMap.get(target_container, -1)
|
||||||
|
if container_index != -1:
|
||||||
|
self._containers[container_index].setProperty(key, property_name, new_value)
|
||||||
|
else:
|
||||||
|
raise IndexError("Invalid target container {type}".format(type = target_container))
|
||||||
|
|
||||||
|
## Overridden from ContainerStack
|
||||||
|
#
|
||||||
|
# Since we have a fixed order of containers in the stack and this method would modify the container
|
||||||
|
# ordering, we disallow this operation.
|
||||||
|
@override(ContainerStack)
|
||||||
|
def addContainer(self, container: ContainerInterface) -> None:
|
||||||
|
raise Exceptions.InvalidOperationError("Cannot add a container to Global stack")
|
||||||
|
|
||||||
|
## Overridden from ContainerStack
|
||||||
|
#
|
||||||
|
# Since we have a fixed order of containers in the stack and this method would modify the container
|
||||||
|
# ordering, we disallow this operation.
|
||||||
|
@override(ContainerStack)
|
||||||
|
def insertContainer(self, index: int, container: ContainerInterface) -> None:
|
||||||
|
raise Exceptions.InvalidOperationError("Cannot insert a container into Global stack")
|
||||||
|
|
||||||
|
## Overridden from ContainerStack
|
||||||
|
#
|
||||||
|
# Since we have a fixed order of containers in the stack and this method would modify the container
|
||||||
|
# ordering, we disallow this operation.
|
||||||
|
@override(ContainerStack)
|
||||||
|
def removeContainer(self, index: int = 0) -> None:
|
||||||
|
raise Exceptions.InvalidOperationError("Cannot remove a container from Global stack")
|
||||||
|
|
||||||
|
## Overridden from ContainerStack
|
||||||
|
#
|
||||||
|
# Replaces the container at the specified index with another container.
|
||||||
|
# This version performs checks to make sure the new container has the expected metadata and type.
|
||||||
|
#
|
||||||
|
# \throws Exception.InvalidContainerError Raised when trying to replace a container with a container that has an incorrect type.
|
||||||
|
@override(ContainerStack)
|
||||||
|
def replaceContainer(self, index: int, container: ContainerInterface, postpone_emit: bool = False) -> None:
|
||||||
|
expected_type = _ContainerIndexes.IndexTypeMap[index]
|
||||||
|
if expected_type == "definition":
|
||||||
|
if not isinstance(container, DefinitionContainer):
|
||||||
|
raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not a DefinitionContainer".format(index = index))
|
||||||
|
elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type:
|
||||||
|
raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type")))
|
||||||
|
|
||||||
|
super().replaceContainer(index, container, postpone_emit)
|
||||||
|
|
||||||
|
## Overridden from ContainerStack
|
||||||
|
#
|
||||||
|
# This deserialize will make sure the internal list of containers matches with what we expect.
|
||||||
|
# It will first check to see if the container at a certain index already matches with what we
|
||||||
|
# expect. If it does not, it will search for a matching container with the correct type. Should
|
||||||
|
# no container with the correct type be found, it will use the empty container.
|
||||||
|
#
|
||||||
|
# \throws InvalidContainerStackError Raised when no definition can be found for the stack.
|
||||||
|
@override(ContainerStack)
|
||||||
|
def deserialize(self, contents: str) -> None:
|
||||||
|
super().deserialize(contents)
|
||||||
|
|
||||||
|
new_containers = self._containers.copy()
|
||||||
|
while len(new_containers) < len(_ContainerIndexes.IndexTypeMap):
|
||||||
|
new_containers.append(self._empty_instance_container)
|
||||||
|
|
||||||
|
# Validate and ensure the list of containers matches with what we expect
|
||||||
|
for index, type_name in _ContainerIndexes.IndexTypeMap.items():
|
||||||
|
try:
|
||||||
|
container = new_containers[index]
|
||||||
|
except IndexError:
|
||||||
|
container = None
|
||||||
|
|
||||||
|
if type_name == "definition":
|
||||||
|
if not container or not isinstance(container, DefinitionContainer):
|
||||||
|
definition = self.findContainer(container_type = DefinitionContainer)
|
||||||
|
if not definition:
|
||||||
|
raise InvalidContainerStackError("Stack {id} does not have a definition!".format(id = self._id))
|
||||||
|
|
||||||
|
new_containers[index] = definition
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not container or container.getMetaDataEntry("type") != type_name:
|
||||||
|
actual_container = self.findContainer(type = type_name)
|
||||||
|
if actual_container:
|
||||||
|
new_containers[index] = actual_container
|
||||||
|
else:
|
||||||
|
new_containers[index] = self._empty_instance_container
|
||||||
|
|
||||||
|
self._containers = new_containers
|
||||||
|
|
||||||
|
## Find the variant that should be used as "default" variant.
|
||||||
|
#
|
||||||
|
# This will search for variants that match the current definition and pick the preferred one,
|
||||||
|
# if specified by the machine definition.
|
||||||
|
#
|
||||||
|
# The following criteria are used to find the default variant:
|
||||||
|
# - If the machine definition does not have a metadata entry "has_variants" set to True, return None
|
||||||
|
# - The definition of the variant should be the same as the machine definition for this stack.
|
||||||
|
# - The container should have a metadata entry "type" with value "variant".
|
||||||
|
# - If the machine definition has a metadata entry "preferred_variant", filter the variant IDs based on that.
|
||||||
|
#
|
||||||
|
# \return The container that should be used as default, or None if nothing was found or the machine does not use variants.
|
||||||
|
#
|
||||||
|
# \note This method assumes the stack has a valid machine definition.
|
||||||
|
def findDefaultVariant(self) -> Optional[ContainerInterface]:
|
||||||
|
definition = self._getMachineDefinition()
|
||||||
|
if not definition.getMetaDataEntry("has_variants"):
|
||||||
|
# If the machine does not use variants, we should never set a variant.
|
||||||
|
return None
|
||||||
|
|
||||||
|
# First add any variant. Later, overwrite with preference if the preference is valid.
|
||||||
|
variant = None
|
||||||
|
definition_id = self._findInstanceContainerDefinitionId(definition)
|
||||||
|
variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant")
|
||||||
|
if variants:
|
||||||
|
variant = variants[0]
|
||||||
|
|
||||||
|
preferred_variant_id = definition.getMetaDataEntry("preferred_variant")
|
||||||
|
if preferred_variant_id:
|
||||||
|
preferred_variants = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_id, definition = definition_id, type = "variant")
|
||||||
|
if preferred_variants:
|
||||||
|
variant = preferred_variants[0]
|
||||||
|
else:
|
||||||
|
Logger.log("w", "The preferred variant \"{variant}\" of stack {stack} does not exist or is not a variant.", variant = preferred_variant_id, stack = self.id)
|
||||||
|
# And leave it at the default variant.
|
||||||
|
|
||||||
|
if variant:
|
||||||
|
return variant
|
||||||
|
|
||||||
|
Logger.log("w", "Could not find a valid default variant for stack {stack}", stack = self.id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
## Find the material that should be used as "default" material.
|
||||||
|
#
|
||||||
|
# This will search for materials that match the current definition and pick the preferred one,
|
||||||
|
# if specified by the machine definition.
|
||||||
|
#
|
||||||
|
# The following criteria are used to find the default material:
|
||||||
|
# - If the machine definition does not have a metadata entry "has_materials" set to True, return None
|
||||||
|
# - If the machine definition has a metadata entry "has_machine_materials", the definition of the material should
|
||||||
|
# be the same as the machine definition for this stack. Otherwise, the definition should be "fdmprinter".
|
||||||
|
# - The container should have a metadata entry "type" with value "material".
|
||||||
|
# - If the machine definition has a metadata entry "has_variants" and set to True, the "variant" metadata entry of
|
||||||
|
# the material should be the same as the ID of the variant in the stack. Only applies if "has_machine_materials" is also True.
|
||||||
|
# - If the stack currently has a material set, try to find a material that matches the current material by name.
|
||||||
|
# - Otherwise, if the machine definition has a metadata entry "preferred_material", try to find a material that matches the specified ID.
|
||||||
|
#
|
||||||
|
# \return The container that should be used as default, or None if nothing was found or the machine does not use materials.
|
||||||
|
def findDefaultMaterial(self) -> Optional[ContainerInterface]:
|
||||||
|
definition = self._getMachineDefinition()
|
||||||
|
if not definition.getMetaDataEntry("has_materials"):
|
||||||
|
# Machine does not use materials, never try to set it.
|
||||||
|
return None
|
||||||
|
|
||||||
|
search_criteria = {"type": "material"}
|
||||||
|
if definition.getMetaDataEntry("has_machine_materials"):
|
||||||
|
search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
|
||||||
|
|
||||||
|
if definition.getMetaDataEntry("has_variants"):
|
||||||
|
search_criteria["variant"] = self.variant.id
|
||||||
|
else:
|
||||||
|
search_criteria["definition"] = "fdmprinter"
|
||||||
|
|
||||||
|
if self.material != self._empty_instance_container:
|
||||||
|
search_criteria["name"] = self.material.name
|
||||||
|
else:
|
||||||
|
preferred_material = definition.getMetaDataEntry("preferred_material")
|
||||||
|
if preferred_material:
|
||||||
|
search_criteria["id"] = preferred_material
|
||||||
|
|
||||||
|
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||||
|
if not materials:
|
||||||
|
Logger.log("w", "The preferred material \"{material}\" could not be found for stack {stack}", material = preferred_material, stack = self.id)
|
||||||
|
# We failed to find any materials matching the specified criteria, drop some specific criteria and try to find
|
||||||
|
# a material that sort-of matches what we want.
|
||||||
|
search_criteria.pop("variant", None)
|
||||||
|
search_criteria.pop("id", None)
|
||||||
|
search_criteria.pop("name", None)
|
||||||
|
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
||||||
|
|
||||||
|
if materials:
|
||||||
|
return materials[0]
|
||||||
|
|
||||||
|
Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
## Find the quality that should be used as "default" quality.
|
||||||
|
#
|
||||||
|
# This will search for qualities that match the current definition and pick the preferred one,
|
||||||
|
# if specified by the machine definition.
|
||||||
|
#
|
||||||
|
# \return The container that should be used as default, or None if nothing was found.
|
||||||
|
def findDefaultQuality(self) -> Optional[ContainerInterface]:
|
||||||
|
definition = self._getMachineDefinition()
|
||||||
|
registry = ContainerRegistry.getInstance()
|
||||||
|
material_container = self.material if self.material != self._empty_instance_container else None
|
||||||
|
|
||||||
|
search_criteria = {"type": "quality"}
|
||||||
|
|
||||||
|
if definition.getMetaDataEntry("has_machine_quality"):
|
||||||
|
search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
|
||||||
|
|
||||||
|
if definition.getMetaDataEntry("has_materials") and material_container:
|
||||||
|
search_criteria["material"] = material_container.id
|
||||||
|
else:
|
||||||
|
search_criteria["definition"] = "fdmprinter"
|
||||||
|
|
||||||
|
if self.quality != self._empty_instance_container:
|
||||||
|
search_criteria["name"] = self.quality.name
|
||||||
|
else:
|
||||||
|
preferred_quality = definition.getMetaDataEntry("preferred_quality")
|
||||||
|
if preferred_quality:
|
||||||
|
search_criteria["id"] = preferred_quality
|
||||||
|
|
||||||
|
containers = registry.findInstanceContainers(**search_criteria)
|
||||||
|
if containers:
|
||||||
|
return containers[0]
|
||||||
|
|
||||||
|
if "material" in search_criteria:
|
||||||
|
# First check if we can solve our material not found problem by checking if we can find quality containers
|
||||||
|
# that are assigned to the parents of this material profile.
|
||||||
|
try:
|
||||||
|
inherited_files = material_container.getInheritedFiles()
|
||||||
|
except AttributeError: # Material_container does not support inheritance.
|
||||||
|
inherited_files = []
|
||||||
|
|
||||||
|
if inherited_files:
|
||||||
|
for inherited_file in inherited_files:
|
||||||
|
# Extract the ID from the path we used to load the file.
|
||||||
|
search_criteria["material"] = os.path.basename(inherited_file).split(".")[0]
|
||||||
|
containers = registry.findInstanceContainers(**search_criteria)
|
||||||
|
if containers:
|
||||||
|
return containers[0]
|
||||||
|
|
||||||
|
# We still weren't able to find a quality for this specific material.
|
||||||
|
# Try to find qualities for a generic version of the material.
|
||||||
|
material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
|
||||||
|
if definition.getMetaDataEntry("has_machine_quality"):
|
||||||
|
if self.material != self._empty_instance_container:
|
||||||
|
material_search_criteria["definition"] = material_container.getDefinition().id
|
||||||
|
|
||||||
|
if definition.getMetaDataEntry("has_variants"):
|
||||||
|
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
|
||||||
|
else:
|
||||||
|
material_search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
|
||||||
|
|
||||||
|
if definition.getMetaDataEntry("has_variants") and self.variant != self._empty_instance_container:
|
||||||
|
material_search_criteria["variant"] = self.variant.id
|
||||||
|
else:
|
||||||
|
material_search_criteria["definition"] = "fdmprinter"
|
||||||
|
material_containers = registry.findInstanceContainers(**material_search_criteria)
|
||||||
|
# Try all materials to see if there is a quality profile available.
|
||||||
|
for material_container in material_containers:
|
||||||
|
search_criteria["material"] = material_container.getId()
|
||||||
|
|
||||||
|
containers = registry.findInstanceContainers(**search_criteria)
|
||||||
|
if containers:
|
||||||
|
return containers[0]
|
||||||
|
|
||||||
|
if "name" in search_criteria or "id" in search_criteria:
|
||||||
|
# If a quality by this name can not be found, try a wider set of search criteria
|
||||||
|
search_criteria.pop("name", None)
|
||||||
|
search_criteria.pop("id", None)
|
||||||
|
|
||||||
|
containers = registry.findInstanceContainers(**search_criteria)
|
||||||
|
if containers:
|
||||||
|
return containers[0]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
## protected:
|
||||||
|
|
||||||
|
# Helper to make sure we emit a PyQt signal on container changes.
|
||||||
|
def _onContainersChanged(self, container: Any) -> None:
|
||||||
|
self.pyqtContainersChanged.emit()
|
||||||
|
|
||||||
|
# Helper that can be overridden to get the "machine" definition, that is, the definition that defines the machine
|
||||||
|
# and its properties rather than, for example, the extruder. Defaults to simply returning the definition property.
|
||||||
|
def _getMachineDefinition(self) -> DefinitionContainer:
|
||||||
|
return self.definition
|
||||||
|
|
||||||
|
## Find the ID that should be used when searching for instance containers for a specified definition.
|
||||||
|
#
|
||||||
|
# This handles the situation where the definition specifies we should use a different definition when
|
||||||
|
# searching for instance containers.
|
||||||
|
#
|
||||||
|
# \param machine_definition The definition to find the "quality definition" for.
|
||||||
|
#
|
||||||
|
# \return The ID of the definition container to use when searching for instance containers.
|
||||||
|
@classmethod
|
||||||
|
def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainer) -> str:
|
||||||
|
quality_definition = machine_definition.getMetaDataEntry("quality_definition")
|
||||||
|
if not quality_definition:
|
||||||
|
return machine_definition.id
|
||||||
|
|
||||||
|
definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition)
|
||||||
|
if not definitions:
|
||||||
|
Logger.log("w", "Unable to find parent definition {parent} for machine {machine}", parent = quality_definition, machine = machine_definition.id)
|
||||||
|
return machine_definition.id
|
||||||
|
|
||||||
|
return cls._findInstanceContainerDefinitionId(definitions[0])
|
||||||
|
|
||||||
|
## private:
|
||||||
|
|
||||||
|
# Private helper class to keep track of container positions and their types.
|
||||||
|
class _ContainerIndexes:
|
||||||
|
UserChanges = 0
|
||||||
|
QualityChanges = 1
|
||||||
|
Quality = 2
|
||||||
|
Material = 3
|
||||||
|
Variant = 4
|
||||||
|
DefinitionChanges = 5
|
||||||
|
Definition = 6
|
||||||
|
|
||||||
|
# Simple hash map to map from index to "type" metadata entry
|
||||||
|
IndexTypeMap = {
|
||||||
|
UserChanges: "user",
|
||||||
|
QualityChanges: "quality_changes",
|
||||||
|
Quality: "quality",
|
||||||
|
Material: "material",
|
||||||
|
Variant: "variant",
|
||||||
|
DefinitionChanges: "definition_changes",
|
||||||
|
Definition: "definition",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reverse lookup: type -> index
|
||||||
|
TypeIndexMap = dict([(v, k) for k, v in IndexTypeMap.items()])
|
152
cura/Settings/CuraStackBuilder.py
Normal file
152
cura/Settings/CuraStackBuilder.py
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
|
||||||
|
from .GlobalStack import GlobalStack
|
||||||
|
from .ExtruderStack import ExtruderStack
|
||||||
|
from .CuraContainerStack import CuraContainerStack
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
## Contains helper functions to create new machines.
|
||||||
|
class CuraStackBuilder:
|
||||||
|
## Create a new instance of a machine.
|
||||||
|
#
|
||||||
|
# \param name The name of the new machine.
|
||||||
|
# \param definition_id The ID of the machine definition to use.
|
||||||
|
#
|
||||||
|
# \return The new global stack or None if an error occurred.
|
||||||
|
@classmethod
|
||||||
|
def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]:
|
||||||
|
registry = ContainerRegistry.getInstance()
|
||||||
|
definitions = registry.findDefinitionContainers(id = definition_id)
|
||||||
|
if not definitions:
|
||||||
|
Logger.log("w", "Definition {definition} was not found!", definition = definition_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
machine_definition = definitions[0]
|
||||||
|
name = registry.createUniqueName("machine", "", name, machine_definition.name)
|
||||||
|
|
||||||
|
new_global_stack = cls.createGlobalStack(
|
||||||
|
new_stack_id = name,
|
||||||
|
definition = machine_definition,
|
||||||
|
quality = "default",
|
||||||
|
material = "default",
|
||||||
|
variant = "default",
|
||||||
|
)
|
||||||
|
|
||||||
|
for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id):
|
||||||
|
position = extruder_definition.getMetaDataEntry("position", None)
|
||||||
|
if not position:
|
||||||
|
Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id)
|
||||||
|
|
||||||
|
new_extruder_id = registry.uniqueName(extruder_definition.id)
|
||||||
|
new_extruder = cls.createExtruderStack(
|
||||||
|
new_extruder_id,
|
||||||
|
definition = extruder_definition,
|
||||||
|
machine_definition = machine_definition,
|
||||||
|
quality = "default",
|
||||||
|
material = "default",
|
||||||
|
variant = "default",
|
||||||
|
next_stack = new_global_stack
|
||||||
|
)
|
||||||
|
|
||||||
|
return new_global_stack
|
||||||
|
|
||||||
|
## Create a new Extruder stack
|
||||||
|
#
|
||||||
|
# \param new_stack_id The ID of the new stack.
|
||||||
|
# \param definition The definition to base the new stack on.
|
||||||
|
# \param machine_definition The machine definition to use for the user container.
|
||||||
|
# \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
|
||||||
|
#
|
||||||
|
# \return A new Global stack instance with the specified parameters.
|
||||||
|
@classmethod
|
||||||
|
def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainer, machine_definition: DefinitionContainer, **kwargs) -> ExtruderStack:
|
||||||
|
stack = ExtruderStack(new_stack_id)
|
||||||
|
stack.setName(definition.getName())
|
||||||
|
stack.setDefinition(definition)
|
||||||
|
stack.addMetaDataEntry("position", definition.getMetaDataEntry("position"))
|
||||||
|
|
||||||
|
user_container = InstanceContainer(new_stack_id + "_user")
|
||||||
|
user_container.addMetaDataEntry("type", "user")
|
||||||
|
user_container.addMetaDataEntry("extruder", new_stack_id)
|
||||||
|
user_container.setDefinition(machine_definition)
|
||||||
|
|
||||||
|
stack.setUserChanges(user_container)
|
||||||
|
|
||||||
|
if "next_stack" in kwargs:
|
||||||
|
stack.setNextStack(kwargs["next_stack"])
|
||||||
|
|
||||||
|
# Important! The order here matters, because that allows the stack to
|
||||||
|
# assume the material and variant have already been set.
|
||||||
|
if "definition_changes" in kwargs:
|
||||||
|
stack.setDefinitionChangesById(kwargs["definition_changes"])
|
||||||
|
|
||||||
|
if "variant" in kwargs:
|
||||||
|
stack.setVariantById(kwargs["variant"])
|
||||||
|
|
||||||
|
if "material" in kwargs:
|
||||||
|
stack.setMaterialById(kwargs["material"])
|
||||||
|
|
||||||
|
if "quality" in kwargs:
|
||||||
|
stack.setQualityById(kwargs["quality"])
|
||||||
|
|
||||||
|
if "quality_changes" in kwargs:
|
||||||
|
stack.setQualityChangesById(kwargs["quality_changes"])
|
||||||
|
|
||||||
|
# Only add the created containers to the registry after we have set all the other
|
||||||
|
# properties. This makes the create operation more transactional, since any problems
|
||||||
|
# setting properties will not result in incomplete containers being added.
|
||||||
|
registry = ContainerRegistry.getInstance()
|
||||||
|
registry.addContainer(stack)
|
||||||
|
registry.addContainer(user_container)
|
||||||
|
|
||||||
|
return stack
|
||||||
|
|
||||||
|
## Create a new Global stack
|
||||||
|
#
|
||||||
|
# \param new_stack_id The ID of the new stack.
|
||||||
|
# \param definition The definition to base the new stack on.
|
||||||
|
# \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
|
||||||
|
#
|
||||||
|
# \return A new Global stack instance with the specified parameters.
|
||||||
|
@classmethod
|
||||||
|
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainer, **kwargs) -> GlobalStack:
|
||||||
|
stack = GlobalStack(new_stack_id)
|
||||||
|
stack.setDefinition(definition)
|
||||||
|
|
||||||
|
user_container = InstanceContainer(new_stack_id + "_user")
|
||||||
|
user_container.addMetaDataEntry("type", "user")
|
||||||
|
user_container.addMetaDataEntry("machine", new_stack_id)
|
||||||
|
user_container.setDefinition(definition)
|
||||||
|
|
||||||
|
stack.setUserChanges(user_container)
|
||||||
|
|
||||||
|
# Important! The order here matters, because that allows the stack to
|
||||||
|
# assume the material and variant have already been set.
|
||||||
|
if "definition_changes" in kwargs:
|
||||||
|
stack.setDefinitionChangesById(kwargs["definition_changes"])
|
||||||
|
|
||||||
|
if "variant" in kwargs:
|
||||||
|
stack.setVariantById(kwargs["variant"])
|
||||||
|
|
||||||
|
if "material" in kwargs:
|
||||||
|
stack.setMaterialById(kwargs["material"])
|
||||||
|
|
||||||
|
if "quality" in kwargs:
|
||||||
|
stack.setQualityById(kwargs["quality"])
|
||||||
|
|
||||||
|
if "quality_changes" in kwargs:
|
||||||
|
stack.setQualityChangesById(kwargs["quality_changes"])
|
||||||
|
|
||||||
|
registry = ContainerRegistry.getInstance()
|
||||||
|
registry.addContainer(stack)
|
||||||
|
registry.addContainer(user_container)
|
||||||
|
|
||||||
|
return stack
|
22
cura/Settings/Exceptions.py
Normal file
22
cura/Settings/Exceptions.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
|
||||||
|
## Raised when trying to perform an operation like add on a stack that does not allow that.
|
||||||
|
class InvalidOperationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
## Raised when trying to replace a container with a container that does not have the expected type.
|
||||||
|
class InvalidContainerError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
## Raised when trying to add an extruder to a Global stack that already has the maximum number of extruders.
|
||||||
|
class TooManyExtrudersError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
## Raised when an extruder has no next stack set.
|
||||||
|
class NoGlobalStackError(Exception):
|
||||||
|
pass
|
|
@ -6,14 +6,17 @@ from UM.FlameProfiler import pyqtSlot
|
||||||
|
|
||||||
from UM.Application import Application #To get the global container stack to find the current machine.
|
from UM.Application import Application #To get the global container stack to find the current machine.
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.Decorators import deprecated
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from UM.Scene.Selection import Selection
|
||||||
|
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers by ID.
|
from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers by ID.
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
from typing import Optional
|
from typing import Optional, List
|
||||||
|
|
||||||
## Manages all existing extruder stacks.
|
## Manages all existing extruder stacks.
|
||||||
#
|
#
|
||||||
|
@ -32,12 +35,15 @@ class ExtruderManager(QObject):
|
||||||
## Registers listeners and such to listen to changes to the extruders.
|
## Registers listeners and such to listen to changes to the extruders.
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs.
|
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
|
||||||
self._active_extruder_index = 0
|
self._active_extruder_index = 0
|
||||||
|
self._selected_object_extruders = []
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
|
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
|
||||||
self._global_container_stack_definition_id = None
|
self._global_container_stack_definition_id = None
|
||||||
self._addCurrentMachineExtruders()
|
self._addCurrentMachineExtruders()
|
||||||
|
|
||||||
|
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
||||||
|
|
||||||
## Gets the unique identifier of the currently active extruder stack.
|
## Gets the unique identifier of the currently active extruder stack.
|
||||||
#
|
#
|
||||||
# The currently active extruder stack is the stack that is currently being
|
# The currently active extruder stack is the stack that is currently being
|
||||||
|
@ -117,6 +123,49 @@ class ExtruderManager(QObject):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
## Emitted whenever the selectedObjectExtruders property changes.
|
||||||
|
selectedObjectExtrudersChanged = pyqtSignal()
|
||||||
|
|
||||||
|
## Provides a list of extruder IDs used by the current selected objects.
|
||||||
|
@pyqtProperty("QVariantList", notify = selectedObjectExtrudersChanged)
|
||||||
|
def selectedObjectExtruders(self) -> List[str]:
|
||||||
|
if not self._selected_object_extruders:
|
||||||
|
object_extruders = set()
|
||||||
|
|
||||||
|
# First, build a list of the actual selected objects (including children of groups, excluding group nodes)
|
||||||
|
selected_nodes = []
|
||||||
|
for node in Selection.getAllSelectedObjects():
|
||||||
|
if node.callDecoration("isGroup"):
|
||||||
|
for grouped_node in BreadthFirstIterator(node):
|
||||||
|
if grouped_node.callDecoration("isGroup"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
selected_nodes.append(grouped_node)
|
||||||
|
else:
|
||||||
|
selected_nodes.append(node)
|
||||||
|
|
||||||
|
# Then, figure out which nodes are used by those selected nodes.
|
||||||
|
for node in selected_nodes:
|
||||||
|
extruder = node.callDecoration("getActiveExtruder")
|
||||||
|
if extruder:
|
||||||
|
object_extruders.add(extruder)
|
||||||
|
else:
|
||||||
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_stack.getId() in self._extruder_trains:
|
||||||
|
object_extruders.add(self._extruder_trains[global_stack.getId()]["0"].getId())
|
||||||
|
|
||||||
|
self._selected_object_extruders = list(object_extruders)
|
||||||
|
|
||||||
|
return self._selected_object_extruders
|
||||||
|
|
||||||
|
## Reset the internal list used for the selectedObjectExtruders property
|
||||||
|
#
|
||||||
|
# This will trigger a recalculation of the extruders used for the
|
||||||
|
# selection.
|
||||||
|
def resetSelectedObjectExtruders(self) -> None:
|
||||||
|
self._selected_object_extruders = []
|
||||||
|
self.selectedObjectExtrudersChanged.emit()
|
||||||
|
|
||||||
def getActiveExtruderStack(self) -> ContainerStack:
|
def getActiveExtruderStack(self) -> ContainerStack:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
|
@ -147,6 +196,7 @@ class ExtruderManager(QObject):
|
||||||
#
|
#
|
||||||
# \param machine_definition The machine definition to add the extruders for.
|
# \param machine_definition The machine definition to add the extruders for.
|
||||||
# \param machine_id The machine_id to add the extruders for.
|
# \param machine_id The machine_id to add the extruders for.
|
||||||
|
@deprecated("Use CuraStackBuilder", "2.6")
|
||||||
def addMachineExtruders(self, machine_definition: DefinitionContainer, machine_id: str) -> None:
|
def addMachineExtruders(self, machine_definition: DefinitionContainer, machine_id: str) -> None:
|
||||||
changed = False
|
changed = False
|
||||||
machine_definition_id = machine_definition.getId()
|
machine_definition_id = machine_definition.getId()
|
||||||
|
@ -199,6 +249,7 @@ class ExtruderManager(QObject):
|
||||||
# \param machine_definition The machine that the extruder train belongs to.
|
# \param machine_definition The machine that the extruder train belongs to.
|
||||||
# \param position The position of this extruder train in the extruder slots of the machine.
|
# \param position The position of this extruder train in the extruder slots of the machine.
|
||||||
# \param machine_id The id of the "global" stack this extruder is linked to.
|
# \param machine_id The id of the "global" stack this extruder is linked to.
|
||||||
|
@deprecated("Use CuraStackBuilder::createExtruderStack", "2.6")
|
||||||
def createExtruderTrain(self, extruder_definition: DefinitionContainer, machine_definition: DefinitionContainer,
|
def createExtruderTrain(self, extruder_definition: DefinitionContainer, machine_definition: DefinitionContainer,
|
||||||
position, machine_id: str) -> None:
|
position, machine_id: str) -> None:
|
||||||
# Cache some things.
|
# Cache some things.
|
||||||
|
@ -357,7 +408,8 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
#Get the extruders of all meshes in the scene.
|
#Get the extruders of all meshes in the scene.
|
||||||
support_enabled = False
|
support_enabled = False
|
||||||
support_interface_enabled = False
|
support_bottom_enabled = False
|
||||||
|
support_roof_enabled = False
|
||||||
scene_root = Application.getInstance().getController().getScene().getRoot()
|
scene_root = Application.getInstance().getController().getScene().getRoot()
|
||||||
meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()] #Only use the nodes that will be printed.
|
meshes = [node for node in DepthFirstIterator(scene_root) if type(node) is SceneNode and node.isSelectable()] #Only use the nodes that will be printed.
|
||||||
for mesh in meshes:
|
for mesh in meshes:
|
||||||
|
@ -370,18 +422,22 @@ class ExtruderManager(QObject):
|
||||||
per_mesh_stack = mesh.callDecoration("getStack")
|
per_mesh_stack = mesh.callDecoration("getStack")
|
||||||
if per_mesh_stack:
|
if per_mesh_stack:
|
||||||
support_enabled |= per_mesh_stack.getProperty("support_enable", "value")
|
support_enabled |= per_mesh_stack.getProperty("support_enable", "value")
|
||||||
support_interface_enabled |= per_mesh_stack.getProperty("support_interface_enable", "value")
|
support_bottom_enabled |= per_mesh_stack.getProperty("support_bottom_enable", "value")
|
||||||
|
support_roof_enabled |= per_mesh_stack.getProperty("support_roof_enable", "value")
|
||||||
else: #Take the setting from the build extruder stack.
|
else: #Take the setting from the build extruder stack.
|
||||||
extruder_stack = container_registry.findContainerStacks(id = extruder_stack_id)[0]
|
extruder_stack = container_registry.findContainerStacks(id = extruder_stack_id)[0]
|
||||||
support_enabled |= extruder_stack.getProperty("support_enable", "value")
|
support_enabled |= extruder_stack.getProperty("support_enable", "value")
|
||||||
support_interface_enabled |= extruder_stack.getProperty("support_enable", "value")
|
support_bottom_enabled |= extruder_stack.getProperty("support_bottom_enable", "value")
|
||||||
|
support_roof_enabled |= extruder_stack.getProperty("support_roof_enable", "value")
|
||||||
|
|
||||||
#The support extruders.
|
#The support extruders.
|
||||||
if support_enabled:
|
if support_enabled:
|
||||||
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_infill_extruder_nr", "value"))])
|
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_infill_extruder_nr", "value"))])
|
||||||
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_extruder_nr_layer_0", "value"))])
|
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_extruder_nr_layer_0", "value"))])
|
||||||
if support_interface_enabled:
|
if support_bottom_enabled:
|
||||||
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_interface_extruder_nr", "value"))])
|
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_bottom_extruder_nr", "value"))])
|
||||||
|
if support_roof_enabled:
|
||||||
|
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_roof_extruder_nr", "value"))])
|
||||||
|
|
||||||
#The platform adhesion extruder. Not used if using none.
|
#The platform adhesion extruder. Not used if using none.
|
||||||
if global_stack.getProperty("adhesion_type", "value") != "none":
|
if global_stack.getProperty("adhesion_type", "value") != "none":
|
||||||
|
@ -407,7 +463,6 @@ class ExtruderManager(QObject):
|
||||||
# \param machine_id The machine to get the extruders of.
|
# \param machine_id The machine to get the extruders of.
|
||||||
def getMachineExtruders(self, machine_id):
|
def getMachineExtruders(self, machine_id):
|
||||||
if machine_id not in self._extruder_trains:
|
if machine_id not in self._extruder_trains:
|
||||||
Logger.log("w", "Tried to get the extruder trains for machine %s, which doesn't exist.", machine_id)
|
|
||||||
return []
|
return []
|
||||||
return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
|
return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
|
||||||
|
|
||||||
|
@ -431,19 +486,20 @@ class ExtruderManager(QObject):
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
if global_stack:
|
if global_stack and global_stack.getId() in self._extruder_trains:
|
||||||
for extruder in sorted(self._extruder_trains[global_stack.getId()]):
|
for extruder in sorted(self._extruder_trains[global_stack.getId()]):
|
||||||
result.append(self._extruder_trains[global_stack.getId()][extruder])
|
result.append(self._extruder_trains[global_stack.getId()][extruder])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __globalContainerStackChanged(self) -> None:
|
def __globalContainerStackChanged(self) -> None:
|
||||||
self._addCurrentMachineExtruders()
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id:
|
if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id:
|
||||||
self._global_container_stack_definition_id = global_container_stack.getBottom().getId()
|
self._global_container_stack_definition_id = global_container_stack.getBottom().getId()
|
||||||
self.globalContainerStackDefinitionChanged.emit()
|
self.globalContainerStackDefinitionChanged.emit()
|
||||||
self.activeExtruderChanged.emit()
|
self.activeExtruderChanged.emit()
|
||||||
|
|
||||||
|
self.resetSelectedObjectExtruders()
|
||||||
|
|
||||||
## Adds the extruders of the currently active machine.
|
## Adds the extruders of the currently active machine.
|
||||||
def _addCurrentMachineExtruders(self) -> None:
|
def _addCurrentMachineExtruders(self) -> None:
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
@ -464,6 +520,10 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
||||||
|
# only include values from extruders that are "active" for the current machine instance
|
||||||
|
if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"):
|
||||||
|
continue
|
||||||
|
|
||||||
value = extruder.getRawProperty(key, "value")
|
value = extruder.getRawProperty(key, "value")
|
||||||
|
|
||||||
if value is None:
|
if value is None:
|
||||||
|
@ -522,18 +582,6 @@ class ExtruderManager(QObject):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getResolveOrValue(key):
|
def getResolveOrValue(key):
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
resolved_value = global_stack.getProperty(key, "value")
|
||||||
|
|
||||||
resolved_value = global_stack.getProperty(key, "resolve")
|
return resolved_value
|
||||||
if resolved_value is not None:
|
|
||||||
user_container = global_stack.findContainer({"type": "user"})
|
|
||||||
quality_changes_container = global_stack.findContainer({"type": "quality_changes"})
|
|
||||||
if user_container.hasProperty(key, "value") or quality_changes_container.hasProperty(key, "value"):
|
|
||||||
# Normal case
|
|
||||||
value = global_stack.getProperty(key, "value")
|
|
||||||
else:
|
|
||||||
# We have a resolved value and we're using it because of no user and quality_changes value
|
|
||||||
value = resolved_value
|
|
||||||
else:
|
|
||||||
value = global_stack.getRawProperty(key, "value")
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
85
cura/Settings/ExtruderStack.py
Normal file
85
cura/Settings/ExtruderStack.py
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
|
from UM.Decorators import override
|
||||||
|
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
|
|
||||||
|
from . import Exceptions
|
||||||
|
from .CuraContainerStack import CuraContainerStack
|
||||||
|
from .ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
|
## Represents an Extruder and its related containers.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
class ExtruderStack(CuraContainerStack):
|
||||||
|
def __init__(self, container_id, *args, **kwargs):
|
||||||
|
super().__init__(container_id, *args, **kwargs)
|
||||||
|
|
||||||
|
self.addMetaDataEntry("type", "extruder_train") # For backward compatibility
|
||||||
|
|
||||||
|
## Overridden from ContainerStack
|
||||||
|
#
|
||||||
|
# This will set the next stack and ensure that we register this stack as an extruder.
|
||||||
|
@override(ContainerStack)
|
||||||
|
def setNextStack(self, stack: ContainerStack) -> None:
|
||||||
|
super().setNextStack(stack)
|
||||||
|
stack.addExtruder(self)
|
||||||
|
self.addMetaDataEntry("machine", stack.id)
|
||||||
|
|
||||||
|
# For backward compatibility: Register the extruder with the Extruder Manager
|
||||||
|
ExtruderManager.getInstance().registerExtruder(self, stack.id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getLoadingPriority(cls) -> int:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
## Overridden from ContainerStack
|
||||||
|
#
|
||||||
|
# It will perform a few extra checks when trying to get properties.
|
||||||
|
#
|
||||||
|
# The two extra checks it currently does is to ensure a next stack is set and to bypass
|
||||||
|
# the extruder when the property is not settable per extruder.
|
||||||
|
#
|
||||||
|
# \throws Exceptions.NoGlobalStackError Raised when trying to get a property from an extruder without
|
||||||
|
# having a next stack set.
|
||||||
|
@override(ContainerStack)
|
||||||
|
def getProperty(self, key: str, property_name: str) -> Any:
|
||||||
|
if not self._next_stack:
|
||||||
|
raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id))
|
||||||
|
|
||||||
|
if not super().getProperty(key, "settable_per_extruder"):
|
||||||
|
return self.getNextStack().getProperty(key, property_name)
|
||||||
|
|
||||||
|
return super().getProperty(key, property_name)
|
||||||
|
|
||||||
|
@override(CuraContainerStack)
|
||||||
|
def _getMachineDefinition(self) -> ContainerInterface:
|
||||||
|
if not self.getNextStack():
|
||||||
|
raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id))
|
||||||
|
|
||||||
|
return self.getNextStack()._getMachineDefinition()
|
||||||
|
|
||||||
|
@override(CuraContainerStack)
|
||||||
|
def deserialize(self, contents: str) -> None:
|
||||||
|
super().deserialize(contents)
|
||||||
|
stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", ""))
|
||||||
|
if stacks:
|
||||||
|
self.setNextStack(stacks[0])
|
||||||
|
|
||||||
|
extruder_stack_mime = MimeType(
|
||||||
|
name = "application/x-cura-extruderstack",
|
||||||
|
comment = "Cura Extruder Stack",
|
||||||
|
suffixes = ["extruder.cfg"]
|
||||||
|
)
|
||||||
|
|
||||||
|
MimeTypeDatabase.addMimeType(extruder_stack_mime)
|
||||||
|
ContainerRegistry.addContainerTypeByName(ExtruderStack, "extruder_stack", extruder_stack_mime.name)
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (c) 2016 Ultimaker B.V.
|
# Copyright (c) 2016 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, pyqtSlot
|
||||||
|
|
||||||
import UM.Qt.ListModel
|
import UM.Qt.ListModel
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
@ -33,6 +33,12 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||||
# The ID of the definition of the extruder.
|
# The ID of the definition of the extruder.
|
||||||
DefinitionRole = Qt.UserRole + 5
|
DefinitionRole = Qt.UserRole + 5
|
||||||
|
|
||||||
|
# The material of the extruder.
|
||||||
|
MaterialRole = Qt.UserRole + 6
|
||||||
|
|
||||||
|
# The variant of the extruder.
|
||||||
|
VariantRole = Qt.UserRole + 7
|
||||||
|
|
||||||
## List of colours to display if there is no material or the material has no known
|
## List of colours to display if there is no material or the material has no known
|
||||||
# colour.
|
# colour.
|
||||||
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
||||||
|
@ -49,6 +55,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||||
self.addRoleName(self.ColorRole, "color")
|
self.addRoleName(self.ColorRole, "color")
|
||||||
self.addRoleName(self.IndexRole, "index")
|
self.addRoleName(self.IndexRole, "index")
|
||||||
self.addRoleName(self.DefinitionRole, "definition")
|
self.addRoleName(self.DefinitionRole, "definition")
|
||||||
|
self.addRoleName(self.MaterialRole, "material")
|
||||||
|
self.addRoleName(self.VariantRole, "variant")
|
||||||
|
|
||||||
self._add_global = False
|
self._add_global = False
|
||||||
self._simple_names = False
|
self._simple_names = False
|
||||||
|
@ -136,15 +144,20 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||||
items.append(item)
|
items.append(item)
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
|
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
|
||||||
manager = ExtruderManager.getInstance()
|
manager = ExtruderManager.getInstance()
|
||||||
for extruder in manager.getMachineExtruders(global_container_stack.getId()):
|
for extruder in manager.getMachineExtruders(global_container_stack.getId()):
|
||||||
extruder_name = extruder.getName()
|
extruder_name = extruder.getName()
|
||||||
material = extruder.findContainer({ "type": "material" })
|
material = extruder.findContainer({ "type": "material" })
|
||||||
|
variant = extruder.findContainer({"type": "variant"})
|
||||||
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
||||||
try:
|
try:
|
||||||
position = int(position)
|
position = int(position)
|
||||||
except ValueError: #Not a proper int.
|
except ValueError: #Not a proper int.
|
||||||
position = -1
|
position = -1
|
||||||
|
if position >= machine_extruder_count:
|
||||||
|
continue
|
||||||
|
|
||||||
default_color = self.defaultColors[position] if position >= 0 and position < len(self.defaultColors) else self.defaultColors[0]
|
default_color = self.defaultColors[position] if position >= 0 and position < len(self.defaultColors) else self.defaultColors[0]
|
||||||
color = material.getMetaDataEntry("color_code", default = default_color) if material else default_color
|
color = material.getMetaDataEntry("color_code", default = default_color) if material else default_color
|
||||||
item = { #Construct an item with only the relevant information.
|
item = { #Construct an item with only the relevant information.
|
||||||
|
@ -152,7 +165,9 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||||
"name": extruder_name,
|
"name": extruder_name,
|
||||||
"color": color,
|
"color": color,
|
||||||
"index": position,
|
"index": position,
|
||||||
"definition": extruder.getBottom().getId()
|
"definition": extruder.getBottom().getId(),
|
||||||
|
"material": material.getName() if material else "",
|
||||||
|
"variant": variant.getName() if variant else "",
|
||||||
}
|
}
|
||||||
items.append(item)
|
items.append(item)
|
||||||
changed = True
|
changed = True
|
||||||
|
|
123
cura/Settings/GlobalStack.py
Executable file
123
cura/Settings/GlobalStack.py
Executable file
|
@ -0,0 +1,123 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtProperty
|
||||||
|
|
||||||
|
from UM.Decorators import override
|
||||||
|
|
||||||
|
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
from UM.Settings.SettingInstance import InstanceState
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
from . import Exceptions
|
||||||
|
from .CuraContainerStack import CuraContainerStack
|
||||||
|
|
||||||
|
## Represents the Global or Machine stack and its related containers.
|
||||||
|
#
|
||||||
|
class GlobalStack(CuraContainerStack):
|
||||||
|
def __init__(self, container_id: str, *args, **kwargs):
|
||||||
|
super().__init__(container_id, *args, **kwargs)
|
||||||
|
|
||||||
|
self.addMetaDataEntry("type", "machine") # For backward compatibility
|
||||||
|
|
||||||
|
self._extruders = []
|
||||||
|
|
||||||
|
# This property is used to track which settings we are calculating the "resolve" for
|
||||||
|
# and if so, to bypass the resolve to prevent an infinite recursion that would occur
|
||||||
|
# if the resolve function tried to access the same property it is a resolve for.
|
||||||
|
self._resolving_settings = set()
|
||||||
|
|
||||||
|
## Get the list of extruders of this stack.
|
||||||
|
#
|
||||||
|
# \return The extruders registered with this stack.
|
||||||
|
@pyqtProperty("QVariantList")
|
||||||
|
def extruders(self) -> list:
|
||||||
|
return self._extruders
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def getLoadingPriority(cls) -> int:
|
||||||
|
return 2
|
||||||
|
|
||||||
|
## Add an extruder to the list of extruders of this stack.
|
||||||
|
#
|
||||||
|
# \param extruder The extruder to add.
|
||||||
|
#
|
||||||
|
# \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we
|
||||||
|
# already have the maximum number of extruders.
|
||||||
|
def addExtruder(self, extruder: ContainerStack) -> None:
|
||||||
|
extruder_count = self.getProperty("machine_extruder_count", "value")
|
||||||
|
if extruder_count and len(self._extruders) + 1 > extruder_count:
|
||||||
|
Logger.log("w", "Adding extruder {meta} to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count, meta = str(extruder.getMetaData())))
|
||||||
|
|
||||||
|
self._extruders.append(extruder)
|
||||||
|
|
||||||
|
## Overridden from ContainerStack
|
||||||
|
#
|
||||||
|
# This will return the value of the specified property for the specified setting,
|
||||||
|
# unless the property is "value" and that setting has a "resolve" function set.
|
||||||
|
# When a resolve is set, it will instead try and execute the resolve first and
|
||||||
|
# then fall back to the normal "value" property.
|
||||||
|
#
|
||||||
|
# \param key The setting key to get the property of.
|
||||||
|
# \param property_name The property to get the value of.
|
||||||
|
#
|
||||||
|
# \return The value of the property for the specified setting, or None if not found.
|
||||||
|
@override(ContainerStack)
|
||||||
|
def getProperty(self, key: str, property_name: str) -> Any:
|
||||||
|
if not self.definition.findDefinitions(key = key):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self._shouldResolve(key, property_name):
|
||||||
|
self._resolving_settings.add(key)
|
||||||
|
resolve = super().getProperty(key, "resolve")
|
||||||
|
self._resolving_settings.remove(key)
|
||||||
|
if resolve is not None:
|
||||||
|
return resolve
|
||||||
|
|
||||||
|
return super().getProperty(key, property_name)
|
||||||
|
|
||||||
|
## Overridden from ContainerStack
|
||||||
|
#
|
||||||
|
# This will simply raise an exception since the Global stack cannot have a next stack.
|
||||||
|
@override(ContainerStack)
|
||||||
|
def setNextStack(self, next_stack: ContainerStack) -> None:
|
||||||
|
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
|
||||||
|
|
||||||
|
# protected:
|
||||||
|
|
||||||
|
# Determine whether or not we should try to get the "resolve" property instead of the
|
||||||
|
# requested property.
|
||||||
|
def _shouldResolve(self, key: str, property_name: str) -> bool:
|
||||||
|
if property_name is not "value":
|
||||||
|
# Do not try to resolve anything but the "value" property
|
||||||
|
return False
|
||||||
|
|
||||||
|
if key in self._resolving_settings:
|
||||||
|
# To prevent infinite recursion, if getProperty is called with the same key as
|
||||||
|
# we are already trying to resolve, we should not try to resolve again. Since
|
||||||
|
# this can happen multiple times when trying to resolve a value, we need to
|
||||||
|
# track all settings that are being resolved.
|
||||||
|
return False
|
||||||
|
|
||||||
|
setting_state = super().getProperty(key, "state")
|
||||||
|
if setting_state is not None and setting_state != InstanceState.Default:
|
||||||
|
# When the user has explicitly set a value, we should ignore any resolve and
|
||||||
|
# just return that value.
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
## private:
|
||||||
|
global_stack_mime = MimeType(
|
||||||
|
name = "application/x-cura-globalstack",
|
||||||
|
comment = "Cura Global Stack",
|
||||||
|
suffixes = ["global.cfg"]
|
||||||
|
)
|
||||||
|
|
||||||
|
MimeTypeDatabase.addMimeType(global_stack_mime)
|
||||||
|
ContainerRegistry.addContainerTypeByName(GlobalStack, "global_stack", global_stack_mime.name)
|
|
@ -11,17 +11,23 @@ from UM.Application import Application
|
||||||
from UM.Preferences import Preferences
|
from UM.Preferences import Preferences
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
|
from UM.Decorators import deprecated
|
||||||
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
from UM.Settings.SettingDefinition import SettingDefinition
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
|
from UM.Settings.Validator import ValidatorState
|
||||||
from UM.Signal import postponeSignals
|
from UM.Signal import postponeSignals
|
||||||
|
|
||||||
from cura.QualityManager import QualityManager
|
from cura.QualityManager import QualityManager
|
||||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
|
from .GlobalStack import GlobalStack
|
||||||
|
from .CuraStackBuilder import CuraStackBuilder
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
@ -46,10 +52,12 @@ class MachineManager(QObject):
|
||||||
self.globalContainerChanged.connect(self.activeQualityChanged)
|
self.globalContainerChanged.connect(self.activeQualityChanged)
|
||||||
|
|
||||||
self._stacks_have_errors = None
|
self._stacks_have_errors = None
|
||||||
self._empty_variant_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_variant")[0]
|
|
||||||
self._empty_material_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_material")[0]
|
self._empty_variant_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
self._empty_quality_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality")[0]
|
self._empty_material_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
self._empty_quality_changes_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality_changes")[0]
|
self._empty_quality_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
|
self._empty_quality_changes_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
|
|
||||||
self._onGlobalContainerChanged()
|
self._onGlobalContainerChanged()
|
||||||
|
|
||||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
|
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
|
||||||
|
@ -226,14 +234,22 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
def _onGlobalContainerChanged(self):
|
def _onGlobalContainerChanged(self):
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
|
try:
|
||||||
self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
|
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
|
||||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that was already disconnected.
|
||||||
|
pass
|
||||||
material = self._global_container_stack.findContainer({"type": "material"})
|
try:
|
||||||
|
self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
material = self._global_container_stack.material
|
||||||
material.nameChanged.disconnect(self._onMaterialNameChanged)
|
material.nameChanged.disconnect(self._onMaterialNameChanged)
|
||||||
|
|
||||||
quality = self._global_container_stack.findContainer({"type": "quality"})
|
quality = self._global_container_stack.quality
|
||||||
quality.nameChanged.disconnect(self._onQualityNameChanged)
|
quality.nameChanged.disconnect(self._onQualityNameChanged)
|
||||||
|
|
||||||
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
|
||||||
|
@ -256,23 +272,23 @@ class MachineManager(QObject):
|
||||||
# For multi-extrusion machines, we do not want variant or material profiles in the stack,
|
# For multi-extrusion machines, we do not want variant or material profiles in the stack,
|
||||||
# because these are extruder specific and may cause wrong values to be used for extruders
|
# because these are extruder specific and may cause wrong values to be used for extruders
|
||||||
# that did not specify a value in the extruder.
|
# that did not specify a value in the extruder.
|
||||||
global_variant = self._global_container_stack.findContainer(type = "variant")
|
global_variant = self._global_container_stack.variant
|
||||||
if global_variant != self._empty_variant_container:
|
if global_variant != self._empty_variant_container:
|
||||||
self._global_container_stack.replaceContainer(self._global_container_stack.getContainerIndex(global_variant), self._empty_variant_container)
|
self._global_container_stack.setVariant(self._empty_variant_container)
|
||||||
|
|
||||||
global_material = self._global_container_stack.findContainer(type = "material")
|
global_material = self._global_container_stack.material
|
||||||
if global_material != self._empty_material_container:
|
if global_material != self._empty_material_container:
|
||||||
self._global_container_stack.replaceContainer(self._global_container_stack.getContainerIndex(global_material), self._empty_material_container)
|
self._global_container_stack.setMaterial(self._empty_material_container)
|
||||||
|
|
||||||
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): #Listen for changes on all extruder stacks.
|
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): #Listen for changes on all extruder stacks.
|
||||||
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
|
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||||
extruder_stack.containersChanged.connect(self._onInstanceContainersChanged)
|
extruder_stack.containersChanged.connect(self._onInstanceContainersChanged)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
material = self._global_container_stack.findContainer({"type": "material"})
|
material = self._global_container_stack.material
|
||||||
material.nameChanged.connect(self._onMaterialNameChanged)
|
material.nameChanged.connect(self._onMaterialNameChanged)
|
||||||
|
|
||||||
quality = self._global_container_stack.findContainer({"type": "quality"})
|
quality = self._global_container_stack.quality
|
||||||
quality.nameChanged.connect(self._onQualityNameChanged)
|
quality.nameChanged.connect(self._onQualityNameChanged)
|
||||||
|
|
||||||
self._updateStacksHaveErrors()
|
self._updateStacksHaveErrors()
|
||||||
|
@ -325,41 +341,11 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
def addMachine(self, name: str, definition_id: str) -> None:
|
def addMachine(self, name: str, definition_id: str) -> None:
|
||||||
container_registry = ContainerRegistry.getInstance()
|
new_stack = CuraStackBuilder.createMachine(name, definition_id)
|
||||||
definitions = container_registry.findDefinitionContainers(id = definition_id)
|
if new_stack:
|
||||||
if definitions:
|
Application.getInstance().setGlobalContainerStack(new_stack)
|
||||||
definition = definitions[0]
|
else:
|
||||||
name = self._createUniqueName("machine", "", name, definition.getName())
|
Logger.log("w", "Failed creating a new machine!")
|
||||||
new_global_stack = ContainerStack(name)
|
|
||||||
new_global_stack.addMetaDataEntry("type", "machine")
|
|
||||||
new_global_stack.addContainer(definition)
|
|
||||||
container_registry.addContainer(new_global_stack)
|
|
||||||
|
|
||||||
variant_instance_container = self._updateVariantContainer(definition)
|
|
||||||
material_instance_container = self._updateMaterialContainer(definition, new_global_stack, variant_instance_container)
|
|
||||||
quality_instance_container = self._updateQualityContainer(definition, variant_instance_container, material_instance_container)
|
|
||||||
|
|
||||||
current_settings_instance_container = InstanceContainer(name + "_current_settings")
|
|
||||||
current_settings_instance_container.addMetaDataEntry("machine", name)
|
|
||||||
current_settings_instance_container.addMetaDataEntry("type", "user")
|
|
||||||
current_settings_instance_container.setDefinition(definitions[0])
|
|
||||||
container_registry.addContainer(current_settings_instance_container)
|
|
||||||
|
|
||||||
|
|
||||||
if variant_instance_container:
|
|
||||||
new_global_stack.addContainer(variant_instance_container)
|
|
||||||
if material_instance_container:
|
|
||||||
new_global_stack.addContainer(material_instance_container)
|
|
||||||
if quality_instance_container:
|
|
||||||
new_global_stack.addContainer(quality_instance_container)
|
|
||||||
|
|
||||||
new_global_stack.addContainer(self._empty_quality_changes_container)
|
|
||||||
new_global_stack.addContainer(current_settings_instance_container)
|
|
||||||
|
|
||||||
ExtruderManager.getInstance().addMachineExtruders(definition, new_global_stack.getId())
|
|
||||||
|
|
||||||
Application.getInstance().setGlobalContainerStack(new_global_stack)
|
|
||||||
|
|
||||||
|
|
||||||
## Create a name that is not empty and unique
|
## Create a name that is not empty and unique
|
||||||
# \param container_type \type{string} Type of the container (machine, quality, ...)
|
# \param container_type \type{string} Type of the container (machine, quality, ...)
|
||||||
|
@ -478,6 +464,10 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@pyqtProperty("QObject", notify = globalContainerChanged)
|
||||||
|
def activeMachine(self) -> GlobalStack:
|
||||||
|
return self._global_container_stack
|
||||||
|
|
||||||
@pyqtProperty(str, notify = activeStackChanged)
|
@pyqtProperty(str, notify = activeStackChanged)
|
||||||
def activeStackId(self) -> str:
|
def activeStackId(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
|
@ -488,7 +478,7 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify = activeMaterialChanged)
|
@pyqtProperty(str, notify = activeMaterialChanged)
|
||||||
def activeMaterialName(self) -> str:
|
def activeMaterialName(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
material = self._active_container_stack.findContainer({"type":"material"})
|
material = self._active_container_stack.material
|
||||||
if material:
|
if material:
|
||||||
return material.getName()
|
return material.getName()
|
||||||
|
|
||||||
|
@ -499,18 +489,29 @@ class MachineManager(QObject):
|
||||||
result = []
|
result = []
|
||||||
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
|
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
|
||||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||||
variant_container = stack.findContainer({"type": "variant"})
|
variant_container = stack.variant
|
||||||
if variant_container and variant_container != self._empty_variant_container:
|
if variant_container and variant_container != self._empty_variant_container:
|
||||||
result.append(variant_container.getName())
|
result.append(variant_container.getName())
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantList", notify = activeVariantChanged)
|
||||||
|
def activeMaterialIds(self):
|
||||||
|
result = []
|
||||||
|
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
|
||||||
|
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||||
|
variant_container = stack.findContainer({"type": "variant"})
|
||||||
|
if variant_container and variant_container != self._empty_variant_container:
|
||||||
|
result.append(variant_container.getId())
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", notify = activeMaterialChanged)
|
@pyqtProperty("QVariantList", notify = activeMaterialChanged)
|
||||||
def activeMaterialNames(self):
|
def activeMaterialNames(self):
|
||||||
result = []
|
result = []
|
||||||
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
|
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
|
||||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||||
material_container = stack.findContainer(type="material")
|
material_container = stack.material
|
||||||
if material_container and material_container != self._empty_material_container:
|
if material_container and material_container != self._empty_material_container:
|
||||||
result.append(material_container.getName())
|
result.append(material_container.getName())
|
||||||
return result
|
return result
|
||||||
|
@ -518,12 +519,28 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify=activeMaterialChanged)
|
@pyqtProperty(str, notify=activeMaterialChanged)
|
||||||
def activeMaterialId(self) -> str:
|
def activeMaterialId(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
material = self._active_container_stack.findContainer({"type": "material"})
|
material = self._active_container_stack.material
|
||||||
if material:
|
if material:
|
||||||
return material.getId()
|
return material.getId()
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantMap", notify = activeVariantChanged)
|
||||||
|
def allActiveVariantIds(self):
|
||||||
|
if not self._global_container_stack:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||||
|
variant_container = stack.variant
|
||||||
|
if not variant_container:
|
||||||
|
continue
|
||||||
|
|
||||||
|
result[stack.getId()] = variant_container.getId()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
@pyqtProperty("QVariantMap", notify = activeMaterialChanged)
|
@pyqtProperty("QVariantMap", notify = activeMaterialChanged)
|
||||||
def allActiveMaterialIds(self):
|
def allActiveMaterialIds(self):
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
|
@ -532,7 +549,7 @@ class MachineManager(QObject):
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
||||||
material_container = stack.findContainer(type = "material")
|
material_container = stack.material
|
||||||
if not material_container:
|
if not material_container:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -551,13 +568,13 @@ class MachineManager(QObject):
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
quality_changes = self._global_container_stack.findContainer({"type": "quality_changes"})
|
quality_changes = self._global_container_stack.qualityChanges
|
||||||
if quality_changes:
|
if quality_changes:
|
||||||
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality_changes.getId())
|
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality_changes.getId())
|
||||||
if isinstance(value, SettingFunction):
|
if isinstance(value, SettingFunction):
|
||||||
value = value(self._global_container_stack)
|
value = value(self._global_container_stack)
|
||||||
return value
|
return value
|
||||||
quality = self._global_container_stack.findContainer({"type": "quality"})
|
quality = self._global_container_stack.quality
|
||||||
if quality:
|
if quality:
|
||||||
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality.getId())
|
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality.getId())
|
||||||
if isinstance(value, SettingFunction):
|
if isinstance(value, SettingFunction):
|
||||||
|
@ -571,7 +588,7 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify=activeQualityChanged)
|
@pyqtProperty(str, notify=activeQualityChanged)
|
||||||
def activeQualityMaterialId(self) -> str:
|
def activeQualityMaterialId(self) -> str:
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
quality = self._active_container_stack.findContainer({"type": "quality"})
|
quality = self._active_container_stack.quality
|
||||||
if quality:
|
if quality:
|
||||||
material_id = quality.getMetaDataEntry("material")
|
material_id = quality.getMetaDataEntry("material")
|
||||||
if material_id:
|
if material_id:
|
||||||
|
@ -588,10 +605,10 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify=activeQualityChanged)
|
@pyqtProperty(str, notify=activeQualityChanged)
|
||||||
def activeQualityName(self):
|
def activeQualityName(self):
|
||||||
if self._active_container_stack and self._global_container_stack:
|
if self._active_container_stack and self._global_container_stack:
|
||||||
quality = self._global_container_stack.findContainer({"type": "quality_changes"})
|
quality = self._global_container_stack.qualityChanges
|
||||||
if quality and quality != self._empty_quality_changes_container:
|
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
|
||||||
return quality.getName()
|
return quality.getName()
|
||||||
quality = self._active_container_stack.findContainer({"type": "quality"})
|
quality = self._active_container_stack.quality
|
||||||
if quality:
|
if quality:
|
||||||
return quality.getName()
|
return quality.getName()
|
||||||
return ""
|
return ""
|
||||||
|
@ -599,10 +616,10 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify=activeQualityChanged)
|
@pyqtProperty(str, notify=activeQualityChanged)
|
||||||
def activeQualityId(self):
|
def activeQualityId(self):
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
quality = self._active_container_stack.findContainer({"type": "quality_changes"})
|
quality = self._active_container_stack.qualityChanges
|
||||||
if quality and quality != self._empty_quality_changes_container:
|
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
|
||||||
return quality.getId()
|
return quality.getId()
|
||||||
quality = self._active_container_stack.findContainer({"type": "quality"})
|
quality = self._active_container_stack.quality
|
||||||
if quality:
|
if quality:
|
||||||
return quality.getId()
|
return quality.getId()
|
||||||
return ""
|
return ""
|
||||||
|
@ -610,10 +627,10 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify=activeQualityChanged)
|
@pyqtProperty(str, notify=activeQualityChanged)
|
||||||
def globalQualityId(self):
|
def globalQualityId(self):
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
quality = self._global_container_stack.findContainer({"type": "quality_changes"})
|
quality = self._global_container_stack.qualityChanges
|
||||||
if quality and quality != self._empty_quality_changes_container:
|
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
|
||||||
return quality.getId()
|
return quality.getId()
|
||||||
quality = self._global_container_stack.findContainer({"type": "quality"})
|
quality = self._global_container_stack.quality
|
||||||
if quality:
|
if quality:
|
||||||
return quality.getId()
|
return quality.getId()
|
||||||
return ""
|
return ""
|
||||||
|
@ -621,7 +638,7 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify = activeQualityChanged)
|
@pyqtProperty(str, notify = activeQualityChanged)
|
||||||
def activeQualityType(self):
|
def activeQualityType(self):
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
quality = self._active_container_stack.findContainer(type = "quality")
|
quality = self._active_container_stack.quality
|
||||||
if quality:
|
if quality:
|
||||||
return quality.getMetaDataEntry("quality_type")
|
return quality.getMetaDataEntry("quality_type")
|
||||||
return ""
|
return ""
|
||||||
|
@ -629,7 +646,7 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(bool, notify = activeQualityChanged)
|
@pyqtProperty(bool, notify = activeQualityChanged)
|
||||||
def isActiveQualitySupported(self):
|
def isActiveQualitySupported(self):
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
quality = self._active_container_stack.findContainer(type = "quality")
|
quality = self._active_container_stack.quality
|
||||||
if quality:
|
if quality:
|
||||||
return Util.parseBool(quality.getMetaDataEntry("supported", True))
|
return Util.parseBool(quality.getMetaDataEntry("supported", True))
|
||||||
return False
|
return False
|
||||||
|
@ -644,7 +661,7 @@ class MachineManager(QObject):
|
||||||
def activeQualityContainerId(self):
|
def activeQualityContainerId(self):
|
||||||
# We're using the active stack instead of the global stack in case the list of qualities differs per extruder
|
# We're using the active stack instead of the global stack in case the list of qualities differs per extruder
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
quality = self._active_container_stack.findContainer(type = "quality")
|
quality = self._active_container_stack.quality
|
||||||
if quality:
|
if quality:
|
||||||
return quality.getId()
|
return quality.getId()
|
||||||
return ""
|
return ""
|
||||||
|
@ -652,8 +669,8 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify = activeQualityChanged)
|
@pyqtProperty(str, notify = activeQualityChanged)
|
||||||
def activeQualityChangesId(self):
|
def activeQualityChangesId(self):
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
changes = self._active_container_stack.findContainer(type = "quality_changes")
|
changes = self._active_container_stack.qualityChanges
|
||||||
if changes:
|
if changes and changes.getId() != "empty":
|
||||||
return changes.getId()
|
return changes.getId()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@ -690,21 +707,20 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
Logger.log("d", "Attempting to change the active material to %s", material_id)
|
Logger.log("d", "Attempting to change the active material to %s", material_id)
|
||||||
|
|
||||||
old_material = self._active_container_stack.findContainer({"type": "material"})
|
old_material = self._active_container_stack.material
|
||||||
old_quality = self._active_container_stack.findContainer({"type": "quality"})
|
old_quality = self._active_container_stack.quality
|
||||||
old_quality_changes = self._active_container_stack.findContainer({"type": "quality_changes"})
|
old_quality_changes = self._active_container_stack.qualityChanges
|
||||||
if not old_material:
|
if not old_material:
|
||||||
Logger.log("w", "While trying to set the active material, no material was found to replace it.")
|
Logger.log("w", "While trying to set the active material, no material was found to replace it.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if old_quality_changes.getId() == "empty_quality_changes":
|
if old_quality_changes and old_quality_changes.getId() == "empty_quality_changes":
|
||||||
old_quality_changes = None
|
old_quality_changes = None
|
||||||
|
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
old_material.nameChanged.disconnect(self._onMaterialNameChanged)
|
old_material.nameChanged.disconnect(self._onMaterialNameChanged)
|
||||||
|
|
||||||
material_index = self._active_container_stack.getContainerIndex(old_material)
|
self._active_container_stack.material = material_container
|
||||||
self._active_container_stack.replaceContainer(material_index, material_container)
|
|
||||||
Logger.log("d", "Active material changed")
|
Logger.log("d", "Active material changed")
|
||||||
|
|
||||||
material_container.nameChanged.connect(self._onMaterialNameChanged)
|
material_container.nameChanged.connect(self._onMaterialNameChanged)
|
||||||
|
@ -753,13 +769,12 @@ class MachineManager(QObject):
|
||||||
if not containers or not self._active_container_stack:
|
if not containers or not self._active_container_stack:
|
||||||
return
|
return
|
||||||
Logger.log("d", "Attempting to change the active variant to %s", variant_id)
|
Logger.log("d", "Attempting to change the active variant to %s", variant_id)
|
||||||
old_variant = self._active_container_stack.findContainer({"type": "variant"})
|
old_variant = self._active_container_stack.variant
|
||||||
old_material = self._active_container_stack.findContainer({"type": "material"})
|
old_material = self._active_container_stack.material
|
||||||
if old_variant:
|
if old_variant:
|
||||||
self.blurSettings.emit()
|
self.blurSettings.emit()
|
||||||
variant_index = self._active_container_stack.getContainerIndex(old_variant)
|
self._active_container_stack.variant = containers[0]
|
||||||
self._active_container_stack.replaceContainer(variant_index, containers[0])
|
Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId()))
|
||||||
Logger.log("d", "Active variant changed")
|
|
||||||
preferred_material = None
|
preferred_material = None
|
||||||
if old_material:
|
if old_material:
|
||||||
preferred_material_name = old_material.getName()
|
preferred_material_name = old_material.getName()
|
||||||
|
@ -845,7 +860,7 @@ class MachineManager(QObject):
|
||||||
stacks = [global_container_stack]
|
stacks = [global_container_stack]
|
||||||
|
|
||||||
for stack in stacks:
|
for stack in stacks:
|
||||||
material = stack.findContainer(type="material")
|
material = stack.material
|
||||||
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
|
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
|
||||||
if not quality: #No quality profile is found for this quality type.
|
if not quality: #No quality profile is found for this quality type.
|
||||||
quality = self._empty_quality_container
|
quality = self._empty_quality_container
|
||||||
|
@ -882,7 +897,7 @@ class MachineManager(QObject):
|
||||||
else:
|
else:
|
||||||
Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name)
|
Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name)
|
||||||
return None
|
return None
|
||||||
material = global_container_stack.findContainer(type="material")
|
material = global_container_stack.material
|
||||||
|
|
||||||
# For the global stack, find a quality which matches the quality_type in
|
# For the global stack, find a quality which matches the quality_type in
|
||||||
# the quality changes profile and also satisfies any material constraints.
|
# the quality changes profile and also satisfies any material constraints.
|
||||||
|
@ -905,7 +920,7 @@ class MachineManager(QObject):
|
||||||
else:
|
else:
|
||||||
quality_changes = global_quality_changes
|
quality_changes = global_quality_changes
|
||||||
|
|
||||||
material = stack.findContainer(type="material")
|
material = stack.material
|
||||||
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
|
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
|
||||||
if not quality: #No quality profile found for this quality type.
|
if not quality: #No quality profile found for this quality type.
|
||||||
quality = self._empty_quality_container
|
quality = self._empty_quality_container
|
||||||
|
@ -924,18 +939,18 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
def _replaceQualityOrQualityChangesInStack(self, stack, container, postpone_emit = False):
|
def _replaceQualityOrQualityChangesInStack(self, stack, container, postpone_emit = False):
|
||||||
# Disconnect the signal handling from the old container.
|
# Disconnect the signal handling from the old container.
|
||||||
old_container = stack.findContainer(type=container.getMetaDataEntry("type"))
|
container_type = container.getMetaDataEntry("type")
|
||||||
if old_container:
|
if container_type == "quality":
|
||||||
old_container.nameChanged.disconnect(self._onQualityNameChanged)
|
stack.quality.nameChanged.disconnect(self._onQualityNameChanged)
|
||||||
else:
|
stack.setQuality(container)
|
||||||
Logger.log("e", "Could not find container of type %s in stack %s while replacing quality (changes) with container %s", container.getMetaDataEntry("type"), stack.getId(), container.getId())
|
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
|
||||||
return
|
elif container_type == "quality_changes" or container_type is None:
|
||||||
|
# If the container is an empty container, we need to change the quality_changes.
|
||||||
# Swap in the new container into the stack.
|
# Quality can never be set to empty.
|
||||||
stack.replaceContainer(stack.getContainerIndex(old_container), container, postpone_emit = postpone_emit)
|
stack.qualityChanges.nameChanged.disconnect(self._onQualityNameChanged)
|
||||||
|
stack.setQualityChanges(container)
|
||||||
# Attach the needed signal handling.
|
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
|
||||||
container.nameChanged.connect(self._onQualityNameChanged)
|
self._onQualityNameChanged()
|
||||||
|
|
||||||
def _askUserToKeepOrClearCurrentSettings(self):
|
def _askUserToKeepOrClearCurrentSettings(self):
|
||||||
Application.getInstance().discardOrKeepProfileChanges()
|
Application.getInstance().discardOrKeepProfileChanges()
|
||||||
|
@ -943,7 +958,7 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify = activeVariantChanged)
|
@pyqtProperty(str, notify = activeVariantChanged)
|
||||||
def activeVariantName(self):
|
def activeVariantName(self):
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
variant = self._active_container_stack.findContainer({"type": "variant"})
|
variant = self._active_container_stack.variant
|
||||||
if variant:
|
if variant:
|
||||||
return variant.getName()
|
return variant.getName()
|
||||||
|
|
||||||
|
@ -952,7 +967,7 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify = activeVariantChanged)
|
@pyqtProperty(str, notify = activeVariantChanged)
|
||||||
def activeVariantId(self):
|
def activeVariantId(self):
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
variant = self._active_container_stack.findContainer({"type": "variant"})
|
variant = self._active_container_stack.variant
|
||||||
if variant:
|
if variant:
|
||||||
return variant.getId()
|
return variant.getId()
|
||||||
|
|
||||||
|
@ -998,7 +1013,7 @@ class MachineManager(QObject):
|
||||||
@pyqtProperty(str, notify = activeVariantChanged)
|
@pyqtProperty(str, notify = activeVariantChanged)
|
||||||
def activeQualityVariantId(self):
|
def activeQualityVariantId(self):
|
||||||
if self._active_container_stack:
|
if self._active_container_stack:
|
||||||
variant = self._active_container_stack.findContainer({"type": "variant"})
|
variant = self._active_container_stack.variant
|
||||||
if variant:
|
if variant:
|
||||||
return self.getQualityVariantId(self._global_container_stack.getBottom(), variant)
|
return self.getQualityVariantId(self._global_container_stack.getBottom(), variant)
|
||||||
return ""
|
return ""
|
||||||
|
|
27
cura/Settings/SetObjectExtruderOperation.py
Normal file
27
cura/Settings/SetObjectExtruderOperation.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from UM.Operations.Operation import Operation
|
||||||
|
|
||||||
|
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||||
|
|
||||||
|
## Simple operation to set the extruder a certain object should be printed with.
|
||||||
|
class SetObjectExtruderOperation(Operation):
|
||||||
|
def __init__(self, node: SceneNode, extruder_id: str) -> None:
|
||||||
|
self._node = node
|
||||||
|
self._extruder_id = extruder_id
|
||||||
|
self._previous_extruder_id = None
|
||||||
|
self._decorator_added = False
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
if self._previous_extruder_id:
|
||||||
|
self._node.callDecoration("setActiveExtruder", self._previous_extruder_id)
|
||||||
|
|
||||||
|
def redo(self):
|
||||||
|
stack = self._node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
|
||||||
|
if not stack:
|
||||||
|
self._node.addDecorator(SettingOverrideDecorator())
|
||||||
|
|
||||||
|
self._previous_extruder_id = self._node.callDecoration("getActiveExtruder")
|
||||||
|
self._node.callDecoration("setActiveExtruder", self._extruder_id)
|
|
@ -109,10 +109,13 @@ class SettingInheritanceManager(QObject):
|
||||||
self._settings_with_inheritance_warning.remove(key)
|
self._settings_with_inheritance_warning.remove(key)
|
||||||
settings_with_inheritance_warning_changed = True
|
settings_with_inheritance_warning_changed = True
|
||||||
|
|
||||||
# Find the topmost parent (Assumed to be a category)
|
|
||||||
parent = definitions[0].parent
|
parent = definitions[0].parent
|
||||||
while parent.parent is not None:
|
# Find the topmost parent (Assumed to be a category)
|
||||||
parent = parent.parent
|
if parent is not None:
|
||||||
|
while parent.parent is not None:
|
||||||
|
parent = parent.parent
|
||||||
|
else:
|
||||||
|
parent = definitions[0] # Already at a category
|
||||||
|
|
||||||
if parent.key not in self._settings_with_inheritance_warning and has_overwritten_inheritance:
|
if parent.key not in self._settings_with_inheritance_warning and has_overwritten_inheritance:
|
||||||
# Category was not in the list yet, so needs to be added now.
|
# Category was not in the list yet, so needs to be added now.
|
||||||
|
|
|
@ -109,6 +109,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||||
def setActiveExtruder(self, extruder_stack_id):
|
def setActiveExtruder(self, extruder_stack_id):
|
||||||
self._extruder_stack = extruder_stack_id
|
self._extruder_stack = extruder_stack_id
|
||||||
self._updateNextStack()
|
self._updateNextStack()
|
||||||
|
ExtruderManager.getInstance().resetSelectedObjectExtruders()
|
||||||
self.activeExtruderChanged.emit()
|
self.activeExtruderChanged.emit()
|
||||||
|
|
||||||
def getStack(self):
|
def getStack(self):
|
||||||
|
|
|
@ -13,9 +13,9 @@ from UM.Resources import Resources
|
||||||
from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then.
|
from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then.
|
||||||
from UM.Platform import Platform
|
from UM.Platform import Platform
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
from UM.Qt.Duration import DurationFormat
|
||||||
from PyQt5.QtCore import QObject, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtSlot
|
||||||
|
|
||||||
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from . import ProcessSlicedLayersJob
|
from . import ProcessSlicedLayersJob
|
||||||
from . import StartSliceJob
|
from . import StartSliceJob
|
||||||
|
@ -273,9 +273,15 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
if not extruders:
|
if not extruders:
|
||||||
error_keys = self._global_container_stack.getErrorKeys()
|
error_keys = self._global_container_stack.getErrorKeys()
|
||||||
error_labels = set()
|
error_labels = set()
|
||||||
definition_container = self._global_container_stack.getBottom()
|
|
||||||
for key in error_keys:
|
for key in error_keys:
|
||||||
error_labels.add(definition_container.findDefinitions(key = key)[0].label)
|
for stack in [self._global_container_stack] + extruders: #Search all container stacks for the definition of this setting. Some are only in an extruder stack.
|
||||||
|
definitions = stack.getBottom().findDefinitions(key = key)
|
||||||
|
if definitions:
|
||||||
|
break #Found it! No need to continue search.
|
||||||
|
else: #No stack has a definition for this setting.
|
||||||
|
Logger.log("w", "When checking settings for errors, unable to find definition for key: {key}".format(key = key))
|
||||||
|
continue
|
||||||
|
error_labels.add(definitions[0].label)
|
||||||
|
|
||||||
error_labels = ", ".join(error_labels)
|
error_labels = ", ".join(error_labels)
|
||||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}".format(error_labels)))
|
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}".format(error_labels)))
|
||||||
|
@ -442,6 +448,15 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self.backendStateChange.emit(BackendState.Done)
|
self.backendStateChange.emit(BackendState.Done)
|
||||||
self.processingProgress.emit(1.0)
|
self.processingProgress.emit(1.0)
|
||||||
|
|
||||||
|
for line in self._scene.gcode_list:
|
||||||
|
replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
|
||||||
|
replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths))
|
||||||
|
replaced = replaced.replace("{filament_weight}", str(Application.getInstance().getPrintInformation().materialWeights))
|
||||||
|
replaced = replaced.replace("{filament_cost}", str(Application.getInstance().getPrintInformation().materialCosts))
|
||||||
|
replaced = replaced.replace("{jobname}", str(Application.getInstance().getPrintInformation().jobName))
|
||||||
|
|
||||||
|
self._scene.gcode_list[self._scene.gcode_list.index(line)] = replaced
|
||||||
|
|
||||||
self._slicing = False
|
self._slicing = False
|
||||||
self._need_slicing = False
|
self._need_slicing = False
|
||||||
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
|
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
|
||||||
|
|
|
@ -179,9 +179,10 @@ class ProcessSlicedLayersJob(Job):
|
||||||
# Single extruder via global stack.
|
# Single extruder via global stack.
|
||||||
material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
|
material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
|
||||||
material = global_container_stack.findContainer({"type": "material"})
|
material = global_container_stack.findContainer({"type": "material"})
|
||||||
color_code = material.getMetaDataEntry("color_code")
|
color_code = "#e0e000"
|
||||||
if color_code is None: # not all stacks have a material color
|
if material:
|
||||||
color_code = "#e0e000"
|
if material.getMetaDataEntry("color_code") is not None:
|
||||||
|
color_code = material.getMetaDataEntry("color_code")
|
||||||
color = colorCodeToRGBA(color_code)
|
color = colorCodeToRGBA(color_code)
|
||||||
material_color_map[0, :] = color
|
material_color_map[0, :] = color
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import numpy
|
import numpy
|
||||||
from string import Formatter
|
from string import Formatter
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
import time
|
||||||
|
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
@ -230,25 +231,22 @@ class StartSliceJob(Job):
|
||||||
keys = stack.getAllKeys()
|
keys = stack.getAllKeys()
|
||||||
settings = {}
|
settings = {}
|
||||||
for key in keys:
|
for key in keys:
|
||||||
# Use resolvement value if available, or take the value
|
settings[key] = stack.getProperty(key, "value")
|
||||||
resolved_value = stack.getProperty(key, "resolve")
|
|
||||||
if resolved_value is not None:
|
|
||||||
# There is a resolvement value. Check if we need to use it.
|
|
||||||
user_container = stack.findContainer({"type": "user"})
|
|
||||||
quality_changes_container = stack.findContainer({"type": "quality_changes"})
|
|
||||||
if user_container.hasProperty(key,"value") or quality_changes_container.hasProperty(key,"value"):
|
|
||||||
# Normal case
|
|
||||||
settings[key] = stack.getProperty(key, "value")
|
|
||||||
else:
|
|
||||||
settings[key] = resolved_value
|
|
||||||
else:
|
|
||||||
# Normal case
|
|
||||||
settings[key] = stack.getProperty(key, "value")
|
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
start_gcode = settings["machine_start_gcode"]
|
start_gcode = settings["machine_start_gcode"]
|
||||||
settings["material_bed_temp_prepend"] = "{material_bed_temperature}" not in start_gcode #Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
|
#Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
|
||||||
settings["material_print_temp_prepend"] = "{material_print_temperature}" not in start_gcode
|
bed_temperature_settings = {"material_bed_temperature", "material_bed_temperature_layer_0"}
|
||||||
|
settings["material_bed_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in bed_temperature_settings))
|
||||||
|
print_temperature_settings = {"material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"}
|
||||||
|
settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_temperature_settings))
|
||||||
|
|
||||||
|
settings["print_bed_temperature"] = settings["material_bed_temperature"]
|
||||||
|
settings["print_temperature"] = settings["material_print_temperature"]
|
||||||
|
|
||||||
|
settings["time"] = time.strftime('%H:%M:%S')
|
||||||
|
settings["date"] = time.strftime('%d-%m-%Y')
|
||||||
|
settings["day"] = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][int(time.strftime('%w'))]
|
||||||
|
|
||||||
for key, value in settings.items(): #Add all submessages for each individual setting.
|
for key, value in settings.items(): #Add all submessages for each individual setting.
|
||||||
setting_message = self._slice_message.getMessage("global_settings").addRepeatedMessage("settings")
|
setting_message = self._slice_message.getMessage("global_settings").addRepeatedMessage("settings")
|
||||||
|
|
|
@ -56,7 +56,7 @@ class GCodeProfileReader(ProfileReader):
|
||||||
# TODO: Consider moving settings to the start?
|
# TODO: Consider moving settings to the start?
|
||||||
serialized = "" # Will be filled with the serialized profile.
|
serialized = "" # Will be filled with the serialized profile.
|
||||||
try:
|
try:
|
||||||
with open(file_name) as f:
|
with open(file_name, "r") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
if line.startswith(prefix):
|
if line.startswith(prefix):
|
||||||
# Remove the prefix and the newline from the line and add it to the rest.
|
# Remove the prefix and the newline from the line and add it to the rest.
|
||||||
|
@ -66,9 +66,13 @@ class GCodeProfileReader(ProfileReader):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
serialized = unescapeGcodeComment(serialized)
|
serialized = unescapeGcodeComment(serialized)
|
||||||
Logger.log("i", "Serialized the following from %s: %s" %(file_name, repr(serialized)))
|
|
||||||
|
|
||||||
json_data = json.loads(serialized)
|
# serialized data can be invalid JSON
|
||||||
|
try:
|
||||||
|
json_data = json.loads(serialized)
|
||||||
|
except Exception as e:
|
||||||
|
Logger.log("e", "Could not parse serialized JSON data from GCode %s, error: %s", file_name, e)
|
||||||
|
return None
|
||||||
|
|
||||||
profiles = []
|
profiles = []
|
||||||
global_profile = readQualityProfileFromString(json_data["global_quality"])
|
global_profile = readQualityProfileFromString(json_data["global_quality"])
|
||||||
|
|
|
@ -100,7 +100,7 @@ class GCodeWriter(MeshWriter):
|
||||||
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
|
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
|
||||||
prefix_length = len(prefix)
|
prefix_length = len(prefix)
|
||||||
|
|
||||||
container_with_profile = stack.findContainer({"type": "quality_changes"})
|
container_with_profile = stack.qualityChanges
|
||||||
if not container_with_profile:
|
if not container_with_profile:
|
||||||
Logger.log("e", "No valid quality profile found, not writing settings to GCode!")
|
Logger.log("e", "No valid quality profile found, not writing settings to GCode!")
|
||||||
return ""
|
return ""
|
||||||
|
@ -115,7 +115,7 @@ class GCodeWriter(MeshWriter):
|
||||||
data = {"global_quality": serialized}
|
data = {"global_quality": serialized}
|
||||||
|
|
||||||
for extruder in sorted(ExtruderManager.getInstance().getMachineExtruders(stack.getId()), key = lambda k: k.getMetaDataEntry("position")):
|
for extruder in sorted(ExtruderManager.getInstance().getMachineExtruders(stack.getId()), key = lambda k: k.getMetaDataEntry("position")):
|
||||||
extruder_quality = extruder.findContainer({"type": "quality_changes"})
|
extruder_quality = extruder.qualityChanges
|
||||||
if not extruder_quality:
|
if not extruder_quality:
|
||||||
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
|
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
|
||||||
continue
|
continue
|
||||||
|
|
262
plugins/LayerView/LayerView.qml
Normal file → Executable file
262
plugins/LayerView/LayerView.qml
Normal file → Executable file
|
@ -7,6 +7,7 @@ import QtQuick.Layouts 1.1
|
||||||
import QtQuick.Controls.Styles 1.1
|
import QtQuick.Controls.Styles 1.1
|
||||||
|
|
||||||
import UM 1.0 as UM
|
import UM 1.0 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
|
@ -42,7 +43,8 @@ Item
|
||||||
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
|
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
|
||||||
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
|
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
|
||||||
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
|
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
|
||||||
property bool show_legend: UM.LayerView.compatibilityMode || UM.Preferences.getValue("layerview/layer_view_type") == 1
|
// if we are in compatibility mode, we only show the "line type"
|
||||||
|
property bool show_legend: UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type") == 1
|
||||||
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
|
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
|
||||||
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
|
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
|
||||||
|
|
||||||
|
@ -58,6 +60,7 @@ Item
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
text: catalog.i18nc("@label","View Mode: Layers")
|
text: catalog.i18nc("@label","View Mode: Layers")
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
|
@ -75,6 +78,7 @@ Item
|
||||||
text: catalog.i18nc("@label","Color scheme")
|
text: catalog.i18nc("@label","Color scheme")
|
||||||
visible: !UM.LayerView.compatibilityMode
|
visible: !UM.LayerView.compatibilityMode
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel // matches LayerView.py
|
ListModel // matches LayerView.py
|
||||||
|
@ -102,28 +106,25 @@ Item
|
||||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||||
model: layerViewTypes
|
model: layerViewTypes
|
||||||
visible: !UM.LayerView.compatibilityMode
|
visible: !UM.LayerView.compatibilityMode
|
||||||
|
style: UM.Theme.styles.combobox
|
||||||
|
|
||||||
property int layer_view_type: UM.Preferences.getValue("layerview/layer_view_type")
|
onActivated:
|
||||||
currentIndex: layer_view_type // index matches type_id
|
{
|
||||||
onActivated: {
|
UM.Preferences.setValue("layerview/layer_view_type", index);
|
||||||
// Combobox selection
|
|
||||||
var type_id = index;
|
|
||||||
UM.Preferences.setValue("layerview/layer_view_type", type_id);
|
|
||||||
updateLegend(type_id);
|
|
||||||
}
|
|
||||||
onModelChanged: {
|
|
||||||
updateLegend(UM.Preferences.getValue("layerview/layer_view_type"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update visibility of legend.
|
Component.onCompleted:
|
||||||
function updateLegend(type_id) {
|
{
|
||||||
if (UM.LayerView.compatibilityMode || (type_id == 1)) {
|
currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
|
||||||
// Line type
|
updateLegends(currentIndex);
|
||||||
view_settings.show_legend = true;
|
|
||||||
} else {
|
|
||||||
view_settings.show_legend = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateLegends(type_id)
|
||||||
|
{
|
||||||
|
// update visibility of legends
|
||||||
|
view_settings.show_legend = UM.LayerView.compatibilityMode || (type_id == 1);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Label
|
||||||
|
@ -149,7 +150,8 @@ Item
|
||||||
target: UM.Preferences
|
target: UM.Preferences
|
||||||
onPreferenceChanged:
|
onPreferenceChanged:
|
||||||
{
|
{
|
||||||
layerTypeCombobox.layer_view_type = UM.Preferences.getValue("layerview/layer_view_type");
|
layerTypeCombobox.currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
|
||||||
|
layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
|
||||||
view_settings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
|
view_settings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
|
||||||
view_settings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
|
view_settings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
|
||||||
view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
|
view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
|
||||||
|
@ -161,106 +163,88 @@ Item
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: UM.LayerView.extruderCount
|
model: Cura.ExtrudersModel{}
|
||||||
CheckBox {
|
CheckBox {
|
||||||
checked: view_settings.extruder_opacities[index] > 0.5 || view_settings.extruder_opacities[index] == undefined || view_settings.extruder_opacities[index] == ""
|
checked: view_settings.extruder_opacities[index] > 0.5 || view_settings.extruder_opacities[index] == undefined || view_settings.extruder_opacities[index] == ""
|
||||||
onClicked: {
|
onClicked: {
|
||||||
view_settings.extruder_opacities[index] = checked ? 1.0 : 0.0
|
view_settings.extruder_opacities[index] = checked ? 1.0 : 0.0
|
||||||
UM.Preferences.setValue("layerview/extruder_opacities", view_settings.extruder_opacities.join("|"));
|
UM.Preferences.setValue("layerview/extruder_opacities", view_settings.extruder_opacities.join("|"));
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@label", "Extruder %1").arg(index + 1)
|
text: model.name
|
||||||
visible: !UM.LayerView.compatibilityMode
|
visible: !UM.LayerView.compatibilityMode
|
||||||
enabled: index + 1 <= 4
|
enabled: index + 1 <= 4
|
||||||
|
Rectangle {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
width: UM.Theme.getSize("layerview_legend_size").width
|
||||||
|
height: UM.Theme.getSize("layerview_legend_size").height
|
||||||
|
color: model.color
|
||||||
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
|
border.color: UM.Theme.getColor("lining")
|
||||||
|
visible: !view_settings.show_legend
|
||||||
|
}
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
|
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||||
|
style: UM.Theme.styles.checkbox
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
Repeater {
|
||||||
checked: view_settings.show_travel_moves
|
model: ListModel {
|
||||||
onClicked: {
|
id: typesLegenModel
|
||||||
UM.Preferences.setValue("layerview/show_travel_moves", checked);
|
Component.onCompleted:
|
||||||
|
{
|
||||||
|
typesLegenModel.append({
|
||||||
|
label: catalog.i18nc("@label", "Show Travels"),
|
||||||
|
initialValue: view_settings.show_travel_moves,
|
||||||
|
preference: "layerview/show_travel_moves",
|
||||||
|
colorId: "layerview_move_combing"
|
||||||
|
});
|
||||||
|
typesLegenModel.append({
|
||||||
|
label: catalog.i18nc("@label", "Show Helpers"),
|
||||||
|
initialValue: view_settings.show_helpers,
|
||||||
|
preference: "layerview/show_helpers",
|
||||||
|
colorId: "layerview_support"
|
||||||
|
});
|
||||||
|
typesLegenModel.append({
|
||||||
|
label: catalog.i18nc("@label", "Show Shell"),
|
||||||
|
initialValue: view_settings.show_skin,
|
||||||
|
preference: "layerview/show_skin",
|
||||||
|
colorId: "layerview_inset_0"
|
||||||
|
});
|
||||||
|
typesLegenModel.append({
|
||||||
|
label: catalog.i18nc("@label", "Show Infill"),
|
||||||
|
initialValue: view_settings.show_infill,
|
||||||
|
preference: "layerview/show_infill",
|
||||||
|
colorId: "layerview_infill"
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@label", "Show Travels")
|
|
||||||
Rectangle {
|
CheckBox {
|
||||||
anchors.top: parent.top
|
checked: model.initialValue
|
||||||
anchors.topMargin: 2
|
onClicked: {
|
||||||
anchors.right: parent.right
|
UM.Preferences.setValue(model.preference, checked);
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
}
|
||||||
height: UM.Theme.getSize("layerview_legend_size").height
|
text: label
|
||||||
color: UM.Theme.getColor("layerview_move_combing")
|
Rectangle {
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
border.color: UM.Theme.getColor("lining")
|
anchors.right: parent.right
|
||||||
visible: view_settings.show_legend
|
width: UM.Theme.getSize("layerview_legend_size").width
|
||||||
|
height: UM.Theme.getSize("layerview_legend_size").height
|
||||||
|
color: UM.Theme.getColor(model.colorId)
|
||||||
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
|
border.color: UM.Theme.getColor("lining")
|
||||||
|
visible: view_settings.show_legend
|
||||||
|
}
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||||
|
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||||
|
style: UM.Theme.styles.checkbox
|
||||||
}
|
}
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
|
|
||||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
|
||||||
}
|
|
||||||
CheckBox {
|
|
||||||
checked: view_settings.show_helpers
|
|
||||||
onClicked: {
|
|
||||||
UM.Preferences.setValue("layerview/show_helpers", checked);
|
|
||||||
}
|
|
||||||
text: catalog.i18nc("@label", "Show Helpers")
|
|
||||||
Rectangle {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 2
|
|
||||||
anchors.right: parent.right
|
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
|
||||||
height: UM.Theme.getSize("layerview_legend_size").height
|
|
||||||
color: UM.Theme.getColor("layerview_support")
|
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
|
||||||
border.color: UM.Theme.getColor("lining")
|
|
||||||
visible: view_settings.show_legend
|
|
||||||
}
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
|
|
||||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
|
||||||
}
|
|
||||||
CheckBox {
|
|
||||||
checked: view_settings.show_skin
|
|
||||||
onClicked: {
|
|
||||||
UM.Preferences.setValue("layerview/show_skin", checked);
|
|
||||||
}
|
|
||||||
text: catalog.i18nc("@label", "Show Shell")
|
|
||||||
Rectangle {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 2
|
|
||||||
anchors.right: parent.right
|
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
|
||||||
height: UM.Theme.getSize("layerview_legend_size").height
|
|
||||||
color: UM.Theme.getColor("layerview_inset_0")
|
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
|
||||||
border.color: UM.Theme.getColor("lining")
|
|
||||||
visible: view_settings.show_legend
|
|
||||||
}
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
|
|
||||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
|
||||||
}
|
|
||||||
CheckBox {
|
|
||||||
checked: view_settings.show_infill
|
|
||||||
onClicked: {
|
|
||||||
UM.Preferences.setValue("layerview/show_infill", checked);
|
|
||||||
}
|
|
||||||
text: catalog.i18nc("@label", "Show Infill")
|
|
||||||
Rectangle {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 2
|
|
||||||
anchors.right: parent.right
|
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
|
||||||
height: UM.Theme.getSize("layerview_legend_size").height
|
|
||||||
color: UM.Theme.getColor("layerview_infill")
|
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
|
||||||
border.color: UM.Theme.getColor("lining")
|
|
||||||
visible: view_settings.show_legend
|
|
||||||
}
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
|
|
||||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
checked: view_settings.only_show_top_layers
|
checked: view_settings.only_show_top_layers
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
@ -268,6 +252,7 @@ Item
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@label", "Only Show Top Layers")
|
text: catalog.i18nc("@label", "Only Show Top Layers")
|
||||||
visible: UM.LayerView.compatibilityMode
|
visible: UM.LayerView.compatibilityMode
|
||||||
|
style: UM.Theme.styles.checkbox
|
||||||
}
|
}
|
||||||
CheckBox {
|
CheckBox {
|
||||||
checked: view_settings.top_layer_count == 5
|
checked: view_settings.top_layer_count == 5
|
||||||
|
@ -276,51 +261,44 @@ Item
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
|
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
|
||||||
visible: UM.LayerView.compatibilityMode
|
visible: UM.LayerView.compatibilityMode
|
||||||
|
style: UM.Theme.styles.checkbox
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
Repeater {
|
||||||
{
|
model: ListModel {
|
||||||
id: topBottomLabel
|
id: typesLegenModelNoCheck
|
||||||
anchors.left: parent.left
|
Component.onCompleted:
|
||||||
text: catalog.i18nc("@label","Top / Bottom")
|
{
|
||||||
Rectangle {
|
typesLegenModelNoCheck.append({
|
||||||
anchors.top: parent.top
|
label: catalog.i18nc("@label", "Top / Bottom"),
|
||||||
anchors.topMargin: 2
|
colorId: "layerview_skin",
|
||||||
anchors.right: parent.right
|
});
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
typesLegenModelNoCheck.append({
|
||||||
height: UM.Theme.getSize("layerview_legend_size").height
|
label: catalog.i18nc("@label", "Inner Wall"),
|
||||||
color: UM.Theme.getColor("layerview_skin")
|
colorId: "layerview_inset_x",
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
});
|
||||||
border.color: UM.Theme.getColor("lining")
|
}
|
||||||
}
|
}
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
|
|
||||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
|
||||||
visible: view_settings.show_legend
|
|
||||||
}
|
|
||||||
|
|
||||||
Label
|
Label {
|
||||||
{
|
text: label
|
||||||
id: innerWallLabel
|
|
||||||
anchors.left: parent.left
|
|
||||||
text: catalog.i18nc("@label","Inner Wall")
|
|
||||||
Rectangle {
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.topMargin: 2
|
|
||||||
anchors.right: parent.right
|
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
|
||||||
height: UM.Theme.getSize("layerview_legend_size").height
|
|
||||||
color: UM.Theme.getColor("layerview_inset_x")
|
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
|
||||||
border.color: UM.Theme.getColor("lining")
|
|
||||||
visible: view_settings.show_legend
|
visible: view_settings.show_legend
|
||||||
|
Rectangle {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.right: parent.right
|
||||||
|
width: UM.Theme.getSize("layerview_legend_size").width
|
||||||
|
height: UM.Theme.getSize("layerview_legend_size").height
|
||||||
|
color: UM.Theme.getColor(model.colorId)
|
||||||
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
|
border.color: UM.Theme.getColor("lining")
|
||||||
|
visible: view_settings.show_legend
|
||||||
|
}
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||||
|
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
||||||
|
color: UM.Theme.getColor("text")
|
||||||
}
|
}
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
|
|
||||||
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
|
|
||||||
visible: view_settings.show_legend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item
|
Item
|
||||||
|
|
250
plugins/MachineSettingsAction/MachineSettingsAction.py
Normal file → Executable file
250
plugins/MachineSettingsAction/MachineSettingsAction.py
Normal file → Executable file
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) 2016 Ultimaker B.V.
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the AGPLv3 or higher.
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal
|
||||||
|
@ -7,12 +7,15 @@ from UM.FlameProfiler import pyqtSlot
|
||||||
from cura.MachineAction import MachineAction
|
from cura.MachineAction import MachineAction
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
from UM.Preferences import Preferences
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
|
||||||
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
import UM.i18n
|
import UM.i18n
|
||||||
catalog = UM.i18n.i18nCatalog("cura")
|
catalog = UM.i18n.i18nCatalog("cura")
|
||||||
|
@ -25,36 +28,80 @@ class MachineSettingsAction(MachineAction):
|
||||||
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
|
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
|
||||||
self._qml_url = "MachineSettingsAction.qml"
|
self._qml_url = "MachineSettingsAction.qml"
|
||||||
|
|
||||||
|
self._global_container_stack = None
|
||||||
self._container_index = 0
|
self._container_index = 0
|
||||||
|
self._extruder_container_index = 0
|
||||||
|
|
||||||
self._container_registry = ContainerRegistry.getInstance()
|
self._container_registry = ContainerRegistry.getInstance()
|
||||||
self._container_registry.containerAdded.connect(self._onContainerAdded)
|
self._container_registry.containerAdded.connect(self._onContainerAdded)
|
||||||
|
self._container_registry.containerRemoved.connect(self._onContainerRemoved)
|
||||||
|
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||||
|
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
|
||||||
|
|
||||||
|
self._backend = Application.getInstance().getBackend()
|
||||||
|
|
||||||
|
def _onContainerAdded(self, container):
|
||||||
|
# Add this action as a supported action to all machine definitions
|
||||||
|
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
|
||||||
|
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
||||||
|
|
||||||
|
def _onContainerRemoved(self, container):
|
||||||
|
# Remove definition_changes containers when a stack is removed
|
||||||
|
if container.getMetaDataEntry("type") in ["machine", "extruder_train"]:
|
||||||
|
definition_changes_container = container.findContainer({"type": "definition_changes"})
|
||||||
|
if not definition_changes_container:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._container_registry.removeContainer(definition_changes_container.getId())
|
||||||
|
|
||||||
def _reset(self):
|
def _reset(self):
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
if not self._global_container_stack:
|
||||||
if not global_container_stack:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Make sure there is a definition_changes container to store the machine settings
|
# Make sure there is a definition_changes container to store the machine settings
|
||||||
definition_changes_container = global_container_stack.findContainer({"type": "definition_changes"})
|
definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"})
|
||||||
if not definition_changes_container:
|
if not definition_changes_container:
|
||||||
definition_changes_container = self._createDefinitionChangesContainer(global_container_stack)
|
definition_changes_container = self._createDefinitionChangesContainer(self._global_container_stack, self._global_container_stack.getName() + "_settings")
|
||||||
|
|
||||||
# Notify the UI in which container to store the machine settings data
|
# Notify the UI in which container to store the machine settings data
|
||||||
container_index = global_container_stack.getContainerIndex(definition_changes_container)
|
container_index = self._global_container_stack.getContainerIndex(definition_changes_container)
|
||||||
if container_index != self._container_index:
|
if container_index != self._container_index:
|
||||||
self._container_index = container_index
|
self._container_index = container_index
|
||||||
self.containerIndexChanged.emit()
|
self.containerIndexChanged.emit()
|
||||||
|
|
||||||
def _createDefinitionChangesContainer(self, global_container_stack, container_index = None):
|
# Disable autoslicing while the machineaction is showing
|
||||||
definition_changes_container = InstanceContainer(global_container_stack.getName() + "_settings")
|
self._backend.disableTimer()
|
||||||
definition = global_container_stack.getBottom()
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def onFinishAction(self):
|
||||||
|
# Restore autoslicing when the machineaction is dismissed
|
||||||
|
if self._backend.determineAutoSlicing():
|
||||||
|
self._backend.tickle()
|
||||||
|
|
||||||
|
def _onActiveExtruderStackChanged(self):
|
||||||
|
extruder_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||||
|
if not self._global_container_stack or not extruder_container_stack:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Make sure there is a definition_changes container to store the machine settings
|
||||||
|
definition_changes_container = extruder_container_stack.findContainer({"type": "definition_changes"})
|
||||||
|
if not definition_changes_container:
|
||||||
|
definition_changes_container = self._createDefinitionChangesContainer(extruder_container_stack, extruder_container_stack.getId() + "_settings")
|
||||||
|
|
||||||
|
# Notify the UI in which container to store the machine settings data
|
||||||
|
container_index = extruder_container_stack.getContainerIndex(definition_changes_container)
|
||||||
|
if container_index != self._extruder_container_index:
|
||||||
|
self._extruder_container_index = container_index
|
||||||
|
self.extruderContainerIndexChanged.emit()
|
||||||
|
|
||||||
|
def _createDefinitionChangesContainer(self, container_stack, container_name, container_index = None):
|
||||||
|
definition_changes_container = InstanceContainer(container_name)
|
||||||
|
definition = container_stack.getBottom()
|
||||||
definition_changes_container.setDefinition(definition)
|
definition_changes_container.setDefinition(definition)
|
||||||
definition_changes_container.addMetaDataEntry("type", "definition_changes")
|
definition_changes_container.addMetaDataEntry("type", "definition_changes")
|
||||||
|
|
||||||
self._container_registry.addContainer(definition_changes_container)
|
self._container_registry.addContainer(definition_changes_container)
|
||||||
# Insert definition_changes between the definition and the variant
|
container_stack.definitionChanges = definition_changes_container
|
||||||
global_container_stack.insertContainer(-1, definition_changes_container)
|
|
||||||
|
|
||||||
return definition_changes_container
|
return definition_changes_container
|
||||||
|
|
||||||
|
@ -64,15 +111,129 @@ class MachineSettingsAction(MachineAction):
|
||||||
def containerIndex(self):
|
def containerIndex(self):
|
||||||
return self._container_index
|
return self._container_index
|
||||||
|
|
||||||
def _onContainerAdded(self, container):
|
extruderContainerIndexChanged = pyqtSignal()
|
||||||
# Add this action as a supported action to all machine definitions
|
|
||||||
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
|
@pyqtProperty(int, notify = extruderContainerIndexChanged)
|
||||||
if container.getProperty("machine_extruder_count", "value") > 1:
|
def extruderContainerIndex(self):
|
||||||
# Multiextruder printers are not currently supported
|
return self._extruder_container_index
|
||||||
Logger.log("d", "Not attaching MachineSettingsAction to %s; Multi-extrusion printers are not supported", container.getId())
|
|
||||||
return
|
|
||||||
|
def _onGlobalContainerChanged(self):
|
||||||
|
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
|
# This additional emit is needed because we cannot connect a UM.Signal directly to a pyqtSignal
|
||||||
|
self.globalContainerChanged.emit()
|
||||||
|
|
||||||
|
globalContainerChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(int, notify = globalContainerChanged)
|
||||||
|
def definedExtruderCount(self):
|
||||||
|
if not self._global_container_stack:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains"))
|
||||||
|
|
||||||
|
@pyqtSlot(int)
|
||||||
|
def setMachineExtruderCount(self, extruder_count):
|
||||||
|
machine_manager = Application.getInstance().getMachineManager()
|
||||||
|
extruder_manager = ExtruderManager.getInstance()
|
||||||
|
|
||||||
|
definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"})
|
||||||
|
if not self._global_container_stack or not definition_changes_container:
|
||||||
|
return
|
||||||
|
|
||||||
|
previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
||||||
|
if extruder_count == previous_extruder_count:
|
||||||
|
return
|
||||||
|
|
||||||
|
extruder_material_id = None
|
||||||
|
extruder_variant_id = None
|
||||||
|
if extruder_count == 1:
|
||||||
|
# Get the material and variant of the first extruder before setting the number extruders to 1
|
||||||
|
if machine_manager.hasMaterials:
|
||||||
|
extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]]
|
||||||
|
if machine_manager.hasVariants:
|
||||||
|
extruder_variant_id = machine_manager.allActiveVariantIds[extruder_manager.extruderIds["0"]]
|
||||||
|
|
||||||
|
# Copy any settable_per_extruder setting value from the extruders to the global stack
|
||||||
|
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
|
extruder_stacks.reverse() # make sure the first extruder is done last, so its settings override any higher extruder settings
|
||||||
|
|
||||||
|
global_user_container = self._global_container_stack.getTop()
|
||||||
|
for extruder_stack in extruder_stacks:
|
||||||
|
extruder_index = extruder_stack.getMetaDataEntry("position")
|
||||||
|
extruder_user_container = extruder_stack.getTop()
|
||||||
|
for setting_instance in extruder_user_container.findInstances():
|
||||||
|
setting_key = setting_instance.definition.key
|
||||||
|
settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
|
||||||
|
if settable_per_extruder:
|
||||||
|
limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder")
|
||||||
|
|
||||||
|
if limit_to_extruder == "-1" or limit_to_extruder == extruder_index:
|
||||||
|
global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value"))
|
||||||
|
extruder_user_container.removeInstance(setting_key)
|
||||||
|
|
||||||
|
# Check to see if any features are set to print with an extruder that will no longer exist
|
||||||
|
for setting_key in ["adhesion_extruder_nr", "support_extruder_nr", "support_extruder_nr_layer_0", "support_infill_extruder_nr", "support_interface_extruder_nr"]:
|
||||||
|
if int(self._global_container_stack.getProperty(setting_key, "value")) > extruder_count - 1:
|
||||||
|
Logger.log("i", "Lowering %s setting to match number of extruders", setting_key)
|
||||||
|
self._global_container_stack.getTop().setProperty(setting_key, "value", extruder_count - 1)
|
||||||
|
|
||||||
|
# Check to see if any objects are set to print with an extruder that will no longer exist
|
||||||
|
root_node = Application.getInstance().getController().getScene().getRoot()
|
||||||
|
for node in DepthFirstIterator(root_node):
|
||||||
|
if node.getMeshData():
|
||||||
|
extruder_nr = node.callDecoration("getActiveExtruderPosition")
|
||||||
|
|
||||||
|
if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
|
||||||
|
node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId())
|
||||||
|
|
||||||
|
definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
|
||||||
|
self.forceUpdate()
|
||||||
|
|
||||||
|
if extruder_count > 1:
|
||||||
|
# Multiextrusion
|
||||||
|
|
||||||
|
# Make sure one of the extruder stacks is active
|
||||||
|
if extruder_manager.activeExtruderIndex == -1:
|
||||||
|
extruder_manager.setActiveExtruderIndex(0)
|
||||||
|
|
||||||
|
# Move settable_per_extruder values out of the global container
|
||||||
|
if previous_extruder_count == 1:
|
||||||
|
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
|
global_user_container = self._global_container_stack.getTop()
|
||||||
|
|
||||||
|
for setting_instance in global_user_container.findInstances():
|
||||||
|
setting_key = setting_instance.definition.key
|
||||||
|
settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
|
||||||
|
if settable_per_extruder:
|
||||||
|
limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
|
||||||
|
extruder_stack = extruder_stacks[max(0, limit_to_extruder)]
|
||||||
|
extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
|
||||||
|
global_user_container.removeInstance(setting_key)
|
||||||
|
else:
|
||||||
|
# Single extrusion
|
||||||
|
|
||||||
|
# Make sure the machine stack is active
|
||||||
|
if extruder_manager.activeExtruderIndex > -1:
|
||||||
|
extruder_manager.setActiveExtruderIndex(-1)
|
||||||
|
|
||||||
|
# Restore material and variant on global stack
|
||||||
|
# MachineManager._onGlobalContainerChanged removes the global material and variant of multiextruder machines
|
||||||
|
if extruder_material_id or extruder_variant_id:
|
||||||
|
# Prevent the DiscardOrKeepProfileChangesDialog from popping up (twice) if there are user changes
|
||||||
|
# The dialog is not relevant here, since we're restoring the previous situation as good as possible
|
||||||
|
preferences = Preferences.getInstance()
|
||||||
|
choice_on_profile_override = preferences.getValue("cura/choice_on_profile_override")
|
||||||
|
preferences.setValue("cura/choice_on_profile_override", "always_keep")
|
||||||
|
|
||||||
|
if extruder_material_id:
|
||||||
|
machine_manager.setActiveMaterial(extruder_material_id)
|
||||||
|
if extruder_variant_id:
|
||||||
|
machine_manager.setActiveVariant(extruder_variant_id)
|
||||||
|
|
||||||
|
preferences.setValue("cura/choice_on_profile_override", choice_on_profile_override)
|
||||||
|
|
||||||
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def forceUpdate(self):
|
def forceUpdate(self):
|
||||||
|
@ -83,34 +244,35 @@ class MachineSettingsAction(MachineAction):
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def updateHasMaterialsMetadata(self):
|
def updateHasMaterialsMetadata(self):
|
||||||
# Updates the has_materials metadata flag after switching gcode flavor
|
# Updates the has_materials metadata flag after switching gcode flavor
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
if not self._global_container_stack:
|
||||||
if global_container_stack:
|
return
|
||||||
definition = global_container_stack.getBottom()
|
|
||||||
if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False):
|
|
||||||
has_materials = global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
|
|
||||||
|
|
||||||
material_container = global_container_stack.findContainer({"type": "material"})
|
definition = self._global_container_stack.getBottom()
|
||||||
material_index = global_container_stack.getContainerIndex(material_container)
|
if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False):
|
||||||
|
has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
|
||||||
|
|
||||||
if has_materials:
|
material_container = self._global_container_stack.findContainer({"type": "material"})
|
||||||
if "has_materials" in global_container_stack.getMetaData():
|
material_index = self._global_container_stack.getContainerIndex(material_container)
|
||||||
global_container_stack.setMetaDataEntry("has_materials", True)
|
|
||||||
else:
|
|
||||||
global_container_stack.addMetaDataEntry("has_materials", True)
|
|
||||||
|
|
||||||
# Set the material container to a sane default
|
if has_materials:
|
||||||
if material_container.getId() == "empty_material":
|
if "has_materials" in self._global_container_stack.getMetaData():
|
||||||
search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*" }
|
self._global_container_stack.setMetaDataEntry("has_materials", True)
|
||||||
containers = self._container_registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers:
|
|
||||||
global_container_stack.replaceContainer(material_index, containers[0])
|
|
||||||
else:
|
else:
|
||||||
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
|
self._global_container_stack.addMetaDataEntry("has_materials", True)
|
||||||
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
|
|
||||||
if "has_materials" in global_container_stack.getMetaData():
|
|
||||||
global_container_stack.removeMetaDataEntry("has_materials")
|
|
||||||
|
|
||||||
empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0]
|
# Set the material container to a sane default
|
||||||
global_container_stack.replaceContainer(material_index, empty_material)
|
if material_container.getId() == "empty_material":
|
||||||
|
search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*"}
|
||||||
|
containers = self._container_registry.findInstanceContainers(**search_criteria)
|
||||||
|
if containers:
|
||||||
|
self._global_container_stack.replaceContainer(material_index, containers[0])
|
||||||
|
else:
|
||||||
|
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
|
||||||
|
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
|
||||||
|
if "has_materials" in self._global_container_stack.getMetaData():
|
||||||
|
self._global_container_stack.removeMetaDataEntry("has_materials")
|
||||||
|
|
||||||
Application.getInstance().globalContainerStackChanged.emit()
|
empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0]
|
||||||
|
self._global_container_stack.replaceContainer(material_index, empty_material)
|
||||||
|
|
||||||
|
Application.getInstance().globalContainerStackChanged.emit()
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -26,129 +26,6 @@ Item {
|
||||||
|
|
||||||
spacing: UM.Theme.getSize("default_margin").height
|
spacing: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
Row
|
|
||||||
{
|
|
||||||
spacing: UM.Theme.getSize("default_margin").width
|
|
||||||
Label
|
|
||||||
{
|
|
||||||
text: catalog.i18nc("@label Followed by extruder selection drop-down.", "Print model with")
|
|
||||||
anchors.verticalCenter: extruderSelector.verticalCenter
|
|
||||||
|
|
||||||
color: UM.Theme.getColor("setting_control_text")
|
|
||||||
font: UM.Theme.getFont("default")
|
|
||||||
visible: extruderSelector.visible
|
|
||||||
}
|
|
||||||
ComboBox
|
|
||||||
{
|
|
||||||
id: extruderSelector
|
|
||||||
|
|
||||||
model: Cura.ExtrudersModel
|
|
||||||
{
|
|
||||||
id: extrudersModel
|
|
||||||
onModelChanged: extruderSelector.color = extrudersModel.getItem(extruderSelector.currentIndex).color
|
|
||||||
}
|
|
||||||
property string color: extrudersModel.getItem(extruderSelector.currentIndex).color
|
|
||||||
visible: machineExtruderCount.properties.value > 1
|
|
||||||
textRole: "name"
|
|
||||||
width: UM.Theme.getSize("setting_control").width
|
|
||||||
height: UM.Theme.getSize("section").height
|
|
||||||
MouseArea
|
|
||||||
{
|
|
||||||
anchors.fill: parent
|
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
onWheel: wheel.accepted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
style: ComboBoxStyle
|
|
||||||
{
|
|
||||||
background: Rectangle
|
|
||||||
{
|
|
||||||
color:
|
|
||||||
{
|
|
||||||
if(extruderSelector.hovered || base.activeFocus)
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("setting_control_highlight");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return UM.Theme.getColor("setting_control");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
|
||||||
border.color: UM.Theme.getColor("setting_control_border")
|
|
||||||
}
|
|
||||||
label: Item
|
|
||||||
{
|
|
||||||
Rectangle
|
|
||||||
{
|
|
||||||
id: swatch
|
|
||||||
height: UM.Theme.getSize("setting_control").height / 2
|
|
||||||
width: height
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_lining").width
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
color: extruderSelector.color
|
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
|
||||||
border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : UM.Theme.getColor("setting_control_border")
|
|
||||||
}
|
|
||||||
Label
|
|
||||||
{
|
|
||||||
anchors.left: swatch.right
|
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_lining").width
|
|
||||||
anchors.right: downArrow.left
|
|
||||||
anchors.rightMargin: UM.Theme.getSize("default_lining").width
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
text: extruderSelector.currentText
|
|
||||||
font: UM.Theme.getFont("default")
|
|
||||||
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
|
|
||||||
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
UM.RecolorImage
|
|
||||||
{
|
|
||||||
id: downArrow
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: UM.Theme.getSize("default_lining").width * 2
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
source: UM.Theme.getIcon("arrow_bottom")
|
|
||||||
width: UM.Theme.getSize("standard_arrow").width
|
|
||||||
height: UM.Theme.getSize("standard_arrow").height
|
|
||||||
sourceSize.width: width + 5
|
|
||||||
sourceSize.height: width + 5
|
|
||||||
|
|
||||||
color: UM.Theme.getColor("setting_control_text")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onActivated:
|
|
||||||
{
|
|
||||||
UM.ActiveTool.setProperty("SelectedActiveExtruder", extrudersModel.getItem(index).id);
|
|
||||||
extruderSelector.color = extrudersModel.getItem(index).color;
|
|
||||||
}
|
|
||||||
onModelChanged: updateCurrentIndex();
|
|
||||||
|
|
||||||
function updateCurrentIndex()
|
|
||||||
{
|
|
||||||
for(var i = 0; i < extrudersModel.rowCount(); ++i)
|
|
||||||
{
|
|
||||||
if(extrudersModel.getItem(i).id == UM.ActiveTool.properties.getValue("SelectedActiveExtruder"))
|
|
||||||
{
|
|
||||||
extruderSelector.currentIndex = i;
|
|
||||||
extruderSelector.color = extrudersModel.getItem(i).color;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
extruderSelector.currentIndex = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
// This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar.
|
// This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar.
|
||||||
|
|
|
@ -112,4 +112,4 @@ class PerObjectSettingsTool(Tool):
|
||||||
self._single_model_selected = False # Group is selected, so tool needs to be disabled
|
self._single_model_selected = False # Group is selected, so tool needs to be disabled
|
||||||
else:
|
else:
|
||||||
self._single_model_selected = True
|
self._single_model_selected = True
|
||||||
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, (self._advanced_mode or self._multi_extrusion) and self._single_model_selected)
|
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected)
|
||||||
|
|
|
@ -342,6 +342,8 @@ Cura.MachineAction
|
||||||
{
|
{
|
||||||
regExp: /[a-zA-Z0-9\.\-\_]*/
|
regExp: /[a-zA-Z0-9\.\-\_]*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAccepted: btnOk.clicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,6 +357,7 @@ Cura.MachineAction
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Button {
|
Button {
|
||||||
|
id: btnOk
|
||||||
text: catalog.i18nc("@action:button", "Ok")
|
text: catalog.i18nc("@action:button", "Ok")
|
||||||
onClicked:
|
onClicked:
|
||||||
{
|
{
|
||||||
|
|
|
@ -283,10 +283,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
||||||
#
|
#
|
||||||
# /param temperature The new target temperature of the bed.
|
# /param temperature The new target temperature of the bed.
|
||||||
def _setTargetBedTemperature(self, temperature):
|
def _setTargetBedTemperature(self, temperature):
|
||||||
if self._target_bed_temperature == temperature:
|
if not self._updateTargetBedTemperature(temperature):
|
||||||
return
|
return
|
||||||
self._target_bed_temperature = temperature
|
|
||||||
self.targetBedTemperatureChanged.emit()
|
|
||||||
|
|
||||||
url = QUrl("http://" + self._address + self._api_prefix + "printer/bed/temperature/target")
|
url = QUrl("http://" + self._address + self._api_prefix + "printer/bed/temperature/target")
|
||||||
data = str(temperature)
|
data = str(temperature)
|
||||||
|
@ -294,6 +292,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
||||||
put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
|
put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
|
||||||
self._manager.put(put_request, data.encode())
|
self._manager.put(put_request, data.encode())
|
||||||
|
|
||||||
|
## Updates the target bed temperature from the printer, and emit a signal if it was changed.
|
||||||
|
#
|
||||||
|
# /param temperature The new target temperature of the bed.
|
||||||
|
# /return boolean, True if the temperature was changed, false if the new temperature has the same value as the already stored temperature
|
||||||
|
def _updateTargetBedTemperature(self, temperature):
|
||||||
|
if self._target_bed_temperature == temperature:
|
||||||
|
return False
|
||||||
|
self._target_bed_temperature = temperature
|
||||||
|
self.targetBedTemperatureChanged.emit()
|
||||||
|
return True
|
||||||
|
|
||||||
def _stopCamera(self):
|
def _stopCamera(self):
|
||||||
self._camera_timer.stop()
|
self._camera_timer.stop()
|
||||||
if self._image_reply:
|
if self._image_reply:
|
||||||
|
@ -528,7 +537,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
|
||||||
bed_temperature = self._json_printer_state["bed"]["temperature"]["current"]
|
bed_temperature = self._json_printer_state["bed"]["temperature"]["current"]
|
||||||
self._setBedTemperature(bed_temperature)
|
self._setBedTemperature(bed_temperature)
|
||||||
target_bed_temperature = self._json_printer_state["bed"]["temperature"]["target"]
|
target_bed_temperature = self._json_printer_state["bed"]["temperature"]["target"]
|
||||||
self._setTargetBedTemperature(target_bed_temperature)
|
self._updateTargetBedTemperature(target_bed_temperature)
|
||||||
|
|
||||||
head_x = self._json_printer_state["heads"][0]["position"]["x"]
|
head_x = self._json_printer_state["heads"][0]["position"]["x"]
|
||||||
head_y = self._json_printer_state["heads"][0]["position"]["y"]
|
head_y = self._json_printer_state["heads"][0]["position"]["y"]
|
||||||
|
|
|
@ -259,7 +259,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
|
||||||
i = 0
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
values = winreg.EnumValue(key, i)
|
values = winreg.EnumValue(key, i)
|
||||||
if not only_list_usb or "USBSER" in values[0]:
|
if not only_list_usb or "USBSER" or "VCP" in values[0]:
|
||||||
base_list += [values[1]]
|
base_list += [values[1]]
|
||||||
i += 1
|
i += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
65
plugins/UltimakerMachineActions/UM2UpgradeSelection.py
Normal file
65
plugins/UltimakerMachineActions/UM2UpgradeSelection.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Uranium is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
from cura.MachineAction import MachineAction
|
||||||
|
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Util import parseBool
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
import UM.Settings.InstanceContainer
|
||||||
|
|
||||||
|
|
||||||
|
## The Ultimaker 2 can have a few revisions & upgrades.
|
||||||
|
class UM2UpgradeSelection(MachineAction):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__("UM2UpgradeSelection", catalog.i18nc("@action", "Select upgrades"))
|
||||||
|
self._qml_url = "UM2UpgradeSelectionMachineAction.qml"
|
||||||
|
|
||||||
|
self._container_registry = ContainerRegistry.getInstance()
|
||||||
|
|
||||||
|
def _reset(self):
|
||||||
|
self.hasVariantsChanged.emit()
|
||||||
|
|
||||||
|
hasVariantsChanged = pyqtSignal()
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = hasVariantsChanged)
|
||||||
|
def hasVariants(self):
|
||||||
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack:
|
||||||
|
return parseBool(global_container_stack.getMetaDataEntry("has_variants", "false"))
|
||||||
|
|
||||||
|
@pyqtSlot(bool)
|
||||||
|
def setHasVariants(self, has_variants = True):
|
||||||
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack:
|
||||||
|
variant_container = global_container_stack.variant
|
||||||
|
variant_index = global_container_stack.getContainerIndex(variant_container)
|
||||||
|
|
||||||
|
if has_variants:
|
||||||
|
if "has_variants" in global_container_stack.getMetaData():
|
||||||
|
global_container_stack.setMetaDataEntry("has_variants", True)
|
||||||
|
else:
|
||||||
|
global_container_stack.addMetaDataEntry("has_variants", True)
|
||||||
|
|
||||||
|
# Set the variant container to a sane default
|
||||||
|
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
|
if type(variant_container) == type(empty_container):
|
||||||
|
search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" }
|
||||||
|
containers = self._container_registry.findInstanceContainers(**search_criteria)
|
||||||
|
if containers:
|
||||||
|
global_container_stack.variant = containers[0]
|
||||||
|
else:
|
||||||
|
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
|
||||||
|
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
|
||||||
|
if "has_variants" in global_container_stack.getMetaData():
|
||||||
|
global_container_stack.removeMetaDataEntry("has_variants")
|
||||||
|
|
||||||
|
# Set the variant container to an empty variant
|
||||||
|
global_container_stack.variant = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
|
|
||||||
|
Application.getInstance().globalContainerStackChanged.emit()
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) 2016 Ultimaker B.V.
|
||||||
|
// Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
import QtQuick 2.2
|
||||||
|
import QtQuick.Controls 1.1
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
import QtQuick.Window 2.1
|
||||||
|
|
||||||
|
import UM 1.2 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
|
||||||
|
Cura.MachineAction
|
||||||
|
{
|
||||||
|
anchors.fill: parent;
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
id: upgradeSelectionMachineAction
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: pageTitle
|
||||||
|
width: parent.width
|
||||||
|
text: catalog.i18nc("@title", "Select Printer Upgrades")
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
font.pointSize: 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: pageDescription
|
||||||
|
anchors.top: pageTitle.bottom
|
||||||
|
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
text: catalog.i18nc("@label","Please select any upgrades made to this Ultimaker 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox
|
||||||
|
{
|
||||||
|
anchors.top: pageDescription.bottom
|
||||||
|
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
|
text: catalog.i18nc("@label", "Olsson Block")
|
||||||
|
checked: manager.hasVariants
|
||||||
|
onClicked: manager.setHasVariants(checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ from . import BedLevelMachineAction
|
||||||
from . import UpgradeFirmwareMachineAction
|
from . import UpgradeFirmwareMachineAction
|
||||||
from . import UMOCheckupMachineAction
|
from . import UMOCheckupMachineAction
|
||||||
from . import UMOUpgradeSelection
|
from . import UMOUpgradeSelection
|
||||||
|
from . import UM2UpgradeSelection
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
@ -21,4 +22,10 @@ def getMetaData():
|
||||||
}
|
}
|
||||||
|
|
||||||
def register(app):
|
def register(app):
|
||||||
return { "machine_action": [BedLevelMachineAction.BedLevelMachineAction(), UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(), UMOCheckupMachineAction.UMOCheckupMachineAction(), UMOUpgradeSelection.UMOUpgradeSelection()]}
|
return { "machine_action": [
|
||||||
|
BedLevelMachineAction.BedLevelMachineAction(),
|
||||||
|
UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(),
|
||||||
|
UMOCheckupMachineAction.UMOCheckupMachineAction(),
|
||||||
|
UMOUpgradeSelection.UMOUpgradeSelection(),
|
||||||
|
UM2UpgradeSelection.UM2UpgradeSelection()
|
||||||
|
]}
|
||||||
|
|
|
@ -10,6 +10,10 @@ _removed_settings = { #Settings that were removed in 2.5.
|
||||||
"start_layers_at_same_position"
|
"start_layers_at_same_position"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_split_settings = { #These settings should be copied to all settings it was split into.
|
||||||
|
"support_interface_line_distance": {"support_roof_line_distance", "support_bottom_line_distance"}
|
||||||
|
}
|
||||||
|
|
||||||
## A collection of functions that convert the configuration of the user in Cura
|
## A collection of functions that convert the configuration of the user in Cura
|
||||||
# 2.4 to a configuration for Cura 2.5.
|
# 2.4 to a configuration for Cura 2.5.
|
||||||
#
|
#
|
||||||
|
@ -42,8 +46,16 @@ class VersionUpgrade24to25(VersionUpgrade):
|
||||||
#Remove settings from the visible_settings.
|
#Remove settings from the visible_settings.
|
||||||
if parser.has_section("general") and "visible_settings" in parser["general"]:
|
if parser.has_section("general") and "visible_settings" in parser["general"]:
|
||||||
visible_settings = parser["general"]["visible_settings"].split(";")
|
visible_settings = parser["general"]["visible_settings"].split(";")
|
||||||
visible_settings = filter(lambda setting: setting not in _removed_settings, visible_settings)
|
new_visible_settings = []
|
||||||
parser["general"]["visible_settings"] = ";".join(visible_settings)
|
for setting in visible_settings:
|
||||||
|
if setting in _removed_settings:
|
||||||
|
continue #Skip.
|
||||||
|
if setting in _split_settings:
|
||||||
|
for replaced_setting in _split_settings[setting]:
|
||||||
|
new_visible_settings.append(replaced_setting)
|
||||||
|
continue #Don't add the original.
|
||||||
|
new_visible_settings.append(setting) #No special handling, so just add the original visible setting back.
|
||||||
|
parser["general"]["visible_settings"] = ";".join(new_visible_settings)
|
||||||
|
|
||||||
#Change the version number in the file.
|
#Change the version number in the file.
|
||||||
if parser.has_section("general"): #It better have!
|
if parser.has_section("general"): #It better have!
|
||||||
|
@ -66,6 +78,10 @@ class VersionUpgrade24to25(VersionUpgrade):
|
||||||
if parser.has_section("values"):
|
if parser.has_section("values"):
|
||||||
for removed_setting in (_removed_settings & parser["values"].keys()): #Both in keys that need to be removed and in keys present in the file.
|
for removed_setting in (_removed_settings & parser["values"].keys()): #Both in keys that need to be removed and in keys present in the file.
|
||||||
del parser["values"][removed_setting]
|
del parser["values"][removed_setting]
|
||||||
|
for replaced_setting in (_split_settings.keys() & parser["values"].keys()):
|
||||||
|
for replacement in _split_settings[replaced_setting]:
|
||||||
|
parser["values"][replacement] = parser["values"][replaced_setting] #Copy to replacement before removing the original!
|
||||||
|
del replaced_setting
|
||||||
|
|
||||||
#Change the version number in the file.
|
#Change the version number in the file.
|
||||||
if parser.has_section("general"):
|
if parser.has_section("general"):
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import io
|
import io
|
||||||
|
from typing import Optional
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
|
@ -11,7 +12,7 @@ from UM.Util import parseBool
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
import UM.Dictionary
|
import UM.Dictionary
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer, InvalidInstanceError
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
|
||||||
## Handles serializing and deserializing material containers from an XML file
|
## Handles serializing and deserializing material containers from an XML file
|
||||||
|
@ -370,8 +371,30 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
self._dirty = False
|
self._dirty = False
|
||||||
self._path = ""
|
self._path = ""
|
||||||
|
|
||||||
|
def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]:
|
||||||
|
return "material"
|
||||||
|
|
||||||
|
def getVersionFromSerialized(self, serialized: str) -> Optional[int]:
|
||||||
|
version = None
|
||||||
|
data = ET.fromstring(serialized)
|
||||||
|
metadata = data.iterfind("./um:metadata/*", self.__namespaces)
|
||||||
|
for entry in metadata:
|
||||||
|
tag_name = _tag_without_namespace(entry)
|
||||||
|
if tag_name == "version":
|
||||||
|
try:
|
||||||
|
version = int(entry.text)
|
||||||
|
except Exception as e:
|
||||||
|
raise InvalidInstanceError("Invalid version string '%s': %s" % (entry.text, e))
|
||||||
|
break
|
||||||
|
if version is None:
|
||||||
|
raise InvalidInstanceError("Missing version in metadata")
|
||||||
|
return version
|
||||||
|
|
||||||
## Overridden from InstanceContainer
|
## Overridden from InstanceContainer
|
||||||
def deserialize(self, serialized):
|
def deserialize(self, serialized):
|
||||||
|
# update the serialized data first
|
||||||
|
from UM.Settings.Interfaces import ContainerInterface
|
||||||
|
serialized = ContainerInterface.deserialize(self, serialized)
|
||||||
data = ET.fromstring(serialized)
|
data = ET.fromstring(serialized)
|
||||||
|
|
||||||
# Reset previous metadata
|
# Reset previous metadata
|
||||||
|
@ -406,10 +429,10 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
continue
|
continue
|
||||||
meta_data[tag_name] = entry.text
|
meta_data[tag_name] = entry.text
|
||||||
|
|
||||||
if not "description" in meta_data:
|
if "description" not in meta_data:
|
||||||
meta_data["description"] = ""
|
meta_data["description"] = ""
|
||||||
|
|
||||||
if not "adhesion_info" in meta_data:
|
if "adhesion_info" not in meta_data:
|
||||||
meta_data["adhesion_info"] = ""
|
meta_data["adhesion_info"] = ""
|
||||||
|
|
||||||
property_values = {}
|
property_values = {}
|
||||||
|
@ -583,7 +606,8 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
"Ultimaker 2 Extended": "ultimaker2_extended",
|
"Ultimaker 2 Extended": "ultimaker2_extended",
|
||||||
"Ultimaker 2 Extended+": "ultimaker2_extended_plus",
|
"Ultimaker 2 Extended+": "ultimaker2_extended_plus",
|
||||||
"Ultimaker Original": "ultimaker_original",
|
"Ultimaker Original": "ultimaker_original",
|
||||||
"Ultimaker Original+": "ultimaker_original_plus"
|
"Ultimaker Original+": "ultimaker_original_plus",
|
||||||
|
"IMADE3D JellyBOX": "imade3d_jellybox"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Map of recognised namespaces with a proper prefix.
|
# Map of recognised namespaces with a proper prefix.
|
||||||
|
|
61
resources/definitions/alya3dp.def.json
Normal file
61
resources/definitions/alya3dp.def.json
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"id": "alya3dp",
|
||||||
|
"name": "ALYA",
|
||||||
|
"version": 2,
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "ALYA",
|
||||||
|
"manufacturer": "ALYA",
|
||||||
|
"category": "Other",
|
||||||
|
"file_formats": "text/x-gcode"
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 100
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 133
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 100
|
||||||
|
},
|
||||||
|
"machine_center_is_zero": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"machine_head_shape_min_x": {
|
||||||
|
"default_value": 75
|
||||||
|
},
|
||||||
|
"machine_head_shape_min_y": {
|
||||||
|
"default_value": 18
|
||||||
|
},
|
||||||
|
"machine_head_shape_max_x": {
|
||||||
|
"default_value": 18
|
||||||
|
},
|
||||||
|
"machine_head_shape_max_y": {
|
||||||
|
"default_value": 35
|
||||||
|
},
|
||||||
|
"machine_nozzle_gantry_distance": {
|
||||||
|
"default_value": 55
|
||||||
|
},
|
||||||
|
"machine_nozzle_offset_x_1": {
|
||||||
|
"default_value": 18
|
||||||
|
},
|
||||||
|
"machine_nozzle_offset_y_1": {
|
||||||
|
"default_value": 0
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": ";Sliced at: {day} {date} {time}\n;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}\n;Print time: {print_time}\n;Filament used: {filament_amount}m {filament_weight}g\n;Filament cost: {filament_cost}\n;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line\n;M109 S{print_temperature} ;Uncomment to add your own temperature line\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to max endstops\nG1 Z115.0 F{travel_speed} ;move th e platform up 20mm\nG28 Z0 ;move Z to max endstop\nG1 Z15.0 F{travel_speed} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{travel_speed}\nM301 H1 P26.38 I2.57 D67.78\n;Put printing message on LCD screen\nM117 Printing..."
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG28 Z0\nM84 ;steppers off\nG90 ;absolute positioning\n;{profile_string}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,17 @@
|
||||||
"category": "Custom",
|
"category": "Custom",
|
||||||
"file_formats": "text/x-gcode",
|
"file_formats": "text/x-gcode",
|
||||||
"has_materials": true,
|
"has_materials": true,
|
||||||
|
"machine_extruder_trains":
|
||||||
|
{
|
||||||
|
"0": "custom_extruder_1",
|
||||||
|
"1": "custom_extruder_2",
|
||||||
|
"2": "custom_extruder_3",
|
||||||
|
"3": "custom_extruder_4",
|
||||||
|
"4": "custom_extruder_5",
|
||||||
|
"5": "custom_extruder_6",
|
||||||
|
"6": "custom_extruder_7",
|
||||||
|
"7": "custom_extruder_8"
|
||||||
|
},
|
||||||
"first_start_actions": ["MachineSettingsAction"]
|
"first_start_actions": ["MachineSettingsAction"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,23 +14,29 @@
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"machine_name": { "default_value": "Delta Go" },
|
"machine_name": { "default_value": "Delta Go" },
|
||||||
"material_diameter": { "default_value": 1.75 },
|
"material_diameter": { "default_value": 1.75 },
|
||||||
|
"default_material_print_temperature": { "default_value": 210 },
|
||||||
"speed_travel": { "default_value": 150 },
|
"speed_travel": { "default_value": 150 },
|
||||||
"prime_tower_size": { "default_value": 8.66 },
|
"prime_tower_size": { "default_value": 8.66 },
|
||||||
"infill_sparse_density": { "default_value": 10 },
|
"infill_sparse_density": { "default_value": 10 },
|
||||||
"speed_wall_x": { "default_value": 30 },
|
"speed_wall_x": { "default_value": 30 },
|
||||||
"speed_wall_0": { "default_value": 30 },
|
"speed_wall_0": { "default_value": 30 },
|
||||||
"speed_topbottom": { "default_value": 20 },
|
"speed_topbottom": { "default_value": 20 },
|
||||||
"layer_height": { "default_value": 0.2 },
|
"layer_height": { "default_value": 0.15 },
|
||||||
"speed_print": { "default_value": 30 },
|
"speed_print": { "default_value": 30 },
|
||||||
"machine_heated_bed": { "default_value": false },
|
"machine_heated_bed": { "default_value": false },
|
||||||
"machine_center_is_zero": { "default_value": true },
|
"machine_center_is_zero": { "default_value": true },
|
||||||
"machine_height": { "default_value": 127 },
|
"machine_height": { "default_value": 154 },
|
||||||
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
||||||
"machine_depth": { "default_value": 115 },
|
"machine_depth": { "default_value": 115 },
|
||||||
"machine_width": { "default_value": 115 },
|
"machine_width": { "default_value": 115 },
|
||||||
"retraction_amount": { "default_value": 4.2 },
|
"raft_airgap": { "default_value": 0.15 },
|
||||||
"retraction_speed": { "default_value": 400 },
|
"retraction_hop_enabled": { "value": "True" },
|
||||||
"machine_shape": { "default_value": "elliptic"}
|
"retraction_amount": { "default_value": 4.1 },
|
||||||
|
"retraction_speed": { "default_value": 500 },
|
||||||
|
"retraction_hop": { "value": "0.2" },
|
||||||
|
"retraction_hop_only_when_collides": { "value": "True" },
|
||||||
|
"brim_width": { "value": "5" },
|
||||||
|
"machine_shape": { "default_value": "elliptic"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,22 @@
|
||||||
"type": "extruder",
|
"type": "extruder",
|
||||||
"default_value": "0",
|
"default_value": "0",
|
||||||
"settable_per_mesh": true,
|
"settable_per_mesh": true,
|
||||||
"settable_per_extruder": false,
|
"settable_per_extruder": true,
|
||||||
"settable_per_meshgroup": false,
|
"settable_per_meshgroup": false,
|
||||||
"settable_globally": false
|
"settable_globally": false
|
||||||
},
|
},
|
||||||
|
"machine_nozzle_size":
|
||||||
|
{
|
||||||
|
"label": "Nozzle Diameter",
|
||||||
|
"description": "The inner diameter of the nozzle. Change this setting when using a non-standard nozzle size.",
|
||||||
|
"unit": "mm",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 0.4,
|
||||||
|
"minimum_value": "0.001",
|
||||||
|
"maximum_value_warning": "10",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
"machine_nozzle_offset_x":
|
"machine_nozzle_offset_x":
|
||||||
{
|
{
|
||||||
"label": "Nozzle X Offset",
|
"label": "Nozzle X Offset",
|
||||||
|
|
|
@ -746,7 +746,7 @@
|
||||||
"support_interface_line_width":
|
"support_interface_line_width":
|
||||||
{
|
{
|
||||||
"label": "Support Interface Line Width",
|
"label": "Support Interface Line Width",
|
||||||
"description": "Width of a single support interface line.",
|
"description": "Width of a single line of support roof or floor.",
|
||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
"default_value": 0.4,
|
"default_value": 0.4,
|
||||||
"minimum_value": "0.001",
|
"minimum_value": "0.001",
|
||||||
|
@ -757,7 +757,42 @@
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_interface_extruder_nr",
|
||||||
"value": "line_width",
|
"value": "line_width",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true
|
"settable_per_extruder": true,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"support_roof_line_width":
|
||||||
|
{
|
||||||
|
"label": "Support Roof Line Width",
|
||||||
|
"description": "Width of a single support roof line.",
|
||||||
|
"unit": "mm",
|
||||||
|
"default_value": 0.4,
|
||||||
|
"minimum_value": "0.001",
|
||||||
|
"minimum_value_warning": "0.4 * machine_nozzle_size",
|
||||||
|
"maximum_value_warning": "2 * machine_nozzle_size",
|
||||||
|
"type": "float",
|
||||||
|
"enabled": "support_enable and support_roof_enable",
|
||||||
|
"limit_to_extruder": "support_roof_extruder_nr",
|
||||||
|
"value": "support_interface_line_width",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
|
"support_bottom_line_width":
|
||||||
|
{
|
||||||
|
"label": "Support Floor Line Width",
|
||||||
|
"description": "Width of a single support floor line.",
|
||||||
|
"unit": "mm",
|
||||||
|
"default_value": 0.4,
|
||||||
|
"minimum_value": "0.001",
|
||||||
|
"minimum_value_warning": "0.4 * machine_nozzle_size",
|
||||||
|
"maximum_value_warning": "2 * machine_nozzle_size",
|
||||||
|
"type": "float",
|
||||||
|
"enabled": "support_enable and support_bottom_enable",
|
||||||
|
"limit_to_extruder": "support_bottom_extruder_nr",
|
||||||
|
"value": "support_interface_line_width",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"prime_tower_line_width":
|
"prime_tower_line_width":
|
||||||
{
|
{
|
||||||
|
@ -1301,7 +1336,7 @@
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"maximum_value_warning": "4",
|
"maximum_value_warning": "4",
|
||||||
"maximum_value": "(20 - math.log(infill_line_distance) / math.log(2)) if infill_line_distance > 0 and not spaghetti_infill_enabled else 0",
|
"maximum_value": "0 if spaghetti_infill_enabled else (999999 if infill_line_distance == 0 else (20 - math.log(infill_line_distance) / math.log(2)))",
|
||||||
"enabled": "infill_sparse_density > 0 and infill_pattern != 'cubicsubdiv' and not spaghetti_infill_enabled",
|
"enabled": "infill_sparse_density > 0 and infill_pattern != 'cubicsubdiv' and not spaghetti_infill_enabled",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
|
@ -1348,8 +1383,8 @@
|
||||||
{
|
{
|
||||||
"expand_upper_skins":
|
"expand_upper_skins":
|
||||||
{
|
{
|
||||||
"label": "Expand Upper Skins",
|
"label": "Expand Top Skins Into Infill",
|
||||||
"description": "Expand upper skin areas (areas with air above) so that they support infill above.",
|
"description": "Expand the top skin areas (areas with air above) so that they support infill above.",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": false,
|
"default_value": false,
|
||||||
"value": "expand_skins_into_infill",
|
"value": "expand_skins_into_infill",
|
||||||
|
@ -1357,8 +1392,8 @@
|
||||||
},
|
},
|
||||||
"expand_lower_skins":
|
"expand_lower_skins":
|
||||||
{
|
{
|
||||||
"label": "Expand Lower Skins",
|
"label": "Expand Bottom Skins Into Infill",
|
||||||
"description": "Expand lower skin areas (areas with air below) so that they are anchored by the infill layers above and below.",
|
"description": "Expand the bottom skin areas (areas with air below) so that they are anchored by the infill layers above and below.",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": false,
|
"default_value": false,
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
|
@ -1923,7 +1958,7 @@
|
||||||
"speed_support_interface":
|
"speed_support_interface":
|
||||||
{
|
{
|
||||||
"label": "Support Interface Speed",
|
"label": "Support Interface Speed",
|
||||||
"description": "The speed at which the roofs and bottoms of support are printed. Printing the them at lower speeds can improve overhang quality.",
|
"description": "The speed at which the roofs and floors of support are printed. Printing them at lower speeds can improve overhang quality.",
|
||||||
"unit": "mm/s",
|
"unit": "mm/s",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 40,
|
"default_value": 40,
|
||||||
|
@ -1934,7 +1969,42 @@
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_interface_extruder_nr",
|
||||||
"value": "speed_support / 1.5",
|
"value": "speed_support / 1.5",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true
|
"settable_per_extruder": true,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"speed_support_roof":
|
||||||
|
{
|
||||||
|
"label": "Support Roof Speed",
|
||||||
|
"description": "The speed at which the roofs of support are printed. Printing them at lower speeds can improve overhang quality.",
|
||||||
|
"unit": "mm/s",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 40,
|
||||||
|
"minimum_value": "0.1",
|
||||||
|
"maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
|
||||||
|
"maximum_value_warning": "150",
|
||||||
|
"enabled": "extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable",
|
||||||
|
"limit_to_extruder": "support_roof_extruder_nr",
|
||||||
|
"value": "speed_support_interface",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
|
"speed_support_bottom":
|
||||||
|
{
|
||||||
|
"label": "Support Floor Speed",
|
||||||
|
"description": "The speed at which the floor of support is printed. Printing it at lower speed can improve adhesion of support on top of your model.",
|
||||||
|
"unit": "mm/s",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 40,
|
||||||
|
"minimum_value": "0.1",
|
||||||
|
"maximum_value": "math.sqrt(machine_max_feedrate_x ** 2 + machine_max_feedrate_y ** 2)",
|
||||||
|
"maximum_value_warning": "150",
|
||||||
|
"enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable",
|
||||||
|
"limit_to_extruder": "support_bottom_extruder_nr",
|
||||||
|
"value": "speed_support_interface",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2209,7 +2279,7 @@
|
||||||
"acceleration_support_interface":
|
"acceleration_support_interface":
|
||||||
{
|
{
|
||||||
"label": "Support Interface Acceleration",
|
"label": "Support Interface Acceleration",
|
||||||
"description": "The acceleration with which the roofs and bottoms of support are printed. Printing them at lower accelerations can improve overhang quality.",
|
"description": "The acceleration with which the roofs and floors of support are printed. Printing them at lower acceleration can improve overhang quality.",
|
||||||
"unit": "mm/s²",
|
"unit": "mm/s²",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 3000,
|
"default_value": 3000,
|
||||||
|
@ -2220,7 +2290,42 @@
|
||||||
"enabled": "resolveOrValue('acceleration_enabled') and extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
"enabled": "resolveOrValue('acceleration_enabled') and extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_interface_extruder_nr",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true
|
"settable_per_extruder": true,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"acceleration_support_roof":
|
||||||
|
{
|
||||||
|
"label": "Support Roof Acceleration",
|
||||||
|
"description": "The acceleration with which the roofs of support are printed. Printing them at lower acceleration can improve overhang quality.",
|
||||||
|
"unit": "mm/s²",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 3000,
|
||||||
|
"value": "acceleration_support_interface",
|
||||||
|
"minimum_value": "0.1",
|
||||||
|
"minimum_value_warning": "100",
|
||||||
|
"maximum_value_warning": "10000",
|
||||||
|
"enabled": "resolveOrValue('acceleration_enabled') and extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable",
|
||||||
|
"limit_to_extruder": "support_roof_extruder_nr",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
|
"acceleration_support_bottom":
|
||||||
|
{
|
||||||
|
"label": "Support Floor Acceleration",
|
||||||
|
"description": "The acceleration with which the floors of support are printed. Printing them at lower acceleration can improve adhesion of support on top of your model.",
|
||||||
|
"unit": "mm/s²",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 3000,
|
||||||
|
"value": "acceleration_support_interface",
|
||||||
|
"minimum_value": "0.1",
|
||||||
|
"minimum_value_warning": "100",
|
||||||
|
"maximum_value_warning": "10000",
|
||||||
|
"enabled": "resolveOrValue('acceleration_enabled') and extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable",
|
||||||
|
"limit_to_extruder": "support_bottom_extruder_nr",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2440,7 +2545,7 @@
|
||||||
"jerk_support_interface":
|
"jerk_support_interface":
|
||||||
{
|
{
|
||||||
"label": "Support Interface Jerk",
|
"label": "Support Interface Jerk",
|
||||||
"description": "The maximum instantaneous velocity change with which the roofs and bottoms of support are printed.",
|
"description": "The maximum instantaneous velocity change with which the roofs and floors of support are printed.",
|
||||||
"unit": "mm/s",
|
"unit": "mm/s",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 20,
|
"default_value": 20,
|
||||||
|
@ -2450,7 +2555,42 @@
|
||||||
"enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
"enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_interface_extruder_nr",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true
|
"settable_per_extruder": true,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"jerk_support_roof":
|
||||||
|
{
|
||||||
|
"label": "Support Roof Jerk",
|
||||||
|
"description": "The maximum instantaneous velocity change with which the roofs of support are printed.",
|
||||||
|
"unit": "mm/s",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 20,
|
||||||
|
"value": "jerk_support_interface",
|
||||||
|
"minimum_value": "0.1",
|
||||||
|
"minimum_value_warning": "5",
|
||||||
|
"maximum_value_warning": "50",
|
||||||
|
"enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable",
|
||||||
|
"limit_to_extruder": "support_roof_extruder_nr",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
|
"jerk_support_bottom":
|
||||||
|
{
|
||||||
|
"label": "Support Floor Jerk",
|
||||||
|
"description": "The maximum instantaneous velocity change with which the floors of support are printed.",
|
||||||
|
"unit": "mm/s",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 20,
|
||||||
|
"value": "jerk_support_interface",
|
||||||
|
"minimum_value": "0.1",
|
||||||
|
"minimum_value_warning": "5",
|
||||||
|
"maximum_value_warning": "50",
|
||||||
|
"enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable",
|
||||||
|
"limit_to_extruder": "support_bottom_extruder_nr",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2849,7 +2989,7 @@
|
||||||
"description": "The extruder train to use for printing the support. This is used in multi-extrusion.",
|
"description": "The extruder train to use for printing the support. This is used in multi-extrusion.",
|
||||||
"type": "extruder",
|
"type": "extruder",
|
||||||
"default_value": "0",
|
"default_value": "0",
|
||||||
"enabled": "support_enable and machine_extruder_count > 1",
|
"enabled": "machine_extruder_count > 1",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": false,
|
"settable_per_extruder": false,
|
||||||
"children": {
|
"children": {
|
||||||
|
@ -2860,7 +3000,7 @@
|
||||||
"type": "extruder",
|
"type": "extruder",
|
||||||
"default_value": "0",
|
"default_value": "0",
|
||||||
"value": "support_extruder_nr",
|
"value": "support_extruder_nr",
|
||||||
"enabled": "support_enable and machine_extruder_count > 1",
|
"enabled": "machine_extruder_count > 1",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": false
|
"settable_per_extruder": false
|
||||||
},
|
},
|
||||||
|
@ -2871,20 +3011,45 @@
|
||||||
"type": "extruder",
|
"type": "extruder",
|
||||||
"default_value": "0",
|
"default_value": "0",
|
||||||
"value": "support_extruder_nr",
|
"value": "support_extruder_nr",
|
||||||
"enabled": "support_enable and machine_extruder_count > 1",
|
"enabled": "machine_extruder_count > 1",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": false
|
"settable_per_extruder": false
|
||||||
},
|
},
|
||||||
"support_interface_extruder_nr":
|
"support_interface_extruder_nr":
|
||||||
{
|
{
|
||||||
"label": "Support Interface Extruder",
|
"label": "Support Interface Extruder",
|
||||||
"description": "The extruder train to use for printing the roofs and bottoms of the support. This is used in multi-extrusion.",
|
"description": "The extruder train to use for printing the roofs and floors of the support. This is used in multi-extrusion.",
|
||||||
"type": "extruder",
|
"type": "extruder",
|
||||||
"default_value": "0",
|
"default_value": "0",
|
||||||
"value": "support_extruder_nr",
|
"value": "support_extruder_nr",
|
||||||
"enabled": "support_enable and machine_extruder_count > 1",
|
"enabled": "machine_extruder_count > 1",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": false
|
"settable_per_extruder": false,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"support_roof_extruder_nr":
|
||||||
|
{
|
||||||
|
"label": "Support Roof Extruder",
|
||||||
|
"description": "The extruder train to use for printing the roofs of the support. This is used in multi-extrusion.",
|
||||||
|
"type": "extruder",
|
||||||
|
"default_value": "0",
|
||||||
|
"value": "support_interface_extruder_nr",
|
||||||
|
"enabled": "machine_extruder_count > 1",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": false
|
||||||
|
},
|
||||||
|
"support_bottom_extruder_nr":
|
||||||
|
{
|
||||||
|
"label": "Support Floor Extruder",
|
||||||
|
"description": "The extruder train to use for printing the floors of the support. This is used in multi-extrusion.",
|
||||||
|
"type": "extruder",
|
||||||
|
"default_value": "0",
|
||||||
|
"value": "support_interface_extruder_nr",
|
||||||
|
"enabled": "machine_extruder_count > 1",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2914,7 +3079,7 @@
|
||||||
"maximum_value": "90",
|
"maximum_value": "90",
|
||||||
"maximum_value_warning": "80",
|
"maximum_value_warning": "80",
|
||||||
"default_value": 50,
|
"default_value": 50,
|
||||||
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
|
"limit_to_extruder": "support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr",
|
||||||
"enabled": "support_enable",
|
"enabled": "support_enable",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
|
@ -2933,7 +3098,7 @@
|
||||||
"zigzag": "Zig Zag"
|
"zigzag": "Zig Zag"
|
||||||
},
|
},
|
||||||
"default_value": "zigzag",
|
"default_value": "zigzag",
|
||||||
"enabled": "support_enable",
|
"enabled": true,
|
||||||
"limit_to_extruder": "support_infill_extruder_nr",
|
"limit_to_extruder": "support_infill_extruder_nr",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true
|
"settable_per_extruder": true
|
||||||
|
@ -2944,7 +3109,7 @@
|
||||||
"description": "Connect the ZigZags. This will increase the strength of the zig zag support structure.",
|
"description": "Connect the ZigZags. This will increase the strength of the zig zag support structure.",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": true,
|
"default_value": true,
|
||||||
"enabled": "support_enable and (support_pattern == 'zigzag')",
|
"enabled": "support_pattern == 'zigzag'",
|
||||||
"limit_to_extruder": "support_infill_extruder_nr",
|
"limit_to_extruder": "support_infill_extruder_nr",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true
|
"settable_per_extruder": true
|
||||||
|
@ -2958,7 +3123,7 @@
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"maximum_value_warning": "100",
|
"maximum_value_warning": "100",
|
||||||
"default_value": 15,
|
"default_value": 15,
|
||||||
"enabled": "support_enable",
|
"enabled": true,
|
||||||
"limit_to_extruder": "support_infill_extruder_nr",
|
"limit_to_extruder": "support_infill_extruder_nr",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true,
|
"settable_per_extruder": true,
|
||||||
|
@ -2973,7 +3138,7 @@
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"minimum_value_warning": "support_line_width",
|
"minimum_value_warning": "support_line_width",
|
||||||
"default_value": 2.66,
|
"default_value": 2.66,
|
||||||
"enabled": "support_enable",
|
"enabled": true,
|
||||||
"value": "(support_line_width * 100) / support_infill_rate * (2 if support_pattern == 'grid' else (3 if support_pattern == 'triangles' else 1))",
|
"value": "(support_line_width * 100) / support_infill_rate * (2 if support_pattern == 'grid' else (3 if support_pattern == 'triangles' else 1))",
|
||||||
"limit_to_extruder": "support_infill_extruder_nr",
|
"limit_to_extruder": "support_infill_extruder_nr",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
|
@ -3006,7 +3171,7 @@
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"enabled": "support_enable",
|
"enabled": "support_enable",
|
||||||
"value": "extruderValue(support_extruder_nr, 'support_z_distance')",
|
"value": "extruderValue(support_extruder_nr, 'support_z_distance')",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
|
"limit_to_extruder": "support_roof_extruder_nr if support_roof_enable else support_infill_extruder_nr",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
"support_bottom_distance":
|
"support_bottom_distance":
|
||||||
|
@ -3018,7 +3183,7 @@
|
||||||
"maximum_value_warning": "machine_nozzle_size",
|
"maximum_value_warning": "machine_nozzle_size",
|
||||||
"default_value": 0.1,
|
"default_value": 0.1,
|
||||||
"value": "extruderValue(support_extruder_nr, 'support_z_distance') if resolveOrValue('support_type') == 'everywhere' else 0",
|
"value": "extruderValue(support_extruder_nr, 'support_z_distance') if resolveOrValue('support_type') == 'everywhere' else 0",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
|
"limit_to_extruder": "support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"enabled": "support_enable and resolveOrValue('support_type') == 'everywhere'",
|
"enabled": "support_enable and resolveOrValue('support_type') == 'everywhere'",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
|
@ -3070,16 +3235,29 @@
|
||||||
"support_bottom_stair_step_height":
|
"support_bottom_stair_step_height":
|
||||||
{
|
{
|
||||||
"label": "Support Stair Step Height",
|
"label": "Support Stair Step Height",
|
||||||
"description": "The height of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures.",
|
"description": "The height of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures. Set to zero to turn off the stair-like behaviour.",
|
||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 0.3,
|
"default_value": 0.3,
|
||||||
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
|
"limit_to_extruder": "support_bottom_extruder_nr if support_bottom_enable else support_infill_extruder_nr",
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"maximum_value_warning": "1.0",
|
"maximum_value_warning": "1.0",
|
||||||
"enabled": "support_enable",
|
"enabled": "support_enable",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
|
"support_bottom_stair_step_width":
|
||||||
|
{
|
||||||
|
"label": "Support Stair Step Maximum Width",
|
||||||
|
"description": "The maximum width of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures.",
|
||||||
|
"unit": "mm",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 5.0,
|
||||||
|
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
|
||||||
|
"minimum_value": "0",
|
||||||
|
"maximum_value_warning": "10.0",
|
||||||
|
"enabled": "support_enable",
|
||||||
|
"settable_per_mesh": true
|
||||||
|
},
|
||||||
"support_join_distance":
|
"support_join_distance":
|
||||||
{
|
{
|
||||||
"label": "Support Join Distance",
|
"label": "Support Join Distance",
|
||||||
|
@ -3113,8 +3291,33 @@
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default_value": false,
|
"default_value": false,
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_interface_extruder_nr",
|
||||||
"enabled": "support_enable",
|
"enabled": true,
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"support_roof_enable":
|
||||||
|
{
|
||||||
|
"label": "Enable Support Roof",
|
||||||
|
"description": "Generate a dense slab of material between the top of support and the model. This will create a skin between the model and support.",
|
||||||
|
"type": "bool",
|
||||||
|
"default_value": false,
|
||||||
|
"value": "support_interface_enable",
|
||||||
|
"limit_to_extruder": "support_roof_extruder_nr",
|
||||||
|
"enabled": true,
|
||||||
|
"settable_per_mesh": true
|
||||||
|
},
|
||||||
|
"support_bottom_enable":
|
||||||
|
{
|
||||||
|
"label": "Enable Support Floor",
|
||||||
|
"description": "Generate a dense slab of material between the bottom of the support and the model. This will create a skin between the model and support.",
|
||||||
|
"type": "bool",
|
||||||
|
"default_value": false,
|
||||||
|
"value": "support_interface_enable",
|
||||||
|
"limit_to_extruder": "support_bottom_extruder_nr",
|
||||||
|
"enabled": true,
|
||||||
|
"settable_per_mesh": true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"support_interface_height":
|
"support_interface_height":
|
||||||
{
|
{
|
||||||
|
@ -3127,7 +3330,7 @@
|
||||||
"minimum_value_warning": "0.2 + resolveOrValue('layer_height')",
|
"minimum_value_warning": "0.2 + resolveOrValue('layer_height')",
|
||||||
"maximum_value_warning": "10",
|
"maximum_value_warning": "10",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_interface_extruder_nr",
|
||||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable')",
|
||||||
"settable_per_mesh": true,
|
"settable_per_mesh": true,
|
||||||
"children":
|
"children":
|
||||||
{
|
{
|
||||||
|
@ -3141,70 +3344,118 @@
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"minimum_value_warning": "0.2 + resolveOrValue('layer_height')",
|
"minimum_value_warning": "0.2 + resolveOrValue('layer_height')",
|
||||||
"maximum_value_warning": "10",
|
"maximum_value_warning": "10",
|
||||||
"value": "extruderValue(support_interface_extruder_nr, 'support_interface_height')",
|
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_height')",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_roof_extruder_nr",
|
||||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
"enabled": "extruderValue(support_roof_extruder_nr, 'support_roof_enable')",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
"support_bottom_height":
|
"support_bottom_height":
|
||||||
{
|
{
|
||||||
"label": "Support Bottom Thickness",
|
"label": "Support Floor Thickness",
|
||||||
"description": "The thickness of the support bottoms. This controls the number of dense layers are printed on top of places of a model on which support rests.",
|
"description": "The thickness of the support floors. This controls the number of dense layers that are printed on top of places of a model on which support rests.",
|
||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 1,
|
"default_value": 1,
|
||||||
"value": "extruderValue(support_interface_extruder_nr, 'support_interface_height')",
|
"value": "extruderValue(support_bottom_extruder_nr, 'support_interface_height')",
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"minimum_value_warning": "min(0.2 + resolveOrValue('layer_height'), extruderValue(support_interface_extruder_nr, 'support_bottom_stair_step_height'))",
|
"minimum_value_warning": "min(0.2 + resolveOrValue('layer_height'), extruderValue(support_bottom_extruder_nr, 'support_bottom_stair_step_height'))",
|
||||||
"maximum_value_warning": "10",
|
"maximum_value_warning": "10",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_bottom_extruder_nr",
|
||||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
"enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable')",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"support_interface_skip_height":
|
"support_interface_skip_height": {
|
||||||
{
|
|
||||||
"label": "Support Interface Resolution",
|
"label": "Support Interface Resolution",
|
||||||
"description": "When checking where there's model above the support, take steps of the given height. Lower values will slice slower, while higher values may cause normal support to be printed in some places where there should have been support interface.",
|
"description": "When checking where there's model above and below the support, take steps of the given height. Lower values will slice slower, while higher values may cause normal support to be printed in some places where there should have been support interface.",
|
||||||
"unit": "mm",
|
"unit": "mm",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 0.3,
|
"default_value": 0.3,
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"maximum_value_warning": "support_interface_height",
|
"maximum_value_warning": "support_interface_height",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_interface_extruder_nr",
|
||||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable')",
|
||||||
"settable_per_mesh": true
|
"settable_per_mesh": true
|
||||||
},
|
},
|
||||||
"support_interface_density":
|
"support_interface_density":
|
||||||
{
|
{
|
||||||
"label": "Support Interface Density",
|
"label": "Support Interface Density",
|
||||||
"description": "Adjusts the density of the roofs and bottoms of the support structure. A higher value results in better overhangs, but the supports are harder to remove.",
|
"description": "Adjusts the density of the roofs and floors of the support structure. A higher value results in better overhangs, but the supports are harder to remove.",
|
||||||
"unit": "%",
|
"unit": "%",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 100,
|
"default_value": 100,
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"maximum_value_warning": "100",
|
"maximum_value_warning": "100",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_interface_extruder_nr",
|
||||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable')",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true,
|
"settable_per_extruder": true,
|
||||||
"children":
|
"children":
|
||||||
{
|
{
|
||||||
"support_interface_line_distance":
|
"support_roof_density":
|
||||||
{
|
{
|
||||||
"label": "Support Interface Line Distance",
|
"label": "Support Roof Density",
|
||||||
"description": "Distance between the printed support interface lines. This setting is calculated by the Support Interface Density, but can be adjusted separately.",
|
"description": "The density of the roofs of the support structure. A higher value results in better overhangs, but the supports are harder to remove.",
|
||||||
"unit": "mm",
|
"unit": "%",
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 0.4,
|
"default_value": 100,
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
"minimum_value_warning": "support_interface_line_width - 0.0001",
|
"maximum_value": "100",
|
||||||
"value": "0 if support_interface_density == 0 else (support_interface_line_width * 100) / support_interface_density * (2 if support_interface_pattern == 'grid' else (3 if support_interface_pattern == 'triangles' else 1))",
|
"limit_to_extruder": "support_roof_extruder_nr",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"enabled": "extruderValue(support_roof_extruder_nr, 'support_roof_enable')",
|
||||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true
|
"settable_per_extruder": true,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"support_roof_line_distance":
|
||||||
|
{
|
||||||
|
"label": "Support Roof Line Distance",
|
||||||
|
"description": "Distance between the printed support roof lines. This setting is calculated by the Support Roof Density, but can be adjusted separately.",
|
||||||
|
"unit": "mm",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 0.4,
|
||||||
|
"minimum_value": "0",
|
||||||
|
"minimum_value_warning": "support_roof_line_width - 0.0001",
|
||||||
|
"value": "0 if support_roof_density == 0 else (support_roof_line_width * 100) / support_roof_density * (2 if support_roof_pattern == 'grid' else (3 if support_roof_pattern == 'triangles' else 1))",
|
||||||
|
"limit_to_extruder": "support_roof_extruder_nr",
|
||||||
|
"enabled": "extruderValue(support_roof_extruder_nr, 'support_roof_enable')",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"support_bottom_density":
|
||||||
|
{
|
||||||
|
"label": "Support Floor Density",
|
||||||
|
"description": "The density of the floors of the support structure. A higher value results in better adhesion of the support on top of the model.",
|
||||||
|
"unit": "%",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 100,
|
||||||
|
"minimum_value": "0",
|
||||||
|
"maximum_value": "100",
|
||||||
|
"limit_to_extruder": "support_bottom_extruder_nr",
|
||||||
|
"enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable')",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"support_bottom_line_distance":
|
||||||
|
{
|
||||||
|
"label": "Support Floor Line Distance",
|
||||||
|
"description": "Distance between the printed support floor lines. This setting is calculated by the Support Floor Density, but can be adjusted separately.",
|
||||||
|
"unit": "mm",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 0.4,
|
||||||
|
"minimum_value": "0",
|
||||||
|
"minimum_value_warning": "support_bottom_line_width - 0.0001",
|
||||||
|
"value": "0 if support_bottom_density == 0 else (support_bottom_line_width * 100) / support_bottom_density * (2 if support_bottom_pattern == 'grid' else (3 if support_bottom_pattern == 'triangles' else 1))",
|
||||||
|
"limit_to_extruder": "support_bottom_extruder_nr",
|
||||||
|
"enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable')",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -3224,9 +3475,54 @@
|
||||||
},
|
},
|
||||||
"default_value": "concentric",
|
"default_value": "concentric",
|
||||||
"limit_to_extruder": "support_interface_extruder_nr",
|
"limit_to_extruder": "support_interface_extruder_nr",
|
||||||
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
|
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable')",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
"settable_per_extruder": true
|
"settable_per_extruder": true,
|
||||||
|
"children":
|
||||||
|
{
|
||||||
|
"support_roof_pattern":
|
||||||
|
{
|
||||||
|
"label": "Support Roof Pattern",
|
||||||
|
"description": "The pattern with which the roofs of the support are printed.",
|
||||||
|
"type": "enum",
|
||||||
|
"options":
|
||||||
|
{
|
||||||
|
"lines": "Lines",
|
||||||
|
"grid": "Grid",
|
||||||
|
"triangles": "Triangles",
|
||||||
|
"concentric": "Concentric",
|
||||||
|
"concentric_3d": "Concentric 3D",
|
||||||
|
"zigzag": "Zig Zag"
|
||||||
|
},
|
||||||
|
"default_value": "concentric",
|
||||||
|
"value": "support_interface_pattern",
|
||||||
|
"limit_to_extruder": "support_roof_extruder_nr",
|
||||||
|
"enabled": "extruderValue(support_roof_extruder_nr, 'support_roof_enable')",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
|
"support_bottom_pattern":
|
||||||
|
{
|
||||||
|
"label": "Support Floor Pattern",
|
||||||
|
"description": "The pattern with which the floors of the support are printed.",
|
||||||
|
"type": "enum",
|
||||||
|
"options":
|
||||||
|
{
|
||||||
|
"lines": "Lines",
|
||||||
|
"grid": "Grid",
|
||||||
|
"triangles": "Triangles",
|
||||||
|
"concentric": "Concentric",
|
||||||
|
"concentric_3d": "Concentric 3D",
|
||||||
|
"zigzag": "Zig Zag"
|
||||||
|
},
|
||||||
|
"default_value": "concentric",
|
||||||
|
"value": "support_interface_pattern",
|
||||||
|
"limit_to_extruder": "support_bottom_extruder_nr",
|
||||||
|
"enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable')",
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"support_use_towers":
|
"support_use_towers":
|
||||||
{
|
{
|
||||||
|
@ -3290,6 +3586,16 @@
|
||||||
"description": "Adhesion",
|
"description": "Adhesion",
|
||||||
"children":
|
"children":
|
||||||
{
|
{
|
||||||
|
"prime_blob_enable":
|
||||||
|
{
|
||||||
|
"label": "Enable Prime Blob",
|
||||||
|
"description": "Whether to prime the filament with a blob before printing. Turning this setting on will ensure that the extruder will have material ready at the nozzle before printing. Printing Brim or Skirt can act like priming too, in which case turning this setting off saves some time.",
|
||||||
|
"type": "bool",
|
||||||
|
"resolve": "any(extruderValues('prime_blob_enable'))",
|
||||||
|
"default_value": true,
|
||||||
|
"settable_per_mesh": false,
|
||||||
|
"settable_per_extruder": true
|
||||||
|
},
|
||||||
"extruder_prime_pos_x":
|
"extruder_prime_pos_x":
|
||||||
{
|
{
|
||||||
"label": "Extruder Prime X Position",
|
"label": "Extruder Prime X Position",
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
"machine_center_is_zero": { "default_value": false },
|
"machine_center_is_zero": { "default_value": false },
|
||||||
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
||||||
"machine_start_gcode": {
|
"machine_start_gcode": {
|
||||||
"default_value": ";---------------------------------------\n; ; ; Jellybox Start Script Begin ; ; ;\n;_______________________________________\n; M92 E140 ;optionally adjust steps per mm for your filament\n\n; Print Settings Summary\n; (leave these alone: this is only a list of the slicing settings)\n; (overwriting these values will NOT change your printer's behavior)\n; sliced for : {machine_name}\n; nozzle diameter : {machine_nozzle_size}\n; filament diameter : {material_diameter}\n; layer height : {layer_height}\n; 1st layer height : {layer_height_0}\n; line width : {line_width}\n; outer wall wipe dist. : {wall_0_wipe_dist}\n; infill line width : {infill_line_width}\n; wall thickness : {wall_thickness}\n; top thickness : {top_thickness}\n; bottom thickness : {bottom_thickness}\n; infill density : {infill_sparse_density}\n; infill pattern : {infill_pattern}\n; print temperature : {material_print_temperature}\n; 1st layer print temp. : {material_print_temperature_layer_0}\n; heated bed temperature : {material_bed_temperature}\n; 1st layer bed temp. : {material_bed_temperature_layer_0}\n; regular fan speed : {cool_fan_speed_min}\n; max fan speed : {cool_fan_speed_max}\n; retraction amount : {retraction_amount}\n; retr. retract speed : {retraction_retract_speed}\n; retr. prime speed : {retraction_prime_speed}\n; build plate adhesion : {adhesion_type}\n; support ? {support_enable}\n; spiralized ? {magic_spiralize}\n\nM117 Preparing ;write Preparing\nM140 S{material_bed_temperature_layer_0} ;set bed temperature and move on\nM104 S{material_print_temperature_layer_0} ;set extruder temperature and move on\nM206 X10.0 Y0.0 ;set x homing offset for default bed leveling\nG21 ;metric values\nG90 ;absolute positioning\nM107 ;start with the fan off\nM82 ;set extruder to absolute mode\nG28 ;home all axes\nM203 Z4 ;slow Z speed down for greater accuracy when probing\nG29 ;auto bed leveling procedure\nM203 Z7 ;pick up z speed again for printing\nM190 S{material_bed_temperature_layer_0} ;wait for the bed to reach desired temperature\nM109 S{material_print_temperature_layer_0} ;wait for the extruder to reach desired temperature\nG92 E0 ;reset the extruder position\nG1 F1500 E15 ;extrude 15mm of feed stock\nG92 E0 ;reset the extruder position again\nM117 Print starting ;write Print starting\n;---------------------------------------------\n; ; ; Jellybox Printer Start Script End ; ; ;\n;_____________________________________________\n"
|
"default_value": ";---------------------------------------\n; ; ; Jellybox Start Script Begin ; ; ;\n;_______________________________________\n; M92 E140 ;optionally adjust steps per mm for your filament\n\n; Print Settings Summary\n; (leave these alone: this is only a list of the slicing settings)\n; (overwriting these values will NOT change your printer's behavior)\n; sliced for : {machine_name}\n; nozzle diameter : {machine_nozzle_size}\n; filament diameter : {material_diameter}\n; layer height : {layer_height}\n; 1st layer height : {layer_height_0}\n; line width : {line_width}\n; outer wall wipe dist. : {wall_0_wipe_dist}\n; infill line width : {infill_line_width}\n; wall thickness : {wall_thickness}\n; top thickness : {top_thickness}\n; bottom thickness : {bottom_thickness}\n; infill density : {infill_sparse_density}\n; infill pattern : {infill_pattern}\n; print temperature : {material_print_temperature}\n; 1st layer print temp. : {material_print_temperature_layer_0}\n; heated bed temperature : {material_bed_temperature}\n; 1st layer bed temp. : {material_bed_temperature_layer_0}\n; regular fan speed : {cool_fan_speed_min}\n; max fan speed : {cool_fan_speed_max}\n; retraction amount : {retraction_amount}\n; retr. retract speed : {retraction_retract_speed}\n; retr. prime speed : {retraction_prime_speed}\n; build plate adhesion : {adhesion_type}\n; support ? {support_enable}\n; spiralized ? {magic_spiralize}\n\nM117 Preparing ;write Preparing\nM140 S{material_bed_temperature_layer_0} ;set bed temperature and move on\nM109 S{material_print_temperature} ; wait for the extruder to reach desired temperature\nM206 X10.0 Y0.0 ;set x homing offset for default bed leveling\nG21 ;metric values\nG90 ;absolute positioning\nM107 ;start with the fan off\nM82 ;set extruder to absolute mode\nG28 ;home all axes\nM203 Z4 ;slow Z speed down for greater accuracy when probing\nG29 ;auto bed leveling procedure\nM203 Z7 ;pick up z speed again for printing\nM190 S{material_bed_temperature_layer_0} ;wait for the bed to reach desired temperature\nM109 S{material_print_temperature_layer_0} ;wait for the extruder to reach desired temperature\nG92 E0 ;reset the extruder position\nG1 F1500 E15 ;extrude 15mm of feed stock\nG92 E0 ;reset the extruder position again\nM117 Print starting ;write Print starting\n;---------------------------------------------\n; ; ; Jellybox Printer Start Script End ; ; ;\n;_____________________________________________\n"
|
||||||
},
|
},
|
||||||
"machine_end_gcode": {
|
"machine_end_gcode": {
|
||||||
"default_value": "\n;---------------------------------\n;;; Jellybox End Script Begin ;;;\n;_________________________________\nM117 Finishing Up ;write Finishing Up\n\nM104 S0 ;extruder heater off\nM140 S0 ;bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG28 X ;home x, so the head is out of the way\nG1 Y100 ;move Y forward, so the print is more accessible\nM84 ;steppers off\n\nM117 Print finished ;write Print finished\n;---------------------------------------\n;;; Jellybox End Script End ;;;\n;_______________________________________"
|
"default_value": "\n;---------------------------------\n;;; Jellybox End Script Begin ;;;\n;_________________________________\nM117 Finishing Up ;write Finishing Up\n\nM104 S0 ;extruder heater off\nM140 S0 ;bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG28 X ;home x, so the head is out of the way\nG1 Y100 ;move Y forward, so the print is more accessible\nM84 ;steppers off\n\nM117 Print finished ;write Print finished\n;---------------------------------------\n;;; Jellybox End Script End ;;;\n;_______________________________________"
|
||||||
|
|
130
resources/definitions/rigid3d_zero2.def.json
Normal file
130
resources/definitions/rigid3d_zero2.def.json
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
{
|
||||||
|
"id": "rigid3d_zero2",
|
||||||
|
"name": "Rigid3D Zero2",
|
||||||
|
"version": 2,
|
||||||
|
"inherits": "fdmprinter",
|
||||||
|
"metadata": {
|
||||||
|
"visible": true,
|
||||||
|
"author": "Rigid3D",
|
||||||
|
"manufacturer": "Rigid3D",
|
||||||
|
"category": "Other",
|
||||||
|
"has_materials": false,
|
||||||
|
"file_formats": "text/x-gcode",
|
||||||
|
"platform": "rigid3d_zero2_platform.stl",
|
||||||
|
"platform_offset": [ 5, 0, -35]
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"machine_name": { "default_value": "Rigid3D Zero2" },
|
||||||
|
"machine_head_with_fans_polygon": {
|
||||||
|
"default_value": [[ 30, 30], [ 30, 70], [ 30, 70], [ 30, 30]]
|
||||||
|
},
|
||||||
|
"z_seam_type": {
|
||||||
|
"default_value": "random"
|
||||||
|
},
|
||||||
|
"machine_heated_bed": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"layer_height": {
|
||||||
|
"default_value": 0.2
|
||||||
|
},
|
||||||
|
"layer_height_0": {
|
||||||
|
"default_value": 0.2
|
||||||
|
},
|
||||||
|
"wall_thickness": {
|
||||||
|
"default_value": 0.8
|
||||||
|
},
|
||||||
|
"top_bottom_thickness": {
|
||||||
|
"default_value": 0.8
|
||||||
|
},
|
||||||
|
"xy_offset": {
|
||||||
|
"default_value": -0.2
|
||||||
|
},
|
||||||
|
"material_print_temperature": {
|
||||||
|
"value": 235
|
||||||
|
},
|
||||||
|
"material_bed_temperature": {
|
||||||
|
"default_value": 100
|
||||||
|
},
|
||||||
|
"material_diameter": {
|
||||||
|
"default_value": 1.75
|
||||||
|
},
|
||||||
|
"speed_print": {
|
||||||
|
"default_value": 40
|
||||||
|
},
|
||||||
|
"speed_layer_0": {
|
||||||
|
"value": 15
|
||||||
|
},
|
||||||
|
"speed_travel": {
|
||||||
|
"value": 100
|
||||||
|
},
|
||||||
|
"support_enable": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"infill_sparse_density": {
|
||||||
|
"default_value": 15
|
||||||
|
},
|
||||||
|
"infill_pattern": {
|
||||||
|
"default_value": "lines",
|
||||||
|
"value": "lines"
|
||||||
|
},
|
||||||
|
"retraction_amount": {
|
||||||
|
"default_value": 1
|
||||||
|
},
|
||||||
|
"machine_width": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_height": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_depth": {
|
||||||
|
"default_value": 200
|
||||||
|
},
|
||||||
|
"machine_center_is_zero": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"machine_nozzle_size": {
|
||||||
|
"default_value": 0.4
|
||||||
|
},
|
||||||
|
"gantry_height": {
|
||||||
|
"default_value": 25
|
||||||
|
},
|
||||||
|
"machine_gcode_flavor": {
|
||||||
|
"default_value": "RepRap"
|
||||||
|
},
|
||||||
|
"cool_fan_enabled": {
|
||||||
|
"default_value": false
|
||||||
|
},
|
||||||
|
"cool_fan_speed": {
|
||||||
|
"default_value": 50,
|
||||||
|
"value": 50
|
||||||
|
},
|
||||||
|
"cool_fan_speed_min": {
|
||||||
|
"default_value": 0
|
||||||
|
},
|
||||||
|
"cool_fan_full_at_height": {
|
||||||
|
"default_value": 1.0,
|
||||||
|
"value": 1.0
|
||||||
|
},
|
||||||
|
"support_z_distance": {
|
||||||
|
"default_value": 0.2
|
||||||
|
},
|
||||||
|
"support_interface_enable": {
|
||||||
|
"default_value": true
|
||||||
|
},
|
||||||
|
"support_interface_height": {
|
||||||
|
"default_value": 0.8
|
||||||
|
},
|
||||||
|
"support_interface_density": {
|
||||||
|
"default_value": 70
|
||||||
|
},
|
||||||
|
"support_interface_pattern": {
|
||||||
|
"default_value": "grid"
|
||||||
|
},
|
||||||
|
"machine_start_gcode": {
|
||||||
|
"default_value": "G21\nG28 ; Home extruder\nM107 ; Turn off fan\nG91 ; Relative positioning\nG1 Z5 F180;\nG1 X100 Y100 F3000;\nG1 Z-5 F180;\nG90 ; Absolute positioning\nM82 ; Extruder in absolute mode\nG92 E0 ; Reset extruder position\n"
|
||||||
|
},
|
||||||
|
"machine_end_gcode": {
|
||||||
|
"default_value": "G1 X0 Y180 ; Get extruder out of way.\nM107 ; Turn off fan\nG91 ; Relative positioning\nG0 Z20 ; Lift extruder up\nT0\nG1 E-1 ; Reduce filament pressure\nM104 T0 S0 ; Turn extruder heater off\nG90 ; Absolute positioning\nG92 E0 ; Reset extruder position\nM140 S0 ; Disable heated bed\nM84 ; Turn steppers off\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,8 @@
|
||||||
"platform_texture": "Ultimaker2backplate.png",
|
"platform_texture": "Ultimaker2backplate.png",
|
||||||
"platform_offset": [9, 0, 0],
|
"platform_offset": [9, 0, 0],
|
||||||
"has_materials": false,
|
"has_materials": false,
|
||||||
"supported_actions":["UpgradeFirmware"]
|
"first_start_actions": ["UM2UpgradeSelection"],
|
||||||
|
"supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"]
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"machine_name": { "default_value": "Ultimaker 2" },
|
"machine_name": { "default_value": "Ultimaker 2" },
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"has_materials": true,
|
"has_materials": true,
|
||||||
"has_machine_materials": true,
|
"has_machine_materials": true,
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
|
"first_start_actions": [],
|
||||||
"supported_actions":["UpgradeFirmware"]
|
"supported_actions":["UpgradeFirmware"]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
"machine_start_gcode": { "default_value": "" },
|
"machine_start_gcode": { "default_value": "" },
|
||||||
"machine_end_gcode": { "default_value": "" },
|
"machine_end_gcode": { "default_value": "" },
|
||||||
"prime_tower_position_x": { "default_value": 175 },
|
"prime_tower_position_x": { "default_value": 175 },
|
||||||
"prime_tower_position_y": { "default_value": 179 },
|
"prime_tower_position_y": { "default_value": 178 },
|
||||||
"prime_tower_wipe_enabled": { "default_value": false },
|
"prime_tower_wipe_enabled": { "default_value": false },
|
||||||
|
|
||||||
"acceleration_enabled": { "value": "True" },
|
"acceleration_enabled": { "value": "True" },
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
"layer_height_0": { "value": "round(machine_nozzle_size / 1.5, 2)" },
|
"layer_height_0": { "value": "round(machine_nozzle_size / 1.5, 2)" },
|
||||||
"layer_start_x": { "value": "sum(extruderValues('machine_extruder_start_pos_x')) / len(extruderValues('machine_extruder_start_pos_x'))" },
|
"layer_start_x": { "value": "sum(extruderValues('machine_extruder_start_pos_x')) / len(extruderValues('machine_extruder_start_pos_x'))" },
|
||||||
"layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" },
|
"layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" },
|
||||||
"line_width": { "value": "machine_nozzle_size * 0.875" },
|
"line_width": { "value": "round(machine_nozzle_size * 0.875, 3)" },
|
||||||
"machine_min_cool_heat_time_window": { "value": "15" },
|
"machine_min_cool_heat_time_window": { "value": "15" },
|
||||||
"default_material_print_temperature": { "value": "200" },
|
"default_material_print_temperature": { "value": "200" },
|
||||||
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
|
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
|
||||||
|
|
17
resources/extruders/custom_extruder_1.def.json
Normal file
17
resources/extruders/custom_extruder_1.def.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "custom_extruder_1",
|
||||||
|
"version": 2,
|
||||||
|
"name": "Extruder 1",
|
||||||
|
"inherits": "fdmextruder",
|
||||||
|
"metadata": {
|
||||||
|
"machine": "custom",
|
||||||
|
"position": "0"
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"extruder_nr": {
|
||||||
|
"default_value": 0,
|
||||||
|
"maximum_value": "7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
resources/extruders/custom_extruder_2.def.json
Normal file
17
resources/extruders/custom_extruder_2.def.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "custom_extruder_2",
|
||||||
|
"version": 2,
|
||||||
|
"name": "Extruder 2",
|
||||||
|
"inherits": "fdmextruder",
|
||||||
|
"metadata": {
|
||||||
|
"machine": "custom",
|
||||||
|
"position": "1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"extruder_nr": {
|
||||||
|
"default_value": 1,
|
||||||
|
"maximum_value": "7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
resources/extruders/custom_extruder_3.def.json
Normal file
17
resources/extruders/custom_extruder_3.def.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "custom_extruder_3",
|
||||||
|
"version": 2,
|
||||||
|
"name": "Extruder 3",
|
||||||
|
"inherits": "fdmextruder",
|
||||||
|
"metadata": {
|
||||||
|
"machine": "custom",
|
||||||
|
"position": "2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"extruder_nr": {
|
||||||
|
"default_value": 2,
|
||||||
|
"maximum_value": "7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
resources/extruders/custom_extruder_4.def.json
Normal file
17
resources/extruders/custom_extruder_4.def.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "custom_extruder_4",
|
||||||
|
"version": 2,
|
||||||
|
"name": "Extruder 4",
|
||||||
|
"inherits": "fdmextruder",
|
||||||
|
"metadata": {
|
||||||
|
"machine": "custom",
|
||||||
|
"position": "3"
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"extruder_nr": {
|
||||||
|
"default_value": 3,
|
||||||
|
"maximum_value": "7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
resources/extruders/custom_extruder_5.def.json
Normal file
17
resources/extruders/custom_extruder_5.def.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "custom_extruder_5",
|
||||||
|
"version": 2,
|
||||||
|
"name": "Extruder 5",
|
||||||
|
"inherits": "fdmextruder",
|
||||||
|
"metadata": {
|
||||||
|
"machine": "custom",
|
||||||
|
"position": "4"
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"extruder_nr": {
|
||||||
|
"default_value": 4,
|
||||||
|
"maximum_value": "7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
resources/extruders/custom_extruder_6.def.json
Normal file
17
resources/extruders/custom_extruder_6.def.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "custom_extruder_6",
|
||||||
|
"version": 2,
|
||||||
|
"name": "Extruder 6",
|
||||||
|
"inherits": "fdmextruder",
|
||||||
|
"metadata": {
|
||||||
|
"machine": "custom",
|
||||||
|
"position": "5"
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"extruder_nr": {
|
||||||
|
"default_value": 5,
|
||||||
|
"maximum_value": "7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
resources/extruders/custom_extruder_7.def.json
Normal file
17
resources/extruders/custom_extruder_7.def.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "custom_extruder_7",
|
||||||
|
"version": 2,
|
||||||
|
"name": "Extruder 7",
|
||||||
|
"inherits": "fdmextruder",
|
||||||
|
"metadata": {
|
||||||
|
"machine": "custom",
|
||||||
|
"position": "6"
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"extruder_nr": {
|
||||||
|
"default_value": 6,
|
||||||
|
"maximum_value": "7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
resources/extruders/custom_extruder_8.def.json
Normal file
17
resources/extruders/custom_extruder_8.def.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"id": "custom_extruder_8",
|
||||||
|
"version": 2,
|
||||||
|
"name": "Extruder 8",
|
||||||
|
"inherits": "fdmextruder",
|
||||||
|
"metadata": {
|
||||||
|
"machine": "custom",
|
||||||
|
"position": "7"
|
||||||
|
},
|
||||||
|
|
||||||
|
"overrides": {
|
||||||
|
"extruder_nr": {
|
||||||
|
"default_value": 7,
|
||||||
|
"maximum_value": "7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
BIN
resources/meshes/rigid3d_zero2_platform.stl
Normal file
BIN
resources/meshes/rigid3d_zero2_platform.stl
Normal file
Binary file not shown.
|
@ -18,6 +18,8 @@ Item
|
||||||
property alias redo: redoAction;
|
property alias redo: redoAction;
|
||||||
|
|
||||||
property alias deleteSelection: deleteSelectionAction;
|
property alias deleteSelection: deleteSelectionAction;
|
||||||
|
property alias centerSelection: centerSelectionAction;
|
||||||
|
property alias multiplySelection: multiplySelectionAction;
|
||||||
|
|
||||||
property alias deleteObject: deleteObjectAction;
|
property alias deleteObject: deleteObjectAction;
|
||||||
property alias centerObject: centerObjectAction;
|
property alias centerObject: centerObjectAction;
|
||||||
|
@ -181,11 +183,29 @@ Item
|
||||||
Action
|
Action
|
||||||
{
|
{
|
||||||
id: deleteSelectionAction;
|
id: deleteSelectionAction;
|
||||||
text: catalog.i18nc("@action:inmenu menubar:edit","Delete &Selection");
|
text: catalog.i18ncp("@action:inmenu menubar:edit", "Delete &Selected Model", "Delete &Selected Models", UM.Selection.selectionCount);
|
||||||
enabled: UM.Controller.toolsEnabled;
|
enabled: UM.Controller.toolsEnabled && UM.Selection.hasSelection;
|
||||||
iconName: "edit-delete";
|
iconName: "edit-delete";
|
||||||
shortcut: StandardKey.Delete;
|
shortcut: StandardKey.Delete;
|
||||||
onTriggered: CuraApplication.deleteSelection();
|
onTriggered: CuraActions.deleteSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
Action
|
||||||
|
{
|
||||||
|
id: centerSelectionAction;
|
||||||
|
text: catalog.i18ncp("@action:inmenu menubar:edit", "Center Selected Model", "Center Selected Models", UM.Selection.selectionCount);
|
||||||
|
enabled: UM.Controller.toolsEnabled && UM.Selection.hasSelection;
|
||||||
|
iconName: "align-vertical-center";
|
||||||
|
onTriggered: CuraActions.centerSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
Action
|
||||||
|
{
|
||||||
|
id: multiplySelectionAction;
|
||||||
|
text: catalog.i18ncp("@action:inmenu menubar:edit", "Multiply Selected Model", "Multiply Selected Models", UM.Selection.selectionCount);
|
||||||
|
enabled: UM.Controller.toolsEnabled && UM.Selection.hasSelection;
|
||||||
|
iconName: "edit-duplicate";
|
||||||
|
shortcut: "Ctrl+M"
|
||||||
}
|
}
|
||||||
|
|
||||||
Action
|
Action
|
||||||
|
|
|
@ -594,102 +594,8 @@ UM.MainWindow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu
|
ContextMenu {
|
||||||
{
|
id: contextMenu
|
||||||
id: objectContextMenu;
|
|
||||||
|
|
||||||
property variant objectId: -1;
|
|
||||||
MenuItem { action: Cura.Actions.centerObject; }
|
|
||||||
MenuItem { action: Cura.Actions.deleteObject; }
|
|
||||||
MenuItem { action: Cura.Actions.multiplyObject; }
|
|
||||||
MenuSeparator { }
|
|
||||||
MenuItem { action: Cura.Actions.selectAll; }
|
|
||||||
MenuItem { action: Cura.Actions.arrangeAll; }
|
|
||||||
MenuItem { action: Cura.Actions.deleteAll; }
|
|
||||||
MenuItem { action: Cura.Actions.reloadAll; }
|
|
||||||
MenuItem { action: Cura.Actions.resetAllTranslation; }
|
|
||||||
MenuItem { action: Cura.Actions.resetAll; }
|
|
||||||
MenuSeparator { }
|
|
||||||
MenuItem { action: Cura.Actions.groupObjects; }
|
|
||||||
MenuItem { action: Cura.Actions.mergeObjects; }
|
|
||||||
MenuItem { action: Cura.Actions.unGroupObjects; }
|
|
||||||
|
|
||||||
Connections
|
|
||||||
{
|
|
||||||
target: Cura.Actions.deleteObject
|
|
||||||
onTriggered:
|
|
||||||
{
|
|
||||||
if(objectContextMenu.objectId != 0)
|
|
||||||
{
|
|
||||||
CuraApplication.deleteObject(objectContextMenu.objectId);
|
|
||||||
objectContextMenu.objectId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiplyObjectOptions
|
|
||||||
{
|
|
||||||
id: multiplyObjectOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections
|
|
||||||
{
|
|
||||||
target: Cura.Actions.multiplyObject
|
|
||||||
onTriggered:
|
|
||||||
{
|
|
||||||
if(objectContextMenu.objectId != 0)
|
|
||||||
{
|
|
||||||
multiplyObjectOptions.objectId = objectContextMenu.objectId;
|
|
||||||
multiplyObjectOptions.visible = true;
|
|
||||||
multiplyObjectOptions.reset();
|
|
||||||
objectContextMenu.objectId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections
|
|
||||||
{
|
|
||||||
target: Cura.Actions.centerObject
|
|
||||||
onTriggered:
|
|
||||||
{
|
|
||||||
if(objectContextMenu.objectId != 0)
|
|
||||||
{
|
|
||||||
CuraApplication.centerObject(objectContextMenu.objectId);
|
|
||||||
objectContextMenu.objectId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Menu
|
|
||||||
{
|
|
||||||
id: contextMenu;
|
|
||||||
MenuItem { action: Cura.Actions.selectAll; }
|
|
||||||
MenuItem { action: Cura.Actions.arrangeAll; }
|
|
||||||
MenuItem { action: Cura.Actions.deleteAll; }
|
|
||||||
MenuItem { action: Cura.Actions.reloadAll; }
|
|
||||||
MenuItem { action: Cura.Actions.resetAllTranslation; }
|
|
||||||
MenuItem { action: Cura.Actions.resetAll; }
|
|
||||||
MenuSeparator { }
|
|
||||||
MenuItem { action: Cura.Actions.groupObjects; }
|
|
||||||
MenuItem { action: Cura.Actions.mergeObjects; }
|
|
||||||
MenuItem { action: Cura.Actions.unGroupObjects; }
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections
|
|
||||||
{
|
|
||||||
target: UM.Controller
|
|
||||||
onContextMenuRequested:
|
|
||||||
{
|
|
||||||
if(objectId == 0)
|
|
||||||
{
|
|
||||||
contextMenu.popup();
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
objectContextMenu.objectId = objectId;
|
|
||||||
objectContextMenu.popup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
|
|
80
resources/qml/ExtruderButton.qml
Normal file
80
resources/qml/ExtruderButton.qml
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
// Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
import QtQuick 2.2
|
||||||
|
import QtQuick.Controls 1.1
|
||||||
|
|
||||||
|
import UM 1.2 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
Button
|
||||||
|
{
|
||||||
|
id: base
|
||||||
|
|
||||||
|
property var extruder;
|
||||||
|
|
||||||
|
text: catalog.i18ncp("@label", "Print Selected Model with %1", "Print Selected Models With %1", UM.Selection.selectionCount).arg(extruder.name)
|
||||||
|
|
||||||
|
style: UM.Theme.styles.tool_button;
|
||||||
|
iconSource: checked ? UM.Theme.getIcon("material_selected") : UM.Theme.getIcon("material_not_selected");
|
||||||
|
|
||||||
|
checked: ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1
|
||||||
|
enabled: UM.Selection.hasSelection
|
||||||
|
|
||||||
|
property color customColor: base.hovered ? UM.Theme.getColor("button_hover") : UM.Theme.getColor("button");
|
||||||
|
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: UM.Theme.getSize("default_lining").width;
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
border.width: base.checked ? UM.Theme.getSize("default_lining").width : 0;
|
||||||
|
border.color: UM.Theme.getColor("button_text")
|
||||||
|
}
|
||||||
|
|
||||||
|
Item
|
||||||
|
{
|
||||||
|
anchors
|
||||||
|
{
|
||||||
|
right: parent.right;
|
||||||
|
top: parent.top;
|
||||||
|
margins: UM.Theme.getSize("default_lining").width * 3
|
||||||
|
}
|
||||||
|
width: UM.Theme.getSize("default_margin").width
|
||||||
|
height: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
|
Text
|
||||||
|
{
|
||||||
|
anchors.centerIn: parent;
|
||||||
|
text: index + 1;
|
||||||
|
color: parent.enabled ? UM.Theme.getColor("button_text") : UM.Theme.getColor("button_disabled_text")
|
||||||
|
font: UM.Theme.getFont("default_bold");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
anchors
|
||||||
|
{
|
||||||
|
left: parent.left;
|
||||||
|
top: parent.top;
|
||||||
|
margins: UM.Theme.getSize("default_lining").width * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
color: model.color
|
||||||
|
|
||||||
|
width: UM.Theme.getSize("default_margin").width
|
||||||
|
height: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
|
border.color: UM.Theme.getColor("lining");
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
forceActiveFocus() //First grab focus, so all the text fields are updated
|
||||||
|
CuraActions.setExtruderForSelection(extruder.id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -212,8 +212,9 @@ Item {
|
||||||
{
|
{
|
||||||
lengths.push(base.printMaterialLengths[index].toFixed(2));
|
lengths.push(base.printMaterialLengths[index].toFixed(2));
|
||||||
weights.push(String(Math.floor(base.printMaterialWeights[index])));
|
weights.push(String(Math.floor(base.printMaterialWeights[index])));
|
||||||
costs.push(base.printMaterialCosts[index].toFixed(2));
|
var cost = base.printMaterialCosts[index] == undefined ? 0 : base.printMaterialCosts[index].toFixed(2);
|
||||||
if(base.printMaterialCosts[index] > 0)
|
costs.push(cost);
|
||||||
|
if(cost > 0)
|
||||||
{
|
{
|
||||||
someCostsKnown = true;
|
someCostsKnown = true;
|
||||||
}
|
}
|
||||||
|
|
144
resources/qml/Menus/ContextMenu.qml
Executable file
144
resources/qml/Menus/ContextMenu.qml
Executable file
|
@ -0,0 +1,144 @@
|
||||||
|
// Copyright (c) 2016 Ultimaker B.V.
|
||||||
|
// Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
import QtQuick 2.2
|
||||||
|
import QtQuick.Controls 1.1
|
||||||
|
import QtQuick.Dialogs 1.2
|
||||||
|
import QtQuick.Window 2.1
|
||||||
|
|
||||||
|
import UM 1.2 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
Menu
|
||||||
|
{
|
||||||
|
id: base
|
||||||
|
|
||||||
|
property bool shouldShowExtruders: machineExtruderCount.properties.value > 1;
|
||||||
|
|
||||||
|
// Selection-related actions.
|
||||||
|
MenuItem { action: Cura.Actions.centerSelection; }
|
||||||
|
MenuItem { action: Cura.Actions.deleteSelection; }
|
||||||
|
MenuItem { action: Cura.Actions.multiplySelection; }
|
||||||
|
|
||||||
|
// Extruder selection - only visible if there is more than 1 extruder
|
||||||
|
MenuSeparator { visible: base.shouldShowExtruders }
|
||||||
|
MenuItem { id: extruderHeader; text: catalog.i18ncp("@label", "Print Selected Model With:", "Print Selected Models With:", UM.Selection.selectionCount); enabled: false; visible: base.shouldShowExtruders }
|
||||||
|
Instantiator
|
||||||
|
{
|
||||||
|
model: Cura.ExtrudersModel { id: extrudersModel }
|
||||||
|
MenuItem {
|
||||||
|
text: "%1: %2 - %3".arg(model.name).arg(model.material).arg(model.variant)
|
||||||
|
visible: base.shouldShowExtruders
|
||||||
|
enabled: UM.Selection.hasSelection
|
||||||
|
checkable: true
|
||||||
|
checked: ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1
|
||||||
|
onTriggered: CuraActions.setExtruderForSelection(model.id)
|
||||||
|
shortcut: "Ctrl+" + (model.index + 1)
|
||||||
|
}
|
||||||
|
onObjectAdded: base.insertItem(index, object)
|
||||||
|
onObjectRemoved: base.removeItem(object)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global actions
|
||||||
|
MenuSeparator {}
|
||||||
|
MenuItem { action: Cura.Actions.selectAll; }
|
||||||
|
MenuItem { action: Cura.Actions.arrangeAll; }
|
||||||
|
MenuItem { action: Cura.Actions.deleteAll; }
|
||||||
|
MenuItem { action: Cura.Actions.reloadAll; }
|
||||||
|
MenuItem { action: Cura.Actions.resetAllTranslation; }
|
||||||
|
MenuItem { action: Cura.Actions.resetAll; }
|
||||||
|
|
||||||
|
// Group actions
|
||||||
|
MenuSeparator {}
|
||||||
|
MenuItem { action: Cura.Actions.groupObjects; }
|
||||||
|
MenuItem { action: Cura.Actions.mergeObjects; }
|
||||||
|
MenuItem { action: Cura.Actions.unGroupObjects; }
|
||||||
|
|
||||||
|
Connections
|
||||||
|
{
|
||||||
|
target: UM.Controller
|
||||||
|
onContextMenuRequested: base.popup();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections
|
||||||
|
{
|
||||||
|
target: Cura.Actions.multiplySelection
|
||||||
|
onTriggered: multiplyDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.SettingPropertyProvider
|
||||||
|
{
|
||||||
|
id: machineExtruderCount
|
||||||
|
|
||||||
|
containerStackId: Cura.MachineManager.activeMachineId
|
||||||
|
key: "machine_extruder_count"
|
||||||
|
watchedProperties: [ "value" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog
|
||||||
|
{
|
||||||
|
id: multiplyDialog
|
||||||
|
|
||||||
|
title: catalog.i18ncp("@title:window", "Multiply Selected Model", "Multiply Selected Models", UM.Selection.selectionCount)
|
||||||
|
|
||||||
|
width: 400 * Screen.devicePixelRatio
|
||||||
|
height: 80 * Screen.devicePixelRatio
|
||||||
|
|
||||||
|
onAccepted: CuraActions.multiplySelection(copiesField.value)
|
||||||
|
|
||||||
|
signal reset()
|
||||||
|
onReset:
|
||||||
|
{
|
||||||
|
copiesField.value = 1;
|
||||||
|
copiesField.focus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged:
|
||||||
|
{
|
||||||
|
copiesField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
standardButtons: StandardButton.Ok | StandardButton.Cancel
|
||||||
|
|
||||||
|
Row
|
||||||
|
{
|
||||||
|
spacing: UM.Theme.getSize("default_margin").width
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@label", "Number of Copies")
|
||||||
|
anchors.verticalCenter: copiesField.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
SpinBox
|
||||||
|
{
|
||||||
|
id: copiesField
|
||||||
|
focus: true
|
||||||
|
minimumValue: 1
|
||||||
|
maximumValue: 99
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the index of an item in the list of child items of this menu.
|
||||||
|
//
|
||||||
|
// This is primarily intended as a helper function so we do not have to
|
||||||
|
// hard-code the position of the extruder selection actions.
|
||||||
|
//
|
||||||
|
// \param item The item to find the index of.
|
||||||
|
//
|
||||||
|
// \return The index of the item or -1 if it was not found.
|
||||||
|
function findItemIndex(item)
|
||||||
|
{
|
||||||
|
for(var i in base.items)
|
||||||
|
{
|
||||||
|
if(base.items[i] == item)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
UM.I18nCatalog { id: catalog; name: "cura" }
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ Menu
|
||||||
{
|
{
|
||||||
text: model.name + " - " + model.layer_height
|
text: model.name + " - " + model.layer_height
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: Cura.MachineManager.activeQualityChangesId == "empty_quality_changes" && Cura.MachineManager.activeQualityType == model.metadata.quality_type
|
checked: Cura.MachineManager.activeQualityChangesId == "" && Cura.MachineManager.activeQualityType == model.metadata.quality_type
|
||||||
exclusiveGroup: group
|
exclusiveGroup: group
|
||||||
onTriggered: Cura.MachineManager.setActiveQuality(model.id)
|
onTriggered: Cura.MachineManager.setActiveQuality(model.id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,17 @@ UM.PreferencesPage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setDefaultTheme(defaultThemeCode)
|
||||||
|
{
|
||||||
|
for(var i = 0; i < themeList.count; i++)
|
||||||
|
{
|
||||||
|
if (themeComboBox.model.get(i).code == defaultThemeCode)
|
||||||
|
{
|
||||||
|
themeComboBox.currentIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setDefaultDiscardOrKeepProfile(code)
|
function setDefaultDiscardOrKeepProfile(code)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < choiceOnProfileOverrideDropDownButton.model.count; i++)
|
for (var i = 0; i < choiceOnProfileOverrideDropDownButton.model.count; i++)
|
||||||
|
@ -55,6 +66,10 @@ UM.PreferencesPage
|
||||||
var defaultLanguage = UM.Preferences.getValue("general/language")
|
var defaultLanguage = UM.Preferences.getValue("general/language")
|
||||||
setDefaultLanguage(defaultLanguage)
|
setDefaultLanguage(defaultLanguage)
|
||||||
|
|
||||||
|
UM.Preferences.resetPreference("general/theme")
|
||||||
|
var defaultTheme = UM.Preferences.getValue("general/theme")
|
||||||
|
setDefaultTheme(defaultTheme)
|
||||||
|
|
||||||
UM.Preferences.resetPreference("physics/automatic_push_free")
|
UM.Preferences.resetPreference("physics/automatic_push_free")
|
||||||
pushFreeCheckbox.checked = boolCheck(UM.Preferences.getValue("physics/automatic_push_free"))
|
pushFreeCheckbox.checked = boolCheck(UM.Preferences.getValue("physics/automatic_push_free"))
|
||||||
UM.Preferences.resetPreference("physics/automatic_drop_down")
|
UM.Preferences.resetPreference("physics/automatic_drop_down")
|
||||||
|
@ -95,6 +110,8 @@ UM.PreferencesPage
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
|
flickableItem.flickableDirection: Flickable.VerticalFlick;
|
||||||
|
|
||||||
Column
|
Column
|
||||||
{
|
{
|
||||||
//: Model used to check if a plugin exists
|
//: Model used to check if a plugin exists
|
||||||
|
@ -109,9 +126,11 @@ UM.PreferencesPage
|
||||||
text: catalog.i18nc("@label","Interface")
|
text: catalog.i18nc("@label","Interface")
|
||||||
}
|
}
|
||||||
|
|
||||||
Row
|
GridLayout
|
||||||
{
|
{
|
||||||
spacing: UM.Theme.getSize("default_margin").width
|
id: interfaceGrid
|
||||||
|
columns: 4
|
||||||
|
|
||||||
Label
|
Label
|
||||||
{
|
{
|
||||||
id: languageLabel
|
id: languageLabel
|
||||||
|
@ -172,22 +191,75 @@ UM.PreferencesPage
|
||||||
{
|
{
|
||||||
id: currencyLabel
|
id: currencyLabel
|
||||||
text: catalog.i18nc("@label","Currency:")
|
text: catalog.i18nc("@label","Currency:")
|
||||||
anchors.verticalCenter: languageComboBox.verticalCenter
|
anchors.verticalCenter: currencyField.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField
|
TextField
|
||||||
{
|
{
|
||||||
id: currencyField
|
id: currencyField
|
||||||
text: UM.Preferences.getValue("cura/currency")
|
text: UM.Preferences.getValue("cura/currency")
|
||||||
onTextChanged: UM.Preferences.setValue("cura/currency", text)
|
onTextChanged: UM.Preferences.setValue("cura/currency", text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: themeLabel
|
||||||
|
text: catalog.i18nc("@label","Theme:")
|
||||||
|
anchors.verticalCenter: themeComboBox.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox
|
||||||
|
{
|
||||||
|
id: themeComboBox
|
||||||
|
|
||||||
|
model: ListModel
|
||||||
|
{
|
||||||
|
id: themeList
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
append({ text: catalog.i18nc("@item:inlistbox", "Ultimaker"), code: "cura" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIndex:
|
||||||
|
{
|
||||||
|
var code = UM.Preferences.getValue("general/theme");
|
||||||
|
for(var i = 0; i < themeList.count; ++i)
|
||||||
|
{
|
||||||
|
if(model.get(i).code == code)
|
||||||
|
{
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onActivated: UM.Preferences.setValue("general/theme", model.get(index).code)
|
||||||
|
|
||||||
|
Component.onCompleted:
|
||||||
|
{
|
||||||
|
// Because ListModel is stupid and does not allow using qsTr() for values.
|
||||||
|
for(var i = 0; i < themeList.count; ++i)
|
||||||
|
{
|
||||||
|
themeList.setProperty(i, "text", catalog.i18n(themeList.get(i).text));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glorious hack time. ComboBox does not update the text properly after changing the
|
||||||
|
// model. So change the indices around to force it to update.
|
||||||
|
currentIndex += 1;
|
||||||
|
currentIndex -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label
|
|
||||||
|
|
||||||
|
|
||||||
|
Label
|
||||||
{
|
{
|
||||||
id: languageCaption
|
id: languageCaption
|
||||||
|
|
||||||
//: Language change warning
|
//: Language change warning
|
||||||
text: catalog.i18nc("@label", "You will need to restart the application for language changes to have effect.")
|
text: catalog.i18nc("@label", "You will need to restart the application for these changes to have effect.")
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
font.italic: true
|
font.italic: true
|
||||||
}
|
}
|
||||||
|
@ -209,7 +281,6 @@ UM.PreferencesPage
|
||||||
CheckBox
|
CheckBox
|
||||||
{
|
{
|
||||||
id: autoSliceCheckbox
|
id: autoSliceCheckbox
|
||||||
|
|
||||||
checked: boolCheck(UM.Preferences.getValue("general/auto_slice"))
|
checked: boolCheck(UM.Preferences.getValue("general/auto_slice"))
|
||||||
onClicked: UM.Preferences.setValue("general/auto_slice", checked)
|
onClicked: UM.Preferences.setValue("general/auto_slice", checked)
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ UM.ManagementPage
|
||||||
visible: base.currentItem != null
|
visible: base.currentItem != null
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
Label
|
Text
|
||||||
{
|
{
|
||||||
id: machineName
|
id: machineName
|
||||||
text: base.currentItem && base.currentItem.name ? base.currentItem.name : ""
|
text: base.currentItem && base.currentItem.name ? base.currentItem.name : ""
|
||||||
|
@ -146,26 +146,28 @@ UM.ManagementPage
|
||||||
property var connectedPrinter: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
|
property var connectedPrinter: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
|
||||||
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
|
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
|
||||||
|
|
||||||
Label
|
Text
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "Printer type:")
|
text: catalog.i18nc("@label", "Printer type:")
|
||||||
visible: base.currentItem && "definition_name" in base.currentItem.metadata
|
visible: base.currentItem && "definition_name" in base.currentItem.metadata
|
||||||
}
|
}
|
||||||
Label {
|
Text
|
||||||
|
{
|
||||||
text: (base.currentItem && "definition_name" in base.currentItem.metadata) ? base.currentItem.metadata.definition_name : ""
|
text: (base.currentItem && "definition_name" in base.currentItem.metadata) ? base.currentItem.metadata.definition_name : ""
|
||||||
}
|
}
|
||||||
Label
|
Text
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "Connection:")
|
text: catalog.i18nc("@label", "Connection:")
|
||||||
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId
|
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId
|
||||||
}
|
}
|
||||||
Label {
|
Text
|
||||||
|
{
|
||||||
width: parent.width * 0.7
|
width: parent.width * 0.7
|
||||||
text: machineInfo.printerConnected ? machineInfo.connectedPrinter.connectionText : catalog.i18nc("@info:status", "The printer is not connected.")
|
text: machineInfo.printerConnected ? machineInfo.connectedPrinter.connectionText : catalog.i18nc("@info:status", "The printer is not connected.")
|
||||||
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId
|
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
}
|
}
|
||||||
Label
|
Text
|
||||||
{
|
{
|
||||||
text: catalog.i18nc("@label", "State:")
|
text: catalog.i18nc("@label", "State:")
|
||||||
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId && machineInfo.printerAcceptsCommands
|
visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId && machineInfo.printerAcceptsCommands
|
||||||
|
|
|
@ -273,17 +273,28 @@ TabView
|
||||||
{
|
{
|
||||||
id: spinBox
|
id: spinBox
|
||||||
anchors.left: label.right
|
anchors.left: label.right
|
||||||
value: parseFloat(provider.properties.value);
|
value: {
|
||||||
width: base.secondColumnWidth;
|
if (!isNaN(parseFloat(materialPropertyProvider.properties.value)))
|
||||||
|
{
|
||||||
|
return parseFloat(materialPropertyProvider.properties.value);
|
||||||
|
}
|
||||||
|
if (!isNaN(parseFloat(machinePropertyProvider.properties.value)))
|
||||||
|
{
|
||||||
|
return parseFloat(machinePropertyProvider.properties.value);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
width: base.secondColumnWidth
|
||||||
readOnly: !base.editingEnabled
|
readOnly: !base.editingEnabled
|
||||||
suffix: model.unit
|
suffix: " " + model.unit
|
||||||
maximumValue: 99999
|
maximumValue: 99999
|
||||||
decimals: model.unit == "mm" ? 2 : 0
|
decimals: model.unit == "mm" ? 2 : 0
|
||||||
|
|
||||||
onEditingFinished: provider.setPropertyValue("value", value)
|
onEditingFinished: materialPropertyProvider.setPropertyValue("value", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.ContainerPropertyProvider { id: provider; containerId: base.containerId; watchedProperties: [ "value" ]; key: model.key }
|
UM.ContainerPropertyProvider { id: materialPropertyProvider; containerId: base.containerId; watchedProperties: [ "value" ]; key: model.key }
|
||||||
|
UM.ContainerPropertyProvider { id: machinePropertyProvider; containerId: Cura.MachineManager.activeDefinitionId; watchedProperties: [ "value" ]; key: model.key }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -649,6 +649,7 @@ Column
|
||||||
sourceComponent: monitorItem
|
sourceComponent: monitorItem
|
||||||
property string label: catalog.i18nc("@label", "Estimated time left")
|
property string label: catalog.i18nc("@label", "Estimated time left")
|
||||||
property string value: connectedPrinter != null ? getPrettyTime(connectedPrinter.timeTotal - connectedPrinter.timeElapsed) : ""
|
property string value: connectedPrinter != null ? getPrettyTime(connectedPrinter.timeTotal - connectedPrinter.timeElapsed) : ""
|
||||||
|
visible: connectedPrinter != null && (connectedPrinter.jobState == "printing" || connectedPrinter.jobState == "resuming" || connectedPrinter.jobState == "pausing" || connectedPrinter.jobState == "paused")
|
||||||
}
|
}
|
||||||
|
|
||||||
Component
|
Component
|
||||||
|
|
|
@ -179,7 +179,7 @@ Item
|
||||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||||
enabled:
|
enabled:
|
||||||
{
|
{
|
||||||
if(!ExtruderManager.activeExtruderStackId && ExtruderManager.extruderCount > 0)
|
if(!ExtruderManager.activeExtruderStackId && machineExtruderCount.properties.value > 1)
|
||||||
{
|
{
|
||||||
// disable all controls on the global tab, except categories
|
// disable all controls on the global tab, except categories
|
||||||
return model.type == "category"
|
return model.type == "category"
|
||||||
|
|
134
resources/qml/Sidebar.qml
Normal file → Executable file
134
resources/qml/Sidebar.qml
Normal file → Executable file
|
@ -348,6 +348,7 @@ Rectangle
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: settingsModeSelection
|
id: settingsModeSelection
|
||||||
|
color: "transparent"
|
||||||
width: parent.width * 0.55
|
width: parent.width * 0.55
|
||||||
height: UM.Theme.getSize("sidebar_header_mode_toggle").height
|
height: UM.Theme.getSize("sidebar_header_mode_toggle").height
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
@ -407,14 +408,123 @@ Rectangle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExclusiveGroup { id: modeMenuGroup; }
|
ExclusiveGroup { id: modeMenuGroup; }
|
||||||
ListView{
|
|
||||||
id: modesList
|
Label
|
||||||
property var index: 0
|
{
|
||||||
model: modesListModel
|
id: toggleLeftText
|
||||||
delegate: wizardDelegate
|
anchors.right: modeToggleSwitch.left
|
||||||
anchors.top: parent.top
|
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
anchors.left: parent.left
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: parent.width
|
text: ""
|
||||||
|
color:
|
||||||
|
{
|
||||||
|
if(toggleLeftTextMouseArea.containsMouse)
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("mode_switch_text_hover");
|
||||||
|
}
|
||||||
|
else if(!modeToggleSwitch.checked)
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("mode_switch_text_checked");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("mode_switch_text");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
font: UM.Theme.getFont("default")
|
||||||
|
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
id: toggleLeftTextMouseArea
|
||||||
|
hoverEnabled: true
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
modeToggleSwitch.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted:
|
||||||
|
{
|
||||||
|
clicked.connect(modeToggleSwitch.clicked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch
|
||||||
|
{
|
||||||
|
id: modeToggleSwitch
|
||||||
|
checked: false
|
||||||
|
anchors.right: toggleRightText.left
|
||||||
|
anchors.rightMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
property bool _hovered: modeToggleSwitchMouseArea.containsMouse || toggleLeftTextMouseArea.containsMouse || toggleRightTextMouseArea.containsMouse
|
||||||
|
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
id: modeToggleSwitchMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
}
|
||||||
|
|
||||||
|
onCheckedChanged:
|
||||||
|
{
|
||||||
|
var index = 0;
|
||||||
|
if (checked)
|
||||||
|
{
|
||||||
|
index = 1;
|
||||||
|
}
|
||||||
|
updateActiveMode(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateActiveMode(index)
|
||||||
|
{
|
||||||
|
base.currentModeIndex = index;
|
||||||
|
UM.Preferences.setValue("cura/active_mode", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
style: UM.Theme.styles.mode_switch
|
||||||
|
}
|
||||||
|
|
||||||
|
Label
|
||||||
|
{
|
||||||
|
id: toggleRightText
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: ""
|
||||||
|
color:
|
||||||
|
{
|
||||||
|
if(toggleRightTextMouseArea.containsMouse)
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("mode_switch_text_hover");
|
||||||
|
}
|
||||||
|
else if(modeToggleSwitch.checked)
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("mode_switch_text_checked");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return UM.Theme.getColor("mode_switch_text");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
font: UM.Theme.getFont("default")
|
||||||
|
|
||||||
|
MouseArea
|
||||||
|
{
|
||||||
|
id: toggleRightTextMouseArea
|
||||||
|
hoverEnabled: true
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked:
|
||||||
|
{
|
||||||
|
modeToggleSwitch.checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted:
|
||||||
|
{
|
||||||
|
clicked.connect(modeToggleSwitch.clicked)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -541,10 +651,14 @@ Rectangle
|
||||||
})
|
})
|
||||||
sidebarContents.push({ "item": modesListModel.get(base.currentModeIndex).item, "immediate": true });
|
sidebarContents.push({ "item": modesListModel.get(base.currentModeIndex).item, "immediate": true });
|
||||||
|
|
||||||
var index = parseInt(UM.Preferences.getValue("cura/active_mode"))
|
toggleLeftText.text = modesListModel.get(0).text;
|
||||||
if(index)
|
toggleRightText.text = modesListModel.get(1).text;
|
||||||
|
|
||||||
|
var index = parseInt(UM.Preferences.getValue("cura/active_mode"));
|
||||||
|
if (index)
|
||||||
{
|
{
|
||||||
currentModeIndex = index;
|
currentModeIndex = index;
|
||||||
|
modeToggleSwitch.checked = index > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ Item
|
||||||
property Action configureSettings;
|
property Action configureSettings;
|
||||||
property variant minimumPrintTime: PrintInformation.minimumPrintTime;
|
property variant minimumPrintTime: PrintInformation.minimumPrintTime;
|
||||||
property variant maximumPrintTime: PrintInformation.maximumPrintTime;
|
property variant maximumPrintTime: PrintInformation.maximumPrintTime;
|
||||||
property bool settingsEnabled: ExtruderManager.activeExtruderStackId || ExtruderManager.extruderCount == 0
|
property bool settingsEnabled: ExtruderManager.activeExtruderStackId || machineExtruderCount.properties.value == 1
|
||||||
|
|
||||||
Component.onCompleted: PrintInformation.enabled = true
|
Component.onCompleted: PrintInformation.enabled = true
|
||||||
Component.onDestruction: PrintInformation.enabled = false
|
Component.onDestruction: PrintInformation.enabled = false
|
||||||
|
@ -240,6 +240,8 @@ Item
|
||||||
CheckBox
|
CheckBox
|
||||||
{
|
{
|
||||||
id: enableSupportCheckBox
|
id: enableSupportCheckBox
|
||||||
|
property alias _hovered: enableSupportMouseArea.containsMouse
|
||||||
|
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: enableSupportLabel.right
|
anchors.left: enableSupportLabel.right
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
|
|
@ -6,28 +6,33 @@ import QtQuick.Controls 1.1
|
||||||
import QtQuick.Controls.Styles 1.1
|
import QtQuick.Controls.Styles 1.1
|
||||||
import QtQuick.Layouts 1.1
|
import QtQuick.Layouts 1.1
|
||||||
|
|
||||||
import UM 1.0 as UM
|
import UM 1.2 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
Item {
|
Item
|
||||||
|
{
|
||||||
id: base;
|
id: base;
|
||||||
|
|
||||||
width: buttons.width;
|
width: buttons.width;
|
||||||
height: buttons.height
|
height: buttons.height
|
||||||
property int activeY
|
property int activeY
|
||||||
|
|
||||||
ColumnLayout {
|
Column
|
||||||
|
{
|
||||||
id: buttons;
|
id: buttons;
|
||||||
|
|
||||||
anchors.bottom: parent.bottom;
|
anchors.bottom: parent.bottom;
|
||||||
anchors.left: parent.left;
|
anchors.left: parent.left;
|
||||||
spacing: UM.Theme.getSize("button_lining").width
|
spacing: UM.Theme.getSize("button_lining").width
|
||||||
|
|
||||||
Repeater {
|
Repeater
|
||||||
|
{
|
||||||
id: repeat
|
id: repeat
|
||||||
|
|
||||||
model: UM.ToolModel { }
|
model: UM.ToolModel { }
|
||||||
|
|
||||||
Button {
|
Button
|
||||||
|
{
|
||||||
text: model.name
|
text: model.name
|
||||||
iconSource: UM.Theme.getIcon(model.icon);
|
iconSource: UM.Theme.getIcon(model.icon);
|
||||||
|
|
||||||
|
@ -45,9 +50,11 @@ Item {
|
||||||
}
|
}
|
||||||
//Workaround since using ToolButton"s onClicked would break the binding of the checked property, instead
|
//Workaround since using ToolButton"s onClicked would break the binding of the checked property, instead
|
||||||
//just catch the click so we do not trigger that behaviour.
|
//just catch the click so we do not trigger that behaviour.
|
||||||
MouseArea {
|
MouseArea
|
||||||
|
{
|
||||||
anchors.fill: parent;
|
anchors.fill: parent;
|
||||||
onClicked: {
|
onClicked:
|
||||||
|
{
|
||||||
forceActiveFocus() //First grab focus, so all the text fields are updated
|
forceActiveFocus() //First grab focus, so all the text fields are updated
|
||||||
if(parent.checked)
|
if(parent.checked)
|
||||||
{
|
{
|
||||||
|
@ -61,9 +68,19 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item { height: UM.Theme.getSize("default_margin").height; width: 1; visible: extruders.count > 0 }
|
||||||
|
|
||||||
|
Repeater
|
||||||
|
{
|
||||||
|
id: extruders
|
||||||
|
model: Cura.ExtrudersModel { id: extrudersModel }
|
||||||
|
ExtruderButton { extruder: model }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.PointingRectangle {
|
UM.PointingRectangle
|
||||||
|
{
|
||||||
id: panelBorder;
|
id: panelBorder;
|
||||||
|
|
||||||
anchors.left: parent.right;
|
anchors.left: parent.right;
|
||||||
|
@ -75,7 +92,8 @@ Item {
|
||||||
target: Qt.point(parent.right, base.activeY + UM.Theme.getSize("button").height/2)
|
target: Qt.point(parent.right, base.activeY + UM.Theme.getSize("button").height/2)
|
||||||
arrowSize: UM.Theme.getSize("default_arrow").width
|
arrowSize: UM.Theme.getSize("default_arrow").width
|
||||||
|
|
||||||
width: {
|
width:
|
||||||
|
{
|
||||||
if (panel.item && panel.width > 0){
|
if (panel.item && panel.width > 0){
|
||||||
return Math.max(panel.width + 2 * UM.Theme.getSize("default_margin").width)
|
return Math.max(panel.width + 2 * UM.Theme.getSize("default_margin").width)
|
||||||
}
|
}
|
||||||
|
@ -90,7 +108,8 @@ Item {
|
||||||
|
|
||||||
color: UM.Theme.getColor("lining");
|
color: UM.Theme.getColor("lining");
|
||||||
|
|
||||||
UM.PointingRectangle {
|
UM.PointingRectangle
|
||||||
|
{
|
||||||
id: panelBackground;
|
id: panelBackground;
|
||||||
|
|
||||||
color: UM.Theme.getColor("tool_panel_background");
|
color: UM.Theme.getColor("tool_panel_background");
|
||||||
|
@ -105,7 +124,8 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader
|
||||||
|
{
|
||||||
id: panel
|
id: panel
|
||||||
|
|
||||||
x: UM.Theme.getSize("default_margin").width;
|
x: UM.Theme.getSize("default_margin").width;
|
||||||
|
@ -116,6 +136,8 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This rectangle displays the information about the current angle etc. when
|
||||||
|
// dragging a tool handle.
|
||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
x: -base.x + base.mouseX + UM.Theme.getSize("default_margin").width
|
x: -base.x + base.mouseX + UM.Theme.getSize("default_margin").width
|
||||||
|
|
77
resources/themes/cura/icons/material_not_selected.svg
Normal file
77
resources/themes/cura/icons/material_not_selected.svg
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="30"
|
||||||
|
height="30"
|
||||||
|
viewBox="0 0 30 30"
|
||||||
|
version="1.1"
|
||||||
|
id="svg4668"
|
||||||
|
sodipodi:docname="material_not_selected.svg"
|
||||||
|
inkscape:version="0.92.1 r">
|
||||||
|
<metadata
|
||||||
|
id="metadata4672">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title>Artboard 3</dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1266"
|
||||||
|
inkscape:window-height="1411"
|
||||||
|
id="namedview4670"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:zoom="9.0769231"
|
||||||
|
inkscape:cx="-5.7118644"
|
||||||
|
inkscape:cy="13"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg4668" />
|
||||||
|
<!-- Generator: Sketch 43.1 (39012) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title
|
||||||
|
id="title4657">Artboard 3</title>
|
||||||
|
<desc
|
||||||
|
id="desc4659">Created with Sketch.</desc>
|
||||||
|
<defs
|
||||||
|
id="defs4661" />
|
||||||
|
<g
|
||||||
|
id="Feature-apply-material-to-model"
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1"
|
||||||
|
transform="translate(0,2)">
|
||||||
|
<g
|
||||||
|
id="Artboard-3"
|
||||||
|
style="fill:#ffffff">
|
||||||
|
<g
|
||||||
|
id="Group-19">
|
||||||
|
<path
|
||||||
|
d="m 13,26 h 1.000227 C 22.844516,26 30,18.836556 30,10 h -1 c 0,8.284271 -6.717306,15 -14.999491,15 H 13 Z"
|
||||||
|
id="Combined-Shape-Copy-64"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="M 0,13 C 0,5.8202982 5.8187196,0 13,0 20.179702,0 26,5.8187196 26,13 26,20.179702 20.18128,26 13,26 5.8202982,26 0,20.18128 0,13 Z m 1.2380952,0 C 1.2380952,19.497349 6.5040794,24.761905 13,24.761905 19.497349,24.761905 24.761905,19.495921 24.761905,13 24.761905,6.5026511 19.495921,1.2380952 13,1.2380952 6.5026511,1.2380952 1.2380952,6.5040794 1.2380952,13 Z M 1.8,12.866667 C 1.8,12.384683 2.1844415,12 2.6586742,12 H 6.1413258 C 6.6091548,12 7,12.38802 7,12.866667 7,13.348651 6.6155584,13.733333 6.1413258,13.733333 H 2.6586742 C 2.1908452,13.733333 1.8,13.345313 1.8,12.866667 Z m 17,0 C 18.8,12.384683 19.184442,12 19.658674,12 h 3.482652 C 23.609155,12 24,12.38802 24,12.866667 c 0,0.481984 -0.384442,0.866666 -0.858674,0.866666 H 19.658674 C 19.190845,13.733333 18.8,13.345313 18.8,12.866667 Z m 1.943614,8.064088 c -0.338454,0.338455 -0.889195,0.336457 -1.22,0.0057 L 17.061008,18.4738 c -0.335334,-0.335333 -0.335163,-0.879186 0.0057,-1.22 0.338454,-0.338455 0.889195,-0.336457 1.22,-0.0057 l 2.462607,2.462607 c 0.335333,0.335333 0.335162,0.879186 -0.0057,1.22 z M 7.5236141,8.9364067 5.0610076,6.4738002 C 4.7256744,6.138467 4.7258451,5.594614 5.0666591,5.2537999 5.4051135,4.9153455 5.9558542,4.9173433 6.2866593,5.2481484 L 8.7492659,7.7107549 C 9.084599,8.0460881 9.0844284,8.5899412 8.7436144,8.9307552 8.40516,9.2692096 7.8544192,9.2672118 7.5236141,8.9364067 Z M 5.0666591,20.930755 c -0.340814,-0.340814 -0.3409847,-0.884667 -0.00565,-1.22 l 2.4626065,-2.462607 c 0.3308051,-0.330805 0.8815459,-0.332803 1.2200003,0.0057 0.340814,0.340814 0.3409846,0.884667 0.00565,1.22 L 6.2866593,20.936407 C 5.9558542,21.267212 5.4051135,21.26921 5.0666591,20.930755 Z M 17.066659,8.9307552 c -0.340814,-0.340814 -0.340985,-0.8846671 -0.0057,-1.2200003 l 2.462606,-2.4626065 c 0.330805,-0.3308051 0.881546,-0.3328029 1.22,0.00565 0.340814,0.3408141 0.340985,0.8846671 0.0057,1.2200003 l -2.462607,2.4626065 c -0.330805,0.3308051 -0.881546,0.3328029 -1.219999,-0.00565 z M 13.133333,2 C 13.615317,2 14,2.3844416 14,2.8586742 V 6.3413258 C 14,6.8091548 13.61198,7.2 13.133333,7.2 12.65135,7.2 12.266667,6.8155584 12.266667,6.3413258 V 2.8586742 C 12.266667,2.3908452 12.654687,2 13.133333,2 Z m 0,17 C 13.615317,19 14,19.384442 14,19.858674 v 3.482652 C 14,23.809155 13.61198,24.2 13.133333,24.2 12.65135,24.2 12.266667,23.815558 12.266667,23.341326 V 19.858674 C 12.266667,19.390845 12.654687,19 13.133333,19 Z M 8.6666667,13 c 0,-2.393234 1.9449693,-4.3333333 4.3333333,-4.3333333 2.393234,0 4.333333,1.9449693 4.333333,4.3333333 0,2.393234 -1.944969,4.333333 -4.333333,4.333333 -2.393234,0 -4.3333333,-1.944969 -4.3333333,-4.333333 z m 1.2380952,0 c 0,1.705974 1.3857851,3.095238 3.0952381,3.095238 1.705974,0 3.095238,-1.385785 3.095238,-3.095238 0,-1.705974 -1.385785,-3.0952381 -3.095238,-3.0952381 -1.705974,0 -3.0952381,1.3857851 -3.0952381,3.0952381 z"
|
||||||
|
id="Combined-Shape-Copy-60"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.1 KiB |
78
resources/themes/cura/icons/material_selected.svg
Normal file
78
resources/themes/cura/icons/material_selected.svg
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="30"
|
||||||
|
height="30"
|
||||||
|
viewBox="0 0 30 30"
|
||||||
|
version="1.1"
|
||||||
|
id="svg4595"
|
||||||
|
sodipodi:docname="material_selected.svg"
|
||||||
|
inkscape:version="0.92.1 r">
|
||||||
|
<metadata
|
||||||
|
id="metadata4599">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title>Artboard 3 Copy</dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="2502"
|
||||||
|
inkscape:window-height="1411"
|
||||||
|
id="namedview4597"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:zoom="23.442308"
|
||||||
|
inkscape:cx="18.780195"
|
||||||
|
inkscape:cy="17.941232"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg4595" />
|
||||||
|
<!-- Generator: Sketch 43.1 (39012) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title
|
||||||
|
id="title4584">Artboard 3 Copy</title>
|
||||||
|
<desc
|
||||||
|
id="desc4586">Created with Sketch.</desc>
|
||||||
|
<defs
|
||||||
|
id="defs4588" />
|
||||||
|
<g
|
||||||
|
id="Feature-apply-material-to-model"
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1"
|
||||||
|
transform="translate(0,2)">
|
||||||
|
<g
|
||||||
|
id="Artboard-3-Copy"
|
||||||
|
style="fill:#ffffff">
|
||||||
|
<g
|
||||||
|
id="Group-20-Copy">
|
||||||
|
<path
|
||||||
|
d="M 13,26 C 5.8202982,26 0,20.179702 0,13 0,5.8202982 5.8202982,0 13,0 c 7.179702,0 13,5.8202982 13,13 0,7.179702 -5.820298,13 -13,13 z m 0,-8.666667 c 2.393234,0 4.333333,-1.940099 4.333333,-4.333333 0,-2.393234 -1.940099,-4.3333333 -4.333333,-4.3333333 -2.393234,0 -4.3333333,1.9400993 -4.3333333,4.3333333 0,2.393234 1.9400993,4.333333 4.3333333,4.333333 z m 0,1.733334 c -0.481984,0 -0.866667,0.384441 -0.866667,0.858674 v 3.482651 c 0,0.467829 0.38802,0.858675 0.866667,0.858675 0.481984,0 0.866667,-0.384442 0.866667,-0.858675 v -3.482651 c 0,-0.467829 -0.38802,-0.858674 -0.866667,-0.858674 z M 13,1.7333333 c -0.481984,0 -0.866667,0.3844416 -0.866667,0.8586743 v 3.4826515 c 0,0.4678291 0.38802,0.8586742 0.866667,0.8586742 0.481984,0 0.866667,-0.3844415 0.866667,-0.8586742 V 2.5920076 C 13.866667,2.1241785 13.478647,1.7333333 13,1.7333333 Z M 8.6231145,8.6231145 C 8.9639285,8.2823004 8.9640991,7.7384474 8.628766,7.4031142 L 6.1661595,4.9405077 C 5.8353543,4.6097026 5.2846136,4.6077048 4.9461592,4.9461592 4.6053452,5.2869732 4.6051746,5.8308263 4.9405077,6.1661595 L 7.4031142,8.628766 C 7.7339193,8.9595711 8.2846601,8.9615689 8.6231145,8.623116 Z M 20.756448,20.756448 c 0.340814,-0.340814 0.340984,-0.884667 0.0057,-1.22 l -2.462606,-2.462607 c -0.330805,-0.330805 -0.881546,-0.332803 -1.220001,0.0057 -0.340814,0.340815 -0.340984,0.884668 -0.0057,1.220001 l 2.462607,2.462606 c 0.330805,0.330805 0.881545,0.332803 1.22,-0.0057 z M 18.299493,8.628766 20.762099,6.1661595 c 0.335333,-0.3353332 0.335163,-0.8791863 -0.0057,-1.2200003 -0.338455,-0.3384544 -0.889195,-0.3364566 -1.22,-0.00565 l -2.462607,2.4626065 c -0.335333,0.3353332 -0.335163,0.8791862 0.0057,1.2200003 0.338455,0.3384544 0.889196,0.3364566 1.220001,0.00565 z M 4.9461592,20.756448 c 0.3384544,0.338454 0.8891951,0.336456 1.2200003,0.0057 L 8.628766,18.299493 C 8.9640991,17.96416 8.9639285,17.420307 8.6231145,17.079492 8.2846601,16.741038 7.7339193,16.743036 7.4031142,17.073841 l -2.4626065,2.462607 c -0.3353331,0.335333 -0.3351625,0.879186 0.00565,1.22 z M 6.9333333,13 c 0,-0.481984 -0.3844415,-0.866667 -0.8586742,-0.866667 H 2.5920076 c -0.4678291,0 -0.8586743,0.38802 -0.8586743,0.866667 0,0.481984 0.3844416,0.866667 0.8586743,0.866667 h 3.4826515 c 0.4678291,0 0.8586742,-0.38802 0.8586742,-0.866667 z m 17.3333337,0 c 0,-0.481984 -0.384442,-0.866667 -0.858675,-0.866667 h -3.482651 c -0.467829,0 -0.858674,0.38802 -0.858674,0.866667 0,0.481984 0.384441,0.866667 0.858674,0.866667 h 3.482651 c 0.467829,0 0.858675,-0.38802 0.858675,-0.866667 z"
|
||||||
|
id="Combined-Shape-Copy-58"
|
||||||
|
transform="rotate(-90,13,13)"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 13,26 h 1.000227 C 22.844516,26 30,18.836556 30,10 h -1 c 0,8.284271 -6.717306,15 -14.999491,15 H 13 Z"
|
||||||
|
id="Combined-Shape-Copy-59"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.8 KiB |
48
resources/themes/cura/styles.qml
Normal file → Executable file
48
resources/themes/cura/styles.qml
Normal file → Executable file
|
@ -8,6 +8,50 @@ import QtQuick.Controls.Styles 1.1
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
|
property Component mode_switch: Component {
|
||||||
|
SwitchStyle {
|
||||||
|
groove: Rectangle {
|
||||||
|
implicitWidth: UM.Theme.getSize("mode_switch").width
|
||||||
|
implicitHeight: UM.Theme.getSize("mode_switch").height
|
||||||
|
radius: implicitHeight / 2
|
||||||
|
color: {
|
||||||
|
if(control.hovered || control._hovered) {
|
||||||
|
return UM.Theme.getColor("mode_switch_hover");
|
||||||
|
} else {
|
||||||
|
return UM.Theme.getColor("mode_switch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on color { ColorAnimation { duration: 50; } }
|
||||||
|
border.color: {
|
||||||
|
if(control.hovered || control._hovered) {
|
||||||
|
return UM.Theme.getColor("mode_switch_border_hover");
|
||||||
|
} else {
|
||||||
|
return UM.Theme.getColor("mode_switch_border");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on border.color { ColorAnimation { duration: 50; } }
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
handle: Rectangle {
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
implicitHeight: UM.Theme.getSize("mode_switch").height
|
||||||
|
radius: implicitHeight / 2
|
||||||
|
|
||||||
|
color: {
|
||||||
|
if (control.pressed || (control.checkable && control.checked)) {
|
||||||
|
return UM.Theme.getColor("sidebar_header_active");
|
||||||
|
} else if(control.hovered) {
|
||||||
|
return UM.Theme.getColor("sidebar_header_hover");
|
||||||
|
} else {
|
||||||
|
return UM.Theme.getColor("sidebar_header_bar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Behavior on color { ColorAnimation { duration: 50; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property Component sidebar_header_button: Component {
|
property Component sidebar_header_button: Component {
|
||||||
ButtonStyle {
|
ButtonStyle {
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
|
@ -168,7 +212,9 @@ QtObject {
|
||||||
property bool down: control.pressed || (control.checkable && control.checked);
|
property bool down: control.pressed || (control.checkable && control.checked);
|
||||||
|
|
||||||
color: {
|
color: {
|
||||||
if(control.checkable && control.checked && control.hovered) {
|
if(control.customColor !== undefined && control.customColor !== null) {
|
||||||
|
return control.customColor
|
||||||
|
} else if(control.checkable && control.checked && control.hovered) {
|
||||||
return Theme.getColor("button_active_hover");
|
return Theme.getColor("button_active_hover");
|
||||||
} else if(control.pressed || (control.checkable && control.checked)) {
|
} else if(control.pressed || (control.checkable && control.checked)) {
|
||||||
return Theme.getColor("button_active");
|
return Theme.getColor("button_active");
|
||||||
|
|
|
@ -175,6 +175,15 @@
|
||||||
"checkbox_mark": [24, 41, 77, 255],
|
"checkbox_mark": [24, 41, 77, 255],
|
||||||
"checkbox_text": [24, 41, 77, 255],
|
"checkbox_text": [24, 41, 77, 255],
|
||||||
|
|
||||||
|
"mode_switch": [255, 255, 255, 255],
|
||||||
|
"mode_switch_hover": [255, 255, 255, 255],
|
||||||
|
"mode_switch_border": [127, 127, 127, 255],
|
||||||
|
"mode_switch_border_hover": [12, 169, 227, 255],
|
||||||
|
"mode_switch_handle": [24, 41, 77, 255],
|
||||||
|
"mode_switch_text": [24, 41, 77, 255],
|
||||||
|
"mode_switch_text_hover": [24, 41, 77, 255],
|
||||||
|
"mode_switch_text_checked": [12, 169, 227, 255],
|
||||||
|
|
||||||
"tooltip": [12, 169, 227, 255],
|
"tooltip": [12, 169, 227, 255],
|
||||||
"tooltip_text": [255, 255, 255, 255],
|
"tooltip_text": [255, 255, 255, 255],
|
||||||
|
|
||||||
|
@ -238,7 +247,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"sizes": {
|
"sizes": {
|
||||||
"window_minimum_size": [70, 54],
|
"window_minimum_size": [70, 50],
|
||||||
"window_margin": [1.0, 1.0],
|
"window_margin": [1.0, 1.0],
|
||||||
"default_margin": [1.0, 1.0],
|
"default_margin": [1.0, 1.0],
|
||||||
"default_lining": [0.08, 0.08],
|
"default_lining": [0.08, 0.08],
|
||||||
|
@ -301,6 +310,7 @@
|
||||||
"layerview_row_spacing": [0.0, 0.5],
|
"layerview_row_spacing": [0.0, 0.5],
|
||||||
|
|
||||||
"checkbox": [2.0, 2.0],
|
"checkbox": [2.0, 2.0],
|
||||||
|
"mode_switch": [2.0, 1.0],
|
||||||
|
|
||||||
"tooltip": [20.0, 10.0],
|
"tooltip": [20.0, 10.0],
|
||||||
"tooltip_margins": [1.0, 1.0],
|
"tooltip_margins": [1.0, 1.0],
|
||||||
|
|
12
resources/variants/ultimaker2_0.25.inst.cfg
Normal file
12
resources/variants/ultimaker2_0.25.inst.cfg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[general]
|
||||||
|
name = 0.25 mm
|
||||||
|
version = 2
|
||||||
|
definition = ultimaker2
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
author = Ultimaker
|
||||||
|
type = variant
|
||||||
|
|
||||||
|
[values]
|
||||||
|
machine_nozzle_size = 0.25
|
||||||
|
machine_nozzle_tip_outer_diameter = 0.8
|
12
resources/variants/ultimaker2_0.4.inst.cfg
Normal file
12
resources/variants/ultimaker2_0.4.inst.cfg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[general]
|
||||||
|
name = 0.4 mm
|
||||||
|
version = 2
|
||||||
|
definition = ultimaker2
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
author = Ultimaker
|
||||||
|
type = variant
|
||||||
|
|
||||||
|
[values]
|
||||||
|
machine_nozzle_size = 0.4
|
||||||
|
machine_nozzle_tip_outer_diameter = 1.05
|
12
resources/variants/ultimaker2_0.6.inst.cfg
Normal file
12
resources/variants/ultimaker2_0.6.inst.cfg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[general]
|
||||||
|
name = 0.6 mm
|
||||||
|
version = 2
|
||||||
|
definition = ultimaker2
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
author = Ultimaker
|
||||||
|
type = variant
|
||||||
|
|
||||||
|
[values]
|
||||||
|
machine_nozzle_size = 0.6
|
||||||
|
machine_nozzle_tip_outer_diameter = 1.25
|
12
resources/variants/ultimaker2_0.8.inst.cfg
Normal file
12
resources/variants/ultimaker2_0.8.inst.cfg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[general]
|
||||||
|
name = 0.8 mm
|
||||||
|
version = 2
|
||||||
|
definition = ultimaker2
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
author = Ultimaker
|
||||||
|
type = variant
|
||||||
|
|
||||||
|
[values]
|
||||||
|
machine_nozzle_size = 0.8
|
||||||
|
machine_nozzle_tip_outer_diameter = 1.35
|
99
tests/Settings/TestCuraContainerRegistry.py
Normal file
99
tests/Settings/TestCuraContainerRegistry.py
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
import os #To find the directory with test files and find the test files.
|
||||||
|
import pytest #This module contains unit tests.
|
||||||
|
import shutil #To copy files to make a temporary file.
|
||||||
|
import unittest.mock #To mock and monkeypatch stuff.
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
|
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry #The class we're testing.
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack #Testing for returning the correct types of stacks.
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack #Testing for returning the correct types of stacks.
|
||||||
|
from UM.Resources import Resources #Mocking some functions of this.
|
||||||
|
import UM.Settings.ContainerRegistry #Making empty container stacks.
|
||||||
|
import UM.Settings.ContainerStack #Setting the container registry here properly.
|
||||||
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
|
|
||||||
|
## Gives a fresh CuraContainerRegistry instance.
|
||||||
|
@pytest.fixture()
|
||||||
|
def container_registry():
|
||||||
|
return CuraContainerRegistry()
|
||||||
|
|
||||||
|
def teardown():
|
||||||
|
#If the temporary file for the legacy file rename test still exists, remove it.
|
||||||
|
temporary_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "stacks", "temporary.stack.cfg")
|
||||||
|
if os.path.isfile(temporary_file):
|
||||||
|
os.remove(temporary_file)
|
||||||
|
|
||||||
|
## Tests whether loading gives objects of the correct type.
|
||||||
|
@pytest.mark.parametrize("filename, output_class", [
|
||||||
|
("ExtruderLegacy.stack.cfg", ExtruderStack),
|
||||||
|
("MachineLegacy.stack.cfg", GlobalStack),
|
||||||
|
("Left.extruder.cfg", ExtruderStack),
|
||||||
|
("Global.global.cfg", GlobalStack),
|
||||||
|
("Global.stack.cfg", GlobalStack)
|
||||||
|
])
|
||||||
|
def test_loadTypes(filename, output_class, container_registry):
|
||||||
|
#Mock some dependencies.
|
||||||
|
UM.Settings.ContainerStack.setContainerRegistry(container_registry)
|
||||||
|
Resources.getAllResourcesOfType = unittest.mock.MagicMock(return_value = [os.path.join(os.path.dirname(os.path.abspath(__file__)), "stacks", filename)]) #Return just this tested file.
|
||||||
|
|
||||||
|
def findContainers(container_type = 0, id = None):
|
||||||
|
if id == "some_instance":
|
||||||
|
return [UM.Settings.ContainerRegistry._EmptyInstanceContainer(id)]
|
||||||
|
elif id == "some_definition":
|
||||||
|
return [DefinitionContainer(container_id = id)]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
container_registry.findContainers = findContainers
|
||||||
|
|
||||||
|
with unittest.mock.patch("cura.Settings.GlobalStack.GlobalStack.findContainer"):
|
||||||
|
with unittest.mock.patch("os.remove"):
|
||||||
|
container_registry.load()
|
||||||
|
|
||||||
|
#Check whether the resulting type was correct.
|
||||||
|
stack_id = filename.split(".")[0]
|
||||||
|
for container in container_registry._containers: #Stupid ContainerRegistry class doesn't expose any way of getting at this except by prodding the privates.
|
||||||
|
if container.getId() == stack_id: #This is the one we're testing.
|
||||||
|
assert type(container) == output_class
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
assert False #Container stack with specified ID was not loaded.
|
||||||
|
|
||||||
|
## Tests whether loading a legacy file moves the upgraded file properly.
|
||||||
|
def test_loadLegacyFileRenamed(container_registry):
|
||||||
|
#Create a temporary file for the registry to load.
|
||||||
|
stacks_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), "stacks")
|
||||||
|
temp_file = os.path.join(stacks_folder, "temporary.stack.cfg")
|
||||||
|
temp_file_source = os.path.join(stacks_folder, "MachineLegacy.stack.cfg")
|
||||||
|
shutil.copyfile(temp_file_source, temp_file)
|
||||||
|
|
||||||
|
#Mock some dependencies.
|
||||||
|
UM.Settings.ContainerStack.setContainerRegistry(container_registry)
|
||||||
|
Resources.getAllResourcesOfType = unittest.mock.MagicMock(return_value = [temp_file]) #Return a temporary file that we'll make for this test.
|
||||||
|
|
||||||
|
def findContainers(container_type = 0, id = None):
|
||||||
|
if id == "MachineLegacy":
|
||||||
|
return None
|
||||||
|
|
||||||
|
container = UM.Settings.ContainerRegistry._EmptyInstanceContainer(id)
|
||||||
|
container.getNextStack = unittest.mock.MagicMock()
|
||||||
|
return [container]
|
||||||
|
|
||||||
|
old_find_containers = container_registry.findContainers
|
||||||
|
container_registry.findContainers = findContainers
|
||||||
|
|
||||||
|
with unittest.mock.patch("cura.Settings.GlobalStack.GlobalStack.findContainer"):
|
||||||
|
container_registry.load()
|
||||||
|
|
||||||
|
container_registry.findContainers = old_find_containers
|
||||||
|
|
||||||
|
container_registry.saveAll()
|
||||||
|
print("all containers in registry", container_registry._containers)
|
||||||
|
assert not os.path.isfile(temp_file)
|
||||||
|
mime_type = container_registry.getMimeTypeForContainer(GlobalStack)
|
||||||
|
file_name = urllib.parse.quote_plus("MachineLegacy") + "." + mime_type.preferredSuffix
|
||||||
|
path = Resources.getStoragePath(Resources.ContainerStacks, file_name)
|
||||||
|
assert os.path.isfile(path)
|
391
tests/Settings/TestExtruderStack.py
Normal file
391
tests/Settings/TestExtruderStack.py
Normal file
|
@ -0,0 +1,391 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
import pytest #This module contains automated tests.
|
||||||
|
import unittest.mock #For the mocking and monkeypatching functionality.
|
||||||
|
|
||||||
|
import UM.Settings.ContainerRegistry #To create empty instance containers.
|
||||||
|
import UM.Settings.ContainerStack #To set the container registry the container stacks use.
|
||||||
|
from UM.Settings.DefinitionContainer import DefinitionContainer #To check against the class of DefinitionContainer.
|
||||||
|
from UM.Settings.InstanceContainer import InstanceContainer #To check against the class of InstanceContainer.
|
||||||
|
import cura.Settings.ExtruderStack #The module we're testing.
|
||||||
|
from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationError #To check whether the correct exceptions are raised.
|
||||||
|
|
||||||
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
|
## Fake container registry that always provides all containers you ask of.
|
||||||
|
@pytest.yield_fixture()
|
||||||
|
def container_registry():
|
||||||
|
registry = unittest.mock.MagicMock()
|
||||||
|
registry.return_value = unittest.mock.NonCallableMagicMock()
|
||||||
|
registry.findInstanceContainers = lambda *args, registry = registry, **kwargs: [registry.return_value]
|
||||||
|
registry.findDefinitionContainers = lambda *args, registry = registry, **kwargs: [registry.return_value]
|
||||||
|
|
||||||
|
UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = registry
|
||||||
|
UM.Settings.ContainerStack._containerRegistry = registry
|
||||||
|
|
||||||
|
yield registry
|
||||||
|
|
||||||
|
UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = None
|
||||||
|
UM.Settings.ContainerStack._containerRegistry = None
|
||||||
|
|
||||||
|
## An empty extruder stack to test with.
|
||||||
|
@pytest.fixture()
|
||||||
|
def extruder_stack() -> cura.Settings.ExtruderStack.ExtruderStack:
|
||||||
|
return cura.Settings.ExtruderStack.ExtruderStack("TestStack")
|
||||||
|
|
||||||
|
## Gets an instance container with a specified container type.
|
||||||
|
#
|
||||||
|
# \param container_type The type metadata for the instance container.
|
||||||
|
# \return An instance container instance.
|
||||||
|
def getInstanceContainer(container_type) -> InstanceContainer:
|
||||||
|
container = InstanceContainer(container_id = "InstanceContainer")
|
||||||
|
container.addMetaDataEntry("type", container_type)
|
||||||
|
return container
|
||||||
|
|
||||||
|
class DefinitionContainerSubClass(DefinitionContainer):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(container_id = "SubDefinitionContainer")
|
||||||
|
|
||||||
|
class InstanceContainerSubClass(InstanceContainer):
|
||||||
|
def __init__(self, container_type):
|
||||||
|
super().__init__(container_id = "SubInstanceContainer")
|
||||||
|
self.addMetaDataEntry("type", container_type)
|
||||||
|
|
||||||
|
#############################START OF TEST CASES################################
|
||||||
|
|
||||||
|
## Tests whether adding a container is properly forbidden.
|
||||||
|
def test_addContainer(extruder_stack):
|
||||||
|
with pytest.raises(InvalidOperationError):
|
||||||
|
extruder_stack.addContainer(unittest.mock.MagicMock())
|
||||||
|
|
||||||
|
#Tests setting user changes profiles to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainUserChangesInvalid(container, extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
extruder_stack.userChanges = container
|
||||||
|
|
||||||
|
#Tests setting user changes profiles.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "user"),
|
||||||
|
InstanceContainerSubClass(container_type = "user")
|
||||||
|
])
|
||||||
|
def test_constrainUserChangesValid(container, extruder_stack):
|
||||||
|
extruder_stack.userChanges = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting quality changes profiles to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainQualityChangesInvalid(container, extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
extruder_stack.qualityChanges = container
|
||||||
|
|
||||||
|
#Test setting quality changes profiles.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "quality_changes"),
|
||||||
|
InstanceContainerSubClass(container_type = "quality_changes")
|
||||||
|
])
|
||||||
|
def test_constrainQualityChangesValid(container, extruder_stack):
|
||||||
|
extruder_stack.qualityChanges = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting quality profiles to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainQualityInvalid(container, extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
extruder_stack.quality = container
|
||||||
|
|
||||||
|
#Test setting quality profiles.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "quality"),
|
||||||
|
InstanceContainerSubClass(container_type = "quality")
|
||||||
|
])
|
||||||
|
def test_constrainQualityValid(container, extruder_stack):
|
||||||
|
extruder_stack.quality = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting materials to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "quality"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainMaterialInvalid(container, extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
extruder_stack.material = container
|
||||||
|
|
||||||
|
#Test setting materials.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "material"),
|
||||||
|
InstanceContainerSubClass(container_type = "material")
|
||||||
|
])
|
||||||
|
def test_constrainMaterialValid(container, extruder_stack):
|
||||||
|
extruder_stack.material = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting variants to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainVariantInvalid(container, extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
extruder_stack.variant = container
|
||||||
|
|
||||||
|
#Test setting variants.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "variant"),
|
||||||
|
InstanceContainerSubClass(container_type = "variant")
|
||||||
|
])
|
||||||
|
def test_constrainVariantValid(container, extruder_stack):
|
||||||
|
extruder_stack.variant = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting definitions to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong class"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong class.
|
||||||
|
])
|
||||||
|
def test_constrainVariantInvalid(container, extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
extruder_stack.definition = container
|
||||||
|
|
||||||
|
#Test setting definitions.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
DefinitionContainer(container_id = "DefinitionContainer"),
|
||||||
|
DefinitionContainerSubClass()
|
||||||
|
])
|
||||||
|
def test_constrainDefinitionValid(container, extruder_stack):
|
||||||
|
extruder_stack.definition = container #Should not give an error.
|
||||||
|
|
||||||
|
## Tests whether deserialising completes the missing containers with empty
|
||||||
|
# ones.
|
||||||
|
@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now.
|
||||||
|
def test_deserializeCompletesEmptyContainers(extruder_stack: cura.Settings.ExtruderStack):
|
||||||
|
extruder_stack._containers = [DefinitionContainer(container_id = "definition")] #Set the internal state of this stack manually.
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
extruder_stack.deserialize("")
|
||||||
|
|
||||||
|
assert len(extruder_stack.getContainers()) == len(cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap) #Needs a slot for every type.
|
||||||
|
for container_type_index in cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap:
|
||||||
|
if container_type_index == cura.Settings.CuraContainerStack._ContainerIndexes.Definition: #We're not checking the definition.
|
||||||
|
continue
|
||||||
|
assert extruder_stack.getContainer(container_type_index).getId() == "empty" #All others need to be empty.
|
||||||
|
|
||||||
|
## Tests whether an instance container with the wrong type gets removed when
|
||||||
|
# deserialising.
|
||||||
|
def test_deserializeRemovesWrongInstanceContainer(extruder_stack):
|
||||||
|
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "wrong type")
|
||||||
|
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
extruder_stack.deserialize("")
|
||||||
|
|
||||||
|
assert extruder_stack.quality == extruder_stack._empty_instance_container #Replaced with empty.
|
||||||
|
|
||||||
|
## Tests whether a container with the wrong class gets removed when
|
||||||
|
# deserialising.
|
||||||
|
def test_deserializeRemovesWrongContainerClass(extruder_stack):
|
||||||
|
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = DefinitionContainer(container_id = "wrong class")
|
||||||
|
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
extruder_stack.deserialize("")
|
||||||
|
|
||||||
|
assert extruder_stack.quality == extruder_stack._empty_instance_container #Replaced with empty.
|
||||||
|
|
||||||
|
## Tests whether an instance container in the definition spot results in an
|
||||||
|
# error.
|
||||||
|
def test_deserializeWrongDefinitionClass(extruder_stack):
|
||||||
|
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = getInstanceContainer(container_type = "definition") #Correct type but wrong class.
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
with pytest.raises(UM.Settings.ContainerStack.InvalidContainerStackError): #Must raise an error that there is no definition container.
|
||||||
|
extruder_stack.deserialize("")
|
||||||
|
|
||||||
|
## Tests whether an instance container with the wrong type is moved into the
|
||||||
|
# correct slot by deserialising.
|
||||||
|
def test_deserializeMoveInstanceContainer(extruder_stack):
|
||||||
|
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "material") #Not in the correct spot.
|
||||||
|
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
extruder_stack.deserialize("")
|
||||||
|
|
||||||
|
assert extruder_stack.quality.getId() == "empty"
|
||||||
|
assert extruder_stack.material.getId() != "empty"
|
||||||
|
|
||||||
|
## Tests whether a definition container in the wrong spot is moved into the
|
||||||
|
# correct spot by deserialising.
|
||||||
|
@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now.
|
||||||
|
def test_deserializeMoveDefinitionContainer(extruder_stack):
|
||||||
|
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot.
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
extruder_stack.deserialize("")
|
||||||
|
|
||||||
|
assert extruder_stack.material.getId() == "empty"
|
||||||
|
assert extruder_stack.definition.getId() != "empty"
|
||||||
|
|
||||||
|
UM.Settings.ContainerStack._containerRegistry = None
|
||||||
|
|
||||||
|
## Tests whether getProperty properly applies the stack-like behaviour on its
|
||||||
|
# containers.
|
||||||
|
def test_getPropertyFallThrough(extruder_stack):
|
||||||
|
# ExtruderStack.setNextStack calls registerExtruder for backward compatibility, but we do not need a complete extruder manager
|
||||||
|
ExtruderManager._ExtruderManager__instance = unittest.mock.MagicMock()
|
||||||
|
|
||||||
|
#A few instance container mocks to put in the stack.
|
||||||
|
mock_layer_heights = {} #For each container type, a mock container that defines layer height to something unique.
|
||||||
|
mock_no_settings = {} #For each container type, a mock container that has no settings at all.
|
||||||
|
container_indices = cura.Settings.CuraContainerStack._ContainerIndexes #Cache.
|
||||||
|
for type_id, type_name in container_indices.IndexTypeMap.items():
|
||||||
|
container = unittest.mock.MagicMock()
|
||||||
|
container.getProperty = lambda key, property, type_id = type_id: type_id if (key == "layer_height" and property == "value") else None #Returns the container type ID as layer height, in order to identify it.
|
||||||
|
container.hasProperty = lambda key, property: key == "layer_height"
|
||||||
|
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name)
|
||||||
|
mock_layer_heights[type_id] = container
|
||||||
|
|
||||||
|
container = unittest.mock.MagicMock()
|
||||||
|
container.getProperty = unittest.mock.MagicMock(return_value = None) #Has no settings at all.
|
||||||
|
container.hasProperty = unittest.mock.MagicMock(return_value = False)
|
||||||
|
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name)
|
||||||
|
mock_no_settings[type_id] = container
|
||||||
|
|
||||||
|
extruder_stack.userChanges = mock_no_settings[container_indices.UserChanges]
|
||||||
|
extruder_stack.qualityChanges = mock_no_settings[container_indices.QualityChanges]
|
||||||
|
extruder_stack.quality = mock_no_settings[container_indices.Quality]
|
||||||
|
extruder_stack.material = mock_no_settings[container_indices.Material]
|
||||||
|
extruder_stack.variant = mock_no_settings[container_indices.Variant]
|
||||||
|
with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking.
|
||||||
|
extruder_stack.definition = mock_layer_heights[container_indices.Definition] #There's a layer height in here!
|
||||||
|
extruder_stack.setNextStack(unittest.mock.MagicMock())
|
||||||
|
|
||||||
|
assert extruder_stack.getProperty("layer_height", "value") == container_indices.Definition
|
||||||
|
extruder_stack.variant = mock_layer_heights[container_indices.Variant]
|
||||||
|
assert extruder_stack.getProperty("layer_height", "value") == container_indices.Variant
|
||||||
|
extruder_stack.material = mock_layer_heights[container_indices.Material]
|
||||||
|
assert extruder_stack.getProperty("layer_height", "value") == container_indices.Material
|
||||||
|
extruder_stack.quality = mock_layer_heights[container_indices.Quality]
|
||||||
|
assert extruder_stack.getProperty("layer_height", "value") == container_indices.Quality
|
||||||
|
extruder_stack.qualityChanges = mock_layer_heights[container_indices.QualityChanges]
|
||||||
|
assert extruder_stack.getProperty("layer_height", "value") == container_indices.QualityChanges
|
||||||
|
extruder_stack.userChanges = mock_layer_heights[container_indices.UserChanges]
|
||||||
|
assert extruder_stack.getProperty("layer_height", "value") == container_indices.UserChanges
|
||||||
|
|
||||||
|
## Tests whether inserting a container is properly forbidden.
|
||||||
|
def test_insertContainer(extruder_stack):
|
||||||
|
with pytest.raises(InvalidOperationError):
|
||||||
|
extruder_stack.insertContainer(0, unittest.mock.MagicMock())
|
||||||
|
|
||||||
|
## Tests whether removing a container is properly forbidden.
|
||||||
|
def test_removeContainer(extruder_stack):
|
||||||
|
with pytest.raises(InvalidOperationError):
|
||||||
|
extruder_stack.removeContainer(unittest.mock.MagicMock())
|
||||||
|
|
||||||
|
## Tests setting definitions by specifying an ID of a definition that exists.
|
||||||
|
def test_setDefinitionByIdExists(extruder_stack, container_registry):
|
||||||
|
container_registry.return_value = DefinitionContainer(container_id = "some_definition")
|
||||||
|
extruder_stack.setDefinitionById("some_definition")
|
||||||
|
assert extruder_stack.definition.getId() == "some_definition"
|
||||||
|
|
||||||
|
## Tests setting definitions by specifying an ID of a definition that doesn't
|
||||||
|
# exist.
|
||||||
|
def test_setDefinitionByIdDoesntExist(extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
extruder_stack.setDefinitionById("some_definition") #Container registry is empty now.
|
||||||
|
|
||||||
|
## Tests setting materials by specifying an ID of a material that exists.
|
||||||
|
def test_setMaterialByIdExists(extruder_stack, container_registry):
|
||||||
|
container_registry.return_value = getInstanceContainer(container_type = "material")
|
||||||
|
extruder_stack.setMaterialById("InstanceContainer")
|
||||||
|
assert extruder_stack.material.getId() == "InstanceContainer"
|
||||||
|
|
||||||
|
## Tests setting materials by specifying an ID of a material that doesn't
|
||||||
|
# exist.
|
||||||
|
def test_setMaterialByIdDoesntExist(extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
extruder_stack.setMaterialById("some_material") #Container registry is empty now.
|
||||||
|
|
||||||
|
## Tests setting properties directly on the extruder stack.
|
||||||
|
@pytest.mark.parametrize("key, property, value", [
|
||||||
|
("layer_height", "value", 0.1337),
|
||||||
|
("foo", "value", 100),
|
||||||
|
("support_enabled", "value", True),
|
||||||
|
("layer_height", "default_value", 0.1337),
|
||||||
|
("layer_height", "is_bright_pink", "of course")
|
||||||
|
])
|
||||||
|
def test_setPropertyUser(key, property, value, extruder_stack):
|
||||||
|
user_changes = unittest.mock.MagicMock()
|
||||||
|
user_changes.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user")
|
||||||
|
extruder_stack.userChanges = user_changes
|
||||||
|
|
||||||
|
extruder_stack.setProperty(key, property, value) #The actual test.
|
||||||
|
|
||||||
|
extruder_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call.
|
||||||
|
|
||||||
|
## Tests setting properties on specific containers on the global stack.
|
||||||
|
@pytest.mark.parametrize("target_container, stack_variable", [
|
||||||
|
("user", "userChanges"),
|
||||||
|
("quality_changes", "qualityChanges"),
|
||||||
|
("quality", "quality"),
|
||||||
|
("material", "material"),
|
||||||
|
("variant", "variant")
|
||||||
|
])
|
||||||
|
def test_setPropertyOtherContainers(target_container, stack_variable, extruder_stack):
|
||||||
|
#Other parameters that don't need to be varied.
|
||||||
|
key = "layer_height"
|
||||||
|
property = "value"
|
||||||
|
value = 0.1337
|
||||||
|
#A mock container in the right spot.
|
||||||
|
container = unittest.mock.MagicMock()
|
||||||
|
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = target_container)
|
||||||
|
setattr(extruder_stack, stack_variable, container) #For instance, set global_stack.qualityChanges = container.
|
||||||
|
|
||||||
|
extruder_stack.setProperty(key, property, value, target_container = target_container) #The actual test.
|
||||||
|
|
||||||
|
getattr(extruder_stack, stack_variable).setProperty.assert_called_once_with(key, property, value) #Make sure that the proper container gets a setProperty call.
|
||||||
|
|
||||||
|
## Tests setting qualities by specifying an ID of a quality that exists.
|
||||||
|
def test_setQualityByIdExists(extruder_stack, container_registry):
|
||||||
|
container_registry.return_value = getInstanceContainer(container_type = "quality")
|
||||||
|
extruder_stack.setQualityById("InstanceContainer")
|
||||||
|
assert extruder_stack.quality.getId() == "InstanceContainer"
|
||||||
|
|
||||||
|
## Tests setting qualities by specifying an ID of a quality that doesn't exist.
|
||||||
|
def test_setQualityByIdDoesntExist(extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
extruder_stack.setQualityById("some_quality") #Container registry is empty now.
|
||||||
|
|
||||||
|
## Tests setting quality changes by specifying an ID of a quality change that
|
||||||
|
# exists.
|
||||||
|
def test_setQualityChangesByIdExists(extruder_stack, container_registry):
|
||||||
|
container_registry.return_value = getInstanceContainer(container_type = "quality_changes")
|
||||||
|
extruder_stack.setQualityChangesById("InstanceContainer")
|
||||||
|
assert extruder_stack.qualityChanges.getId() == "InstanceContainer"
|
||||||
|
|
||||||
|
## Tests setting quality changes by specifying an ID of a quality change that
|
||||||
|
# doesn't exist.
|
||||||
|
def test_setQualityChangesByIdDoesntExist(extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
extruder_stack.setQualityChangesById("some_quality_changes") #Container registry is empty now.
|
||||||
|
|
||||||
|
## Tests setting variants by specifying an ID of a variant that exists.
|
||||||
|
def test_setVariantByIdExists(extruder_stack, container_registry):
|
||||||
|
container_registry.return_value = getInstanceContainer(container_type = "variant")
|
||||||
|
extruder_stack.setVariantById("InstanceContainer")
|
||||||
|
assert extruder_stack.variant.getId() == "InstanceContainer"
|
||||||
|
|
||||||
|
## Tests setting variants by specifying an ID of a variant that doesn't exist.
|
||||||
|
def test_setVariantByIdDoesntExist(extruder_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
extruder_stack.setVariantById("some_variant") #Container registry is empty now.
|
558
tests/Settings/TestGlobalStack.py
Normal file
558
tests/Settings/TestGlobalStack.py
Normal file
|
@ -0,0 +1,558 @@
|
||||||
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
|
import pytest #This module contains unit tests.
|
||||||
|
import unittest.mock #To monkeypatch some mocks in place of dependencies.
|
||||||
|
|
||||||
|
import cura.Settings.GlobalStack #The module we're testing.
|
||||||
|
import cura.Settings.CuraContainerStack #To get the list of container types.
|
||||||
|
from cura.Settings.Exceptions import TooManyExtrudersError, InvalidContainerError, InvalidOperationError #To test raising these errors.
|
||||||
|
from UM.Settings.DefinitionContainer import DefinitionContainer #To test against the class DefinitionContainer.
|
||||||
|
from UM.Settings.InstanceContainer import InstanceContainer #To test against the class InstanceContainer.
|
||||||
|
from UM.Settings.SettingInstance import InstanceState
|
||||||
|
import UM.Settings.ContainerRegistry
|
||||||
|
import UM.Settings.ContainerStack
|
||||||
|
|
||||||
|
## Fake container registry that always provides all containers you ask of.
|
||||||
|
@pytest.yield_fixture()
|
||||||
|
def container_registry():
|
||||||
|
registry = unittest.mock.MagicMock()
|
||||||
|
registry.return_value = unittest.mock.NonCallableMagicMock()
|
||||||
|
registry.findInstanceContainers = lambda *args, registry = registry, **kwargs: [registry.return_value]
|
||||||
|
registry.findDefinitionContainers = lambda *args, registry = registry, **kwargs: [registry.return_value]
|
||||||
|
|
||||||
|
UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = registry
|
||||||
|
UM.Settings.ContainerStack._containerRegistry = registry
|
||||||
|
|
||||||
|
yield registry
|
||||||
|
|
||||||
|
UM.Settings.ContainerRegistry.ContainerRegistry._ContainerRegistry__instance = None
|
||||||
|
UM.Settings.ContainerStack._containerRegistry = None
|
||||||
|
|
||||||
|
#An empty global stack to test with.
|
||||||
|
@pytest.fixture()
|
||||||
|
def global_stack() -> cura.Settings.GlobalStack.GlobalStack:
|
||||||
|
return cura.Settings.GlobalStack.GlobalStack("TestStack")
|
||||||
|
|
||||||
|
## Gets an instance container with a specified container type.
|
||||||
|
#
|
||||||
|
# \param container_type The type metadata for the instance container.
|
||||||
|
# \return An instance container instance.
|
||||||
|
def getInstanceContainer(container_type) -> InstanceContainer:
|
||||||
|
container = InstanceContainer(container_id = "InstanceContainer")
|
||||||
|
container.addMetaDataEntry("type", container_type)
|
||||||
|
return container
|
||||||
|
|
||||||
|
class DefinitionContainerSubClass(DefinitionContainer):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(container_id = "SubDefinitionContainer")
|
||||||
|
|
||||||
|
class InstanceContainerSubClass(InstanceContainer):
|
||||||
|
def __init__(self, container_type):
|
||||||
|
super().__init__(container_id = "SubInstanceContainer")
|
||||||
|
self.addMetaDataEntry("type", container_type)
|
||||||
|
|
||||||
|
#############################START OF TEST CASES################################
|
||||||
|
|
||||||
|
## Tests whether adding a container is properly forbidden.
|
||||||
|
def test_addContainer(global_stack):
|
||||||
|
with pytest.raises(InvalidOperationError):
|
||||||
|
global_stack.addContainer(unittest.mock.MagicMock())
|
||||||
|
|
||||||
|
## Tests adding extruders to the global stack.
|
||||||
|
def test_addExtruder(global_stack):
|
||||||
|
mock_definition = unittest.mock.MagicMock()
|
||||||
|
mock_definition.getProperty = lambda key, property: 2 if key == "machine_extruder_count" and property == "value" else None
|
||||||
|
|
||||||
|
with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock):
|
||||||
|
global_stack.definition = mock_definition
|
||||||
|
|
||||||
|
assert len(global_stack.extruders) == 0
|
||||||
|
first_extruder = unittest.mock.MagicMock()
|
||||||
|
with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock):
|
||||||
|
global_stack.addExtruder(first_extruder)
|
||||||
|
assert len(global_stack.extruders) == 1
|
||||||
|
assert global_stack.extruders[0] == first_extruder
|
||||||
|
second_extruder = unittest.mock.MagicMock()
|
||||||
|
with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock):
|
||||||
|
global_stack.addExtruder(second_extruder)
|
||||||
|
assert len(global_stack.extruders) == 2
|
||||||
|
assert global_stack.extruders[1] == second_extruder
|
||||||
|
with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock):
|
||||||
|
with pytest.raises(TooManyExtrudersError): #Should be limited to 2 extruders because of machine_extruder_count.
|
||||||
|
global_stack.addExtruder(unittest.mock.MagicMock())
|
||||||
|
assert len(global_stack.extruders) == 2 #Didn't add the faulty extruder.
|
||||||
|
|
||||||
|
#Tests setting user changes profiles to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainUserChangesInvalid(container, global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
global_stack.userChanges = container
|
||||||
|
|
||||||
|
#Tests setting user changes profiles.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "user"),
|
||||||
|
InstanceContainerSubClass(container_type = "user")
|
||||||
|
])
|
||||||
|
def test_constrainUserChangesValid(container, global_stack):
|
||||||
|
global_stack.userChanges = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting quality changes profiles to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainQualityChangesInvalid(container, global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
global_stack.qualityChanges = container
|
||||||
|
|
||||||
|
#Test setting quality changes profiles.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "quality_changes"),
|
||||||
|
InstanceContainerSubClass(container_type = "quality_changes")
|
||||||
|
])
|
||||||
|
def test_constrainQualityChangesValid(container, global_stack):
|
||||||
|
global_stack.qualityChanges = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting quality profiles to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainQualityInvalid(container, global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
global_stack.quality = container
|
||||||
|
|
||||||
|
#Test setting quality profiles.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "quality"),
|
||||||
|
InstanceContainerSubClass(container_type = "quality")
|
||||||
|
])
|
||||||
|
def test_constrainQualityValid(container, global_stack):
|
||||||
|
global_stack.quality = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting materials to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "quality"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainMaterialInvalid(container, global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
global_stack.material = container
|
||||||
|
|
||||||
|
#Test setting materials.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "material"),
|
||||||
|
InstanceContainerSubClass(container_type = "material")
|
||||||
|
])
|
||||||
|
def test_constrainMaterialValid(container, global_stack):
|
||||||
|
global_stack.material = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting variants to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainVariantInvalid(container, global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
global_stack.variant = container
|
||||||
|
|
||||||
|
#Test setting variants.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "variant"),
|
||||||
|
InstanceContainerSubClass(container_type = "variant")
|
||||||
|
])
|
||||||
|
def test_constrainVariantValid(container, global_stack):
|
||||||
|
global_stack.variant = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting definition changes profiles to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong container type"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong type.
|
||||||
|
DefinitionContainer(container_id = "wrong class")
|
||||||
|
])
|
||||||
|
def test_constrainDefinitionChangesInvalid(container, global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
global_stack.definitionChanges = container
|
||||||
|
|
||||||
|
#Test setting definition changes profiles.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "definition_changes"),
|
||||||
|
InstanceContainerSubClass(container_type = "definition_changes")
|
||||||
|
])
|
||||||
|
def test_constrainDefinitionChangesValid(container, global_stack):
|
||||||
|
global_stack.definitionChanges = container #Should not give an error.
|
||||||
|
|
||||||
|
#Tests setting definitions to invalid containers.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
getInstanceContainer(container_type = "wrong class"),
|
||||||
|
getInstanceContainer(container_type = "material"), #Existing, but still wrong class.
|
||||||
|
])
|
||||||
|
def test_constrainVariantInvalid(container, global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError): #Invalid container, should raise an error.
|
||||||
|
global_stack.definition = container
|
||||||
|
|
||||||
|
#Test setting definitions.
|
||||||
|
@pytest.mark.parametrize("container", [
|
||||||
|
DefinitionContainer(container_id = "DefinitionContainer"),
|
||||||
|
DefinitionContainerSubClass()
|
||||||
|
])
|
||||||
|
def test_constrainDefinitionValid(container, global_stack):
|
||||||
|
global_stack.definition = container #Should not give an error.
|
||||||
|
|
||||||
|
## Tests whether deserialising completes the missing containers with empty
|
||||||
|
# ones.
|
||||||
|
@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now.
|
||||||
|
def test_deserializeCompletesEmptyContainers(global_stack: cura.Settings.GlobalStack):
|
||||||
|
global_stack._containers = [DefinitionContainer(container_id = "definition")] #Set the internal state of this stack manually.
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
global_stack.deserialize("")
|
||||||
|
|
||||||
|
assert len(global_stack.getContainers()) == len(cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap) #Needs a slot for every type.
|
||||||
|
for container_type_index in cura.Settings.CuraContainerStack._ContainerIndexes.IndexTypeMap:
|
||||||
|
if container_type_index == cura.Settings.CuraContainerStack._ContainerIndexes.Definition: #We're not checking the definition.
|
||||||
|
continue
|
||||||
|
assert global_stack.getContainer(container_type_index).getId() == "empty" #All others need to be empty.
|
||||||
|
|
||||||
|
## Tests whether an instance container with the wrong type gets removed when
|
||||||
|
# deserialising.
|
||||||
|
def test_deserializeRemovesWrongInstanceContainer(global_stack):
|
||||||
|
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "wrong type")
|
||||||
|
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
global_stack.deserialize("")
|
||||||
|
|
||||||
|
assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty.
|
||||||
|
|
||||||
|
## Tests whether a container with the wrong class gets removed when
|
||||||
|
# deserialising.
|
||||||
|
def test_deserializeRemovesWrongContainerClass(global_stack):
|
||||||
|
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = DefinitionContainer(container_id = "wrong class")
|
||||||
|
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
global_stack.deserialize("")
|
||||||
|
|
||||||
|
assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty.
|
||||||
|
|
||||||
|
## Tests whether an instance container in the definition spot results in an
|
||||||
|
# error.
|
||||||
|
def test_deserializeWrongDefinitionClass(global_stack):
|
||||||
|
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = getInstanceContainer(container_type = "definition") #Correct type but wrong class.
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
with pytest.raises(UM.Settings.ContainerStack.InvalidContainerStackError): #Must raise an error that there is no definition container.
|
||||||
|
global_stack.deserialize("")
|
||||||
|
|
||||||
|
## Tests whether an instance container with the wrong type is moved into the
|
||||||
|
# correct slot by deserialising.
|
||||||
|
def test_deserializeMoveInstanceContainer(global_stack):
|
||||||
|
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "material") #Not in the correct spot.
|
||||||
|
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
global_stack.deserialize("")
|
||||||
|
|
||||||
|
assert global_stack.quality.getId() == "empty"
|
||||||
|
assert global_stack.material.getId() != "empty"
|
||||||
|
|
||||||
|
## Tests whether a definition container in the wrong spot is moved into the
|
||||||
|
# correct spot by deserialising.
|
||||||
|
@pytest.mark.skip #The test currently fails because the definition container doesn't have a category, which is wrong but we don't have time to refactor that right now.
|
||||||
|
def test_deserializeMoveDefinitionContainer(global_stack):
|
||||||
|
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot.
|
||||||
|
|
||||||
|
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||||
|
global_stack.deserialize("")
|
||||||
|
|
||||||
|
assert global_stack.material.getId() == "empty"
|
||||||
|
assert global_stack.definition.getId() != "empty"
|
||||||
|
|
||||||
|
UM.Settings.ContainerStack._containerRegistry = None
|
||||||
|
|
||||||
|
## Tests whether getProperty properly applies the stack-like behaviour on its
|
||||||
|
# containers.
|
||||||
|
def test_getPropertyFallThrough(global_stack):
|
||||||
|
#A few instance container mocks to put in the stack.
|
||||||
|
mock_layer_heights = {} #For each container type, a mock container that defines layer height to something unique.
|
||||||
|
mock_no_settings = {} #For each container type, a mock container that has no settings at all.
|
||||||
|
container_indexes = cura.Settings.CuraContainerStack._ContainerIndexes #Cache.
|
||||||
|
for type_id, type_name in container_indexes.IndexTypeMap.items():
|
||||||
|
container = unittest.mock.MagicMock()
|
||||||
|
container.getProperty = lambda key, property, type_id = type_id: type_id if (key == "layer_height" and property == "value") else None #Returns the container type ID as layer height, in order to identify it.
|
||||||
|
container.hasProperty = lambda key, property: key == "layer_height"
|
||||||
|
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name)
|
||||||
|
mock_layer_heights[type_id] = container
|
||||||
|
|
||||||
|
container = unittest.mock.MagicMock()
|
||||||
|
container.getProperty = unittest.mock.MagicMock(return_value = None) #Has no settings at all.
|
||||||
|
container.hasProperty = unittest.mock.MagicMock(return_value = False)
|
||||||
|
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = type_name)
|
||||||
|
mock_no_settings[type_id] = container
|
||||||
|
|
||||||
|
global_stack.userChanges = mock_no_settings[container_indexes.UserChanges]
|
||||||
|
global_stack.qualityChanges = mock_no_settings[container_indexes.QualityChanges]
|
||||||
|
global_stack.quality = mock_no_settings[container_indexes.Quality]
|
||||||
|
global_stack.material = mock_no_settings[container_indexes.Material]
|
||||||
|
global_stack.variant = mock_no_settings[container_indexes.Variant]
|
||||||
|
global_stack.definitionChanges = mock_no_settings[container_indexes.DefinitionChanges]
|
||||||
|
with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking.
|
||||||
|
global_stack.definition = mock_layer_heights[container_indexes.Definition] #There's a layer height in here!
|
||||||
|
|
||||||
|
assert global_stack.getProperty("layer_height", "value") == container_indexes.Definition
|
||||||
|
global_stack.definitionChanges = mock_layer_heights[container_indexes.DefinitionChanges]
|
||||||
|
assert global_stack.getProperty("layer_height", "value") == container_indexes.DefinitionChanges
|
||||||
|
global_stack.variant = mock_layer_heights[container_indexes.Variant]
|
||||||
|
assert global_stack.getProperty("layer_height", "value") == container_indexes.Variant
|
||||||
|
global_stack.material = mock_layer_heights[container_indexes.Material]
|
||||||
|
assert global_stack.getProperty("layer_height", "value") == container_indexes.Material
|
||||||
|
global_stack.quality = mock_layer_heights[container_indexes.Quality]
|
||||||
|
assert global_stack.getProperty("layer_height", "value") == container_indexes.Quality
|
||||||
|
global_stack.qualityChanges = mock_layer_heights[container_indexes.QualityChanges]
|
||||||
|
assert global_stack.getProperty("layer_height", "value") == container_indexes.QualityChanges
|
||||||
|
global_stack.userChanges = mock_layer_heights[container_indexes.UserChanges]
|
||||||
|
assert global_stack.getProperty("layer_height", "value") == container_indexes.UserChanges
|
||||||
|
|
||||||
|
## In definitions, test whether having no resolve allows us to find the value.
|
||||||
|
def test_getPropertyNoResolveInDefinition(global_stack):
|
||||||
|
value = unittest.mock.MagicMock() #Just sets the value for bed temperature.
|
||||||
|
value.getProperty = lambda key, property: 10 if (key == "material_bed_temperature" and property == "value") else None
|
||||||
|
|
||||||
|
with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking.
|
||||||
|
global_stack.definition = value
|
||||||
|
assert global_stack.getProperty("material_bed_temperature", "value") == 10 #No resolve, so fall through to value.
|
||||||
|
|
||||||
|
## In definitions, when the value is asked and there is a resolve function, it
|
||||||
|
# must get the resolve first.
|
||||||
|
def test_getPropertyResolveInDefinition(global_stack):
|
||||||
|
resolve_and_value = unittest.mock.MagicMock() #Sets the resolve and value for bed temperature.
|
||||||
|
resolve_and_value.getProperty = lambda key, property: (7.5 if property == "resolve" else 5) if (key == "material_bed_temperature" and property in ("resolve", "value")) else None #7.5 resolve, 5 value.
|
||||||
|
|
||||||
|
with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking.
|
||||||
|
global_stack.definition = resolve_and_value
|
||||||
|
assert global_stack.getProperty("material_bed_temperature", "value") == 7.5 #Resolve wins in the definition.
|
||||||
|
|
||||||
|
## In instance containers, when the value is asked and there is a resolve
|
||||||
|
# function, it must get the value first.
|
||||||
|
def test_getPropertyResolveInInstance(global_stack):
|
||||||
|
container_indices = cura.Settings.CuraContainerStack._ContainerIndexes
|
||||||
|
instance_containers = {}
|
||||||
|
for container_type in container_indices.IndexTypeMap:
|
||||||
|
instance_containers[container_type] = unittest.mock.MagicMock() #Sets the resolve and value for bed temperature.
|
||||||
|
instance_containers[container_type].getProperty = lambda key, property: (7.5 if property == "resolve" else (InstanceState.User if property == "state" else 5)) if (key == "material_bed_temperature") else None #7.5 resolve, 5 value.
|
||||||
|
instance_containers[container_type].getMetaDataEntry = unittest.mock.MagicMock(return_value = container_indices.IndexTypeMap[container_type]) #Make queries for the type return the desired type.
|
||||||
|
instance_containers[container_indices.Definition].getProperty = lambda key, property: 10 if (key == "material_bed_temperature" and property == "value") else None #Definition only has value.
|
||||||
|
with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking.
|
||||||
|
global_stack.definition = instance_containers[container_indices.Definition] #Stack must have a definition.
|
||||||
|
|
||||||
|
#For all instance container slots, the value reigns over resolve.
|
||||||
|
global_stack.definitionChanges = instance_containers[container_indices.DefinitionChanges]
|
||||||
|
assert global_stack.getProperty("material_bed_temperature", "value") == 5
|
||||||
|
global_stack.variant = instance_containers[container_indices.Variant]
|
||||||
|
assert global_stack.getProperty("material_bed_temperature", "value") == 5
|
||||||
|
global_stack.material = instance_containers[container_indices.Material]
|
||||||
|
assert global_stack.getProperty("material_bed_temperature", "value") == 5
|
||||||
|
global_stack.quality = instance_containers[container_indices.Quality]
|
||||||
|
assert global_stack.getProperty("material_bed_temperature", "value") == 5
|
||||||
|
global_stack.qualityChanges = instance_containers[container_indices.QualityChanges]
|
||||||
|
assert global_stack.getProperty("material_bed_temperature", "value") == 5
|
||||||
|
global_stack.userChanges = instance_containers[container_indices.UserChanges]
|
||||||
|
assert global_stack.getProperty("material_bed_temperature", "value") == 5
|
||||||
|
|
||||||
|
## Tests whether the value in instances gets evaluated before the resolve in
|
||||||
|
# definitions.
|
||||||
|
def test_getPropertyInstancesBeforeResolve(global_stack):
|
||||||
|
value = unittest.mock.MagicMock() #Sets just the value.
|
||||||
|
value.getProperty = lambda key, property: (10 if property == "value" else InstanceState.User) if key == "material_bed_temperature" else None
|
||||||
|
value.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality")
|
||||||
|
resolve = unittest.mock.MagicMock() #Sets just the resolve.
|
||||||
|
resolve.getProperty = lambda key, property: 7.5 if (key == "material_bed_temperature" and property == "resolve") else None
|
||||||
|
|
||||||
|
with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking.
|
||||||
|
global_stack.definition = resolve
|
||||||
|
global_stack.quality = value
|
||||||
|
|
||||||
|
assert global_stack.getProperty("material_bed_temperature", "value") == 10
|
||||||
|
|
||||||
|
## Tests whether the hasUserValue returns true for settings that are changed in
|
||||||
|
# the user-changes container.
|
||||||
|
def test_hasUserValueUserChanges(global_stack):
|
||||||
|
container = unittest.mock.MagicMock()
|
||||||
|
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user")
|
||||||
|
container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set.
|
||||||
|
global_stack.userChanges = container
|
||||||
|
|
||||||
|
assert global_stack.hasUserValue("layer_height")
|
||||||
|
assert not global_stack.hasUserValue("infill_sparse_density")
|
||||||
|
assert not global_stack.hasUserValue("")
|
||||||
|
|
||||||
|
## Tests whether the hasUserValue returns true for settings that are changed in
|
||||||
|
# the quality-changes container.
|
||||||
|
def test_hasUserValueQualityChanges(global_stack):
|
||||||
|
container = unittest.mock.MagicMock()
|
||||||
|
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality_changes")
|
||||||
|
container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set.
|
||||||
|
global_stack.qualityChanges = container
|
||||||
|
|
||||||
|
assert global_stack.hasUserValue("layer_height")
|
||||||
|
assert not global_stack.hasUserValue("infill_sparse_density")
|
||||||
|
assert not global_stack.hasUserValue("")
|
||||||
|
|
||||||
|
## Tests whether a container in some other place on the stack is correctly not
|
||||||
|
# recognised as user value.
|
||||||
|
def test_hasNoUserValue(global_stack):
|
||||||
|
container = unittest.mock.MagicMock()
|
||||||
|
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality")
|
||||||
|
container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set.
|
||||||
|
global_stack.quality = container
|
||||||
|
|
||||||
|
assert not global_stack.hasUserValue("layer_height") #However this container is quality, so it's not a user value.
|
||||||
|
|
||||||
|
## Tests whether inserting a container is properly forbidden.
|
||||||
|
def test_insertContainer(global_stack):
|
||||||
|
with pytest.raises(InvalidOperationError):
|
||||||
|
global_stack.insertContainer(0, unittest.mock.MagicMock())
|
||||||
|
|
||||||
|
## Tests whether removing a container is properly forbidden.
|
||||||
|
def test_removeContainer(global_stack):
|
||||||
|
with pytest.raises(InvalidOperationError):
|
||||||
|
global_stack.removeContainer(unittest.mock.MagicMock())
|
||||||
|
|
||||||
|
## Tests setting definitions by specifying an ID of a definition that exists.
|
||||||
|
def test_setDefinitionByIdExists(global_stack, container_registry):
|
||||||
|
container_registry.return_value = DefinitionContainer(container_id = "some_definition")
|
||||||
|
global_stack.setDefinitionById("some_definition")
|
||||||
|
assert global_stack.definition.getId() == "some_definition"
|
||||||
|
|
||||||
|
## Tests setting definitions by specifying an ID of a definition that doesn't
|
||||||
|
# exist.
|
||||||
|
def test_setDefinitionByIdDoesntExist(global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
global_stack.setDefinitionById("some_definition") #Container registry is empty now.
|
||||||
|
|
||||||
|
## Tests setting definition changes by specifying an ID of a container that
|
||||||
|
# exists.
|
||||||
|
def test_setDefinitionChangesByIdExists(global_stack, container_registry):
|
||||||
|
container_registry.return_value = getInstanceContainer(container_type = "definition_changes")
|
||||||
|
global_stack.setDefinitionChangesById("InstanceContainer")
|
||||||
|
assert global_stack.definitionChanges.getId() == "InstanceContainer"
|
||||||
|
|
||||||
|
## Tests setting definition changes by specifying an ID of a container that
|
||||||
|
# doesn't exist.
|
||||||
|
def test_setDefinitionChangesByIdDoesntExist(global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
global_stack.setDefinitionChangesById("some_definition_changes") #Container registry is empty now.
|
||||||
|
|
||||||
|
## Tests setting materials by specifying an ID of a material that exists.
|
||||||
|
def test_setMaterialByIdExists(global_stack, container_registry):
|
||||||
|
container_registry.return_value = getInstanceContainer(container_type = "material")
|
||||||
|
global_stack.setMaterialById("InstanceContainer")
|
||||||
|
assert global_stack.material.getId() == "InstanceContainer"
|
||||||
|
|
||||||
|
## Tests setting materials by specifying an ID of a material that doesn't
|
||||||
|
# exist.
|
||||||
|
def test_setMaterialByIdDoesntExist(global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
global_stack.setMaterialById("some_material") #Container registry is empty now.
|
||||||
|
|
||||||
|
## Tests whether changing the next stack is properly forbidden.
|
||||||
|
def test_setNextStack(global_stack):
|
||||||
|
with pytest.raises(InvalidOperationError):
|
||||||
|
global_stack.setNextStack(unittest.mock.MagicMock())
|
||||||
|
|
||||||
|
## Tests setting properties directly on the global stack.
|
||||||
|
@pytest.mark.parametrize("key, property, value", [
|
||||||
|
("layer_height", "value", 0.1337),
|
||||||
|
("foo", "value", 100),
|
||||||
|
("support_enabled", "value", True),
|
||||||
|
("layer_height", "default_value", 0.1337),
|
||||||
|
("layer_height", "is_bright_pink", "of course")
|
||||||
|
])
|
||||||
|
def test_setPropertyUser(key, property, value, global_stack):
|
||||||
|
user_changes = unittest.mock.MagicMock()
|
||||||
|
user_changes.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user")
|
||||||
|
global_stack.userChanges = user_changes
|
||||||
|
|
||||||
|
global_stack.setProperty(key, property, value) #The actual test.
|
||||||
|
|
||||||
|
global_stack.userChanges.setProperty.assert_called_once_with(key, property, value) #Make sure that the user container gets a setProperty call.
|
||||||
|
|
||||||
|
## Tests setting properties on specific containers on the global stack.
|
||||||
|
@pytest.mark.parametrize("target_container, stack_variable", [
|
||||||
|
("user", "userChanges"),
|
||||||
|
("quality_changes", "qualityChanges"),
|
||||||
|
("quality", "quality"),
|
||||||
|
("material", "material"),
|
||||||
|
("variant", "variant"),
|
||||||
|
("definition_changes", "definitionChanges")
|
||||||
|
])
|
||||||
|
def test_setPropertyOtherContainers(target_container, stack_variable, global_stack):
|
||||||
|
#Other parameters that don't need to be varied.
|
||||||
|
key = "layer_height"
|
||||||
|
property = "value"
|
||||||
|
value = 0.1337
|
||||||
|
#A mock container in the right spot.
|
||||||
|
container = unittest.mock.MagicMock()
|
||||||
|
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = target_container)
|
||||||
|
setattr(global_stack, stack_variable, container) #For instance, set global_stack.qualityChanges = container.
|
||||||
|
|
||||||
|
global_stack.setProperty(key, property, value, target_container = target_container) #The actual test.
|
||||||
|
|
||||||
|
getattr(global_stack, stack_variable).setProperty.assert_called_once_with(key, property, value) #Make sure that the proper container gets a setProperty call.
|
||||||
|
|
||||||
|
## Tests setting qualities by specifying an ID of a quality that exists.
|
||||||
|
def test_setQualityByIdExists(global_stack, container_registry):
|
||||||
|
container_registry.return_value = getInstanceContainer(container_type = "quality")
|
||||||
|
global_stack.setQualityById("InstanceContainer")
|
||||||
|
assert global_stack.quality.getId() == "InstanceContainer"
|
||||||
|
|
||||||
|
## Tests setting qualities by specifying an ID of a quality that doesn't exist.
|
||||||
|
def test_setQualityByIdDoesntExist(global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
global_stack.setQualityById("some_quality") #Container registry is empty now.
|
||||||
|
|
||||||
|
## Tests setting quality changes by specifying an ID of a quality change that
|
||||||
|
# exists.
|
||||||
|
def test_setQualityChangesByIdExists(global_stack, container_registry):
|
||||||
|
container_registry.return_value = getInstanceContainer(container_type = "quality_changes")
|
||||||
|
global_stack.setQualityChangesById("InstanceContainer")
|
||||||
|
assert global_stack.qualityChanges.getId() == "InstanceContainer"
|
||||||
|
|
||||||
|
## Tests setting quality changes by specifying an ID of a quality change that
|
||||||
|
# doesn't exist.
|
||||||
|
def test_setQualityChangesByIdDoesntExist(global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
global_stack.setQualityChangesById("some_quality_changes") #Container registry is empty now.
|
||||||
|
|
||||||
|
## Tests setting variants by specifying an ID of a variant that exists.
|
||||||
|
def test_setVariantByIdExists(global_stack, container_registry):
|
||||||
|
container_registry.return_value = getInstanceContainer(container_type = "variant")
|
||||||
|
global_stack.setVariantById("InstanceContainer")
|
||||||
|
assert global_stack.variant.getId() == "InstanceContainer"
|
||||||
|
|
||||||
|
## Tests setting variants by specifying an ID of a variant that doesn't exist.
|
||||||
|
def test_setVariantByIdDoesntExist(global_stack):
|
||||||
|
with pytest.raises(InvalidContainerError):
|
||||||
|
global_stack.setVariantById("some_variant") #Container registry is empty now.
|
||||||
|
|
||||||
|
## Smoke test for findDefaultVariant
|
||||||
|
def test_smoke_findDefaultVariant(global_stack):
|
||||||
|
global_stack.findDefaultVariant()
|
||||||
|
|
||||||
|
## Smoke test for findDefaultMaterial
|
||||||
|
def test_smoke_findDefaultMaterial(global_stack):
|
||||||
|
global_stack.findDefaultMaterial()
|
||||||
|
|
||||||
|
## Smoke test for findDefaultQuality
|
||||||
|
def test_smoke_findDefaultQuality(global_stack):
|
||||||
|
global_stack.findDefaultQuality()
|
12
tests/Settings/stacks/Complete.extruder.cfg
Normal file
12
tests/Settings/stacks/Complete.extruder.cfg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Complete
|
||||||
|
id = Complete
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
0 = some_user_changes
|
||||||
|
1 = some_quality_changes
|
||||||
|
2 = some_quality
|
||||||
|
3 = some_material
|
||||||
|
4 = some_variant
|
||||||
|
5 = some_definition
|
13
tests/Settings/stacks/Complete.global.cfg
Normal file
13
tests/Settings/stacks/Complete.global.cfg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Complete
|
||||||
|
id = Complete
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
0 = some_user_changes
|
||||||
|
1 = some_quality_changes
|
||||||
|
2 = some_quality
|
||||||
|
3 = some_material
|
||||||
|
4 = some_variant
|
||||||
|
5 = some_definition_changes
|
||||||
|
6 = some_definition
|
11
tests/Settings/stacks/ExtruderLegacy.stack.cfg
Normal file
11
tests/Settings/stacks/ExtruderLegacy.stack.cfg
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Legacy Extruder Stack
|
||||||
|
id = ExtruderLegacy
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
type = extruder_train
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
3 = some_instance
|
||||||
|
5 = some_definition
|
8
tests/Settings/stacks/Global.global.cfg
Normal file
8
tests/Settings/stacks/Global.global.cfg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Global
|
||||||
|
id = Global
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
3 = some_instance
|
||||||
|
6 = some_definition
|
11
tests/Settings/stacks/Global.stack.cfg
Normal file
11
tests/Settings/stacks/Global.stack.cfg
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Global
|
||||||
|
id = Global
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
type = machine
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
3 = some_instance
|
||||||
|
6 = some_definition
|
8
tests/Settings/stacks/Left.extruder.cfg
Normal file
8
tests/Settings/stacks/Left.extruder.cfg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Left
|
||||||
|
id = Left
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
3 = some_instance
|
||||||
|
5 = some_definition
|
11
tests/Settings/stacks/MachineLegacy.stack.cfg
Normal file
11
tests/Settings/stacks/MachineLegacy.stack.cfg
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Legacy Global Stack
|
||||||
|
id = MachineLegacy
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
type = machine
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
3 = some_instance
|
||||||
|
6 = some_definition
|
7
tests/Settings/stacks/OnlyDefinition.extruder.cfg
Normal file
7
tests/Settings/stacks/OnlyDefinition.extruder.cfg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Only Definition
|
||||||
|
id = OnlyDefinition
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
5 = some_definition
|
7
tests/Settings/stacks/OnlyDefinition.global.cfg
Normal file
7
tests/Settings/stacks/OnlyDefinition.global.cfg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Only Definition
|
||||||
|
id = OnlyDefinition
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
6 = some_definition
|
8
tests/Settings/stacks/OnlyDefinitionChanges.global.cfg
Normal file
8
tests/Settings/stacks/OnlyDefinitionChanges.global.cfg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Only Definition Changes
|
||||||
|
id = OnlyDefinitionChanges
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
5 = some_instance
|
||||||
|
6 = some_definition
|
8
tests/Settings/stacks/OnlyMaterial.extruder.cfg
Normal file
8
tests/Settings/stacks/OnlyMaterial.extruder.cfg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Only Material
|
||||||
|
id = OnlyMaterial
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
3 = some_instance
|
||||||
|
5 = some_definition
|
8
tests/Settings/stacks/OnlyMaterial.global.cfg
Normal file
8
tests/Settings/stacks/OnlyMaterial.global.cfg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Only Material
|
||||||
|
id = OnlyMaterial
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
3 = some_instance
|
||||||
|
6 = some_definition
|
8
tests/Settings/stacks/OnlyQuality.extruder.cfg
Normal file
8
tests/Settings/stacks/OnlyQuality.extruder.cfg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Only Quality
|
||||||
|
id = OnlyQuality
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
2 = some_instance
|
||||||
|
5 = some_definition
|
8
tests/Settings/stacks/OnlyQuality.global.cfg
Normal file
8
tests/Settings/stacks/OnlyQuality.global.cfg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Only Quality
|
||||||
|
id = OnlyQuality
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
2 = some_instance
|
||||||
|
6 = some_definition
|
8
tests/Settings/stacks/OnlyQualityChanges.extruder.cfg
Normal file
8
tests/Settings/stacks/OnlyQualityChanges.extruder.cfg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[general]
|
||||||
|
version = 3
|
||||||
|
name = Only Quality Changes
|
||||||
|
id = OnlyQualityChanges
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
1 = some_instance
|
||||||
|
5 = some_definition
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue