Merge branch 'master' into fix_material_uniqueness

This commit is contained in:
fieldOfView 2017-05-11 12:23:01 +02:00
commit e45f04f391
207 changed files with 5283 additions and 1264 deletions

2
.gitignore vendored
View file

@ -35,7 +35,9 @@ plugins/GodMode
plugins/PostProcessingPlugin
plugins/X3GWriter
plugins/FlatProfileExporter
plugins/ProfileFlattener
plugins/cura-god-mode-plugin
plugins/cura-big-flame-graph
#Build stuff
CMakeCache.txt

View file

@ -1,5 +1,4 @@
[Desktop Entry]
Version=1
Name=Cura
Name[de]=Cura
GenericName=3D Printing Software

View file

@ -600,6 +600,7 @@ class BuildVolume(SceneNode):
result_areas[extruder_id].append(polygon) #Don't perform the offset on these.
# Add prime tower location as disallowed area.
if len(used_extruders) > 1: #No prime tower in single-extrusion.
prime_tower_collision = False
prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
for extruder_id in prime_tower_areas:

View file

@ -48,35 +48,32 @@ def show(exception_type, value, tb):
dialog = QDialog()
dialog.setMinimumWidth(640)
dialog.setMinimumHeight(640)
dialog.setWindowTitle(catalog.i18nc("@title:window", "Oops!"))
dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))
layout = QVBoxLayout(dialog)
label = QLabel(dialog)
pixmap = QPixmap()
try:
data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read()
pixmap.loadFromData(data)
except:
try:
from UM.Resources import Resources
path = Resources.getPath(Resources.Images, "kitten.jpg")
pixmap.load(path)
except:
pass
pixmap = pixmap.scaled(150, 150)
label.setPixmap(pixmap)
label.setAlignment(Qt.AlignCenter)
layout.addWidget(label)
#label = QLabel(dialog)
#pixmap = QPixmap()
#try:
# data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read()
# pixmap.loadFromData(data)
#except:
# try:
# from UM.Resources import Resources
# path = Resources.getPath(Resources.Images, "kitten.jpg")
# pixmap.load(path)
# except:
# pass
#pixmap = pixmap.scaled(150, 150)
#label.setPixmap(pixmap)
#label.setAlignment(Qt.AlignCenter)
#layout.addWidget(label)
label = QLabel(dialog)
layout.addWidget(label)
#label.setScaledContents(True)
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>
"""))

View file

@ -69,6 +69,8 @@ from cura.Settings.ContainerSettingsModel import ContainerSettingsModel
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from cura.Settings.QualitySettingsModel import QualitySettingsModel
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 UM.FlameProfiler import pyqtSlot
@ -106,6 +108,7 @@ class CuraApplication(QtApplication):
UserInstanceContainer = Resources.UserType + 6
MachineStack = Resources.UserType + 7
ExtruderStack = Resources.UserType + 8
DefinitionChangesContainer = Resources.UserType + 9
Q_ENUMS(ResourceTypes)
@ -151,6 +154,7 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.UserInstanceContainer, "user")
Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
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.VariantInstanceContainer)
@ -158,6 +162,7 @@ class CuraApplication(QtApplication):
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer)
## Initialise the version upgrade manager with Cura's storage paths.
import UM.VersionUpgradeManager #Needs to be here to prevent circular dependencies.
@ -326,6 +331,7 @@ class CuraApplication(QtApplication):
blackmagic
print_sequence
infill_mesh
cutting_mesh
experimental
""".replace("\n", ";").replace(" ", ""))
@ -413,7 +419,7 @@ class CuraApplication(QtApplication):
elif instance_type == "variant":
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
elif instance_type == "definition_changes":
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
path = Resources.getStoragePath(self.ResourceTypes.DefinitionChangesContainer, file_name)
if path:
instance.setPath(path)
@ -436,13 +442,15 @@ class CuraApplication(QtApplication):
mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack))
file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix
stack_type = stack.getMetaDataEntry("type", None)
path = None
if not stack_type or stack_type == "machine":
if isinstance(stack, GlobalStack):
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)
if path:
else:
path = Resources.getStoragePath(Resources.ContainerStacks, file_name)
stack.setPath(path)
with SaveFile(path, "wt") as f:
f.write(data)
@ -1274,6 +1282,8 @@ class CuraApplication(QtApplication):
arranger = Arrange.create(scene_root = root)
min_offset = 8
self.fileLoaded.emit(filename)
for node in nodes:
node.setSelectable(True)
node.setName(os.path.basename(filename))

View file

@ -52,6 +52,19 @@ class PrintInformation(QObject):
super().__init__(parent)
self._current_print_time = Duration(None, self)
self._print_times_per_feature = {
"none": Duration(None, self),
"inset_0": Duration(None, self),
"inset_x": Duration(None, self),
"skin": Duration(None, self),
"support": Duration(None, self),
"skirt": Duration(None, self),
"infill": Duration(None, self),
"support_infill": Duration(None, self),
"travel": Duration(None, self),
"retract": Duration(None, self),
"support_interface": Duration(None, self)
}
self._material_lengths = []
self._material_weights = []
@ -93,6 +106,10 @@ class PrintInformation(QObject):
def currentPrintTime(self):
return self._current_print_time
@pyqtProperty("QVariantMap", notify = currentPrintTimeChanged)
def printTimesPerFeature(self):
return self._print_times_per_feature
materialLengthsChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialLengthsChanged)
@ -111,11 +128,15 @@ class PrintInformation(QObject):
def materialCosts(self):
return self._material_costs
def _onPrintDurationMessage(self, total_time, material_amounts):
if total_time != total_time: # Check for NaN. Engine can sometimes give us weird values.
def _onPrintDurationMessage(self, time_per_feature, material_amounts):
total_time = 0
for feature, time in time_per_feature.items():
if time != time: # Check for NaN. Engine can sometimes give us weird values.
self._print_times_per_feature[feature].setDuration(0)
Logger.log("w", "Received NaN for print duration message")
self._current_print_time.setDuration(0)
else:
continue
total_time += time
self._print_times_per_feature[feature].setDuration(time)
self._current_print_time.setDuration(total_time)
self.currentPrintTimeChanged.emit()
@ -183,7 +204,10 @@ class PrintInformation(QObject):
def _onActiveMaterialChanged(self):
if self._active_material_container:
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_containers = ContainerRegistry.getInstance().findInstanceContainers(id=active_material_id)

View file

@ -430,7 +430,7 @@ class ContainerManager(QObject):
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
# 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():
Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
continue
@ -483,8 +483,8 @@ class ContainerManager(QObject):
# Go through the active stacks and create quality_changes containers from the user containers.
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
user_container = stack.getTop()
quality_container = stack.findContainer(type = "quality")
quality_changes_container = stack.findContainer(type = "quality_changes")
quality_container = stack.quality
quality_changes_container = stack.qualityChanges
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())
continue
@ -605,7 +605,7 @@ class ContainerManager(QObject):
machine_definition = global_stack.getBottom()
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,
QualityManager.getInstance().getParentMachineDefinition(machine_definition),

View file

@ -6,6 +6,7 @@ import os.path
import re
from PyQt5.QtWidgets import QMessageBox
from UM.Decorators import override
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
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.Util import parseBool
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ContainerManager import ContainerManager
from . import ExtruderStack
from . import GlobalStack
from .ContainerManager import ContainerManager
from .ExtruderManager import ExtruderManager
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -26,6 +29,20 @@ class CuraContainerRegistry(ContainerRegistry):
def __init__(self, *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
# \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
@ -212,6 +229,11 @@ class CuraContainerRegistry(ContainerRegistry):
# 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)}
@override(ContainerRegistry)
def load(self):
super().load()
self._fixupExtruders()
def _configureProfile(self, profile, id_seed, new_name):
profile.setReadOnly(False)
profile.setDirty(True) # Ensure the profiles are correctly saved
@ -284,3 +306,47 @@ class CuraContainerRegistry(ContainerRegistry):
if global_container_stack:
return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", 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())

View file

@ -0,0 +1,608 @@
# 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, pyqtSignal
from UM.FlameProfiler import pyqtSlot
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, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit)
## 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, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
## 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, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
## 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()])

View 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

View 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

View file

@ -6,6 +6,7 @@ from UM.FlameProfiler import pyqtSlot
from UM.Application import Application #To get the global container stack to find the current machine.
from UM.Logger import Logger
from UM.Decorators import deprecated
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection
@ -34,7 +35,7 @@ class ExtruderManager(QObject):
## Registers listeners and such to listen to changes to the extruders.
def __init__(self, parent = None):
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._selected_object_extruders = []
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
@ -150,6 +151,7 @@ class ExtruderManager(QObject):
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)
@ -194,6 +196,7 @@ class ExtruderManager(QObject):
#
# \param machine_definition The machine definition 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:
changed = False
machine_definition_id = machine_definition.getId()
@ -246,6 +249,7 @@ class ExtruderManager(QObject):
# \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 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,
position, machine_id: str) -> None:
# Cache some things.
@ -459,7 +463,6 @@ class ExtruderManager(QObject):
# \param machine_id The machine to get the extruders of.
def getMachineExtruders(self, machine_id):
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 [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
@ -483,13 +486,12 @@ class ExtruderManager(QObject):
global_stack = Application.getInstance().getGlobalContainerStack()
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()]):
result.append(self._extruder_trains[global_stack.getId()][extruder])
return result
def __globalContainerStackChanged(self) -> None:
self._addCurrentMachineExtruders()
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:
self._global_container_stack_definition_id = global_container_stack.getBottom().getId()
@ -518,6 +520,10 @@ class ExtruderManager(QObject):
result = []
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")
if value is None:
@ -576,18 +582,6 @@ class ExtruderManager(QObject):
@staticmethod
def getResolveOrValue(key):
global_stack = Application.getInstance().getGlobalContainerStack()
resolved_value = global_stack.getProperty(key, "value")
resolved_value = global_stack.getProperty(key, "resolve")
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
return resolved_value

View 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)

View file

@ -1,11 +1,11 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, pyqtSlot
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
import UM.Qt.ListModel
from UM.Application import Application
import UM.FlameProfiler
from cura.Settings.ExtruderManager import ExtruderManager
## Model that holds extruders.
@ -58,6 +58,11 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
self.addRoleName(self.MaterialRole, "material")
self.addRoleName(self.VariantRole, "variant")
self._update_extruder_timer = QTimer()
self._update_extruder_timer.setInterval(250)
self._update_extruder_timer.setSingleShot(True)
self._update_extruder_timer.timeout.connect(self.__updateExtruders)
self._add_global = False
self._simple_names = False
@ -111,28 +116,33 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
active_extruder_stack.containersChanged.connect(self._onExtruderStackContainersChanged)
self._active_extruder_stack = active_extruder_stack
def _onExtruderStackContainersChanged(self, container):
# Update when there is an empty container or material change
if container.getMetaDataEntry("type") == "material" or container.getMetaDataEntry("type") is None:
# The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name
self._updateExtruders()
modelChanged = pyqtSignal()
def _updateExtruders(self):
self._update_extruder_timer.start()
## Update the list of extruders.
#
# This should be called whenever the list of extruders changes.
def _updateExtruders(self):
@UM.FlameProfiler.profile
def __updateExtruders(self):
changed = False
if self.rowCount() != 0:
changed = True
items = []
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
if self._add_global:
material = global_container_stack.findContainer({ "type": "material" })
material = global_container_stack.material
color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0]
item = {
"id": global_container_stack.getId(),
@ -144,16 +154,20 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
items.append(item)
changed = True
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
manager = ExtruderManager.getInstance()
for extruder in manager.getMachineExtruders(global_container_stack.getId()):
extruder_name = extruder.getName()
material = extruder.findContainer({ "type": "material" })
variant = extruder.findContainer({"type": "variant"})
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
try:
position = int(position)
except ValueError: #Not a proper int.
position = -1
if position >= machine_extruder_count:
continue
extruder_name = extruder.getName()
material = extruder.material
variant = extruder.variant
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
item = { #Construct an item with only the relevant information.

123
cura/Settings/GlobalStack.py Executable file
View 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)

View file

@ -11,17 +11,24 @@ from UM.Application import Application
from UM.Preferences import Preferences
from UM.Logger import Logger
from UM.Message import Message
from UM.Decorators import deprecated
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingDefinition import SettingDefinition
from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.Validator import ValidatorState
from UM.Signal import postponeSignals
import UM.FlameProfiler
from cura.QualityManager import QualityManager
from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.Settings.ExtruderManager import ExtruderManager
from .GlobalStack import GlobalStack
from .CuraStackBuilder import CuraStackBuilder
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -39,6 +46,16 @@ class MachineManager(QObject):
self._active_container_stack = None # type: ContainerStack
self._global_container_stack = None # type: ContainerStack
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
self._instance_container_timer = QTimer()
self._instance_container_timer.setInterval(250)
self._instance_container_timer.setSingleShot(True)
self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged)
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
## When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged)
@ -46,10 +63,12 @@ class MachineManager(QObject):
self.globalContainerChanged.connect(self.activeQualityChanged)
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_quality_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality")[0]
self._empty_quality_changes_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality_changes")[0]
self._empty_variant_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_material_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_quality_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_quality_changes_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._onGlobalContainerChanged()
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
@ -86,10 +105,7 @@ class MachineManager(QObject):
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
"The selected material is incompatible with the selected machine or configuration."))
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value)
activeMaterialChanged = pyqtSignal()
@ -226,14 +242,22 @@ class MachineManager(QObject):
def _onGlobalContainerChanged(self):
if self._global_container_stack:
try:
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that was already disconnected.
pass
try:
self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
except TypeError:
pass
try:
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
material = self._global_container_stack.findContainer({"type": "material"})
except TypeError:
pass
material = self._global_container_stack.material
material.nameChanged.disconnect(self._onMaterialNameChanged)
quality = self._global_container_stack.findContainer({"type": "quality"})
quality = self._global_container_stack.quality
quality.nameChanged.disconnect(self._onQualityNameChanged)
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
@ -256,26 +280,25 @@ class MachineManager(QObject):
# 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
# 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:
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:
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.
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
extruder_stack.containersChanged.connect(self._onInstanceContainersChanged)
else:
material = self._global_container_stack.findContainer({"type": "material"})
material = self._global_container_stack.material
material.nameChanged.connect(self._onMaterialNameChanged)
quality = self._global_container_stack.findContainer({"type": "quality"})
quality = self._global_container_stack.quality
quality.nameChanged.connect(self._onQualityNameChanged)
self._updateStacksHaveErrors()
self._error_check_timer.start()
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
def _updateStacksHaveErrors(self):
@ -292,21 +315,21 @@ class MachineManager(QObject):
if not self._active_container_stack:
self._active_container_stack = self._global_container_stack
self._updateStacksHaveErrors()
self._error_check_timer.start()
if old_active_container_stack != self._active_container_stack:
# Many methods and properties related to the active quality actually depend
# on _active_container_stack. If it changes, then the properties change.
self.activeQualityChanged.emit()
def _onInstanceContainersChanged(self, container):
container_type = container.getMetaDataEntry("type")
def __onInstanceContainersChanged(self):
self.activeQualityChanged.emit()
self.activeVariantChanged.emit()
self.activeMaterialChanged.emit()
self.activeQualityChanged.emit()
self._error_check_timer.start()
self._updateStacksHaveErrors()
def _onInstanceContainersChanged(self, container):
self._instance_container_timer.start()
def _onPropertyChanged(self, key, property_name):
if property_name == "value":
@ -325,41 +348,11 @@ class MachineManager(QObject):
@pyqtSlot(str, str)
def addMachine(self, name: str, definition_id: str) -> None:
container_registry = ContainerRegistry.getInstance()
definitions = container_registry.findDefinitionContainers(id = definition_id)
if definitions:
definition = definitions[0]
name = self._createUniqueName("machine", "", name, definition.getName())
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)
new_stack = CuraStackBuilder.createMachine(name, definition_id)
if new_stack:
Application.getInstance().setGlobalContainerStack(new_stack)
else:
Logger.log("w", "Failed creating a new machine!")
## Create a name that is not empty and unique
# \param container_type \type{string} Type of the container (machine, quality, ...)
@ -478,6 +471,10 @@ class MachineManager(QObject):
return ""
@pyqtProperty("QObject", notify = globalContainerChanged)
def activeMachine(self) -> GlobalStack:
return self._global_container_stack
@pyqtProperty(str, notify = activeStackChanged)
def activeStackId(self) -> str:
if self._active_container_stack:
@ -488,7 +485,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = activeMaterialChanged)
def activeMaterialName(self) -> str:
if self._active_container_stack:
material = self._active_container_stack.findContainer({"type":"material"})
material = self._active_container_stack.material
if material:
return material.getName()
@ -499,18 +496,29 @@ class MachineManager(QObject):
result = []
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
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:
result.append(variant_container.getName())
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)
def activeMaterialNames(self):
result = []
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
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:
result.append(material_container.getName())
return result
@ -518,12 +526,28 @@ class MachineManager(QObject):
@pyqtProperty(str, notify=activeMaterialChanged)
def activeMaterialId(self) -> str:
if self._active_container_stack:
material = self._active_container_stack.findContainer({"type": "material"})
material = self._active_container_stack.material
if material:
return material.getId()
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)
def allActiveMaterialIds(self):
if not self._global_container_stack:
@ -532,7 +556,7 @@ class MachineManager(QObject):
result = {}
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
material_container = stack.findContainer(type = "material")
material_container = stack.material
if not material_container:
continue
@ -551,13 +575,13 @@ class MachineManager(QObject):
if not self._global_container_stack:
return 0
quality_changes = self._global_container_stack.findContainer({"type": "quality_changes"})
quality_changes = self._global_container_stack.qualityChanges
if quality_changes:
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality_changes.getId())
if isinstance(value, SettingFunction):
value = value(self._global_container_stack)
return value
quality = self._global_container_stack.findContainer({"type": "quality"})
quality = self._global_container_stack.quality
if quality:
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality.getId())
if isinstance(value, SettingFunction):
@ -571,7 +595,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify=activeQualityChanged)
def activeQualityMaterialId(self) -> str:
if self._active_container_stack:
quality = self._active_container_stack.findContainer({"type": "quality"})
quality = self._active_container_stack.quality
if quality:
material_id = quality.getMetaDataEntry("material")
if material_id:
@ -588,10 +612,10 @@ class MachineManager(QObject):
@pyqtProperty(str, notify=activeQualityChanged)
def activeQualityName(self):
if self._active_container_stack and self._global_container_stack:
quality = self._global_container_stack.findContainer({"type": "quality_changes"})
if quality and quality != self._empty_quality_changes_container:
quality = self._global_container_stack.qualityChanges
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
return quality.getName()
quality = self._active_container_stack.findContainer({"type": "quality"})
quality = self._active_container_stack.quality
if quality:
return quality.getName()
return ""
@ -599,10 +623,10 @@ class MachineManager(QObject):
@pyqtProperty(str, notify=activeQualityChanged)
def activeQualityId(self):
if self._active_container_stack:
quality = self._active_container_stack.findContainer({"type": "quality_changes"})
if quality and quality != self._empty_quality_changes_container:
quality = self._active_container_stack.qualityChanges
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
return quality.getId()
quality = self._active_container_stack.findContainer({"type": "quality"})
quality = self._active_container_stack.quality
if quality:
return quality.getId()
return ""
@ -610,10 +634,10 @@ class MachineManager(QObject):
@pyqtProperty(str, notify=activeQualityChanged)
def globalQualityId(self):
if self._global_container_stack:
quality = self._global_container_stack.findContainer({"type": "quality_changes"})
if quality and quality != self._empty_quality_changes_container:
quality = self._global_container_stack.qualityChanges
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
return quality.getId()
quality = self._global_container_stack.findContainer({"type": "quality"})
quality = self._global_container_stack.quality
if quality:
return quality.getId()
return ""
@ -621,7 +645,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = activeQualityChanged)
def activeQualityType(self):
if self._active_container_stack:
quality = self._active_container_stack.findContainer(type = "quality")
quality = self._active_container_stack.quality
if quality:
return quality.getMetaDataEntry("quality_type")
return ""
@ -629,7 +653,7 @@ class MachineManager(QObject):
@pyqtProperty(bool, notify = activeQualityChanged)
def isActiveQualitySupported(self):
if self._active_container_stack:
quality = self._active_container_stack.findContainer(type = "quality")
quality = self._active_container_stack.quality
if quality:
return Util.parseBool(quality.getMetaDataEntry("supported", True))
return False
@ -644,7 +668,7 @@ class MachineManager(QObject):
def activeQualityContainerId(self):
# 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:
quality = self._active_container_stack.findContainer(type = "quality")
quality = self._active_container_stack.quality
if quality:
return quality.getId()
return ""
@ -652,8 +676,8 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = activeQualityChanged)
def activeQualityChangesId(self):
if self._active_container_stack:
changes = self._active_container_stack.findContainer(type = "quality_changes")
if changes:
changes = self._active_container_stack.qualityChanges
if changes and changes.getId() != "empty":
return changes.getId()
return ""
@ -690,21 +714,20 @@ class MachineManager(QObject):
Logger.log("d", "Attempting to change the active material to %s", material_id)
old_material = self._active_container_stack.findContainer({"type": "material"})
old_quality = self._active_container_stack.findContainer({"type": "quality"})
old_quality_changes = self._active_container_stack.findContainer({"type": "quality_changes"})
old_material = self._active_container_stack.material
old_quality = self._active_container_stack.quality
old_quality_changes = self._active_container_stack.qualityChanges
if not old_material:
Logger.log("w", "While trying to set the active material, no material was found to replace it.")
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
self.blurSettings.emit()
old_material.nameChanged.disconnect(self._onMaterialNameChanged)
material_index = self._active_container_stack.getContainerIndex(old_material)
self._active_container_stack.replaceContainer(material_index, material_container)
self._active_container_stack.material = material_container
Logger.log("d", "Active material changed")
material_container.nameChanged.connect(self._onMaterialNameChanged)
@ -753,13 +776,12 @@ class MachineManager(QObject):
if not containers or not self._active_container_stack:
return
Logger.log("d", "Attempting to change the active variant to %s", variant_id)
old_variant = self._active_container_stack.findContainer({"type": "variant"})
old_material = self._active_container_stack.findContainer({"type": "material"})
old_variant = self._active_container_stack.variant
old_material = self._active_container_stack.material
if old_variant:
self.blurSettings.emit()
variant_index = self._active_container_stack.getContainerIndex(old_variant)
self._active_container_stack.replaceContainer(variant_index, containers[0])
Logger.log("d", "Active variant changed")
self._active_container_stack.variant = containers[0]
Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId()))
preferred_material = None
if old_material:
preferred_material_name = old_material.getName()
@ -808,8 +830,8 @@ class MachineManager(QObject):
name_changed_connect_stacks.append(stack_quality)
name_changed_connect_stacks.append(stack_quality_changes)
self._replaceQualityOrQualityChangesInStack(stack, stack_quality)
self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes)
self._replaceQualityOrQualityChangesInStack(stack, stack_quality, postpone_emit=True)
self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes, postpone_emit=True)
# Send emits that are postponed in replaceContainer.
# Here the stacks are finished replacing and every value can be resolved based on the current state.
@ -829,6 +851,7 @@ class MachineManager(QObject):
#
# \param quality_name \type{str} the name of the quality.
# \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
@UM.FlameProfiler.profile
def determineQualityAndQualityChangesForQualityType(self, quality_type):
quality_manager = QualityManager.getInstance()
result = []
@ -845,7 +868,7 @@ class MachineManager(QObject):
stacks = [global_container_stack]
for stack in stacks:
material = stack.findContainer(type="material")
material = stack.material
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
if not quality: #No quality profile is found for this quality type.
quality = self._empty_quality_container
@ -882,7 +905,7 @@ class MachineManager(QObject):
else:
Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name)
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
# the quality changes profile and also satisfies any material constraints.
@ -905,7 +928,7 @@ class MachineManager(QObject):
else:
quality_changes = global_quality_changes
material = stack.findContainer(type="material")
material = stack.material
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
if not quality: #No quality profile found for this quality type.
quality = self._empty_quality_container
@ -924,18 +947,18 @@ class MachineManager(QObject):
def _replaceQualityOrQualityChangesInStack(self, stack, container, postpone_emit = False):
# Disconnect the signal handling from the old container.
old_container = stack.findContainer(type=container.getMetaDataEntry("type"))
if old_container:
old_container.nameChanged.disconnect(self._onQualityNameChanged)
else:
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())
return
# Swap in the new container into the stack.
stack.replaceContainer(stack.getContainerIndex(old_container), container, postpone_emit = postpone_emit)
# Attach the needed signal handling.
container.nameChanged.connect(self._onQualityNameChanged)
container_type = container.getMetaDataEntry("type")
if container_type == "quality":
stack.quality.nameChanged.disconnect(self._onQualityNameChanged)
stack.setQuality(container, postpone_emit = postpone_emit)
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
elif container_type == "quality_changes" or container_type is None:
# If the container is an empty container, we need to change the quality_changes.
# Quality can never be set to empty.
stack.qualityChanges.nameChanged.disconnect(self._onQualityNameChanged)
stack.setQualityChanges(container, postpone_emit = postpone_emit)
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
self._onQualityNameChanged()
def _askUserToKeepOrClearCurrentSettings(self):
Application.getInstance().discardOrKeepProfileChanges()
@ -943,7 +966,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = activeVariantChanged)
def activeVariantName(self):
if self._active_container_stack:
variant = self._active_container_stack.findContainer({"type": "variant"})
variant = self._active_container_stack.variant
if variant:
return variant.getName()
@ -952,7 +975,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = activeVariantChanged)
def activeVariantId(self):
if self._active_container_stack:
variant = self._active_container_stack.findContainer({"type": "variant"})
variant = self._active_container_stack.variant
if variant:
return variant.getId()
@ -998,7 +1021,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = activeVariantChanged)
def activeQualityVariantId(self):
if self._active_container_stack:
variant = self._active_container_stack.findContainer({"type": "variant"})
variant = self._active_container_stack.variant
if variant:
return self.getQualityVariantId(self._global_container_stack.getBottom(), variant)
return ""

300
plugins/3MFReader/ThreeMFWorkspaceReader.py Normal file → Executable file
View file

@ -15,7 +15,10 @@ from .WorkspaceDialog import WorkspaceDialog
import xml.etree.ElementTree as ET
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack
from configparser import ConfigParser
import zipfile
import io
import configparser
@ -31,10 +34,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._dialog = WorkspaceDialog()
self._3mf_mesh_reader = None
self._container_registry = ContainerRegistry.getInstance()
self._definition_container_suffix = ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix
# suffixes registered with the MineTypes don't start with a dot '.'
self._definition_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix
self._material_container_suffix = None # We have to wait until all other plugins are loaded before we can set it
self._instance_container_suffix = ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix
self._container_stack_suffix = ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix
self._instance_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix
self._container_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix
self._extruder_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ExtruderStack).preferredSuffix
self._global_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(GlobalStack).preferredSuffix
self._resolve_strategies = {}
@ -47,6 +54,49 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
return self._id_mapping[old_id]
## Separates the given file list into a list of GlobalStack files and a list of ExtruderStack files.
#
# In old versions, extruder stack files have the same suffix as container stack files ".stack.cfg".
#
def _determineGlobalAndExtruderStackFiles(self, project_file_name, file_list):
archive = zipfile.ZipFile(project_file_name, "r")
global_stack_file_list = [name for name in file_list if name.endswith(self._global_stack_suffix)]
extruder_stack_file_list = [name for name in file_list if name.endswith(self._extruder_stack_suffix)]
# separate container stack files and extruder stack files
files_to_determine = [name for name in file_list if name.endswith(self._container_stack_suffix)]
for file_name in files_to_determine:
# FIXME: HACK!
# We need to know the type of the stack file, but we can only know it if we deserialize it.
# The default ContainerStack.deserialize() will connect signals, which is not desired in this case.
# Since we know that the stack files are INI files, so we directly use the ConfigParser to parse them.
serialized = archive.open(file_name).read().decode("utf-8")
stack_config = ConfigParser()
stack_config.read_string(serialized)
# sanity check
if not stack_config.has_option("metadata", "type"):
Logger.log("e", "%s in %s doesn't seem to be valid stack file", file_name, project_file_name)
continue
stack_type = stack_config.get("metadata", "type")
if stack_type == "extruder_train":
extruder_stack_file_list.append(file_name)
elif stack_type == "machine":
global_stack_file_list.append(file_name)
else:
Logger.log("w", "Unknown container stack type '%s' from %s in %s",
stack_type, file_name, project_file_name)
if len(global_stack_file_list) != 1:
raise RuntimeError("More than one global stack file found: [%s]" % str(global_stack_file_list))
return global_stack_file_list[0], extruder_stack_file_list
## read some info so we can make decisions
# \param file_name
# \param show_dialog In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
def preRead(self, file_name, show_dialog=True, *args, **kwargs):
self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
@ -63,23 +113,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Check if there are any conflicts, so we can ask the user.
archive = zipfile.ZipFile(file_name, "r")
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
machine_conflict = False
quality_changes_conflict = False
for container_stack_file in container_stack_files:
container_id = self._stripFileToId(container_stack_file)
serialized = archive.open(container_stack_file).read().decode("utf-8")
if machine_name == "":
machine_name = self._getMachineNameFromSerializedStack(serialized)
stacks = self._container_registry.findContainerStacks(id=container_id)
if stacks:
# Check if there are any changes at all in any of the container stacks.
id_list = self._getContainerIdListFromSerialized(serialized)
for index, container_id in enumerate(id_list):
if stacks[0].getContainer(index).getId() != container_id:
machine_conflict = True
Job.yieldThread()
# A few lists of containers in this project files.
# When loading the global stack file, it may be associated with those containers, which may or may not be
# in Cura already, so we need to provide them as alternative search lists.
definition_container_list = []
instance_container_list = []
material_container_list = []
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
for definition_container_file in definition_container_files:
@ -93,6 +133,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
else:
definition_container = definitions[0]
definition_container_list.append(definition_container)
if definition_container.getMetaDataEntry("type") != "extruder":
machine_type = definition_container.getName()
variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)
@ -131,6 +173,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Deserialize InstanceContainer by converting read data from bytes to string
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
instance_container_list.append(instance_container)
container_type = instance_container.getMetaDataEntry("type")
if container_type == "quality_changes":
quality_name = instance_container.getName()
@ -151,6 +196,27 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
num_user_settings += len(instance_container._instances)
Job.yieldThread()
# Load ContainerStack files and ExtruderStack files
global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(
file_name, cura_file_names)
self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
machine_conflict = False
quality_changes_conflict = False
for container_stack_file in [global_stack_file] + extruder_stack_files:
container_id = self._stripFileToId(container_stack_file)
serialized = archive.open(container_stack_file).read().decode("utf-8")
if machine_name == "":
machine_name = self._getMachineNameFromSerializedStack(serialized)
stacks = self._container_registry.findContainerStacks(id = container_id)
if stacks:
# Check if there are any changes at all in any of the container stacks.
id_list = self._getContainerIdListFromSerialized(serialized)
for index, container_id in enumerate(id_list):
if stacks[0].getContainer(index).getId() != container_id:
machine_conflict = True
Job.yieldThread()
num_visible_settings = 0
try:
temp_preferences = Preferences()
@ -196,9 +262,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
return WorkspaceReader.PreReadResult.cancelled
self._resolve_strategies = self._dialog.getResult()
# Default values
for k, v in self._resolve_strategies.items():
if v is None:
self._resolve_strategies[k] = "new"
return WorkspaceReader.PreReadResult.accepted
## Read the project file
# Add all the definitions / materials / quality changes that do not exist yet. Then it loads
# all the stacks into the container registry. In some cases it will reuse the container for the global stack.
# It handles old style project files containing .stack.cfg as well as new style project files
# containing global.cfg / extruder.cfg
#
# \param file_name
def read(self, file_name):
archive = zipfile.ZipFile(file_name, "r")
@ -232,6 +309,23 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# We do this so that if something goes wrong, it's easier to clean up.
containers_to_add = []
global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names)
global_stack = None
extruder_stacks = []
extruder_stacks_added = []
container_stacks_added = []
global_stack_id_original = self._stripFileToId(global_stack_file)
global_stack_id_new = global_stack_id_original
global_stack_need_rename = False
if self._resolve_strategies["machine"] == "new":
# We need a new id if the id already exists
if self._container_registry.findContainerStacks(id = global_stack_id_original):
global_stack_id_new = self.getNewId(global_stack_id_original)
global_stack_need_rename = True
# TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
# TODO: cases that the container loaded is the same (most notable in materials & definitions).
# TODO: It might be possible that we need to add smarter checking in the future.
@ -240,7 +334,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
for definition_container_file in definition_container_files:
container_id = self._stripFileToId(definition_container_file)
definitions = self._container_registry.findDefinitionContainers(id=container_id)
definitions = self._container_registry.findDefinitionContainers(id = container_id)
if not definitions:
definition_container = DefinitionContainer(container_id)
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
@ -257,7 +351,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
for material_container_file in material_container_files:
container_id = self._stripFileToId(material_container_file)
materials = self._container_registry.findInstanceContainers(id=container_id)
materials = self._container_registry.findInstanceContainers(id = container_id)
if not materials:
material_container = xml_material_profile(container_id)
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
@ -279,7 +373,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Get quality_changes and user profiles saved in the workspace
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
user_instance_containers = []
quality_changes_instance_containers = []
quality_and_definition_changes_instance_containers = []
for instance_container_file in instance_container_files:
container_id = self._stripFileToId(instance_container_file)
instance_container = InstanceContainer(container_id)
@ -290,7 +384,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Job.yieldThread()
if container_type == "user":
# Check if quality changes already exists.
user_containers = self._container_registry.findInstanceContainers(id=container_id)
user_containers = self._container_registry.findInstanceContainers(id = container_id)
if not user_containers:
containers_to_add.append(instance_container)
else:
@ -314,20 +408,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
instance_container.setMetaDataEntry("machine", self.getNewId(machine_id))
containers_to_add.append(instance_container)
user_instance_containers.append(instance_container)
elif container_type == "quality_changes":
elif container_type in ("quality_changes", "definition_changes"):
# Check if quality changes already exists.
quality_changes = self._container_registry.findInstanceContainers(id = container_id)
if not quality_changes:
changes_containers = self._container_registry.findInstanceContainers(id = container_id)
if not changes_containers:
containers_to_add.append(instance_container)
else:
if self._resolve_strategies["quality_changes"] == "override":
quality_changes[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
elif self._resolve_strategies["quality_changes"] is None:
if self._resolve_strategies[container_type] == "override":
changes_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
elif self._resolve_strategies[container_type] is None:
# The ID already exists, but nothing in the values changed, so do nothing.
pass
quality_changes_instance_containers.append(instance_container)
quality_and_definition_changes_instance_containers.append(instance_container)
else:
continue
existing_container = self._container_registry.findInstanceContainers(id = container_id)
if not existing_container:
containers_to_add.append(instance_container)
if global_stack_need_rename:
if instance_container.getMetaDataEntry("machine"):
instance_container.setMetaDataEntry("machine", global_stack_id_new)
# Add all the containers right before we try to add / serialize the stack
for container in containers_to_add:
@ -336,42 +435,99 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Get the stack(s) saved in the workspace.
Logger.log("d", "Workspace loading is checking stacks containers...")
container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
global_stack = None
extruder_stacks = []
container_stacks_added = []
try:
for container_stack_file in container_stack_files:
container_id = self._stripFileToId(container_stack_file)
# Check if a stack by this ID already exists;
container_stacks = self._container_registry.findContainerStacks(id=container_id)
# load extruder stack files
try:
for index, extruder_stack_file in enumerate(extruder_stack_files):
container_id = self._stripFileToId(extruder_stack_file)
container_stacks = self._container_registry.findContainerStacks(id = container_id)
if container_stacks:
# this container stack already exists, try to resolve
stack = container_stacks[0]
if self._resolve_strategies["machine"] == "override":
pass # do nothing
elif self._resolve_strategies["machine"] == "new":
# create a new extruder stack from this one
new_id = self.getNewId(container_id)
stack = ExtruderStack(new_id)
stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8"))
# Ensure a unique ID and name
stack._id = new_id
self._container_registry.addContainer(stack)
extruder_stacks_added.append(stack)
else:
if self._resolve_strategies["machine"] == "override":
global_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
# deserialize new extruder stack over the current ones
if global_stacks:
old_extruder_stack_id = global_stacks[0].extruders[index].getId()
# HACK delete file
self._container_registry._deleteFiles(global_stacks[0].extruders[index])
global_stacks[0].extruders[index].deserialize(archive.open(extruder_stack_file).read().decode("utf-8"))
# HACK
global_stacks[0]._extruders = global_stacks[0]._extruders[:2]
# HACK update cache
del self._container_registry._id_container_cache[old_extruder_stack_id]
new_extruder_stack_id = global_stacks[0].extruders[index].getId()
self._container_registry._id_container_cache[new_extruder_stack_id] = global_stacks[0].extruders[index]
stack = global_stacks[0].extruders[index]
else:
Logger.log("w", "Could not find global stack, while I expected it: %s" % global_stack_id_original)
elif self._resolve_strategies["machine"] == "new":
# container not found, create a new one
stack = ExtruderStack(container_id)
stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8"))
self._container_registry.addContainer(stack)
extruder_stacks_added.append(stack)
else:
Logger.log("w", "Unknown resolve strategy: %s" % str(self._resolve_strategies["machine"]))
if global_stack_need_rename:
if stack.getMetaDataEntry("machine"):
stack.setMetaDataEntry("machine", global_stack_id_new)
extruder_stacks.append(stack)
except:
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
# Something went really wrong. Try to remove any data that we added.
for container in extruder_stacks:
self._container_registry.removeContainer(container.getId())
return None
# load global stack file
try:
# Check if a stack by this ID already exists;
container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
if container_stacks:
stack = container_stacks[0]
if self._resolve_strategies["machine"] == "override":
# TODO: HACK
# There is a machine, check if it has authenticationd data. If so, keep that data.
# There is a machine, check if it has authentication data. If so, keep that data.
network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
container_stacks[0].deserialize(archive.open(container_stack_file).read().decode("utf-8"))
container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"))
if network_authentication_id:
container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
if network_authentication_key:
container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
elif self._resolve_strategies["machine"] == "new":
new_id = self.getNewId(container_id)
stack = ContainerStack(new_id)
stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))
stack = GlobalStack(global_stack_id_new)
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
# Ensure a unique ID and name
stack._id = new_id
stack._id = global_stack_id_new
# Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
# bound machine also needs to change.
if stack.getMetaDataEntry("machine", None):
stack.setMetaDataEntry("machine", self.getNewId(stack.getMetaDataEntry("machine")))
stack.setMetaDataEntry("machine", global_stack_id_new)
if stack.getMetaDataEntry("type") != "extruder_train":
# Only machines need a new name, stacks may be non-unique
stack.setName(self._container_registry.uniqueName(stack.getName()))
container_stacks_added.append(stack)
@ -379,25 +535,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
else:
Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
else:
stack = ContainerStack(container_id)
# no existing container stack, so we create a new one
stack = GlobalStack(global_stack_id_new)
# Deserialize stack by converting read data from bytes to string
stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
container_stacks_added.append(stack)
self._container_registry.addContainer(stack)
if stack.getMetaDataEntry("type") == "extruder_train":
extruder_stacks.append(stack)
else:
global_stack = stack
Job.yieldThread()
except:
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
# Something went really wrong. Try to remove any data that we added.
for container in containers_to_add:
self._container_registry.getInstance().removeContainer(container.getId())
self._container_registry.removeContainer(container.getId())
for container in container_stacks_added:
self._container_registry.getInstance().removeContainer(container.getId())
self._container_registry.removeContainer(container.getId())
for container in extruder_stacks_added:
self._container_registry.removeContainer(container.getId())
return None
@ -416,9 +573,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
global_stack.replaceContainer(0, container)
continue
if self._resolve_strategies["quality_changes"] == "new":
for container_type in ("quality_changes", "definition_changes"):
if self._resolve_strategies[container_type] == "new":
# Quality changes needs to get a new ID, added to registry and to the right stacks
for container in quality_changes_instance_containers:
for container in quality_and_definition_changes_instance_containers:
old_id = container.getId()
container.setName(self._container_registry.uniqueName(container.getName()))
# We're not really supposed to change the ID in normal cases, but this is an exception.
@ -427,18 +585,22 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# The container was not added yet, as it didn't have an unique ID. It does now, so add it.
self._container_registry.addContainer(container)
# Replace the quality changes container
old_container = global_stack.findContainer({"type": "quality_changes"})
# Replace the quality/definition changes container
if container_type == "quality_changes":
old_container = global_stack.qualityChanges
elif container_type == "definition_changes":
old_container = global_stack.definitionChanges
# old_container = global_stack.findContainer({"type": container_type})
if old_container.getId() == old_id:
quality_changes_index = global_stack.getContainerIndex(old_container)
global_stack.replaceContainer(quality_changes_index, container)
changes_index = global_stack.getContainerIndex(old_container)
global_stack.replaceContainer(changes_index, container)
continue
for stack in extruder_stacks:
old_container = stack.findContainer({"type": "quality_changes"})
old_container = stack.findContainer({"type": container_type})
if old_container.getId() == old_id:
quality_changes_index = stack.getContainerIndex(old_container)
stack.replaceContainer(quality_changes_index, container)
changes_index = stack.getContainerIndex(old_container)
stack.replaceContainer(changes_index, container)
if self._resolve_strategies["material"] == "new":
for material in material_containers:
@ -455,6 +617,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
stack.replaceContainer(material_index, material)
continue
if extruder_stacks:
for stack in extruder_stacks:
ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
else:
@ -463,9 +626,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
# Notify everything/one that is to notify about changes.
global_stack.containersChanged.emit(global_stack.getTop())
if self._resolve_strategies["machine"] == "new":
for stack in extruder_stacks:
stack.setNextStack(global_stack)
stack.containersChanged.emit(stack.getTop())
@ -473,6 +634,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Actually change the active machine.
Application.getInstance().setGlobalContainerStack(global_stack)
# Notify everything/one that is to notify about changes.
global_stack.containersChanged.emit(global_stack.getTop())
# Load all the nodes / meshdata of the workspace
nodes = self._3mf_mesh_reader.read(file_name)
if nodes is None:

View file

@ -1,7 +1,7 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import Qt, QUrl, pyqtSignal, QObject, pyqtProperty, QCoreApplication
from PyQt5.QtCore import QUrl, pyqtSignal, QObject, pyqtProperty, QCoreApplication
from UM.FlameProfiler import pyqtSlot
from PyQt5.QtQml import QQmlComponent, QQmlContext
from UM.PluginRegistry import PluginRegistry
@ -29,11 +29,13 @@ class WorkspaceDialog(QObject):
self._default_strategy = "override"
self._result = {"machine": self._default_strategy,
"quality_changes": self._default_strategy,
"definition_changes": self._default_strategy,
"material": self._default_strategy}
self._visible = False
self.showDialogSignal.connect(self.__show)
self._has_quality_changes_conflict = False
self._has_definition_changes_conflict = False
self._has_machine_conflict = False
self._has_material_conflict = False
self._num_visible_settings = 0
@ -51,6 +53,7 @@ class WorkspaceDialog(QObject):
machineConflictChanged = pyqtSignal()
qualityChangesConflictChanged = pyqtSignal()
definitionChangesConflictChanged = pyqtSignal()
materialConflictChanged = pyqtSignal()
numVisibleSettingsChanged = pyqtSignal()
activeModeChanged = pyqtSignal()
@ -185,6 +188,10 @@ class WorkspaceDialog(QObject):
def qualityChangesConflict(self):
return self._has_quality_changes_conflict
@pyqtProperty(bool, notify=definitionChangesConflictChanged)
def definitionChangesConflict(self):
return self._has_definition_changes_conflict
@pyqtProperty(bool, notify=materialConflictChanged)
def materialConflict(self):
return self._has_material_conflict
@ -214,11 +221,18 @@ class WorkspaceDialog(QObject):
self._has_quality_changes_conflict = quality_changes_conflict
self.qualityChangesConflictChanged.emit()
def setDefinitionChangesConflict(self, definition_changes_conflict):
if self._has_definition_changes_conflict != definition_changes_conflict:
self._has_definition_changes_conflict = definition_changes_conflict
self.definitionChangesConflictChanged.emit()
def getResult(self):
if "machine" in self._result and not self._has_machine_conflict:
self._result["machine"] = None
if "quality_changes" in self._result and not self._has_quality_changes_conflict:
self._result["quality_changes"] = None
if "definition_changes" in self._result and not self._has_definition_changes_conflict:
self._result["definition_changes"] = None
if "material" in self._result and not self._has_material_conflict:
self._result["material"] = None
return self._result
@ -240,6 +254,7 @@ class WorkspaceDialog(QObject):
# Reset the result
self._result = {"machine": self._default_strategy,
"quality_changes": self._default_strategy,
"definition_changes": self._default_strategy,
"material": self._default_strategy}
self._visible = True
self.showDialogSignal.emit()

View file

@ -90,9 +90,21 @@ message GCodeLayer {
}
message PrintTimeMaterialEstimates { // The print time for the whole print and material estimates for the extruder
float time = 1; // Total time estimate
repeated MaterialEstimates materialEstimates = 2; // materialEstimates data
message PrintTimeMaterialEstimates { // The print time for each feature and material estimates for the extruder
// Time estimate in each feature
float time_none = 1;
float time_inset_0 = 2;
float time_inset_x = 3;
float time_skin = 4;
float time_support = 5;
float time_skirt = 6;
float time_infill = 7;
float time_support_infill = 8;
float time_travel = 9;
float time_retract = 10;
float time_support_interface = 11;
repeated MaterialEstimates materialEstimates = 12; // materialEstimates data
}
message MaterialEstimates {

View file

@ -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.Platform import Platform
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Qt.Duration import DurationFormat
from PyQt5.QtCore import QObject, pyqtSlot
from cura.Settings.ExtruderManager import ExtruderManager
from . import ProcessSlicedLayersJob
from . import StartSliceJob
@ -187,7 +187,19 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
return
self.printDurationMessage.emit(0, [0])
self.printDurationMessage.emit({
"none": 0,
"inset_0": 0,
"inset_x": 0,
"skin": 0,
"support": 0,
"skirt": 0,
"infill": 0,
"support_infill": 0,
"travel": 0,
"retract": 0,
"support_interface": 0
}, [0])
self._stored_layer_data = []
self._stored_optimized_layer_data = []
@ -273,9 +285,15 @@ class CuraEngineBackend(QObject, Backend):
if not extruders:
error_keys = self._global_container_stack.getErrorKeys()
error_labels = set()
definition_container = self._global_container_stack.getBottom()
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)
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 +460,15 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.Done)
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._need_slicing = False
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
@ -466,13 +493,26 @@ class CuraEngineBackend(QObject, Backend):
## Called when a print time message is received from the engine.
#
# \param message The protobuff message containing the print time and
# \param message The protobuf message containing the print time per feature and
# material amount per extruder
def _onPrintTimeMaterialEstimates(self, message):
material_amounts = []
for index in range(message.repeatedMessageCount("materialEstimates")):
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
self.printDurationMessage.emit(message.time, material_amounts)
feature_times = {
"none": message.time_none,
"inset_0": message.time_inset_0,
"inset_x": message.time_inset_x,
"skin": message.time_skin,
"support": message.time_support,
"skirt": message.time_skirt,
"infill": message.time_infill,
"support_infill": message.time_support_infill,
"travel": message.time_travel,
"retract": message.time_retract,
"support_interface": message.time_support_interface
}
self.printDurationMessage.emit(feature_times, material_amounts)
## Creates a new socket connection.
def _createSocket(self):

View file

@ -179,9 +179,10 @@ class ProcessSlicedLayersJob(Job):
# Single extruder via global stack.
material_color_map = numpy.zeros((1, 4), dtype=numpy.float32)
material = global_container_stack.findContainer({"type": "material"})
color_code = material.getMetaDataEntry("color_code")
if color_code is None: # not all stacks have a material color
color_code = "#e0e000"
if material:
if material.getMetaDataEntry("color_code") is not None:
color_code = material.getMetaDataEntry("color_code")
color = colorCodeToRGBA(color_code)
material_color_map[0, :] = color

View file

@ -4,6 +4,7 @@
import numpy
from string import Formatter
from enum import IntEnum
import time
from UM.Job import Job
from UM.Application import Application
@ -230,25 +231,22 @@ class StartSliceJob(Job):
keys = stack.getAllKeys()
settings = {}
for key in keys:
# Use resolvement value if available, or take the 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()
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
settings["material_print_temp_prepend"] = "{material_print_temperature}" not in start_gcode
#Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
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.
setting_message = self._slice_message.getMessage("global_settings").addRepeatedMessage("settings")

View file

@ -56,7 +56,7 @@ class GCodeProfileReader(ProfileReader):
# TODO: Consider moving settings to the start?
serialized = "" # Will be filled with the serialized profile.
try:
with open(file_name) as f:
with open(file_name, "r") as f:
for line in f:
if line.startswith(prefix):
# Remove the prefix and the newline from the line and add it to the rest.
@ -66,9 +66,13 @@ class GCodeProfileReader(ProfileReader):
return None
serialized = unescapeGcodeComment(serialized)
Logger.log("i", "Serialized the following from %s: %s" %(file_name, repr(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 = []
global_profile = readQualityProfileFromString(json_data["global_quality"])

View file

@ -100,7 +100,7 @@ class GCodeWriter(MeshWriter):
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
prefix_length = len(prefix)
container_with_profile = stack.findContainer({"type": "quality_changes"})
container_with_profile = stack.qualityChanges
if not container_with_profile:
Logger.log("e", "No valid quality profile found, not writing settings to GCode!")
return ""
@ -115,7 +115,7 @@ class GCodeWriter(MeshWriter):
data = {"global_quality": serialized}
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:
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
continue

View file

@ -43,7 +43,8 @@ Item
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
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 int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
@ -107,27 +108,23 @@ Item
visible: !UM.LayerView.compatibilityMode
style: UM.Theme.styles.combobox
property int layer_view_type: UM.Preferences.getValue("layerview/layer_view_type")
currentIndex: layer_view_type // index matches type_id
onActivated: {
// 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"));
onActivated:
{
UM.Preferences.setValue("layerview/layer_view_type", index);
}
// Update visibility of legend.
function updateLegend(type_id) {
if (UM.LayerView.compatibilityMode || (type_id == 1)) {
// Line type
view_settings.show_legend = true;
} else {
view_settings.show_legend = false;
Component.onCompleted:
{
currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
updateLegends(currentIndex);
}
function updateLegends(type_id)
{
// update visibility of legends
view_settings.show_legend = UM.LayerView.compatibilityMode || (type_id == 1);
}
}
Label
@ -153,7 +150,8 @@ Item
target: UM.Preferences
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.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
@ -273,17 +271,18 @@ Item
{
typesLegenModelNoCheck.append({
label: catalog.i18nc("@label", "Top / Bottom"),
colorId: "layerview_skin"
colorId: "layerview_skin",
});
typesLegenModelNoCheck.append({
label: catalog.i18nc("@label", "Inner Wall"),
colorId: "layerview_inset_x"
colorId: "layerview_inset_x",
});
}
}
Label {
text: label
visible: view_settings.show_legend
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right

229
plugins/MachineSettingsAction/MachineSettingsAction.py Normal file → Executable file
View 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.
from PyQt5.QtCore import pyqtProperty, pyqtSignal
@ -7,12 +7,15 @@ from UM.FlameProfiler import pyqtSlot
from cura.MachineAction import MachineAction
from UM.Application import Application
from UM.Preferences import Preferences
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
from cura.Settings.ExtruderManager import ExtruderManager
import UM.i18n
catalog = UM.i18n.i18nCatalog("cura")
@ -25,36 +28,80 @@ class MachineSettingsAction(MachineAction):
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
self._qml_url = "MachineSettingsAction.qml"
self._global_container_stack = None
self._container_index = 0
self._extruder_container_index = 0
self._container_registry = ContainerRegistry.getInstance()
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):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack:
if not self._global_container_stack:
return
# 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:
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
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:
self._container_index = container_index
self.containerIndexChanged.emit()
def _createDefinitionChangesContainer(self, global_container_stack, container_index = None):
definition_changes_container = InstanceContainer(global_container_stack.getName() + "_settings")
definition = global_container_stack.getBottom()
# Disable autoslicing while the machineaction is showing
self._backend.disableTimer()
@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.addMetaDataEntry("type", "definition_changes")
self._container_registry.addContainer(definition_changes_container)
# Insert definition_changes between the definition and the variant
global_container_stack.insertContainer(-1, definition_changes_container)
container_stack.definitionChanges = definition_changes_container
return definition_changes_container
@ -64,15 +111,129 @@ class MachineSettingsAction(MachineAction):
def containerIndex(self):
return self._container_index
def _onContainerAdded(self, container):
# Add this action as a supported action to all machine definitions
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
if container.getProperty("machine_extruder_count", "value") > 1:
# Multiextruder printers are not currently supported
Logger.log("d", "Not attaching MachineSettingsAction to %s; Multi-extrusion printers are not supported", container.getId())
extruderContainerIndexChanged = pyqtSignal()
@pyqtProperty(int, notify = extruderContainerIndexChanged)
def extruderContainerIndex(self):
return self._extruder_container_index
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
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
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)
@pyqtSlot()
def forceUpdate(self):
@ -83,34 +244,34 @@ class MachineSettingsAction(MachineAction):
@pyqtSlot()
def updateHasMaterialsMetadata(self):
# Updates the has_materials metadata flag after switching gcode flavor
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
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"
if not self._global_container_stack:
return
material_container = global_container_stack.findContainer({"type": "material"})
material_index = global_container_stack.getContainerIndex(material_container)
definition = self._global_container_stack.getBottom()
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"
material_container = self._global_container_stack.material
material_index = self._global_container_stack.getContainerIndex(material_container)
if has_materials:
if "has_materials" in global_container_stack.getMetaData():
global_container_stack.setMetaDataEntry("has_materials", True)
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.setMetaDataEntry("has_materials", True)
else:
global_container_stack.addMetaDataEntry("has_materials", True)
self._global_container_stack.addMetaDataEntry("has_materials", True)
# Set the material container to a sane default
if material_container.getId() == "empty_material":
search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*" }
search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*"}
containers = self._container_registry.findInstanceContainers(**search_criteria)
if containers:
global_container_stack.replaceContainer(material_index, containers[0])
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 global_container_stack.getMetaData():
global_container_stack.removeMetaDataEntry("has_materials")
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.removeMetaDataEntry("has_materials")
empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0]
global_container_stack.replaceContainer(material_index, empty_material)
self._global_container_stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer()
Application.getInstance().globalContainerStackChanged.emit()

View file

@ -12,6 +12,32 @@ import Cura 1.0 as Cura
Cura.MachineAction
{
id: base
property var extrudersModel: Cura.ExtrudersModel{}
property int extruderTabsCount: 0
Connections
{
target: base.extrudersModel
onModelChanged:
{
var extruderCount = base.extrudersModel.rowCount();
base.extruderTabsCount = extruderCount > 1 ? extruderCount : 0;
}
}
Connections
{
target: dialog ? dialog : null
ignoreUnknownSignals: true
// Any which way this action dialog is dismissed, make sure it is properly finished
onNextClicked: manager.onFinishAction()
onBackClicked: manager.onFinishAction()
onAccepted: manager.onFinishAction()
onRejected: manager.onFinishAction()
onClosing: manager.onFinishAction()
}
anchors.fill: parent;
Item
{
@ -28,26 +54,27 @@ Cura.MachineAction
wrapMode: Text.WordWrap
font.pointSize: 18;
}
Label
TabView
{
id: pageDescription
id: settingsTabs
height: parent.height - y
width: parent.width
anchors.left: parent.left
anchors.top: pageTitle.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
width: parent.width
wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Please enter the correct settings for your printer below:")
}
property real columnWidth: Math.floor((width - 3 * UM.Theme.getSize("default_margin").width) / 2)
Tab
{
title: catalog.i18nc("@title:tab", "Printer");
anchors.margins: UM.Theme.getSize("default_margin").width
Column
{
height: parent.height - y
width: parent.width - UM.Theme.getSize("default_margin").width
spacing: UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.top: pageDescription.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
Row
{
width: parent.width
@ -55,7 +82,7 @@ Cura.MachineAction
Column
{
width: parent.width / 2
width: settingsTabs.columnWidth
spacing: UM.Theme.getSize("default_margin").height
Label
@ -66,55 +93,47 @@ Cura.MachineAction
Grid
{
columns: 3
columns: 2
columnSpacing: UM.Theme.getSize("default_margin").width
rowSpacing: UM.Theme.getSize("default_lining").width
Label
{
text: catalog.i18nc("@label", "X (Width)")
}
TextField
Loader
{
id: buildAreaWidthField
text: machineWidthProvider.properties.value
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
onEditingFinished: { machineWidthProvider.setPropertyValue("value", text); manager.forceUpdate() }
}
Label
{
text: catalog.i18nc("@label", "mm")
sourceComponent: numericTextFieldWithUnit
property var propertyProvider: machineWidthProvider
property string unit: catalog.i18nc("@label", "mm")
property bool forceUpdateOnChange: true
}
Label
{
text: catalog.i18nc("@label", "Y (Depth)")
}
TextField
Loader
{
id: buildAreaDepthField
text: machineDepthProvider.properties.value
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
onEditingFinished: { machineDepthProvider.setPropertyValue("value", text); manager.forceUpdate() }
}
Label
{
text: catalog.i18nc("@label", "mm")
sourceComponent: numericTextFieldWithUnit
property var propertyProvider: machineDepthProvider
property string unit: catalog.i18nc("@label", "mm")
property bool forceUpdateOnChange: true
}
Label
{
text: catalog.i18nc("@label", "Z (Height)")
}
TextField
Loader
{
id: buildAreaHeightField
text: machineHeightProvider.properties.value
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
onEditingFinished: { machineHeightProvider.setPropertyValue("value", text); manager.forceUpdate() }
}
Label
{
text: catalog.i18nc("@label", "mm")
sourceComponent: numericTextFieldWithUnit
property var propertyProvider: machineHeightProvider
property string unit: catalog.i18nc("@label", "mm")
property bool forceUpdateOnChange: true
}
}
@ -164,12 +183,15 @@ Cura.MachineAction
return index
}
onActivated:
{
if(machineShapeProvider.properties.value != shapesModel.get(index).value)
{
machineShapeProvider.setPropertyValue("value", shapesModel.get(index).value);
manager.forceUpdate();
}
}
}
}
CheckBox
{
id: centerIsZeroCheckBox
@ -243,7 +265,7 @@ Cura.MachineAction
Column
{
width: parent.width / 2
width: settingsTabs.columnWidth
spacing: UM.Theme.getSize("default_margin").height
Label
@ -254,8 +276,9 @@ Cura.MachineAction
Grid
{
columns: 3
columns: 2
columnSpacing: UM.Theme.getSize("default_margin").width
rowSpacing: UM.Theme.getSize("default_lining").width
Label
{
@ -268,10 +291,6 @@ Cura.MachineAction
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
onEditingFinished: setHeadPolygon()
}
Label
{
text: catalog.i18nc("@label", "mm")
}
Label
{
@ -284,10 +303,6 @@ Cura.MachineAction
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
onEditingFinished: setHeadPolygon()
}
Label
{
text: catalog.i18nc("@label", "mm")
}
Label
{
@ -300,10 +315,6 @@ Cura.MachineAction
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
onEditingFinished: setHeadPolygon()
}
Label
{
text: catalog.i18nc("@label", "mm")
}
Label
{
@ -316,12 +327,7 @@ Cura.MachineAction
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
onEditingFinished: setHeadPolygon()
}
Label
{
text: catalog.i18nc("@label", "mm")
}
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
@ -329,39 +335,71 @@ Cura.MachineAction
{
text: catalog.i18nc("@label", "Gantry height")
}
TextField
Loader
{
id: gantryHeightField
text: gantryHeightProvider.properties.value
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
onEditingFinished: { gantryHeightProvider.setPropertyValue("value", text) }
sourceComponent: numericTextFieldWithUnit
property var propertyProvider: gantryHeightProvider
property string unit: catalog.i18nc("@label", "mm")
property bool forceUpdateOnChange: false
}
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
Label
{
text: catalog.i18nc("@label", "mm")
text: catalog.i18nc("@label", "Number of Extruders")
visible: extruderCountComboBox.visible
}
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
Item { width: UM.Theme.getSize("default_margin").width; height: UM.Theme.getSize("default_margin").height }
ComboBox
{
id: extruderCountComboBox
visible: manager.definedExtruderCount > 1
model: ListModel
{
id: extruderCountModel
Component.onCompleted:
{
for(var i = 0; i < manager.definedExtruderCount; i++)
{
extruderCountModel.append({text: String(i + 1), value: i});
}
}
}
currentIndex: machineExtruderCountProvider.properties.value - 1
onActivated:
{
manager.setMachineExtruderCount(index + 1);
}
}
Label
{
text: catalog.i18nc("@label", "Material Diameter")
}
Loader
{
id: materialDiameterField
sourceComponent: numericTextFieldWithUnit
property var propertyProvider: materialDiameterProvider
property string unit: catalog.i18nc("@label", "mm")
property bool forceUpdateOnChange: false
}
Label
{
text: catalog.i18nc("@label", "Nozzle size")
visible: !Cura.MachineManager.hasVariants
visible: nozzleSizeField.visible
}
TextField
Loader
{
id: nozzleSizeField
text: machineNozzleSizeProvider.properties.value
visible: !Cura.MachineManager.hasVariants
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
onEditingFinished: { machineNozzleSizeProvider.setPropertyValue("value", text) }
}
Label
{
text: catalog.i18nc("@label", "mm")
visible: !Cura.MachineManager.hasVariants
visible: !Cura.MachineManager.hasVariants && machineExtruderCountProvider.properties.value == 1
sourceComponent: numericTextFieldWithUnit
property var propertyProvider: machineNozzleSizeProvider
property string unit: catalog.i18nc("@label", "mm")
property bool forceUpdateOnChange: false
}
}
}
@ -376,10 +414,11 @@ Cura.MachineAction
Column
{
height: parent.height
width: parent.width / 2
width: settingsTabs.columnWidth
Label
{
text: catalog.i18nc("@label", "Start Gcode")
font.bold: true
}
TextArea
{
@ -387,7 +426,6 @@ Cura.MachineAction
width: parent.width
height: parent.height - y
font: UM.Theme.getFont("fixed")
wrapMode: TextEdit.NoWrap
text: machineStartGcodeProvider.properties.value
onActiveFocusChanged:
{
@ -396,14 +434,20 @@ Cura.MachineAction
machineStartGcodeProvider.setPropertyValue("value", machineStartGcodeField.text)
}
}
Component.onCompleted:
{
wrapMode = TextEdit.NoWrap;
}
}
}
Column {
height: parent.height
width: parent.width / 2
width: settingsTabs.columnWidth
Label
{
text: catalog.i18nc("@label", "End Gcode")
font.bold: true
}
TextArea
{
@ -411,7 +455,6 @@ Cura.MachineAction
width: parent.width
height: parent.height - y
font: UM.Theme.getFont("fixed")
wrapMode: TextEdit.NoWrap
text: machineEndGcodeProvider.properties.value
onActiveFocusChanged:
{
@ -420,7 +463,9 @@ Cura.MachineAction
machineEndGcodeProvider.setPropertyValue("value", machineEndGcodeField.text)
}
}
}
Component.onCompleted:
{
wrapMode = TextEdit.NoWrap;
}
}
}
@ -448,9 +493,200 @@ Cura.MachineAction
polygon.push([-parseFloat(printheadXMinField.text),-parseFloat(printheadYMinField.text)]);
polygon.push([ parseFloat(printheadXMaxField.text), parseFloat(printheadYMaxField.text)]);
polygon.push([ parseFloat(printheadXMaxField.text),-parseFloat(printheadYMinField.text)]);
machineHeadPolygonProvider.setPropertyValue("value", JSON.stringify(polygon));
var polygon_string = JSON.stringify(polygon);
if(polygon != machineHeadPolygonProvider.properties.value)
{
machineHeadPolygonProvider.setPropertyValue("value", polygon_string);
manager.forceUpdate();
}
}
}
}
onCurrentIndexChanged:
{
if(currentIndex > 0)
{
contentItem.forceActiveFocus();
ExtruderManager.setActiveExtruderIndex(currentIndex - 1);
}
}
Repeater
{
id: extruderTabsRepeater
model: base.extruderTabsCount
Tab
{
title: base.extrudersModel.getItem(index).name
anchors.margins: UM.Theme.getSize("default_margin").width
Column
{
spacing: UM.Theme.getSize("default_margin").width
Label
{
text: catalog.i18nc("@label", "Nozzle Settings")
font.bold: true
}
Grid
{
columns: 2
columnSpacing: UM.Theme.getSize("default_margin").width
rowSpacing: UM.Theme.getSize("default_lining").width
Label
{
text: catalog.i18nc("@label", "Nozzle size")
visible: extruderNozzleSizeField.visible
}
Loader
{
id: extruderNozzleSizeField
visible: !Cura.MachineManager.hasVariants
sourceComponent: numericTextFieldWithUnit
property var propertyProvider: extruderNozzleSizeProvider
property string unit: catalog.i18nc("@label", "mm")
property bool forceUpdateOnChange: false
}
Label
{
text: catalog.i18nc("@label", "Nozzle offset X")
}
Loader
{
id: extruderOffsetXField
sourceComponent: numericTextFieldWithUnit
property var propertyProvider: extruderOffsetXProvider
property string unit: catalog.i18nc("@label", "mm")
property bool forceUpdateOnChange: true
}
Label
{
text: catalog.i18nc("@label", "Nozzle offset Y")
}
Loader
{
id: extruderOffsetYField
sourceComponent: numericTextFieldWithUnit
property var propertyProvider: extruderOffsetYProvider
property string unit: catalog.i18nc("@label", "mm")
property bool forceUpdateOnChange: true
}
}
Row
{
spacing: UM.Theme.getSize("default_margin").width
anchors.left: parent.left
anchors.right: parent.right
height: parent.height - y
Column
{
height: parent.height
width: settingsTabs.columnWidth
Label
{
text: catalog.i18nc("@label", "Extruder Start Gcode")
font.bold: true
}
TextArea
{
id: extruderStartGcodeField
width: parent.width
height: parent.height - y
font: UM.Theme.getFont("fixed")
text: (extruderStartGcodeProvider.properties.value) ? extruderStartGcodeProvider.properties.value : ""
onActiveFocusChanged:
{
if(!activeFocus)
{
extruderStartGcodeProvider.setPropertyValue("value", extruderStartGcodeField.text)
}
}
Component.onCompleted:
{
wrapMode = TextEdit.NoWrap;
}
}
}
Column {
height: parent.height
width: settingsTabs.columnWidth
Label
{
text: catalog.i18nc("@label", "Extruder End Gcode")
font.bold: true
}
TextArea
{
id: extruderEndGcodeField
width: parent.width
height: parent.height - y
font: UM.Theme.getFont("fixed")
text: (extruderEndGcodeProvider.properties.value) ? extruderEndGcodeProvider.properties.value : ""
onActiveFocusChanged:
{
if(!activeFocus)
{
extruderEndGcodeProvider.setPropertyValue("value", extruderEndGcodeField.text)
}
}
Component.onCompleted:
{
wrapMode = TextEdit.NoWrap;
}
}
}
}
}
}
}
}
}
Component
{
id: numericTextFieldWithUnit
Item {
height: textField.height
width: textField.width
TextField
{
id: textField
text: (propertyProvider.properties.value) ? propertyProvider.properties.value : ""
validator: RegExpValidator { regExp: /[0-9\.]{0,6}/ }
onEditingFinished:
{
if (propertyProvider && text != propertyProvider.properties.value)
{
propertyProvider.setPropertyValue("value", text);
if(forceUpdateOnChange)
{
var extruderIndex = ExtruderManager.activeExtruderIndex;
manager.forceUpdate();
if(ExtruderManager.activeExtruderIndex != extruderIndex)
{
ExtruderManager.setActiveExtruderIndex(extruderIndex)
}
}
}
}
}
Label
{
text: unit
anchors.right: textField.right
anchors.rightMargin: y - textField.y
anchors.verticalCenter: textField.verticalCenter
}
}
}
UM.SettingPropertyProvider
{
@ -522,6 +758,16 @@ Cura.MachineAction
storeIndex: manager.containerIndex
}
UM.SettingPropertyProvider
{
id: materialDiameterProvider
containerStackId: Cura.MachineManager.activeMachineId
key: "material_diameter"
watchedProperties: [ "value" ]
storeIndex: manager.containerIndex
}
UM.SettingPropertyProvider
{
id: machineNozzleSizeProvider
@ -532,6 +778,16 @@ Cura.MachineAction
storeIndex: manager.containerIndex
}
UM.SettingPropertyProvider
{
id: machineExtruderCountProvider
containerStackId: Cura.MachineManager.activeMachineId
key: "machine_extruder_count"
watchedProperties: [ "value" ]
storeIndex: manager.containerIndex
}
UM.SettingPropertyProvider
{
id: gantryHeightProvider
@ -573,4 +829,53 @@ Cura.MachineAction
storeIndex: manager.containerIndex
}
UM.SettingPropertyProvider
{
id: extruderNozzleSizeProvider
containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : ""
key: "machine_nozzle_size"
watchedProperties: [ "value" ]
storeIndex: manager.containerIndex
}
UM.SettingPropertyProvider
{
id: extruderOffsetXProvider
containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : ""
key: "machine_nozzle_offset_x"
watchedProperties: [ "value" ]
storeIndex: manager.containerIndex
}
UM.SettingPropertyProvider
{
id: extruderOffsetYProvider
containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : ""
key: "machine_nozzle_offset_y"
watchedProperties: [ "value" ]
storeIndex: manager.containerIndex
}
UM.SettingPropertyProvider
{
id: extruderStartGcodeProvider
containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : ""
key: "machine_extruder_start_code"
watchedProperties: [ "value" ]
storeIndex: manager.containerIndex
}
UM.SettingPropertyProvider
{
id: extruderEndGcodeProvider
containerStackId: settingsTabs.currentIndex > 0 ? Cura.MachineManager.activeStackId : ""
key: "machine_extruder_end_code"
watchedProperties: [ "value" ]
storeIndex: manager.containerIndex
}
}

View file

@ -26,129 +26,6 @@ Item {
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
{
// This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar.
@ -264,14 +141,6 @@ Item {
storeIndex: 0
removeUnusedValue: false
}
// If the extruder by which the object needs to be printed is changed, ensure that the
// display is also notified of the fact.
Connections
{
target: extruderSelector
onActivated: provider.forcePropertiesChanged()
}
}
}
}

View file

@ -112,4 +112,4 @@ class PerObjectSettingsTool(Tool):
self._single_model_selected = False # Group is selected, so tool needs to be disabled
else:
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)

View file

@ -342,6 +342,8 @@ Cura.MachineAction
{
regExp: /[a-zA-Z0-9\.\-\_]*/
}
onAccepted: btnOk.clicked()
}
}
@ -355,6 +357,7 @@ Cura.MachineAction
}
},
Button {
id: btnOk
text: catalog.i18nc("@action:button", "Ok")
onClicked:
{

View file

@ -638,7 +638,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "":
Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1)
self._error_message = Message(
i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No PrinterCore loaded in slot {0}".format(index + 1)))
i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No Printcore loaded in slot {0}".format(index + 1)))
self._error_message.show()
return
if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "":

View file

@ -259,7 +259,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
i = 0
while True:
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]]
i += 1
except Exception as e:

View 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()

View file

@ -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"; }
}
}

View file

@ -5,6 +5,7 @@ from . import BedLevelMachineAction
from . import UpgradeFirmwareMachineAction
from . import UMOCheckupMachineAction
from . import UMOUpgradeSelection
from . import UM2UpgradeSelection
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -21,4 +22,10 @@ def getMetaData():
}
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()
]}

View file

@ -10,6 +10,10 @@ _removed_settings = { #Settings that were removed in 2.5.
"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
# 2.4 to a configuration for Cura 2.5.
#
@ -42,8 +46,16 @@ class VersionUpgrade24to25(VersionUpgrade):
#Remove settings from the visible_settings.
if parser.has_section("general") and "visible_settings" in parser["general"]:
visible_settings = parser["general"]["visible_settings"].split(";")
visible_settings = filter(lambda setting: setting not in _removed_settings, visible_settings)
parser["general"]["visible_settings"] = ";".join(visible_settings)
new_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.
if parser.has_section("general"): #It better have!
@ -66,6 +78,10 @@ class VersionUpgrade24to25(VersionUpgrade):
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.
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.
if parser.has_section("general"):

View file

@ -0,0 +1,55 @@
{
"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_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}"
}
}
}

View file

@ -17,9 +17,9 @@
"has_variants": true,
"variants_name": "Nozzle size",
"preferred_variant": "*0.4*",
"preferred_variant": "*0.8*",
"preferred_material": "*pla*",
"preferred_quality": "*normal*",
"preferred_quality": "*high*",
"machine_extruder_trains":
{
@ -35,7 +35,8 @@
},
"overrides": {
"machine_extruder_count": { "default_value": 4 },
"machine_extruder_count": { "default_value": 2 },
"material_diameter": { "default_value": 1.75 },
"machine_heated_bed": { "default_value": true },
"machine_center_is_zero": { "default_value": false },
"gantry_height": { "default_value": 35 },
@ -45,20 +46,18 @@
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"material_print_temp_wait": { "default_value": false },
"material_bed_temp_wait": { "default_value": false },
"infill_pattern": { "default_value": "grid"},
"prime_tower_enable": { "default_value": true },
"prime_tower_wall_thickness": { "resolve": 0.7 },
"prime_tower_position_x": { "default_value": 50 },
"prime_tower_position_y": { "default_value": 71 },
"prime_tower_position_y": { "default_value": 150 },
"machine_start_gcode": {
"default_value": "\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nM92 E159\n\nG21\nG90\nM42 S255 P13;chamber lights\nM42 S255 P12;fume extraction\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S600 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n"
"default_value": "\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nM92 E159\n\nG21\nG90\nM42 S255 P13;chamber lights\nM42 S255 P12;fume extraction\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S1200 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\nG1 Z10 F900\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n"
},
"machine_end_gcode": {
"default_value": "; -- END GCODE --\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nM104 S5 T2\nM104 S5 T3\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\n; -- end of GCODE --"
"default_value": "; -- END GCODE --\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nM104 S5 T2\nM104 S5 T3\n\nG91\nG1 Z1 F900\nG90\n\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\n; -- end of GCODE --"
},
"layer_height": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
"layer_height_0": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
"layer_height_0": { "resolve": "0.2 if min(extruderValues('machine_nozzle_size')) < 0.3 else 0.3 "},
"machine_nozzle_heat_up_speed": {"default_value": 20},
"machine_nozzle_cool_down_speed": {"default_value": 20},
"machine_min_cool_heat_time_window": {"default_value": 5}

View file

@ -10,6 +10,17 @@
"category": "Custom",
"file_formats": "text/x-gcode",
"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"]
}
}

View file

@ -15,22 +15,28 @@
"overrides": {
"machine_name": { "default_value": "Delta Go" },
"material_diameter": { "default_value": 1.75 },
"default_material_print_temperature": { "default_value": 210 },
"speed_travel": { "default_value": 150 },
"prime_tower_size": { "default_value": 8.66 },
"infill_sparse_density": { "default_value": 10 },
"speed_wall_x": { "default_value": 30 },
"speed_wall_0": { "default_value": 30 },
"speed_topbottom": { "default_value": 20 },
"layer_height": { "default_value": 0.2 },
"layer_height": { "default_value": 0.15 },
"speed_print": { "default_value": 30 },
"machine_heated_bed": { "default_value": false },
"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_depth": { "default_value": 115 },
"machine_width": { "default_value": 115 },
"retraction_amount": { "default_value": 4.2 },
"retraction_speed": { "default_value": 400 },
"raft_airgap": { "default_value": 0.15 },
"retraction_hop_enabled": { "value": "True" },
"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"}
}
}

View file

@ -25,10 +25,22 @@
"type": "extruder",
"default_value": "0",
"settable_per_mesh": true,
"settable_per_extruder": false,
"settable_per_extruder": true,
"settable_per_meshgroup": 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":
{
"label": "Nozzle X Offset",

View file

@ -1346,7 +1346,7 @@
"description": "The height of infill of a given density before switching to half the density.",
"unit": "mm",
"type": "float",
"default_value": 5.0,
"default_value": 1.5,
"minimum_value": "0.0001",
"minimum_value_warning": "3 * resolveOrValue('layer_height')",
"maximum_value_warning": "100",
@ -1383,8 +1383,8 @@
{
"expand_upper_skins":
{
"label": "Expand Upper Skins",
"description": "Expand upper skin areas (areas with air above) so that they support infill above.",
"label": "Expand Top Skins Into Infill",
"description": "Expand the top skin areas (areas with air above) so that they support infill above.",
"type": "bool",
"default_value": false,
"value": "expand_skins_into_infill",
@ -1392,8 +1392,8 @@
},
"expand_lower_skins":
{
"label": "Expand Lower Skins",
"description": "Expand lower skin areas (areas with air below) so that they are anchored by the infill layers above and below.",
"label": "Expand Bottom Skins Into Infill",
"description": "Expand the bottom skin areas (areas with air below) so that they are anchored by the infill layers above and below.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true
@ -3235,7 +3235,7 @@
"support_bottom_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",
"type": "float",
"default_value": 0.3,
@ -3245,6 +3245,19 @@
"enabled": "support_enable",
"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":
{
"label": "Support Join Distance",
@ -3573,6 +3586,17 @@
"description": "Adhesion",
"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,
"enabled": false
},
"extruder_prime_pos_x":
{
"label": "Extruder Prime X Position",
@ -4192,7 +4216,7 @@
"type": "float",
"unit": "mm",
"enabled": "resolveOrValue('prime_tower_enable')",
"default_value": 15,
"default_value": 20,
"resolve": "max(extruderValues('prime_tower_size'))",
"minimum_value": "0",
"maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)",
@ -4449,6 +4473,31 @@
"settable_per_meshgroup": false,
"settable_globally": false
},
"infill_mesh_order":
{
"label": "Infill Mesh Order",
"description": "Determines which infill mesh is inside the infill of another infill mesh. An infill mesh with a higher order will modify the infill of infill meshes with lower order and normal meshes.",
"default_value": 0,
"value": "1 if infill_mesh else 0",
"minimum_value_warning": "1",
"maximum_value_warning": "50",
"type": "int",
"settable_per_mesh": true,
"settable_per_extruder": false,
"settable_per_meshgroup": false,
"settable_globally": false
},
"cutting_mesh":
{
"label": "Cutting Mesh",
"description": "Limit the volume of this mesh to within other meshes. You can use this to make certain areas of one mesh print with different settings and with a whole different extruder.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true,
"settable_per_extruder": false,
"settable_per_meshgroup": false,
"settable_globally": false
},
"mold_enabled":
{
"label": "Mold",
@ -4483,20 +4532,6 @@
"settable_per_mesh": true,
"enabled": "mold_enabled"
},
"infill_mesh_order":
{
"label": "Infill Mesh Order",
"description": "Determines which infill mesh is inside the infill of another infill mesh. An infill mesh with a higher order will modify the infill of infill meshes with lower order and normal meshes.",
"default_value": 0,
"value": "1 if infill_mesh else 0",
"minimum_value_warning": "1",
"maximum_value_warning": "50",
"type": "int",
"settable_per_mesh": true,
"settable_per_extruder": false,
"settable_per_meshgroup": false,
"settable_globally": false
},
"support_mesh":
{
"label": "Support Mesh",
@ -4553,6 +4588,14 @@
"default_value": false,
"settable_per_mesh": false,
"settable_per_extruder": false
},
"smooth_spiralized_contours":
{
"label": "Smooth Spiralized Contours",
"description": "Smooth the spiralized contours to reduce the visibility of the Z seam (the Z-seam should be barely visible on the print but will still be visible in the layer view). Note that smoothing will tend to blur fine surface details.",
"type": "bool",
"default_value": true,
"enabled": "magic_spiralize"
}
}
},

View file

@ -44,12 +44,6 @@
"gantry_height": {
"default_value": 82.3
},
"machine_nozzle_offset_x": {
"default_value": 0
},
"machine_nozzle_offset_y": {
"default_value": 15
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},

View file

@ -4,6 +4,7 @@
"name": "3DMaker Starter",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "tvlgiao",
"manufacturer": "3DMaker",
"category": "Other",

View file

@ -54,7 +54,7 @@
"speed_layer_0": {
"value": 15
},
"speed_tarvel": {
"speed_travel": {
"value": 100
},
"support_enable": {

View file

@ -15,7 +15,8 @@
"platform_texture": "Ultimaker2backplate.png",
"platform_offset": [9, 0, 0],
"has_materials": false,
"supported_actions":["UpgradeFirmware"]
"first_start_actions": ["UM2UpgradeSelection"],
"supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"]
},
"overrides": {
"machine_name": { "default_value": "Ultimaker 2" },

View file

@ -13,6 +13,7 @@
"platform": "ultimaker2go_platform.obj",
"platform_texture": "Ultimaker2Gobackplate.png",
"platform_offset": [0, 0, 0],
"first_start_actions": [],
"supported_actions":["UpgradeFirmware"]
},

View file

@ -16,6 +16,7 @@
"has_materials": true,
"has_machine_materials": true,
"has_machine_quality": true,
"first_start_actions": [],
"supported_actions":["UpgradeFirmware"]
},

View file

@ -73,6 +73,8 @@
"prime_tower_position_y": { "default_value": 178 },
"prime_tower_wipe_enabled": { "default_value": false },
"prime_blob_enable": { "enabled": true },
"acceleration_enabled": { "value": "True" },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
@ -103,7 +105,7 @@
"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_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" },
"default_material_print_temperature": { "value": "200" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
@ -111,7 +113,7 @@
"material_bed_temperature_layer_0": { "maximum_value": "115" },
"material_standby_temperature": { "value": "100" },
"multiple_mesh_overlap": { "value": "0" },
"prime_tower_enable": { "value": "True" },
"prime_tower_enable": { "default_value": true },
"raft_airgap": { "value": "0" },
"raft_base_thickness": { "value": "0.3" },
"raft_interface_line_spacing": { "value": "0.5" },

View file

@ -19,7 +19,7 @@
"default_value": "\n;start extruder_0\n\nM117 printing\n"
},
"machine_extruder_end_code": {
"default_value": "\nM104 T0 S160\nG91\nG1 Z0.5 F900\nG90\nG1 X10 Y260 F9000\n;end extruder_0\nM117 temp is {material_print_temp}"
"default_value": "\nM104 T0 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_0\nM117 temp is {material_print_temp}"
}
}
}

View file

@ -19,7 +19,7 @@
"default_value": "\n;start extruder_1\n\nM117 printing\n"
},
"machine_extruder_end_code": {
"default_value": "\nM104 T1 S160\nG91\nG1 Z0.5 F900\nG90\nG1 X10 Y260 F9000\n;end extruder_1\n"
"default_value": "\nM104 T1 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_1\n"
}
}
}

View file

@ -19,7 +19,7 @@
"default_value": "\n;start extruder_2\n\nM117 printing\n"
},
"machine_extruder_end_code": {
"default_value": "\nM104 T2 S160\nG91\nG1 Z0.5 F900\nG90\nG1 X10 Y260 F9000\n;end extruder_2\n"
"default_value": "\nM104 T2 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_2\n"
}
}
}

View file

@ -19,7 +19,7 @@
"default_value": "\n;start extruder_3\n\nM117 printing\n"
},
"machine_extruder_end_code": {
"default_value": "\nM104 T3 S160\nG91\nG1 Z0.5 F900\nG90\nG1 X10 Y260 F9000\n;end extruder_3\n"
"default_value": "\nM104 T3 S160\nG91\nG1 Z5 F900\nG90\nG1 X1 Y260 F9000\n;end extruder_3\n"
}
}
}

View 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"
}
}
}

View 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"
}
}
}

View 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"
}
}
}

View 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"
}
}
}

View 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"
}
}
}

View 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"
}
}
}

View 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"
}
}
}

View 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.

View file

@ -205,6 +205,7 @@ Item
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

View file

@ -180,7 +180,7 @@ UM.Dialog
anchors.bottom:parent.bottom
spacing: UM.Theme.getSize("default_margin").width
Text
Label
{
text: catalog.i18nc("@label", "Printer Name:")
anchors.verticalCenter: machineName.verticalCenter

View file

@ -14,8 +14,8 @@ UM.Dialog
id: base
title: catalog.i18nc("@title:window", "Discard or Keep changes")
width: 800 * Screen.devicePixelRatio
height: 400 * Screen.devicePixelRatio
width: 800
height: 400
property var changesModel: Cura.UserChangesModel{ id: userChangesModel}
onVisibilityChanged:
{
@ -36,9 +36,14 @@ UM.Dialog
}
}
Column
Row
{
anchors.fill: parent
id: infoTextRow
height: childrenRect.height
anchors.margins: UM.Theme.getSize("default_margin").width
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
spacing: UM.Theme.getSize("default_margin").width
UM.I18nCatalog
@ -47,14 +52,6 @@ UM.Dialog
name: "cura"
}
Row
{
height: childrenRect.height
anchors.margins: UM.Theme.getSize("default_margin").width
anchors.left: parent.left
anchors.right: parent.right
spacing: UM.Theme.getSize("default_margin").width
Label
{
text: catalog.i18nc("@text:window", "You have customized some profile settings.\nWould you like to keep or discard those settings?")
@ -64,12 +61,17 @@ UM.Dialog
}
}
TableView
Item
{
anchors.margins: UM.Theme.getSize("default_margin").width
anchors.top: infoTextRow.bottom
anchors.bottom: optionRow.top
anchors.left: parent.left
anchors.right: parent.right
height: base.height - 150 * Screen.devicePixelRatio
TableView
{
anchors.fill: parent
height: base.height - 150
id: tableView
Component
{
@ -132,13 +134,16 @@ UM.Dialog
model: base.changesModel
}
}
Item
{
id: optionRow
anchors.bottom: buttonsRow.top
anchors.right: parent.right
anchors.left: parent.left
anchors.margins: UM.Theme.getSize("default_margin").width
height:childrenRect.height
height: childrenRect.height
ComboBox
{
@ -179,6 +184,8 @@ UM.Dialog
Item
{
id: buttonsRow
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.margins: UM.Theme.getSize("default_margin").width
@ -219,5 +226,4 @@ UM.Dialog
onClicked: base.hide()
}
}
}
}

View 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);
}
}

View file

@ -24,6 +24,7 @@ Item {
UM.I18nCatalog { id: catalog; name:"cura"}
property variant printDuration: PrintInformation.currentPrintTime
property variant printDurationPerFeature: PrintInformation.printTimesPerFeature
property variant printMaterialLengths: PrintInformation.materialLengths
property variant printMaterialWeights: PrintInformation.materialWeights
property variant printMaterialCosts: PrintInformation.materialCosts
@ -159,7 +160,7 @@ Item {
UM.RecolorImage
{
id: timeIcon
anchors.right: timeSpec.left
anchors.right: timeSpecPerFeatureTooltipArea.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width/2
anchors.verticalCenter: parent.verticalCenter
width: UM.Theme.getSize("save_button_specs_icons").width
@ -169,16 +170,51 @@ Item {
color: UM.Theme.getColor("text_subtext")
source: UM.Theme.getIcon("print_time")
}
Text
UM.TooltipArea
{
id: timeSpec
id: timeSpecPerFeatureTooltipArea
text: {
var order = ["inset_0", "inset_x", "skin", "infill", "support_infill", "support_interface", "support", "travel", "retract", "none"];
var visible_names = {
"inset_0": catalog.i18nc("@tooltip", "Outer Wall"),
"inset_x": catalog.i18nc("@tooltip", "Inner Walls"),
"skin": catalog.i18nc("@tooltip", "Skin"),
"infill": catalog.i18nc("@tooltip", "Infill"),
"support_infill": catalog.i18nc("@tooltip", "Support Infill"),
"support_interface": catalog.i18nc("@tooltip", "Support Interface"),
"support": catalog.i18nc("@tooltip", "Support"),
"travel": catalog.i18nc("@tooltip", "Travel"),
"retract": catalog.i18nc("@tooltip", "Retractions"),
"none": catalog.i18nc("@tooltip", "Other")
};
var result = "";
for(var feature in order)
{
feature = order[feature];
if(base.printDurationPerFeature[feature] && base.printDurationPerFeature[feature].totalSeconds > 0)
{
result += "<br/>" + visible_names[feature] + ": " + base.printDurationPerFeature[feature].getDisplayString(UM.DurationFormat.Short);
}
}
result = result.replace(/^\<br\/\>/, ""); // remove newline before first item
return result;
}
width: childrenRect.width
height: childrenRect.height
anchors.right: lengthIcon.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
Text
{
id: timeSpec
anchors.left: parent.left
anchors.top: parent.top
font: UM.Theme.getFont("small")
color: UM.Theme.getColor("text_subtext")
text: (!base.printDuration || !base.printDuration.valid) ? catalog.i18nc("@label", "00h 00min") : base.printDuration.getDisplayString(UM.DurationFormat.Short)
}
}
UM.RecolorImage
{
id: lengthIcon
@ -212,8 +248,9 @@ Item {
{
lengths.push(base.printMaterialLengths[index].toFixed(2));
weights.push(String(Math.floor(base.printMaterialWeights[index])));
costs.push(base.printMaterialCosts[index].toFixed(2));
if(base.printMaterialCosts[index] > 0)
var cost = base.printMaterialCosts[index] == undefined ? 0 : base.printMaterialCosts[index].toFixed(2);
costs.push(cost);
if(cost > 0)
{
someCostsKnown = true;
}

6
resources/qml/Menus/ContextMenu.qml Normal file → Executable file
View file

@ -93,6 +93,11 @@ Menu
copiesField.focus = true;
}
onVisibleChanged:
{
copiesField.forceActiveFocus();
}
standardButtons: StandardButton.Ok | StandardButton.Cancel
Row
@ -108,6 +113,7 @@ Menu
SpinBox
{
id: copiesField
focus: true
minimumValue: 1
maximumValue: 99
}

View file

@ -19,7 +19,7 @@ Menu
{
text: model.name + " - " + model.layer_height
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
onTriggered: Cura.MachineManager.setActiveQuality(model.id)
}

View file

@ -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)
{
for (var i = 0; i < choiceOnProfileOverrideDropDownButton.model.count; i++)
@ -55,6 +66,10 @@ UM.PreferencesPage
var defaultLanguage = UM.Preferences.getValue("general/language")
setDefaultLanguage(defaultLanguage)
UM.Preferences.resetPreference("general/theme")
var defaultTheme = UM.Preferences.getValue("general/theme")
setDefaultTheme(defaultTheme)
UM.Preferences.resetPreference("physics/automatic_push_free")
pushFreeCheckbox.checked = boolCheck(UM.Preferences.getValue("physics/automatic_push_free"))
UM.Preferences.resetPreference("physics/automatic_drop_down")
@ -111,9 +126,11 @@ UM.PreferencesPage
text: catalog.i18nc("@label","Interface")
}
Row
GridLayout
{
spacing: UM.Theme.getSize("default_margin").width
id: interfaceGrid
columns: 4
Label
{
id: languageLabel
@ -174,22 +191,75 @@ UM.PreferencesPage
{
id: currencyLabel
text: catalog.i18nc("@label","Currency:")
anchors.verticalCenter: languageComboBox.verticalCenter
anchors.verticalCenter: currencyField.verticalCenter
}
TextField
{
id: currencyField
text: UM.Preferences.getValue("cura/currency")
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
{
id: languageCaption
//: 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
font.italic: true
}
@ -211,7 +281,6 @@ UM.PreferencesPage
CheckBox
{
id: autoSliceCheckbox
checked: boolCheck(UM.Preferences.getValue("general/auto_slice"))
onClicked: UM.Preferences.setValue("general/auto_slice", checked)
@ -253,7 +322,7 @@ UM.PreferencesPage
UM.TooltipArea {
width: childrenRect.width;
height: childrenRect.height;
text: catalog.i18nc("@info:tooltip","Moves the camera so the model is in the center of the view when an model is selected")
text: catalog.i18nc("@info:tooltip","Moves the camera so the model is in the center of the view when a model is selected")
CheckBox
{

View file

@ -154,7 +154,7 @@ Item
id: definitionsModel;
containerId: Cura.MachineManager.activeDefinitionId
visibilityHandler: UM.SettingPreferenceVisibilityHandler { }
exclude: ["machine_settings", "command_line_settings", "infill_mesh", "infill_mesh_order", "support_mesh", "anti_overhang_mesh"] // TODO: infill_mesh settigns are excluded hardcoded, but should be based on the fact that settable_globally, settable_per_meshgroup and settable_per_extruder are false.
exclude: ["machine_settings", "command_line_settings", "infill_mesh", "infill_mesh_order", "cutting_mesh", "support_mesh", "anti_overhang_mesh"] // TODO: infill_mesh settigns are excluded hardcoded, but should be based on the fact that settable_globally, settable_per_meshgroup and settable_per_extruder are false.
expanded: CuraApplication.expandedCategories
onExpandedChanged:
{
@ -179,7 +179,7 @@ Item
Behavior on opacity { NumberAnimation { duration: 100 } }
enabled:
{
if(!ExtruderManager.activeExtruderStackId && ExtruderManager.extruderCount > 0)
if(!ExtruderManager.activeExtruderStackId && machineExtruderCount.properties.value > 1)
{
// disable all controls on the global tab, except categories
return model.type == "category"

View file

@ -348,6 +348,7 @@ Rectangle
Rectangle {
id: settingsModeSelection
color: "transparent"
width: parent.width * 0.55
height: UM.Theme.getSize("sidebar_header_mode_toggle").height
anchors.right: parent.right
@ -408,18 +409,34 @@ Rectangle
}
ExclusiveGroup { id: modeMenuGroup; }
Text
Label
{
id: toggleLeftText
anchors.right: modeToggleSwitch.left
anchors.rightMargin: UM.Theme.getSize("toggle_button_text_anchoring_margin").width
anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
text: ""
color: UM.Theme.getColor("toggle_active_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:
{
@ -438,10 +455,20 @@ Rectangle
id: modeToggleSwitch
checked: false
anchors.right: toggleRightText.left
anchors.rightMargin: UM.Theme.getSize("toggle_button_text_anchoring_margin").width
anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter
onClicked:
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)
@ -457,20 +484,36 @@ Rectangle
UM.Preferences.setValue("cura/active_mode", index);
}
style: UM.Theme.styles.toggle_button
style: UM.Theme.styles.mode_switch
}
Text
Label
{
id: toggleRightText
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: ""
color: UM.Theme.getColor("toggle_active_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:
{

View file

@ -19,7 +19,7 @@ Item
property Action configureSettings;
property variant minimumPrintTime: PrintInformation.minimumPrintTime;
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.onDestruction: PrintInformation.enabled = false
@ -30,6 +30,7 @@ Item
id: infillCellLeft
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: UM.Theme.getSize("default_margin").height
width: base.width * .45 - UM.Theme.getSize("default_margin").width
height: childrenRect.height
@ -47,12 +48,13 @@ Item
}
}
Flow
Row
{
id: infillCellRight
height: childrenRect.height;
width: base.width * .55
width: base.width * .5
spacing: UM.Theme.getSize("default_margin").width
anchors.left: infillCellLeft.right
@ -63,10 +65,11 @@ Item
id: infillListView
property int activeIndex:
{
var density = parseInt(infillDensity.properties.value)
var density = parseInt(infillDensity.properties.value);
var steps = parseInt(infillSteps.properties.value);
for(var i = 0; i < infillModel.count; ++i)
{
if(density > infillModel.get(i).percentageMin && density <= infillModel.get(i).percentageMax )
if(density > infillModel.get(i).percentageMin && density <= infillModel.get(i).percentageMax && steps > infillModel.get(i).stepsMin && steps <= infillModel.get(i).stepsMax)
{
return i;
}
@ -85,7 +88,7 @@ Item
{
id: infillIconLining
width: (infillCellRight.width - 3 * UM.Theme.getSize("default_margin").width) / 4;
width: (infillCellRight.width - ((infillModel.count - 1) * UM.Theme.getSize("default_margin").width)) / (infillModel.count);
height: width
border.color:
@ -150,6 +153,7 @@ Item
if (infillListView.activeIndex != index)
{
infillDensity.setPropertyValue("value", model.percentage)
infillSteps.setPropertyValue("value", model.steps)
}
}
onEntered:
@ -181,37 +185,61 @@ Item
Component.onCompleted:
{
infillModel.append({
name: catalog.i18nc("@label", "Hollow"),
name: catalog.i18nc("@label", "Empty"),
percentage: 0,
steps: 0,
percentageMin: -1,
percentageMax: 0,
text: catalog.i18nc("@label", "No (0%) infill will leave your model hollow at the cost of low strength"),
stepsMin: -1,
stepsMax: 0,
text: catalog.i18nc("@label", "Empty infill will leave your model hollow with low strength."),
icon: "hollow"
})
infillModel.append({
name: catalog.i18nc("@label", "Light"),
percentage: 20,
steps: 0,
percentageMin: 0,
percentageMax: 30,
text: catalog.i18nc("@label", "Light (20%) infill will give your model an average strength"),
stepsMin: -1,
stepsMax: 0,
text: catalog.i18nc("@label", "Light (20%) infill will give your model an average strength."),
icon: "sparse"
})
infillModel.append({
name: catalog.i18nc("@label", "Dense"),
percentage: 50,
steps: 0,
percentageMin: 30,
percentageMax: 70,
text: catalog.i18nc("@label", "Dense (50%) infill will give your model an above average strength"),
stepsMin: -1,
stepsMax: 0,
text: catalog.i18nc("@label", "Dense (50%) infill will give your model an above average strength."),
icon: "dense"
})
infillModel.append({
name: catalog.i18nc("@label", "Solid"),
percentage: 100,
steps: 0,
percentageMin: 70,
percentageMax: 100,
text: catalog.i18nc("@label", "Solid (100%) infill will make your model completely solid"),
percentageMax: 9999999999,
stepsMin: -1,
stepsMax: 0,
text: catalog.i18nc("@label", "Solid (100%) infill will make your model completely solid."),
icon: "solid"
})
infillModel.append({
name: catalog.i18nc("@label", "Gradual"),
percentage: 90,
steps: 5,
percentageMin: 0,
percentageMax: 9999999999,
stepsMin: 0,
stepsMax: 9999999999,
infill_layer_height: 1.5,
text: catalog.i18nc("@label", "Gradual infill will gradually increase the amount of infill towards the top."),
icon: "gradual"
})
}
}
}
@ -220,7 +248,7 @@ Item
{
id: helpersCell
anchors.top: infillCellRight.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.topMargin: UM.Theme.getSize("default_margin").height * 2
anchors.left: parent.left
anchors.right: parent.right
height: childrenRect.height
@ -240,6 +268,8 @@ Item
CheckBox
{
id: enableSupportCheckBox
property alias _hovered: enableSupportMouseArea.containsMouse
anchors.top: parent.top
anchors.left: enableSupportLabel.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
@ -390,7 +420,7 @@ Item
property alias _hovered: adhesionMouseArea.containsMouse
anchors.top: supportExtruderCombobox.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.topMargin: UM.Theme.getSize("default_margin").height * 2
anchors.left: adhesionHelperLabel.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
@ -465,7 +495,7 @@ Item
{
id: tipsCell
anchors.top: helpersCell.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.topMargin: UM.Theme.getSize("default_margin").height * 2
anchors.left: parent.left
width: parent.width
height: childrenRect.height
@ -478,7 +508,7 @@ Item
anchors.rightMargin: UM.Theme.getSize("default_margin").width
wrapMode: Text.WordWrap
//: Tips label
text: catalog.i18nc("@label", "Need help improving your prints? Read the <a href='%1'>Ultimaker Troubleshooting Guides</a>").arg("https://ultimaker.com/en/troubleshooting");
text: catalog.i18nc("@label", "Need help improving your prints?<br>Read the <a href='%1'>Ultimaker Troubleshooting Guides</a>").arg("https://ultimaker.com/en/troubleshooting");
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text");
linkColor: UM.Theme.getColor("text_link")
@ -496,6 +526,16 @@ Item
storeIndex: 0
}
UM.SettingPropertyProvider
{
id: infillSteps
containerStackId: Cura.MachineManager.activeStackId
key: "gradual_infill_steps"
watchedProperties: [ "value" ]
storeIndex: 0
}
UM.SettingPropertyProvider
{
id: platformAdhesionType

View file

@ -6,28 +6,33 @@ import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 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;
width: buttons.width;
height: buttons.height
property int activeY
ColumnLayout {
Column
{
id: buttons;
anchors.bottom: parent.bottom;
anchors.left: parent.left;
spacing: UM.Theme.getSize("button_lining").width
Repeater {
Repeater
{
id: repeat
model: UM.ToolModel { }
Button {
Button
{
text: model.name
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
//just catch the click so we do not trigger that behaviour.
MouseArea {
MouseArea
{
anchors.fill: parent;
onClicked: {
onClicked:
{
forceActiveFocus() //First grab focus, so all the text fields are updated
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;
anchors.left: parent.right;
@ -75,7 +92,8 @@ Item {
target: Qt.point(parent.right, base.activeY + UM.Theme.getSize("button").height/2)
arrowSize: UM.Theme.getSize("default_arrow").width
width: {
width:
{
if (panel.item && panel.width > 0){
return Math.max(panel.width + 2 * UM.Theme.getSize("default_margin").width)
}
@ -90,7 +108,8 @@ Item {
color: UM.Theme.getColor("lining");
UM.PointingRectangle {
UM.PointingRectangle
{
id: panelBackground;
color: UM.Theme.getColor("tool_panel_background");
@ -105,7 +124,8 @@ Item {
}
}
Loader {
Loader
{
id: panel
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
{
x: -base.x + base.mouseX + UM.Theme.getSize("default_margin").width

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = high
material = generic_abs_cartesio_0.25_mm
material = generic_abs_175_cartesio_0.25_mm
weight = 1
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = normal
material = generic_abs_cartesio_0.25_mm
material = generic_abs_175_cartesio_0.25_mm
weight = 2
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = high
material = generic_abs_cartesio_0.4_mm
material = generic_abs_175_cartesio_0.4_mm
weight = 1
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = normal
material = generic_abs_cartesio_0.4_mm
material = generic_abs_175_cartesio_0.4_mm
weight = 2
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = coarse
material = generic_abs_cartesio_0.8_mm
material = generic_abs_175_cartesio_0.8_mm
weight = 3
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = extra coarse
material = generic_abs_cartesio_0.8_mm
material = generic_abs_175_cartesio_0.8_mm
weight = 4
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = high
material = generic_abs_cartesio_0.8_mm
material = generic_abs_175_cartesio_0.8_mm
weight = 1
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = normal
material = generic_abs_cartesio_0.8_mm
material = generic_abs_175_cartesio_0.8_mm
weight = 2
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -0,0 +1,62 @@
[general]
version = 2
name = High Quality
definition = cartesio
[metadata]
type = quality
quality_type = high
material = dsm_arnitel2045_175_cartesio_0.4_mm
weight = 1
[values]
infill_line_width = 0.5
wall_thickness = 1.2
top_bottom_thickness = 0.8
wall_0_inset = -0.05
fill_perimeter_gaps = nowhere
travel_compensate_overlapping_walls_enabled =
infill_sparse_density = 40
infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 2
switch_extruder_retraction_amount = 2
switch_extruder_retraction_speeds = =retraction_speed
switch_extruder_prime_speed = =retraction_prime_speed
speed_print = 25
speed_infill = =speed_print
speed_layer_0 = =round(speed_print / 5 * 4)
speed_wall = =round(speed_print / 2)
speed_wall_0 = =10 if speed_wall < 11 else (speed_print / 5 *3)
speed_topbottom = =round(speed_print / 5 * 4)
speed_slowdown_layers = 1
speed_travel = =round(speed_print if magic_spiralize else 150)
speed_travel_layer_0 = =speed_travel
speed_support_interface = =speed_topbottom
speed_equalize_flow_enabled = True
speed_equalize_flow_max = =speed_print
acceleration_enabled = True
acceleration_print = 100
acceleration_travel = 300
jerk_print = 5
retraction_hop_enabled = True
retraction_hop = 1
cool_min_layer_time_fan_speed_max = =cool_min_layer_time
cool_min_layer_time = 20
skirt_brim_minimal_length = 50
coasting_enable = True
coasting_volume = 0.1
coasting_min_volume = 0.17
coasting_speed = 90

View file

@ -0,0 +1,62 @@
[general]
version = 2
name = Normal Quality
definition = cartesio
[metadata]
type = quality
quality_type = normal
material = dsm_arnitel2045_175_cartesio_0.4_mm
weight = 2
[values]
infill_line_width = 0.5
wall_thickness = 1.2
top_bottom_thickness = 0.8
wall_0_inset = -0.05
fill_perimeter_gaps = nowhere
travel_compensate_overlapping_walls_enabled =
infill_sparse_density = 40
infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 2
switch_extruder_retraction_amount = 2
switch_extruder_retraction_speeds = =retraction_speed
switch_extruder_prime_speed = =retraction_prime_speed
speed_print = 25
speed_infill = =speed_print
speed_layer_0 = =round(speed_print / 5 * 4)
speed_wall = =round(speed_print / 2)
speed_wall_0 = =10 if speed_wall < 11 else (speed_print / 5 *3)
speed_topbottom = =round(speed_print / 5 * 4)
speed_slowdown_layers = 1
speed_travel = =round(speed_print if magic_spiralize else 150)
speed_travel_layer_0 = =speed_travel
speed_support_interface = =speed_topbottom
speed_equalize_flow_enabled = True
speed_equalize_flow_max = =speed_print
acceleration_enabled = True
acceleration_print = 100
acceleration_travel = 300
jerk_print = 5
retraction_hop_enabled = True
retraction_hop = 1
cool_min_layer_time_fan_speed_max = =cool_min_layer_time
cool_min_layer_time = 20
skirt_brim_minimal_length = 50
coasting_enable = True
coasting_volume = 0.1
coasting_min_volume = 0.17
coasting_speed = 90

View file

@ -11,15 +11,22 @@ weight = 0
[values]
layer_height = 0.4
layer_height_0 = =layer_height
skin_angles = [0,90]
infill_before_walls = False
infill_angles = [0,90]
speed_slowdown_layers = 1
retraction_combing = off
support_z_distance = 0
support_xy_distance = 0.5
support_xy_distance = 1
support_join_distance = 10
support_interface_enable = True
support_interface_pattern = lines
adhesion_type = skirt
skirt_gap = 0.5
skirt_gap = 1

View file

@ -11,15 +11,22 @@ weight = 0
[values]
layer_height = 0.6
layer_height_0 = =layer_height
skin_angles = [0,90]
infill_before_walls = False
infill_angles = [0,90]
speed_slowdown_layers = 1
retraction_combing = off
support_z_distance = 0
support_xy_distance = 0.5
support_xy_distance = 1
support_join_distance = 10
support_interface_enable = True
support_interface_pattern = lines
adhesion_type = skirt
skirt_gap = 0.5
skirt_gap = 1

View file

@ -11,15 +11,23 @@ weight = 0
[values]
layer_height = 0.1
layer_height_0 = =0.2 if min(extruderValues('machine_nozzle_size')) < 0.3 else 0.3
skin_angles = [0,90]
infill_before_walls = False
infill_angles = [0,90]
speed_slowdown_layers = 1
retraction_combing = off
support_z_distance = 0
support_xy_distance = 0.5
support_xy_distance = 1
support_join_distance = 10
support_interface_enable = True
support_interface_height = 0.5
support_interface_pattern = lines
adhesion_type = skirt
skirt_gap = 0.5
skirt_gap = 1

View file

@ -11,15 +11,22 @@ weight = 0
[values]
layer_height = 0.2
layer_height_0 = =0.2 if min(extruderValues('machine_nozzle_size')) < 0.3 else 0.3
skin_angles = [0,90]
infill_before_walls = False
infill_angles = [0,90]
speed_slowdown_layers = 1
retraction_combing = off
support_z_distance = 0
support_xy_distance = 0.5
support_xy_distance = 1
support_join_distance = 10
support_interface_enable = True
support_interface_pattern = lines
adhesion_type = skirt
skirt_gap = 0.5
skirt_gap = 1

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = high
material = generic_hips_cartesio_0.25_mm
material = generic_hips_175_cartesio_0.25_mm
weight = 1
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = normal
material = generic_hips_cartesio_0.25_mm
material = generic_hips_175_cartesio_0.25_mm
weight = 2
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = high
material = generic_hips_cartesio_0.4_mm
material = generic_hips_175_cartesio_0.4_mm
weight = 1
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = normal
material = generic_hips_cartesio_0.4_mm
material = generic_hips_175_cartesio_0.4_mm
weight = 2
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

View file

@ -6,7 +6,7 @@ definition = cartesio
[metadata]
type = quality
quality_type = coarse
material = generic_hips_cartesio_0.8_mm
material = generic_hips_175_cartesio_0.8_mm
weight = 3
[values]
@ -24,7 +24,6 @@ infill_pattern = grid
material_print_temperature_layer_0 = =material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature
material_final_print_temperature = =material_print_temperature
material_diameter = 1.75
retraction_min_travel = =round(line_width * 10)
retraction_prime_speed = 10
switch_extruder_retraction_amount = 2

Some files were not shown because too many files have changed in this diff Show more