Merge branch 'feature_intent_container_tree' of github.com:Ultimaker/Cura into feature_intent_upgrade

This commit is contained in:
Jaime van Kessel 2019-09-10 15:07:57 +02:00
commit d6d06fb85b
No known key found for this signature in database
GPG key ID: 3710727397403C91
190 changed files with 13989 additions and 1140 deletions

3
.gitignore vendored
View file

@ -35,7 +35,7 @@ cura.desktop
.pydevproject .pydevproject
.settings .settings
#Externally located plug-ins. #Externally located plug-ins commonly installed by our devs.
plugins/cura-big-flame-graph plugins/cura-big-flame-graph
plugins/cura-god-mode-plugin plugins/cura-god-mode-plugin
plugins/cura-siemensnx-plugin plugins/cura-siemensnx-plugin
@ -52,6 +52,7 @@ plugins/FlatProfileExporter
plugins/GodMode plugins/GodMode
plugins/OctoPrintPlugin plugins/OctoPrintPlugin
plugins/ProfileFlattener plugins/ProfileFlattener
plugins/SettingsGuide
plugins/X3GWriter plugins/X3GWriter
#Build stuff #Build stuff

View file

@ -13,6 +13,6 @@ TryExec=@CMAKE_INSTALL_FULL_BINDIR@/cura
Icon=cura-icon Icon=cura-icon
Terminal=false Terminal=false
Type=Application Type=Application
MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml;text/x-gcode; MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;text/x-gcode;application/x-amf;application/x-ply;application/x-ctm;model/vnd.collada+xml;model/gltf-binary;model/gltf+json;model/vnd.collada+xml+zip;
Categories=Graphics; Categories=Graphics;
Keywords=3D;Printing;Slicer; Keywords=3D;Printing;Slicer;

View file

@ -3,15 +3,17 @@
from PyQt5.QtCore import QObject, QUrl from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtGui import QDesktopServices from PyQt5.QtGui import QDesktopServices
from typing import List, cast from typing import List, Optional, cast
from UM.Event import CallFunctionEvent from UM.Event import CallFunctionEvent
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
from UM.Math.Quaternion import Quaternion
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.RotateOperation import RotateOperation
from UM.Operations.TranslateOperation import TranslateOperation from UM.Operations.TranslateOperation import TranslateOperation
import cura.CuraApplication import cura.CuraApplication
@ -73,6 +75,39 @@ class CuraActions(QObject):
operation.addOperation(center_operation) operation.addOperation(center_operation)
operation.push() operation.push()
# Rotate the selection, so that the face that the mouse-pointer is on, faces the build-plate.
@pyqtSlot()
def bottomFaceSelection(self) -> None:
selected_face = Selection.getSelectedFace()
if not selected_face:
Logger.log("e", "Bottom face operation shouldn't have been called without a selected face.")
return
original_node, face_id = selected_face
meshdata = original_node.getMeshDataTransformed()
if not meshdata or face_id < 0 or face_id > Selection.getMaxFaceSelectionId():
return
rotation_point, face_normal = meshdata.getFacePlane(face_id)
rotation_point_vector = Vector(rotation_point[0], rotation_point[1], rotation_point[2])
face_normal_vector = Vector(face_normal[0], face_normal[1], face_normal[2])
rotation_quaternion = Quaternion.rotationTo(face_normal_vector.normalized(), Vector(0.0, -1.0, 0.0))
operation = GroupedOperation()
current_node = None # type: Optional[SceneNode]
for node in Selection.getAllSelectedObjects():
current_node = node
parent_node = current_node.getParent()
while parent_node and parent_node.callDecoration("isGroup"):
current_node = parent_node
parent_node = current_node.getParent()
if current_node is None:
return
rotate_operation = RotateOperation(current_node, rotation_quaternion, rotation_point_vector)
operation.addOperation(rotate_operation)
operation.push()
## Multiply all objects in the selection ## Multiply all objects in the selection
# #
# \param count The number of times to multiply the selection. # \param count The number of times to multiply the selection.

View file

@ -67,6 +67,7 @@ from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
from cura.Scene.CuraSceneController import CuraSceneController from cura.Scene.CuraSceneController import CuraSceneController
from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.GCodeListDecorator import GCodeListDecorator
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene import ZOffsetDecorator from cura.Scene import ZOffsetDecorator
@ -979,13 +980,13 @@ class CuraApplication(QtApplication):
return self._machine_action_manager return self._machine_action_manager
@pyqtSlot(result = QObject) @pyqtSlot(result = QObject)
def getMaterialManagementModel(self): def getMaterialManagementModel(self) -> MaterialManagementModel:
if not self._material_management_model: if not self._material_management_model:
self._material_management_model = MaterialManagementModel(parent = self) self._material_management_model = MaterialManagementModel(parent = self)
return self._material_management_model return self._material_management_model
@pyqtSlot(result = QObject) @pyqtSlot(result = QObject)
def getQualityManagementModel(self): def getQualityManagementModel(self) -> QualityManagementModel:
if not self._quality_management_model: if not self._quality_management_model:
self._quality_management_model = QualityManagementModel(parent = self) self._quality_management_model = QualityManagementModel(parent = self)
return self._quality_management_model return self._quality_management_model
@ -1347,7 +1348,13 @@ class CuraApplication(QtApplication):
Logger.log("i", "Reloading all loaded mesh data.") Logger.log("i", "Reloading all loaded mesh data.")
nodes = [] nodes = []
has_merged_nodes = False has_merged_nodes = False
gcode_filename = None # type: Optional[str]
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
# Objects loaded from Gcode should also be included.
gcode_filename = node.callDecoration("getGcodeFileName")
if gcode_filename is not None:
break
if not isinstance(node, CuraSceneNode) or not node.getMeshData(): if not isinstance(node, CuraSceneNode) or not node.getMeshData():
if node.getName() == "MergedMesh": if node.getName() == "MergedMesh":
has_merged_nodes = True has_merged_nodes = True
@ -1355,6 +1362,11 @@ class CuraApplication(QtApplication):
nodes.append(node) nodes.append(node)
# We can open only one gcode file at the same time. If the current view has a gcode file open, just reopen it
# for reloading.
if gcode_filename:
self._openFile(gcode_filename)
if not nodes: if not nodes:
return return
@ -1829,3 +1841,40 @@ class CuraApplication(QtApplication):
return main_window.height() return main_window.height()
else: else:
return 0 return 0
@pyqtSlot()
def deleteAll(self, only_selectable: bool = True) -> None:
super().deleteAll(only_selectable = only_selectable)
# Also remove nodes with LayerData
self._removeNodesWithLayerData(only_selectable = only_selectable)
def _removeNodesWithLayerData(self, only_selectable: bool = True) -> None:
Logger.log("i", "Clearing scene")
nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if not isinstance(node, SceneNode):
continue
if not node.isEnabled():
continue
if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
if only_selectable and not node.isSelectable():
continue # Only remove nodes that are selectable.
if not node.callDecoration("isSliceable") and not node.callDecoration("getLayerData") and not node.callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
nodes.append(node)
if nodes:
from UM.Operations.GroupedOperation import GroupedOperation
op = GroupedOperation()
for node in nodes:
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
op.addOperation(RemoveSceneNodeOperation(node))
# Reset the print information
self.getController().getScene().sceneChanged.emit(node)
op.push()
from UM.Scene.Selection import Selection
Selection.clear()

View file

@ -53,9 +53,9 @@ class ContainerTree:
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None: if global_stack is None:
return {} return {}
variant_names = [extruder.variant.getName() for extruder in global_stack.extruders.values()] variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruders.values()] material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruders.values()] extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
return self.machines[global_stack.definition.getId()].getQualityGroups(variant_names, material_bases, extruder_enabled) return self.machines[global_stack.definition.getId()].getQualityGroups(variant_names, material_bases, extruder_enabled)
## Get the quality changes groups available for the currently activated ## Get the quality changes groups available for the currently activated
@ -69,9 +69,9 @@ class ContainerTree:
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None: if global_stack is None:
return [] return []
variant_names = [extruder.variant.getName() for extruder in global_stack.extruders.values()] variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruders.values()] material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruders.values()] extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
return self.machines[global_stack.definition.getId()].getQualityChangesGroups(variant_names, material_bases, extruder_enabled) return self.machines[global_stack.definition.getId()].getQualityChangesGroups(variant_names, material_bases, extruder_enabled)
## Builds the initial container tree. ## Builds the initial container tree.

View file

@ -17,4 +17,4 @@ class IntentNode(ContainerNode):
def __init__(self, container_id: str, quality: "QualityNode") -> None: def __init__(self, container_id: str, quality: "QualityNode") -> None:
super().__init__(container_id) super().__init__(container_id)
self.quality = quality self.quality = quality
self.intent_category = ContainerRegistry.getInstance().findContainersMetadata(id = container_id)[0].get("intent_category", "default") self.intent_category = ContainerRegistry.getInstance().findContainersMetadata(id = container_id)[0].get("intent_category", "default")

View file

@ -104,6 +104,8 @@ class BaseMaterialsModel(ListModel):
# tree. This change may trigger an _update() call when the materials # tree. This change may trigger an _update() call when the materials
# changed for the configuration that this model is looking for. # changed for the configuration that this model is looking for.
def _materialsListChanged(self, material: MaterialNode) -> None: def _materialsListChanged(self, material: MaterialNode) -> None:
if self._extruder_stack is None:
return
if material.variant.container_id != self._extruder_stack.variant.getId(): if material.variant.container_id != self._extruder_stack.variant.getId():
return return
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()

View file

@ -5,9 +5,11 @@ from PyQt5.QtCore import Qt
import collections import collections
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from cura.Machines.Models.IntentModel import IntentModel
from cura.Settings.IntentManager import IntentManager from cura.Settings.IntentManager import IntentManager
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes. from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes.
from PyQt5.QtCore import pyqtProperty, pyqtSignal
if TYPE_CHECKING: if TYPE_CHECKING:
from UM.Settings.ContainerRegistry import ContainerInterface from UM.Settings.ContainerRegistry import ContainerInterface
@ -15,12 +17,14 @@ if TYPE_CHECKING:
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
## Lists the intent categories that are available for the current printer ## Lists the intent categories that are available for the current printer
# configuration. # configuration.
class IntentCategoryModel(ListModel): class IntentCategoryModel(ListModel):
NameRole = Qt.UserRole + 1 NameRole = Qt.UserRole + 1
IntentCategoryRole = Qt.UserRole + 2 IntentCategoryRole = Qt.UserRole + 2
WeightRole = Qt.UserRole + 3 WeightRole = Qt.UserRole + 3
QualitiesRole = Qt.UserRole + 4
#Translations to user-visible string. Ordered by weight. #Translations to user-visible string. Ordered by weight.
#TODO: Create a solution for this name and weight to be used dynamically. #TODO: Create a solution for this name and weight to be used dynamically.
@ -29,6 +33,8 @@ class IntentCategoryModel(ListModel):
name_translation["engineering"] = catalog.i18nc("@label", "Engineering") name_translation["engineering"] = catalog.i18nc("@label", "Engineering")
name_translation["smooth"] = catalog.i18nc("@label", "Smooth") name_translation["smooth"] = catalog.i18nc("@label", "Smooth")
modelUpdated = pyqtSignal()
## Creates a new model for a certain intent category. ## Creates a new model for a certain intent category.
# \param The category to list the intent profiles for. # \param The category to list the intent profiles for.
def __init__(self, intent_category: str) -> None: def __init__(self, intent_category: str) -> None:
@ -38,6 +44,7 @@ class IntentCategoryModel(ListModel):
self.addRoleName(self.NameRole, "name") self.addRoleName(self.NameRole, "name")
self.addRoleName(self.IntentCategoryRole, "intent_category") self.addRoleName(self.IntentCategoryRole, "intent_category")
self.addRoleName(self.WeightRole, "weight") self.addRoleName(self.WeightRole, "weight")
self.addRoleName(self.QualitiesRole, "qualities")
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange) ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange)
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange) ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange)
@ -55,9 +62,13 @@ class IntentCategoryModel(ListModel):
available_categories = IntentManager.getInstance().currentAvailableIntentCategories() available_categories = IntentManager.getInstance().currentAvailableIntentCategories()
result = [] result = []
for category in available_categories: for category in available_categories:
qualities = IntentModel()
qualities.setIntentCategory(category)
result.append({ result.append({
"name": self.name_translation.get(category, catalog.i18nc("@label", "Unknown")), "name": self.name_translation.get(category, catalog.i18nc("@label", "Unknown")),
"intent_category": category, "intent_category": category,
"weight": list(self.name_translation.keys()).index(category) "weight": list(self.name_translation.keys()).index(category),
"qualities": qualities
}) })
self.setItems(result) result.sort(key = lambda k: k["weight"])
self.setItems(result)

View file

@ -6,8 +6,11 @@ from typing import Optional, List, Dict, Any
from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.SettingFunction import SettingFunction
from cura.Machines.ContainerTree import ContainerTree from cura.Machines.ContainerTree import ContainerTree
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.IntentManager import IntentManager from cura.Settings.IntentManager import IntentManager
import cura.CuraApplication import cura.CuraApplication
@ -15,18 +18,26 @@ import cura.CuraApplication
class IntentModel(ListModel): class IntentModel(ListModel):
NameRole = Qt.UserRole + 1 NameRole = Qt.UserRole + 1
QualityTypeRole = Qt.UserRole + 2 QualityTypeRole = Qt.UserRole + 2
LayerHeightRole = Qt.UserRole + 3
AvailableRole = Qt.UserRole + 4
IntentRole = Qt.UserRole + 5
def __init__(self, parent: Optional[QObject] = None) -> None: def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent) super().__init__(parent)
self.addRoleName(self.NameRole, "name") self.addRoleName(self.NameRole, "name")
self.addRoleName(self.QualityTypeRole, "quality_type") self.addRoleName(self.QualityTypeRole, "quality_type")
self.addRoleName(self.LayerHeightRole, "layer_height")
self.addRoleName(self.AvailableRole, "available")
self.addRoleName(self.IntentRole, "intent_category")
self._intent_category = "engineering" self._intent_category = "engineering"
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager() machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
machine_manager.globalContainerChanged.connect(self._update) machine_manager.globalContainerChanged.connect(self._update)
machine_manager.activeStackChanged.connect(self._update) ContainerRegistry.getInstance().containerAdded.connect(self._onChanged)
ContainerRegistry.getInstance().containerRemoved.connect(self._onChanged)
self._layer_height_unit = "" # This is cached
self._update() self._update()
intentCategoryChanged = pyqtSignal() intentCategoryChanged = pyqtSignal()
@ -41,6 +52,10 @@ class IntentModel(ListModel):
def intentCategory(self) -> str: def intentCategory(self) -> str:
return self._intent_category return self._intent_category
def _onChanged(self, container):
if container.getMetaDataEntry("type") == "intent":
self._update()
def _update(self) -> None: def _update(self) -> None:
new_items = [] # type: List[Dict[str, Any]] new_items = [] # type: List[Dict[str, Any]]
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack() global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
@ -49,11 +64,76 @@ class IntentModel(ListModel):
return return
quality_groups = ContainerTree.getInstance().getCurrentQualityGroups() quality_groups = ContainerTree.getInstance().getCurrentQualityGroups()
for intent_category, quality_type in IntentManager.getInstance().getCurrentAvailableIntents(): container_tree = ContainerTree.getInstance()
if intent_category == self._intent_category: machine_node = container_tree.machines[global_stack.definition.getId()]
new_items.append({"name": quality_groups[quality_type].name, "quality_type": quality_type}) active_extruder = ExtruderManager.getInstance().getActiveExtruderStack()
if self._intent_category == "default": #For Default we always list all quality types. We can't filter on available profiles since the empty intent is not a specific quality type. if not active_extruder:
for quality_type in quality_groups.keys(): return
new_items.append({"name": quality_groups[quality_type].name, "quality_type": quality_type}) active_variant_name = active_extruder.variant.getMetaDataEntry("name")
active_variant_node = machine_node.variants[active_variant_name]
active_material_node = active_variant_node.materials[active_extruder.material.getMetaDataEntry("base_file")]
layer_heights_added = []
for quality_id, quality_node in active_material_node.qualities.items():
quality_group = quality_groups[quality_node.quality_type]
layer_height = self._fetchLayerHeight(quality_group)
for intent_id, intent_node in quality_node.intents.items():
if intent_node.intent_category != self._intent_category:
continue
layer_heights_added.append(layer_height)
new_items.append({"name": quality_group.name,
"quality_type": quality_group.quality_type,
"layer_height": layer_height,
"available": quality_group.is_available,
"intent_category": self._intent_category
})
# Now that we added all intents that we found something for, ensure that we set add ticks (and layer_heights)
# for all groups that we don't have anything for (and set it to not available)
for quality_tuple, quality_group in quality_groups.items():
# Add the intents that are of the correct category
if quality_tuple[0] != self._intent_category:
layer_height = self._fetchLayerHeight(quality_group)
if layer_height not in layer_heights_added:
new_items.append({"name": "Unavailable",
"quality_type": "",
"layer_height": layer_height,
"intent_category": self._intent_category,
"available": False})
layer_heights_added.append(layer_height)
new_items = sorted(new_items, key=lambda x: x["layer_height"])
self.setItems(new_items) self.setItems(new_items)
#TODO: Copied this from QualityProfilesDropdownMenuModel for the moment. This code duplication should be fixed.
def _fetchLayerHeight(self, quality_group) -> float:
global_stack = cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeMachine
if not self._layer_height_unit:
unit = global_stack.definition.getProperty("layer_height", "unit")
if not unit:
unit = ""
self._layer_height_unit = unit
default_layer_height = global_stack.definition.getProperty("layer_height", "value")
# Get layer_height from the quality profile for the GlobalStack
if quality_group.node_for_global is None:
return float(default_layer_height)
container = quality_group.node_for_global.container
layer_height = default_layer_height
if container and container.hasProperty("layer_height", "value"):
layer_height = container.getProperty("layer_height", "value")
else:
# Look for layer_height in the GlobalStack from material -> definition
container = global_stack.definition
if container and container.hasProperty("layer_height", "value"):
layer_height = container.getProperty("layer_height", "value")
if isinstance(layer_height, SettingFunction):
layer_height = layer_height(global_stack)
return float(layer_height)
def __repr__(self):
return str(self.items)

View file

@ -77,9 +77,9 @@ class QualityManager(QObject):
# Returns a dict of "custom profile name" -> QualityChangesGroup # Returns a dict of "custom profile name" -> QualityChangesGroup
def getQualityChangesGroups(self, machine: "GlobalStack") -> List[QualityChangesGroup]: def getQualityChangesGroups(self, machine: "GlobalStack") -> List[QualityChangesGroup]:
variant_names = [extruder.variant.getName() for extruder in machine.extruders.values()] variant_names = [extruder.variant.getName() for extruder in machine.extruderList]
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in machine.extruders.values()] material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in machine.extruderList]
extruder_enabled = [extruder.isEnabled for extruder in machine.extruders.values()] extruder_enabled = [extruder.isEnabled for extruder in machine.extruderList]
machine_node = ContainerTree.getInstance().machines[machine.definition.getId()] machine_node = ContainerTree.getInstance().machines[machine.definition.getId()]
return machine_node.getQualityChangesGroups(variant_names, material_bases, extruder_enabled) return machine_node.getQualityChangesGroups(variant_names, material_bases, extruder_enabled)
@ -92,9 +92,9 @@ class QualityManager(QObject):
# for those types as values. # for those types as values.
def getQualityGroups(self, global_stack: "GlobalStack") -> Dict[str, QualityGroup]: def getQualityGroups(self, global_stack: "GlobalStack") -> Dict[str, QualityGroup]:
# Gather up the variant names and material base files for each extruder. # Gather up the variant names and material base files for each extruder.
variant_names = [extruder.variant.getName() for extruder in global_stack.extruders.values()] variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruders.values()] material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruders.values()] extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
definition_id = global_stack.definition.getId() definition_id = global_stack.definition.getId()
return ContainerTree.getInstance().machines[definition_id].getQualityGroups(variant_names, material_bases, extruder_enabled) return ContainerTree.getInstance().machines[definition_id].getQualityGroups(variant_names, material_bases, extruder_enabled)

View file

@ -31,8 +31,8 @@ class QualityNode(ContainerNode):
# Find all intent profiles that fit the current configuration. # Find all intent profiles that fit the current configuration.
from cura.Machines.MachineNode import MachineNode from cura.Machines.MachineNode import MachineNode
if not isinstance(self.parent, MachineNode): # Not a global profile. if not isinstance(self.parent, MachineNode): # Not a global profile.
for intent in container_registry.findInstanceContainersMetadata(type = "intent", definition = self.parent.variant.machine.quality_definition, variant = self.parent.variant.variant_name, material = self.parent.base_file): for intent in container_registry.findInstanceContainersMetadata(type = "intent", definition = self.parent.variant.machine.quality_definition, variant = self.parent.variant.variant_name, material = self.parent.base_file, quality_type = self.quality_type):
self.intents[intent["id"]] = IntentNode(intent["id"], quality = self) self.intents[intent["id"]] = IntentNode(intent["id"], quality = self)
if not self.intents:
self.intents["empty_intent"] = IntentNode("empty_intent", quality = self) self.intents["empty_intent"] = IntentNode("empty_intent", quality = self)
# Otherwise, there are no intents for global profiles. # Otherwise, there are no intents for global profiles.

View file

@ -35,8 +35,6 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType = ConnectionType.NetworkConnection, parent: QObject = None) -> None: def __init__(self, device_id, address: str, properties: Dict[bytes, bytes], connection_type: ConnectionType = ConnectionType.NetworkConnection, parent: QObject = None) -> None:
super().__init__(device_id = device_id, connection_type = connection_type, parent = parent) super().__init__(device_id = device_id, connection_type = connection_type, parent = parent)
self._manager = None # type: Optional[QNetworkAccessManager] self._manager = None # type: Optional[QNetworkAccessManager]
self._last_manager_create_time = None # type: Optional[float]
self._recreate_network_manager_time = 30
self._timeout_time = 10 # After how many seconds of no response should a timeout occur? self._timeout_time = 10 # After how many seconds of no response should a timeout occur?
self._last_response_time = None # type: Optional[float] self._last_response_time = None # type: Optional[float]
@ -133,12 +131,6 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self.setConnectionState(ConnectionState.Closed) self.setConnectionState(ConnectionState.Closed)
# We need to check if the manager needs to be re-created. If we don't, we get some issues when OSX goes to
# sleep.
if time_since_last_response > self._recreate_network_manager_time:
if self._last_manager_create_time is None or time() - self._last_manager_create_time > self._recreate_network_manager_time:
self._createNetworkManager()
assert(self._manager is not None)
elif self._connection_state == ConnectionState.Closed: elif self._connection_state == ConnectionState.Closed:
# Go out of timeout. # Go out of timeout.
if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here if self._connection_state_before_timeout is not None: # sanity check, but it should never be None here
@ -317,7 +309,6 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._manager = QNetworkAccessManager() self._manager = QNetworkAccessManager()
self._manager.finished.connect(self._handleOnFinished) self._manager.finished.connect(self._handleOnFinished)
self._last_manager_create_time = time()
self._manager.authenticationRequired.connect(self._onAuthenticationRequired) self._manager.authenticationRequired.connect(self._onAuthenticationRequired)
if self._properties.get(b"temporary", b"false") != b"true": if self._properties.get(b"temporary", b"false") != b"true":

View file

@ -1,11 +1,18 @@
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from typing import List from typing import List, Optional
class GCodeListDecorator(SceneNodeDecorator): class GCodeListDecorator(SceneNodeDecorator):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._gcode_list = [] # type: List[str] self._gcode_list = [] # type: List[str]
self._filename = None # type: Optional[str]
def getGcodeFileName(self) -> Optional[str]:
return self._filename
def setGcodeFileName(self, filename: str) -> None:
self._filename = filename
def getGCodeList(self) -> List[str]: def getGCodeList(self) -> List[str]:
return self._gcode_list return self._gcode_list

View file

@ -20,7 +20,9 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
import cura.CuraApplication import cura.CuraApplication
from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.MaterialManager import MaterialManager from cura.Machines.MaterialManager import MaterialManager
if TYPE_CHECKING: if TYPE_CHECKING:
@ -271,24 +273,30 @@ class ContainerManager(QObject):
# \return \type{bool} True if successful, False if not. # \return \type{bool} True if successful, False if not.
@pyqtSlot(result = bool) @pyqtSlot(result = bool)
def updateQualityChanges(self) -> bool: def updateQualityChanges(self) -> bool:
global_stack = cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeMachine application = cura.CuraApplication.CuraApplication.getInstance()
global_stack = application.getMachineManager().activeMachine
if not global_stack: if not global_stack:
return False return False
cura.CuraApplication.CuraApplication.getInstance().getMachineManager().blurSettings.emit() application.getMachineManager().blurSettings.emit()
current_quality_changes_name = global_stack.qualityChanges.getName() current_quality_changes_name = global_stack.qualityChanges.getName()
current_quality_type = global_stack.quality.getMetaDataEntry("quality_type") current_quality_type = global_stack.quality.getMetaDataEntry("quality_type")
extruder_stacks = list(global_stack.extruders.values()) extruder_stacks = list(global_stack.extruders.values())
container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry() container_registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
quality_manager = QualityManager.getInstance() machine_definition_id = ContainerTree.getInstance().definitions[global_stack.definition.getId()].quality_definition
for stack in [global_stack] + extruder_stacks: for stack in [global_stack] + extruder_stacks:
# Find the quality_changes container for this stack and merge the contents of the top container into it. # Find the quality_changes container for this stack and merge the contents of the top container into it.
quality_changes = stack.qualityChanges quality_changes = stack.qualityChanges
if quality_changes.getId() == "empty_quality_changes": if quality_changes.getId() == "empty_quality_changes":
quality_changes = quality_manager._createQualityChanges(current_quality_type, current_quality_changes_name, quality_changes = InstanceContainer(container_registry.uniqueName((stack.getId() + "_" + current_quality_changes_name).lower().replace(" ", "_")))
global_stack, stack) quality_changes.setName(current_quality_changes_name)
quality_changes.setMetaDataEntry("type", "quality_changes")
quality_changes.setMetaDataEntry("quality_type", current_quality_type)
quality_changes.setMetaDataEntry("position", stack.getMetaDataEntry("position"))
quality_changes.setMetaDataEntry("setting_version", application.SettingVersion)
quality_changes.setDefinition(machine_definition_id)
container_registry.addContainer(quality_changes) container_registry.addContainer(quality_changes)
stack.qualityChanges = quality_changes stack.qualityChanges = quality_changes

View file

@ -242,12 +242,11 @@ class CuraContainerRegistry(ContainerRegistry):
# And check if the profile_definition matches either one (showing error if not): # And check if the profile_definition matches either one (showing error if not):
if profile_definition != expected_machine_definition: if profile_definition != expected_machine_definition:
Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition) Logger.log("d", "Profile {file_name} is for machine {profile_definition}, but the current active machine is {expected_machine_definition}. Changing profile's definition.".format(file_name = file_name, profile_definition = profile_definition, expected_machine_definition = expected_machine_definition))
return { "status": "error", global_profile.setMetaDataEntry("definition", expected_machine_definition)
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)} for extruder_profile in extruder_profiles:
extruder_profile.setMetaDataEntry("definition", expected_machine_definition)
# Fix the global quality profile's definition field in case it's not correct
global_profile.setMetaDataEntry("definition", expected_machine_definition)
quality_name = global_profile.getName() quality_name = global_profile.getName()
quality_type = global_profile.getMetaDataEntry("quality_type") quality_type = global_profile.getMetaDataEntry("quality_type")
@ -315,9 +314,9 @@ class CuraContainerRegistry(ContainerRegistry):
result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition) result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition)
if result is not None: if result is not None:
return {"status": "error", "message": catalog.i18nc( return {"status": "error", "message": catalog.i18nc(
"@info:status Don't translate the XML tags <filename> or <message>!", "@info:status Don't translate the XML tag <filename>!",
"Failed to import profile from <filename>{0}</filename>:", "Failed to import profile from <filename>{0}</filename>:",
file_name) + " <message>" + result + "</message>"} file_name) + " " + result}
return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())} return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())}

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Any, cast, List, Optional from typing import Any, cast, Dict, List, Optional
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject
from UM.Application import Application from UM.Application import Application
@ -364,3 +364,22 @@ class _ContainerIndexes:
# Reverse lookup: type -> index # Reverse lookup: type -> index
TypeIndexMap = dict([(v, k) for k, v in IndexTypeMap.items()]) TypeIndexMap = dict([(v, k) for k, v in IndexTypeMap.items()])
# Mapping to old values before Intent introduction. Used for reading older versions of input files.
IndexToOldIndexMap = {
UserChanges: 0,
QualityChanges: 1,
Intent: -1, # Wasn't there in the old 'format'!
Quality: 2,
Material: 3,
Variant: 4,
DefinitionChanges: 5,
Definition: 6,
}
# Reverse lookup: old index -> new index
OldIndexToIndexMap = dict([(v, k) for k, v in IndexToOldIndexMap.items()])
@classmethod
def getIndexMapping(cls, setting_version: int) -> Dict[int, int]:
return dict([(x, x) for x in list(range(99))]) if setting_version >= 10 else cls.IndexToOldIndexMap

View file

@ -160,6 +160,7 @@ class CuraStackBuilder:
stack.variant = variant_container stack.variant = variant_container
stack.material = material_container stack.material = material_container
stack.quality = quality_container stack.quality = quality_container
stack.intent = application.empty_intent_container
stack.qualityChanges = application.empty_quality_changes_container stack.qualityChanges = application.empty_quality_changes_container
stack.userChanges = user_container stack.userChanges = user_container
@ -208,6 +209,7 @@ class CuraStackBuilder:
stack.variant = variant_container stack.variant = variant_container
stack.material = material_container stack.material = material_container
stack.quality = quality_container stack.quality = quality_container
stack.intent = application.empty_intent_container
stack.qualityChanges = application.empty_quality_changes_container stack.qualityChanges = application.empty_quality_changes_container
stack.userChanges = user_container stack.userChanges = user_container

View file

@ -1,4 +1,4 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from collections import defaultdict from collections import defaultdict
@ -8,7 +8,7 @@ import uuid
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from UM.Decorators import override from UM.Decorators import deprecated, override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.SettingInstance import InstanceState from UM.Settings.SettingInstance import InstanceState
@ -61,12 +61,13 @@ class GlobalStack(CuraContainerStack):
# #
# \return The extruders registered with this stack. # \return The extruders registered with this stack.
@pyqtProperty("QVariantMap", notify = extrudersChanged) @pyqtProperty("QVariantMap", notify = extrudersChanged)
@deprecated("Please use extruderList instead.", "4.4")
def extruders(self) -> Dict[str, "ExtruderStack"]: def extruders(self) -> Dict[str, "ExtruderStack"]:
return self._extruders return self._extruders
@pyqtProperty("QVariantList", notify = extrudersChanged) @pyqtProperty("QVariantList", notify = extrudersChanged)
def extruderList(self) -> List["ExtruderStack"]: def extruderList(self) -> List["ExtruderStack"]:
result_tuple_list = sorted(list(self.extruders.items()), key=lambda x: int(x[0])) result_tuple_list = sorted(list(self._extruders.items()), key=lambda x: int(x[0]))
result_list = [item[1] for item in result_tuple_list] result_list = [item[1] for item in result_tuple_list]
machine_extruder_count = self.getProperty("machine_extruder_count", "value") machine_extruder_count = self.getProperty("machine_extruder_count", "value")

View file

@ -132,6 +132,7 @@ class MachineManager(QObject):
activeMaterialChanged = pyqtSignal() activeMaterialChanged = pyqtSignal()
activeVariantChanged = pyqtSignal() activeVariantChanged = pyqtSignal()
activeQualityChanged = pyqtSignal() activeQualityChanged = pyqtSignal()
activeIntentChanged = pyqtSignal()
activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value) activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value)
extruderChanged = pyqtSignal() extruderChanged = pyqtSignal()
@ -270,6 +271,7 @@ class MachineManager(QObject):
self.activeQualityChanged.emit() self.activeQualityChanged.emit()
self.activeVariantChanged.emit() self.activeVariantChanged.emit()
self.activeMaterialChanged.emit() self.activeMaterialChanged.emit()
self.activeIntentChanged.emit()
self.rootMaterialChanged.emit() self.rootMaterialChanged.emit()
self.numberExtrudersEnabledChanged.emit() self.numberExtrudersEnabledChanged.emit()
@ -609,6 +611,14 @@ class MachineManager(QObject):
return False return False
return Util.parseBool(global_container_stack.quality.getMetaDataEntry("is_experimental", False)) return Util.parseBool(global_container_stack.quality.getMetaDataEntry("is_experimental", False))
@pyqtProperty(str, notify=activeIntentChanged)
def activeIntentCategory(self):
if not self._active_container_stack:
return ""
intent_category = self._active_container_stack.intent.getMetaDataEntry("intent_category")
return intent_category
## Returns whether there is anything unsupported in the current set-up. ## Returns whether there is anything unsupported in the current set-up.
# #
# The current set-up signifies the global stack and all extruder stacks, # The current set-up signifies the global stack and all extruder stacks,

View file

@ -12,6 +12,10 @@ from .CuraContainerStack import CuraContainerStack
class PerObjectContainerStack(CuraContainerStack): class PerObjectContainerStack(CuraContainerStack):
def isDirty(self):
# This stack should never be auto saved, so always return that there is nothing to save.
return False
@override(CuraContainerStack) @override(CuraContainerStack)
def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any: def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any:
if context is None: if context is None:

View file

@ -47,6 +47,7 @@ EMPTY_INTENT_CONTAINER_ID = "empty_intent"
empty_intent_container = copy.deepcopy(empty_container) empty_intent_container = copy.deepcopy(empty_container)
empty_intent_container.setMetaDataEntry("id", EMPTY_INTENT_CONTAINER_ID) empty_intent_container.setMetaDataEntry("id", EMPTY_INTENT_CONTAINER_ID)
empty_intent_container.setMetaDataEntry("type", "intent") empty_intent_container.setMetaDataEntry("type", "intent")
empty_intent_container.setMetaDataEntry("intent_category", "default")
empty_intent_container.setName(catalog.i18nc("@info:No intent profile selected", "Default")) empty_intent_container.setName(catalog.i18nc("@info:No intent profile selected", "Default"))

View file

@ -141,5 +141,37 @@ import Arcus #@UnusedImport
import Savitar #@UnusedImport import Savitar #@UnusedImport
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
# WORKAROUND: CURA-6739
# The CTM file loading module in Trimesh requires the OpenCTM library to be dynamically loaded. It uses
# ctypes.util.find_library() to find libopenctm.dylib, but this doesn't seem to look in the ".app" application folder
# on Mac OS X. Adding the search path to environment variables such as DYLD_LIBRARY_PATH and DYLD_FALLBACK_LIBRARY_PATH
# makes it work. The workaround here uses DYLD_FALLBACK_LIBRARY_PATH.
if Platform.isOSX() and getattr(sys, "frozen", False):
old_env = os.environ.get("DYLD_FALLBACK_LIBRARY_PATH", "")
# This is where libopenctm.so is in the .app folder.
search_path = os.path.join(CuraApplication.getInstallPrefix(), "MacOS")
path_list = old_env.split(":")
if search_path not in path_list:
path_list.append(search_path)
os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = ":".join(path_list)
import trimesh.exchange.load
os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = old_env
# WORKAROUND: CURA-6739
# Similar CTM file loading fix for Linux, but NOTE THAT this doesn't work directly with Python 3.5.7. There's a fix
# for ctypes.util.find_library() in Python 3.6 and 3.7. That fix makes sure that find_library() will check
# LD_LIBRARY_PATH. With Python 3.5, that fix needs to be backported to make this workaround work.
if Platform.isLinux() and getattr(sys, "frozen", False):
old_env = os.environ.get("LD_LIBRARY_PATH", "")
# This is where libopenctm.so is in the AppImage.
search_path = os.path.join(CuraApplication.getInstallPrefix(), "bin")
path_list = old_env.split(":")
if search_path not in path_list:
path_list.append(search_path)
os.environ["LD_LIBRARY_PATH"] = ":".join(path_list)
import trimesh.exchange.load
os.environ["LD_LIBRARY_PATH"] = old_env
app = CuraApplication() app = CuraApplication()
app.run() app.run()

View file

@ -1,10 +1,10 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from configparser import ConfigParser from configparser import ConfigParser
import zipfile import zipfile
import os import os
from typing import Dict, List, Tuple, cast from typing import cast, Dict, List, Optional, Tuple
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
@ -23,8 +23,8 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from UM.Job import Job from UM.Job import Job
from UM.Preferences import Preferences from UM.Preferences import Preferences
from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.VariantType import VariantType from cura.Machines.VariantType import VariantType
from cura.Settings.CuraStackBuilder import CuraStackBuilder from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
@ -34,13 +34,15 @@ from cura.Settings.CuraContainerStack import _ContainerIndexes
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Utils.Threading import call_on_qt_thread from cura.Utils.Threading import call_on_qt_thread
from PyQt5.QtCore import QCoreApplication
from .WorkspaceDialog import WorkspaceDialog from .WorkspaceDialog import WorkspaceDialog
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
class ContainerInfo: class ContainerInfo:
def __init__(self, file_name: str, serialized: str, parser: ConfigParser) -> None: def __init__(self, file_name: Optional[str], serialized: Optional[str], parser: Optional[ConfigParser]) -> None:
self.file_name = file_name self.file_name = file_name
self.serialized = serialized self.serialized = serialized
self.parser = parser self.parser = parser
@ -231,6 +233,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
else: else:
Logger.log("w", "Unknown definition container type %s for %s", Logger.log("w", "Unknown definition container type %s for %s",
definition_container_type, definition_container_file) definition_container_type, definition_container_file)
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
Job.yieldThread() Job.yieldThread()
if machine_definition_container_count != 1: if machine_definition_container_count != 1:
@ -257,6 +260,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
containers_found_dict["material"] = True containers_found_dict["material"] = True
if not self._container_registry.isReadOnly(container_id): # Only non readonly materials can be in conflict if not self._container_registry.isReadOnly(container_id): # Only non readonly materials can be in conflict
material_conflict = True material_conflict = True
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
Job.yieldThread() Job.yieldThread()
# Check if any quality_changes instance container is in conflict. # Check if any quality_changes instance container is in conflict.
@ -326,7 +330,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Ignore certain instance container types # Ignore certain instance container types
Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type) Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
continue continue
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
Job.yieldThread() Job.yieldThread()
if self._machine_info.quality_changes_info.global_info is None: if self._machine_info.quality_changes_info.global_info is None:
@ -368,7 +372,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Get quality type # Get quality type
parser = ConfigParser(interpolation = None) parser = ConfigParser(interpolation = None)
parser.read_string(serialized) parser.read_string(serialized)
quality_container_id = parser["containers"][str(_ContainerIndexes.Quality)] index_map_version = _ContainerIndexes.getIndexMapping(int(parser["metadata"]["setting_version"]))
quality_container_id = parser["containers"][str(index_map_version[_ContainerIndexes.Quality])]
quality_type = "empty_quality" quality_type = "empty_quality"
if quality_container_id not in ("empty", "empty_quality"): if quality_container_id not in ("empty", "empty_quality"):
quality_type = instance_container_info_dict[quality_container_id].parser["metadata"]["quality_type"] quality_type = instance_container_info_dict[quality_container_id].parser["metadata"]["quality_type"]
@ -378,10 +383,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
serialized = GlobalStack._updateSerialized(serialized, global_stack_file) serialized = GlobalStack._updateSerialized(serialized, global_stack_file)
parser = ConfigParser(interpolation = None) parser = ConfigParser(interpolation = None)
parser.read_string(serialized) parser.read_string(serialized)
definition_changes_id = parser["containers"][str(_ContainerIndexes.DefinitionChanges)] index_map_version = _ContainerIndexes.getIndexMapping(int(parser["metadata"]["setting_version"]))
definition_changes_id = parser["containers"][str(index_map_version[_ContainerIndexes.DefinitionChanges])]
if definition_changes_id not in ("empty", "empty_definition_changes"): if definition_changes_id not in ("empty", "empty_definition_changes"):
self._machine_info.definition_changes_info = instance_container_info_dict[definition_changes_id] self._machine_info.definition_changes_info = instance_container_info_dict[definition_changes_id]
user_changes_id = parser["containers"][str(_ContainerIndexes.UserChanges)] user_changes_id = parser["containers"][str(index_map_version[_ContainerIndexes.UserChanges])]
if user_changes_id not in ("empty", "empty_user_changes"): if user_changes_id not in ("empty", "empty_user_changes"):
self._machine_info.user_changes_info = instance_container_info_dict[user_changes_id] self._machine_info.user_changes_info = instance_container_info_dict[user_changes_id]
@ -391,8 +397,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_info = ExtruderInfo() extruder_info = ExtruderInfo()
extruder_info.position = position extruder_info.position = position
variant_id = parser["containers"][str(_ContainerIndexes.Variant)] variant_id = parser["containers"][str(index_map_version[_ContainerIndexes.Variant])]
material_id = parser["containers"][str(_ContainerIndexes.Material)] material_id = parser["containers"][str(index_map_version[_ContainerIndexes.Material])]
if variant_id not in ("empty", "empty_variant"): if variant_id not in ("empty", "empty_variant"):
extruder_info.variant_info = instance_container_info_dict[variant_id] extruder_info.variant_info = instance_container_info_dict[variant_id]
if material_id not in ("empty", "empty_material"): if material_id not in ("empty", "empty_material"):
@ -400,10 +406,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_info.root_material_id = root_material_id extruder_info.root_material_id = root_material_id
self._machine_info.extruder_info_dict[position] = extruder_info self._machine_info.extruder_info_dict[position] = extruder_info
else: else:
variant_id = parser["containers"][str(_ContainerIndexes.Variant)] variant_id = parser["containers"][str(index_map_version[_ContainerIndexes.Variant])]
if variant_id not in ("empty", "empty_variant"): if variant_id not in ("empty", "empty_variant"):
self._machine_info.variant_info = instance_container_info_dict[variant_id] self._machine_info.variant_info = instance_container_info_dict[variant_id]
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
Job.yieldThread() Job.yieldThread()
# if the global stack is found, we check if there are conflicts in the extruder stacks # if the global stack is found, we check if there are conflicts in the extruder stacks
@ -412,13 +418,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
serialized = ExtruderStack._updateSerialized(serialized, extruder_stack_file) serialized = ExtruderStack._updateSerialized(serialized, extruder_stack_file)
parser = ConfigParser(interpolation = None) parser = ConfigParser(interpolation = None)
parser.read_string(serialized) parser.read_string(serialized)
index_map_version = _ContainerIndexes.getIndexMapping(int(parser["metadata"]["setting_version"]))
# The check should be done for the extruder stack that's associated with the existing global stack, # The check should be done for the extruder stack that's associated with the existing global stack,
# and those extruder stacks may have different IDs. # and those extruder stacks may have different IDs.
# So we check according to the positions # So we check according to the positions
position = parser["metadata"]["position"] position = parser["metadata"]["position"]
variant_id = parser["containers"][str(_ContainerIndexes.Variant)] variant_id = parser["containers"][str(index_map_version[_ContainerIndexes.Variant])]
material_id = parser["containers"][str(_ContainerIndexes.Material)] material_id = parser["containers"][str(index_map_version[_ContainerIndexes.Material])]
extruder_info = ExtruderInfo() extruder_info = ExtruderInfo()
extruder_info.position = position extruder_info.position = position
@ -432,11 +439,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
root_material_id = reverse_material_id_dict[material_id] root_material_id = reverse_material_id_dict[material_id]
extruder_info.root_material_id = root_material_id extruder_info.root_material_id = root_material_id
definition_changes_id = parser["containers"][str(_ContainerIndexes.DefinitionChanges)] definition_changes_id = parser["containers"][str(index_map_version[_ContainerIndexes.DefinitionChanges])]
if definition_changes_id not in ("empty", "empty_definition_changes"): if definition_changes_id not in ("empty", "empty_definition_changes"):
extruder_info.definition_changes_info = instance_container_info_dict[definition_changes_id] extruder_info.definition_changes_info = instance_container_info_dict[definition_changes_id]
user_changes_id = parser["containers"][str(_ContainerIndexes.UserChanges)] user_changes_id = parser["containers"][str(index_map_version[_ContainerIndexes.UserChanges])]
if user_changes_id not in ("empty", "empty_user_changes"): if user_changes_id not in ("empty", "empty_user_changes"):
extruder_info.user_changes_info = instance_container_info_dict[user_changes_id] extruder_info.user_changes_info = instance_container_info_dict[user_changes_id]
self._machine_info.extruder_info_dict[position] = extruder_info self._machine_info.extruder_info_dict[position] = extruder_info
@ -572,9 +579,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
signals = [container_registry.containerAdded, signals = [container_registry.containerAdded,
container_registry.containerRemoved, container_registry.containerRemoved,
container_registry.containerMetaDataChanged] container_registry.containerMetaDataChanged]
# # The container tree updates its lookup tables upon container changes.
# We now have different managers updating their lookup tables upon container changes. It is critical to make # It is critical to make sure that it has a complete set of data when it
# sure that the managers have a complete set of data when they update. # updates.
# #
# In project loading, lots of the container-related signals are loosely emitted, which can create timing gaps # In project loading, lots of the container-related signals are loosely emitted, which can create timing gaps
# for incomplete data update or other kinds of issues to happen. # for incomplete data update or other kinds of issues to happen.
@ -658,6 +665,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
definition_container = self._container_registry.findDefinitionContainers(id = "fdmprinter")[0] #Fall back to defaults. definition_container = self._container_registry.findDefinitionContainers(id = "fdmprinter")[0] #Fall back to defaults.
self._container_registry.addContainer(definition_container) self._container_registry.addContainer(definition_container)
Job.yieldThread() Job.yieldThread()
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
Logger.log("d", "Workspace loading is checking materials...") Logger.log("d", "Workspace loading is checking materials...")
# Get all the material files and check if they exist. If not, add them. # Get all the material files and check if they exist. If not, add them.
@ -707,6 +715,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
material_container.setDirty(True) material_container.setDirty(True)
self._container_registry.addContainer(material_container) self._container_registry.addContainer(material_container)
Job.yieldThread() Job.yieldThread()
QCoreApplication.processEvents() # Ensure that the GUI does not freeze.
# Handle quality changes if any # Handle quality changes if any
self._processQualityChanges(global_stack) self._processQualityChanges(global_stack)
@ -738,7 +747,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
return return
application = CuraApplication.getInstance() application = CuraApplication.getInstance()
quality_manager = application.getQualityManager()
# If we have custom profiles, load them # If we have custom profiles, load them
quality_changes_name = self._machine_info.quality_changes_info.name quality_changes_name = self._machine_info.quality_changes_info.name
@ -764,11 +772,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_stack = None extruder_stack = None
if position is not None: if position is not None:
extruder_stack = global_stack.extruders[position] extruder_stack = global_stack.extruders[position]
container = quality_manager._createQualityChanges(quality_changes_quality_type, container = self._createNewQualityChanges(quality_changes_quality_type, quality_changes_name, global_stack, extruder_stack)
quality_changes_name,
global_stack, extruder_stack)
container_info.container = container container_info.container = container
container.setDirty(True)
self._container_registry.addContainer(container) self._container_registry.addContainer(container)
Logger.log("d", "Created new quality changes container [%s]", container.getId()) Logger.log("d", "Created new quality changes container [%s]", container.getId())
@ -797,10 +802,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
ExtruderManager.getInstance().fixSingleExtrusionMachineExtruderDefinition(global_stack) ExtruderManager.getInstance().fixSingleExtrusionMachineExtruderDefinition(global_stack)
extruder_stack = global_stack.extruders["0"] extruder_stack = global_stack.extruders["0"]
container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name, container = self._createNewQualityChanges(quality_changes_quality_type, quality_changes_name, global_stack, extruder_stack)
global_stack, extruder_stack)
container_info.container = container container_info.container = container
container.setDirty(True)
self._container_registry.addContainer(container) self._container_registry.addContainer(container)
Logger.log("d", "Created new quality changes container [%s]", container.getId()) Logger.log("d", "Created new quality changes container [%s]", container.getId())
@ -826,10 +829,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if container_info.container is None: if container_info.container is None:
extruder_stack = global_stack.extruders[position] extruder_stack = global_stack.extruders[position]
container = quality_manager._createQualityChanges(quality_changes_quality_type, quality_changes_name, container = self._createNewQualityChanges(quality_changes_quality_type, quality_changes_name, global_stack, extruder_stack)
global_stack, extruder_stack)
container_info.container = container container_info.container = container
container.setDirty(True)
self._container_registry.addContainer(container) self._container_registry.addContainer(container)
for key, value in container_info.parser["values"].items(): for key, value in container_info.parser["values"].items():
@ -837,6 +838,42 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._machine_info.quality_changes_info.name = quality_changes_name self._machine_info.quality_changes_info.name = quality_changes_name
## Helper class to create a new quality changes profile.
#
# This will then later be filled with the appropriate data.
# \param quality_type The quality type of the new profile.
# \param name The name for the profile. This will later be made unique so
# it doesn't need to be unique yet.
# \param global_stack The global stack showing the configuration that the
# profile should be created for.
# \param extruder_stack The extruder stack showing the configuration that
# the profile should be created for. If this is None, it will be created
# for the global stack.
def _createNewQualityChanges(self, quality_type: str, name: str, global_stack: GlobalStack, extruder_stack: Optional[ExtruderStack]) -> InstanceContainer:
container_registry = CuraApplication.getInstance().getContainerRegistry()
base_id = global_stack.definition.getId() if extruder_stack is None else extruder_stack.getId()
new_id = base_id + "_" + name
new_id = new_id.lower().replace(" ", "_")
new_id = container_registry.uniqueName(new_id)
# Create a new quality_changes container for the quality.
quality_changes = InstanceContainer(new_id)
quality_changes.setName(name)
quality_changes.setMetaDataEntry("type", "quality_changes")
quality_changes.setMetaDataEntry("quality_type", quality_type)
# If we are creating a container for an extruder, ensure we add that to the container.
if extruder_stack is not None:
quality_changes.setMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
machine_definition_id = ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
quality_changes.setDefinition(machine_definition_id)
quality_changes.setMetaDataEntry("setting_version", CuraApplication.getInstance().SettingVersion)
quality_changes.setDirty(True)
return quality_changes
@staticmethod @staticmethod
def _clearStack(stack): def _clearStack(stack):
application = CuraApplication.getInstance() application = CuraApplication.getInstance()

View file

@ -543,6 +543,15 @@ class CuraEngineBackend(QObject, Backend):
if error.getErrorCode() == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None: if error.getErrorCode() == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None:
self._start_slice_job.setIsCancelled(False) self._start_slice_job.setIsCancelled(False)
# Check if there's any slicable object in the scene.
def hasSlicableObject(self) -> bool:
has_slicable = False
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("isSliceable"):
has_slicable = True
break
return has_slicable
## Remove old layer data (if any) ## Remove old layer data (if any)
def _clearLayerData(self, build_plate_numbers: Set = None) -> None: def _clearLayerData(self, build_plate_numbers: Set = None) -> None:
# Clear out any old gcode # Clear out any old gcode
@ -561,6 +570,10 @@ class CuraEngineBackend(QObject, Backend):
## Convenient function: mark everything to slice, emit state and clear layer data ## Convenient function: mark everything to slice, emit state and clear layer data
def needsSlicing(self) -> None: def needsSlicing(self) -> None:
# CURA-6604: If there's no slicable object, do not (try to) trigger slice, which will clear all the current
# gcode. This can break Gcode file loading if it tries to remove it afterwards.
if not self.hasSlicableObject():
return
self.determineAutoSlicing() self.determineAutoSlicing()
self.stopSlicing() self.stopSlicing()
self.markSliceAll() self.markSliceAll()
@ -632,7 +645,10 @@ class CuraEngineBackend(QObject, Backend):
self.setState(BackendState.Done) self.setState(BackendState.Done)
self.processingProgress.emit(1.0) self.processingProgress.emit(1.0)
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically. try:
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
gcode_list = []
for index, line in enumerate(gcode_list): for index, line in enumerate(gcode_list):
replaced = line.replace("{print_time}", str(self._application.getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601))) replaced = line.replace("{print_time}", str(self._application.getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
replaced = replaced.replace("{filament_amount}", str(self._application.getPrintInformation().materialLengths)) replaced = replaced.replace("{filament_amount}", str(self._application.getPrintInformation().materialLengths))

View file

@ -4,11 +4,11 @@
import configparser import configparser
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
from UM.PluginRegistry import PluginRegistry
from UM.Logger import Logger from UM.Logger import Logger
from UM.Settings.ContainerFormatError import ContainerFormatError from UM.Settings.ContainerFormatError import ContainerFormatError
from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make. from UM.Settings.InstanceContainer import InstanceContainer # The new profile to make.
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.ReaderWriters.ProfileReader import ProfileReader from cura.ReaderWriters.ProfileReader import ProfileReader
import zipfile import zipfile
@ -92,6 +92,14 @@ class CuraProfileReader(ProfileReader):
except Exception as e: except Exception as e:
Logger.log("e", "Error while trying to parse profile: %s", str(e)) Logger.log("e", "Error while trying to parse profile: %s", str(e))
return None return None
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None:
return None
active_quality_definition = getMachineDefinitionIDForQualitySearch(global_stack.definition)
if profile.getMetaDataEntry("definition") != active_quality_definition:
profile.setMetaDataEntry("definition", active_quality_definition)
return profile return profile
## Upgrade a serialized profile to the current profile format. ## Upgrade a serialized profile to the current profile format.

View file

@ -27,6 +27,6 @@ class GCodeGzReader(MeshReader):
file_data = file.read() file_data = file.read()
uncompressed_gcode = gzip.decompress(file_data).decode("utf-8") uncompressed_gcode = gzip.decompress(file_data).decode("utf-8")
PluginRegistry.getInstance().getPluginObject("GCodeReader").preReadFromStream(uncompressed_gcode) PluginRegistry.getInstance().getPluginObject("GCodeReader").preReadFromStream(uncompressed_gcode)
result = PluginRegistry.getInstance().getPluginObject("GCodeReader").readFromStream(uncompressed_gcode) result = PluginRegistry.getInstance().getPluginObject("GCodeReader").readFromStream(uncompressed_gcode, file_name)
return result return result

View file

@ -3,7 +3,7 @@
import math import math
import re import re
from typing import Dict, List, NamedTuple, Optional, Union from typing import Dict, List, NamedTuple, Optional, Union, Set
import numpy import numpy
@ -38,6 +38,8 @@ class FlavorParser:
self._message = None # type: Optional[Message] self._message = None # type: Optional[Message]
self._layer_number = 0 self._layer_number = 0
self._extruder_number = 0 self._extruder_number = 0
# All extruder numbers that have been seen
self._extruders_seen = {0} # type: Set[int]
self._clearValues() self._clearValues()
self._scene_node = None self._scene_node = None
# X, Y, Z position, F feedrate and E extruder values are stored # X, Y, Z position, F feedrate and E extruder values are stored
@ -66,7 +68,7 @@ class FlavorParser:
if n < 0: if n < 0:
return None return None
n += len(code) n += len(code)
pattern = re.compile("[;\s]") pattern = re.compile("[;\\s]")
match = pattern.search(line, n) match = pattern.search(line, n)
m = match.start() if match is not None else -1 m = match.start() if match is not None else -1
try: try:
@ -292,7 +294,12 @@ class FlavorParser:
extruder.getProperty("machine_nozzle_offset_y", "value")] extruder.getProperty("machine_nozzle_offset_y", "value")]
return result return result
def processGCodeStream(self, stream: str) -> Optional[CuraSceneNode]: #
# CURA-6643
# This function needs the filename so it can be set to the SceneNode. Otherwise, if you load a GCode file and press
# F5, that gcode SceneNode will be removed because it doesn't have a file to be reloaded from.
#
def processGCodeStream(self, stream: str, filename: str) -> Optional["CuraSceneNode"]:
Logger.log("d", "Preparing to load GCode") Logger.log("d", "Preparing to load GCode")
self._cancelled = False self._cancelled = False
# We obtain the filament diameter from the selected extruder to calculate line widths # We obtain the filament diameter from the selected extruder to calculate line widths
@ -418,6 +425,7 @@ class FlavorParser:
if line.startswith("T"): if line.startswith("T"):
T = self._getInt(line, "T") T = self._getInt(line, "T")
if T is not None: if T is not None:
self._extruders_seen.add(T)
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0])) self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
current_path.clear() current_path.clear()
@ -453,6 +461,7 @@ class FlavorParser:
scene_node.addDecorator(decorator) scene_node.addDecorator(decorator)
gcode_list_decorator = GCodeListDecorator() gcode_list_decorator = GCodeListDecorator()
gcode_list_decorator.setGcodeFileName(filename)
gcode_list_decorator.setGCodeList(gcode_list) gcode_list_decorator.setGCodeList(gcode_list)
scene_node.addDecorator(gcode_list_decorator) scene_node.addDecorator(gcode_list_decorator)
@ -467,10 +476,9 @@ class FlavorParser:
if self._layer_number == 0: if self._layer_number == 0:
Logger.log("w", "File doesn't contain any valid layers") Logger.log("w", "File doesn't contain any valid layers")
settings = CuraApplication.getInstance().getGlobalContainerStack() if not global_stack.getProperty("machine_center_is_zero", "value"):
if settings is not None and not settings.getProperty("machine_center_is_zero", "value"): machine_width = global_stack.getProperty("machine_width", "value")
machine_width = settings.getProperty("machine_width", "value") machine_depth = global_stack.getProperty("machine_depth", "value")
machine_depth = settings.getProperty("machine_depth", "value")
scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2)) scene_node.setPosition(Vector(-machine_width / 2, 0, machine_depth / 2))
Logger.log("d", "GCode loading finished") Logger.log("d", "GCode loading finished")

View file

@ -2,6 +2,8 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, Union, List, TYPE_CHECKING
from UM.FileHandler.FileReader import FileReader from UM.FileHandler.FileReader import FileReader
from UM.Mesh.MeshReader import MeshReader from UM.Mesh.MeshReader import MeshReader
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
@ -9,8 +11,14 @@ from UM.Application import Application
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
from .FlavorParser import FlavorParser
from . import MarlinFlavorParser, RepRapFlavorParser from . import MarlinFlavorParser, RepRapFlavorParser
if TYPE_CHECKING:
from UM.Scene.SceneNode import SceneNode
from cura.Scene.CuraSceneNode import CuraSceneNode
# Class for loading and parsing G-code files # Class for loading and parsing G-code files
class GCodeReader(MeshReader): class GCodeReader(MeshReader):
@ -30,7 +38,7 @@ class GCodeReader(MeshReader):
) )
self._supported_extensions = [".gcode", ".g"] self._supported_extensions = [".gcode", ".g"]
self._flavor_reader = None self._flavor_reader = None # type: Optional[FlavorParser]
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True) Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
@ -54,10 +62,16 @@ class GCodeReader(MeshReader):
file_data = file.read() file_data = file.read()
return self.preReadFromStream(file_data, args, kwargs) return self.preReadFromStream(file_data, args, kwargs)
def readFromStream(self, stream): def readFromStream(self, stream: str, filename: str) -> Optional["CuraSceneNode"]:
return self._flavor_reader.processGCodeStream(stream) if self._flavor_reader is None:
return None
return self._flavor_reader.processGCodeStream(stream, filename)
def _read(self, file_name): def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]:
with open(file_name, "r", encoding = "utf-8") as file: with open(file_name, "r", encoding = "utf-8") as file:
file_data = file.read() file_data = file.read()
return self.readFromStream(file_data) result = [] # type: List[SceneNode]
node = self.readFromStream(file_data, file_name)
if node is not None:
result.append(node)
return result

View file

@ -116,17 +116,22 @@ class GCodeWriter(MeshWriter):
# \return A serialised string of the settings. # \return A serialised string of the settings.
def _serialiseSettings(self, stack): def _serialiseSettings(self, stack):
container_registry = self._application.getContainerRegistry() container_registry = self._application.getContainerRegistry()
quality_manager = self._application.getQualityManager()
prefix = self._setting_keyword + str(GCodeWriter.version) + " " # The prefix to put before each line. prefix = self._setting_keyword + str(GCodeWriter.version) + " " # The prefix to put before each line.
prefix_length = len(prefix) prefix_length = len(prefix)
quality_type = stack.quality.getMetaDataEntry("quality_type") quality_type = stack.quality.getMetaDataEntry("quality_type")
container_with_profile = stack.qualityChanges container_with_profile = stack.qualityChanges
machine_definition_id_for_quality = ContainerTree.getInstance().machines[stack.definition.getId()].quality_definition
if container_with_profile.getId() == "empty_quality_changes": if container_with_profile.getId() == "empty_quality_changes":
# If the global quality changes is empty, create a new one # If the global quality changes is empty, create a new one
quality_name = container_registry.uniqueName(stack.quality.getName()) quality_name = container_registry.uniqueName(stack.quality.getName())
container_with_profile = quality_manager._createQualityChanges(quality_type, quality_name, stack, None) quality_id = container_registry.uniqueName((stack.definition.getId() + "_" + quality_name).lower().replace(" ", "_"))
container_with_profile = InstanceContainer(quality_id)
container_with_profile.setName(quality_name)
container_with_profile.setMetaDataEntry("type", "quality_changes")
container_with_profile.setMetaDataEntry("quality_type", quality_type)
container_with_profile.setDefinition(machine_definition_id_for_quality)
flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile) flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile)
# If the quality changes is not set, we need to set type manually # If the quality changes is not set, we need to set type manually
@ -138,7 +143,6 @@ class GCodeWriter(MeshWriter):
flat_global_container.setMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal")) flat_global_container.setMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
# Get the machine definition ID for quality profiles # Get the machine definition ID for quality profiles
machine_definition_id_for_quality = ContainerTree.getInstance().machines[stack.definition.getId()].quality_definition
flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality) flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality)
serialized = flat_global_container.serialize() serialized = flat_global_container.serialize()
@ -150,7 +154,12 @@ class GCodeWriter(MeshWriter):
if extruder_quality.getId() == "empty_quality_changes": if extruder_quality.getId() == "empty_quality_changes":
# Same story, if quality changes is empty, create a new one # Same story, if quality changes is empty, create a new one
quality_name = container_registry.uniqueName(stack.quality.getName()) quality_name = container_registry.uniqueName(stack.quality.getName())
extruder_quality = quality_manager._createQualityChanges(quality_type, quality_name, stack, None) quality_id = container_registry.uniqueName((stack.definition.getId() + "_" + quality_name).lower().replace(" ", "_"))
extruder_quality = InstanceContainer(quality_id)
extruder_quality.setName(quality_name)
extruder_quality.setMetaDataEntry("type", "quality_changes")
extruder_quality.setMetaDataEntry("quality_type", quality_type)
extruder_quality.setDefinition(machine_definition_id_for_quality)
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality) flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality)
# If the quality changes is not set, we need to set type manually # If the quality changes is not set, we need to set type manually

View file

@ -5,6 +5,9 @@ import configparser # An input for some functions we're testing.
import os.path # To find the integration test .ini files. import os.path # To find the integration test .ini files.
import pytest # To register tests with. import pytest # To register tests with.
import unittest.mock # To mock the application, plug-in and container registry out. import unittest.mock # To mock the application, plug-in and container registry out.
import os.path
import sys
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
import UM.Application # To mock the application out. import UM.Application # To mock the application out.
import UM.PluginRegistry # To mock the plug-in registry out. import UM.PluginRegistry # To mock the plug-in registry out.
@ -12,11 +15,13 @@ import UM.Settings.ContainerRegistry # To mock the container registry out.
import UM.Settings.InstanceContainer # To intercept the serialised data from the read() function. import UM.Settings.InstanceContainer # To intercept the serialised data from the read() function.
import LegacyProfileReader as LegacyProfileReaderModule # To get the directory of the module. import LegacyProfileReader as LegacyProfileReaderModule # To get the directory of the module.
from LegacyProfileReader import LegacyProfileReader # The module we're testing.
@pytest.fixture @pytest.fixture
def legacy_profile_reader(): def legacy_profile_reader():
return LegacyProfileReader() try:
return LegacyProfileReaderModule.LegacyProfileReader()
except TypeError:
return LegacyProfileReaderModule.LegacyProfileReader.LegacyProfileReader()
test_prepareDefaultsData = [ test_prepareDefaultsData = [
{ {

View file

@ -69,7 +69,7 @@ class DisplayFilenameAndLayerOnLCD(Script):
else: else:
lcd_text = "M117 Printing Layer " lcd_text = "M117 Printing Layer "
else: else:
lcd_text = "M117 Printing " + name + " - Layer " lcd_text = "M117 Printing " + name + " - Layer "
i = self.getSettingValueByKey("startNum") i = self.getSettingValueByKey("startNum")
for layer in data: for layer in data:
display_text = lcd_text + str(i) + " " + name display_text = lcd_text + str(i) + " " + name

View file

@ -106,10 +106,17 @@ class PauseAtHeight(Script):
"standby_temperature": "standby_temperature":
{ {
"label": "Standby Temperature", "label": "Standby Temperature",
"description": "Change the temperature during the pause", "description": "Change the temperature during the pause.",
"unit": "°C", "unit": "°C",
"type": "int", "type": "int",
"default_value": 0 "default_value": 0
},
"display_text":
{
"label": "Display Text",
"description": "Text that should appear on the display while paused. If left empty, there will not be any message.",
"type": "str",
"default_value": ""
} }
} }
}""" }"""
@ -144,6 +151,7 @@ class PauseAtHeight(Script):
firmware_retract = Application.getInstance().getGlobalContainerStack().getProperty("machine_firmware_retract", "value") firmware_retract = Application.getInstance().getGlobalContainerStack().getProperty("machine_firmware_retract", "value")
control_temperatures = Application.getInstance().getGlobalContainerStack().getProperty("machine_nozzle_temp_enabled", "value") control_temperatures = Application.getInstance().getGlobalContainerStack().getProperty("machine_nozzle_temp_enabled", "value")
initial_layer_height = Application.getInstance().getGlobalContainerStack().getProperty("layer_height_0", "value") initial_layer_height = Application.getInstance().getGlobalContainerStack().getProperty("layer_height_0", "value")
display_text = self.getSettingValueByKey("display_text")
is_griffin = False is_griffin = False
@ -265,7 +273,7 @@ class PauseAtHeight(Script):
if not is_griffin: if not is_griffin:
# Retraction # Retraction
prepend_gcode += self.putValue(M = 83) + "\n" prepend_gcode += self.putValue(M = 83) + " ; switch to relative E values for any needed retraction\n"
if retraction_amount != 0: if retraction_amount != 0:
if firmware_retract: #Can't set the distance directly to what the user wants. We have to choose ourselves. if firmware_retract: #Can't set the distance directly to what the user wants. We have to choose ourselves.
retraction_count = 1 if control_temperatures else 3 #Retract more if we don't control the temperature. retraction_count = 1 if control_temperatures else 3 #Retract more if we don't control the temperature.
@ -275,25 +283,28 @@ class PauseAtHeight(Script):
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n" prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
# Move the head away # Move the head away
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n" prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + " ; move up a millimeter to get out of the way\n"
# This line should be ok # This line should be ok
prepend_gcode += self.putValue(G = 1, X = park_x, Y = park_y, F = 9000) + "\n" prepend_gcode += self.putValue(G = 1, X = park_x, Y = park_y, F = 9000) + "\n"
if current_z < 15: if current_z < 15:
prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + "\n" prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + " ; too close to bed--move to at least 15mm\n"
if control_temperatures: if control_temperatures:
# Set extruder standby temperature # Set extruder standby temperature
prepend_gcode += self.putValue(M = 104, S = standby_temperature) + "; standby temperature\n" prepend_gcode += self.putValue(M = 104, S = standby_temperature) + " ; standby temperature\n"
if display_text:
prepend_gcode += "M117 " + display_text + "\n"
# Wait till the user continues printing # Wait till the user continues printing
prepend_gcode += self.putValue(M = 0) + ";Do the actual pause\n" prepend_gcode += self.putValue(M = 0) + " ; Do the actual pause\n"
if not is_griffin: if not is_griffin:
if control_temperatures: if control_temperatures:
# Set extruder resume temperature # Set extruder resume temperature
prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + "; resume temperature\n" prepend_gcode += self.putValue(M = 109, S = int(target_temperature.get(current_t, 0))) + " ; resume temperature\n"
# Push the filament back, # Push the filament back,
if retraction_amount != 0: if retraction_amount != 0:
@ -309,8 +320,10 @@ class PauseAtHeight(Script):
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n" prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
# Move the head back # Move the head back
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n" if current_z < 15:
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
prepend_gcode += self.putValue(G = 1, X = x, Y = y, F = 9000) + "\n" prepend_gcode += self.putValue(G = 1, X = x, Y = y, F = 9000) + "\n"
prepend_gcode += self.putValue(G = 1, Z = current_z, F = 300) + " ; move back down to resume height\n"
if retraction_amount != 0: if retraction_amount != 0:
if firmware_retract: #Can't set the distance directly to what the user wants. We have to choose ourselves. if firmware_retract: #Can't set the distance directly to what the user wants. We have to choose ourselves.
retraction_count = 1 if control_temperatures else 3 #Retract more if we don't control the temperature. retraction_count = 1 if control_temperatures else 3 #Retract more if we don't control the temperature.
@ -319,7 +332,7 @@ class PauseAtHeight(Script):
else: else:
prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n" prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
prepend_gcode += self.putValue(G = 1, F = 9000) + "\n" prepend_gcode += self.putValue(G = 1, F = 9000) + "\n"
prepend_gcode += self.putValue(M = 82) + "\n" prepend_gcode += self.putValue(M = 82) + " ; switch back to absolute E values\n"
# reset extrude value to pre pause value # reset extrude value to pre pause value
prepend_gcode += self.putValue(G = 92, E = current_e) + "\n" prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"

View file

@ -0,0 +1,75 @@
# Copyright (c) 2019 Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
import math
from ..Script import Script
## Continues retracting during all travel moves.
class RetractContinue(Script):
def getSettingDataString(self):
return """{
"name": "Retract Continue",
"key": "RetractContinue",
"metadata": {},
"version": 2,
"settings":
{
"extra_retraction_speed":
{
"label": "Extra Retraction Ratio",
"description": "How much does it retract during the travel move, by ratio of the travel length.",
"type": "float",
"default_value": 0.05
}
}
}"""
def execute(self, data):
current_e = 0
current_x = 0
current_y = 0
extra_retraction_speed = self.getSettingValueByKey("extra_retraction_speed")
for layer_number, layer in enumerate(data):
lines = layer.split("\n")
for line_number, line in enumerate(lines):
if self.getValue(line, "G") in {0, 1}: # Track X,Y location.
current_x = self.getValue(line, "X", current_x)
current_y = self.getValue(line, "Y", current_y)
if self.getValue(line, "G") == 1:
if self.getValue(line, "E"):
new_e = self.getValue(line, "E")
if new_e >= current_e: # Not a retraction.
continue
# A retracted travel move may consist of multiple commands, due to combing.
# This continues retracting over all of these moves and only unretracts at the end.
delta_line = 1
dx = current_x # Track the difference in X for this move only to compute the length of the travel.
dy = current_y
while line_number + delta_line < len(lines) and self.getValue(lines[line_number + delta_line], "G") != 1:
travel_move = lines[line_number + delta_line]
if self.getValue(travel_move, "G") != 0:
delta_line += 1
continue
travel_x = self.getValue(travel_move, "X", dx)
travel_y = self.getValue(travel_move, "Y", dy)
f = self.getValue(travel_move, "F", "no f")
length = math.sqrt((travel_x - dx) * (travel_x - dx) + (travel_y - dy) * (travel_y - dy)) # Length of the travel move.
new_e -= length * extra_retraction_speed # New retraction is by ratio of this travel move.
if f == "no f":
new_travel_move = "G1 X{travel_x} Y{travel_y} E{new_e}".format(travel_x = travel_x, travel_y = travel_y, new_e = new_e)
else:
new_travel_move = "G1 F{f} X{travel_x} Y{travel_y} E{new_e}".format(f = f, travel_x = travel_x, travel_y = travel_y, new_e = new_e)
lines[line_number + delta_line] = new_travel_move
delta_line += 1
dx = travel_x
dy = travel_y
current_e = new_e
new_layer = "\n".join(lines)
data[layer_number] = new_layer
return data

View file

@ -77,10 +77,10 @@ class TimeLapse(Script):
gcode_to_append = ";TimeLapse Begin\n" gcode_to_append = ";TimeLapse Begin\n"
if park_print_head: if park_print_head:
gcode_to_append += self.putValue(G = 1, F = feed_rate, X = x_park, Y = y_park) + ";Park print head\n" gcode_to_append += self.putValue(G = 1, F = feed_rate, X = x_park, Y = y_park) + " ;Park print head\n"
gcode_to_append += self.putValue(M = 400) + ";Wait for moves to finish\n" gcode_to_append += self.putValue(M = 400) + " ;Wait for moves to finish\n"
gcode_to_append += trigger_command + ";Snap Photo\n" gcode_to_append += trigger_command + " ;Snap Photo\n"
gcode_to_append += self.putValue(G = 4, P = pause_length) + ";Wait for camera\n" gcode_to_append += self.putValue(G = 4, P = pause_length) + " ;Wait for camera\n"
gcode_to_append += ";TimeLapse End\n" gcode_to_append += ";TimeLapse End\n"
for layer in data: for layer in data:
# Check that a layer is being printed # Check that a layer is being printed

View file

@ -386,7 +386,7 @@ class SimulationView(CuraView):
self._max_thickness = max(float(p.lineThicknesses.max()), self._max_thickness) self._max_thickness = max(float(p.lineThicknesses.max()), self._max_thickness)
try: try:
self._min_thickness = min(float(p.lineThicknesses[numpy.nonzero(p.lineThicknesses)].min()), self._min_thickness) self._min_thickness = min(float(p.lineThicknesses[numpy.nonzero(p.lineThicknesses)].min()), self._min_thickness)
except: except ValueError:
# Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding # Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding
# the zero) can't be calculated # the zero) can't be calculated
Logger.log("i", "Min thickness can't be calculated because all the values are zero") Logger.log("i", "Min thickness can't be calculated because all the values are zero")
@ -468,6 +468,9 @@ class SimulationView(CuraView):
Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged) Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged) self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self.calculateMaxLayers()
self.calculateMaxPathsOnLayer(self._current_layer_num)
# FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching. # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching.
# This can happen when you do the following steps: # This can happen when you do the following steps:
# 1. Start Cura # 1. Start Cura

View file

@ -139,6 +139,10 @@ class SolidView(View):
shade_factor * int(material_color[5:7], 16) / 255, shade_factor * int(material_color[5:7], 16) / 255,
1.0 1.0
] ]
# Color the currently selected face-id.
face = Selection.getSelectedFace()
uniforms["selected_face"] = (Selection.getMaxFaceSelectionId() + 1) if not face or node != face[0] else face[1]
except ValueError: except ValueError:
pass pass

View file

@ -368,9 +368,9 @@ class Toolbox(QObject, Extension):
default_material_node = material_manager.getDefaultMaterial(global_stack, extruder_nr, global_stack.extruders[extruder_nr].variant.getName()) default_material_node = material_manager.getDefaultMaterial(global_stack, extruder_nr, global_stack.extruders[extruder_nr].variant.getName())
machine_manager.setMaterial(extruder_nr, default_material_node, global_stack = global_stack) machine_manager.setMaterial(extruder_nr, default_material_node, global_stack = global_stack)
for global_stack, extruder_nr, container_id in self._package_used_qualities: for global_stack, extruder_nr, container_id in self._package_used_qualities:
variant_names = [extruder.variant.getName() for extruder in global_stack.extruders.values()] variant_names = [extruder.variant.getName() for extruder in global_stack.extruderList]
material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruders.values()] material_bases = [extruder.material.getMetaDataEntry("base_file") for extruder in global_stack.extruderList]
extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruders.values()] extruder_enabled = [extruder.isEnabled for extruder in global_stack.extruderList]
definition_id = global_stack.definition.getId() definition_id = global_stack.definition.getId()
machine_node = container_tree.machines[definition_id] machine_node = container_tree.machines[definition_id]
default_quality_group = machine_node.getQualityGroups(variant_names, material_bases, extruder_enabled)[machine_node.preferred_quality_type] default_quality_group = machine_node.getQualityGroups(variant_names, material_bases, extruder_enabled)[machine_node.preferred_quality_type]

View file

@ -0,0 +1,161 @@
# Copyright (c) 2019 Ultimaker B.V., fieldOfView
# Cura is released under the terms of the LGPLv3 or higher.
# The _toMeshData function is taken from the AMFReader class which was built by fieldOfView.
from typing import Any, List, Union, TYPE_CHECKING
import numpy # To create the mesh data.
import os.path # To create the mesh name for the resulting mesh.
import trimesh # To load the files into a Trimesh.
from UM.Mesh.MeshData import MeshData, calculateNormalsFromIndexedVertices # To construct meshes from the Trimesh data.
from UM.Mesh.MeshReader import MeshReader # The plug-in type we're extending.
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType # To add file types that we can open.
from UM.Scene.GroupDecorator import GroupDecorator # Added to the parent node if we load multiple nodes at once.
from cura.CuraApplication import CuraApplication
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator # Added to the resulting scene node.
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator # Added to group nodes if we load multiple nodes at once.
from cura.Scene.CuraSceneNode import CuraSceneNode # To create a node in the scene after reading the file.
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator # Added to the resulting scene node.
if TYPE_CHECKING:
from UM.Scene.SceneNode import SceneNode
## Class that leverages Trimesh to import files.
class TrimeshReader(MeshReader):
def __init__(self) -> None:
super().__init__()
self._supported_extensions = [".ctm", ".dae", ".gltf", ".glb", ".ply", ".zae"]
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-ctm",
comment = "Open Compressed Triangle Mesh",
suffixes = ["ctm"]
)
)
MimeTypeDatabase.addMimeType(
MimeType(
name = "model/vnd.collada+xml",
comment = "COLLADA Digital Asset Exchange",
suffixes = ["dae"]
)
)
MimeTypeDatabase.addMimeType(
MimeType(
name = "model/gltf-binary",
comment = "glTF Binary",
suffixes = ["glb"]
)
)
MimeTypeDatabase.addMimeType(
MimeType(
name = "model/gltf+json",
comment = "glTF Embedded JSON",
suffixes = ["gltf"]
)
)
# Trimesh seems to have a bug when reading .off files.
#MimeTypeDatabase.addMimeType(
# MimeType(
# name = "application/x-off",
# comment = "Geomview Object File Format",
# suffixes = ["off"]
# )
#)
MimeTypeDatabase.addMimeType(
MimeType(
name = "application/x-ply", # Wikipedia lists the MIME type as "text/plain" but that won't do as it's not unique to PLY files.
comment = "Stanford Triangle Format",
suffixes = ["ply"]
)
)
MimeTypeDatabase.addMimeType(
MimeType(
name = "model/vnd.collada+xml+zip",
comment = "Compressed COLLADA Digital Asset Exchange",
suffixes = ["zae"]
)
)
## Reads a file using Trimesh.
# \param file_name The file path. This is assumed to be one of the file
# types that Trimesh can read. It will not be checked again.
# \return A scene node that contains the file's contents.
def _read(self, file_name: str) -> Union["SceneNode", List["SceneNode"]]:
# CURA-6739
# GLTF files are essentially JSON files. If you directly give a file name to trimesh.load(), it will
# try to figure out the format, but for GLTF, it loads it as a binary file with flags "rb", and the json.load()
# doesn't like it. For some reason, this seems to happen with 3.5.7, but not 3.7.1. Below is a workaround to
# pass a file object that has been opened with "r" instead "rb" to load a GLTF file.
if file_name.lower().endswith(".gltf"):
mesh_or_scene = trimesh.load(open(file_name, "r", encoding = "utf-8"), file_type = "gltf")
else:
mesh_or_scene = trimesh.load(file_name)
meshes = [] # type: List[Union[trimesh.Trimesh, trimesh.Scene, Any]]
if isinstance(mesh_or_scene, trimesh.Trimesh):
meshes = [mesh_or_scene]
elif isinstance(mesh_or_scene, trimesh.Scene):
meshes = [mesh for mesh in mesh_or_scene.geometry.values()]
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
nodes = [] # type: List[SceneNode]
for mesh in meshes:
if not isinstance(mesh, trimesh.Trimesh): # Trimesh can also receive point clouds, 2D paths, 3D paths or metadata. Skip those.
continue
mesh.merge_vertices()
mesh.remove_unreferenced_vertices()
mesh.fix_normals()
mesh_data = self._toMeshData(mesh)
file_base_name = os.path.basename(file_name)
new_node = CuraSceneNode()
new_node.setMeshData(mesh_data)
new_node.setSelectable(True)
new_node.setName(file_base_name if len(meshes) == 1 else "{file_base_name} {counter}".format(file_base_name = file_base_name, counter = str(len(nodes) + 1)))
new_node.addDecorator(BuildPlateDecorator(active_build_plate))
new_node.addDecorator(SliceableObjectDecorator())
nodes.append(new_node)
if len(nodes) == 1:
return nodes[0]
# Add all nodes to a group so they stay together.
group_node = CuraSceneNode()
group_node.addDecorator(GroupDecorator())
group_node.addDecorator(ConvexHullDecorator())
group_node.addDecorator(BuildPlateDecorator(active_build_plate))
for node in nodes:
node.setParent(group_node)
return group_node
## Converts a Trimesh to Uranium's MeshData.
# \param tri_node A Trimesh containing the contents of a file that was
# just read.
# \return Mesh data from the Trimesh in a way that Uranium can understand
# it.
def _toMeshData(self, tri_node: trimesh.base.Trimesh) -> MeshData:
tri_faces = tri_node.faces
tri_vertices = tri_node.vertices
indices = []
vertices = []
index_count = 0
face_count = 0
for tri_face in tri_faces:
face = []
for tri_index in tri_face:
vertices.append(tri_vertices[tri_index])
face.append(index_count)
index_count += 1
indices.append(face)
face_count += 1
vertices = numpy.asarray(vertices, dtype = numpy.float32)
indices = numpy.asarray(indices, dtype = numpy.int32)
normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)
mesh_data = MeshData(vertices = vertices, indices = indices, normals = normals)
return mesh_data

View file

@ -0,0 +1,46 @@
# Copyright (c) 2019 Ultimaker
# Cura is released under the terms of the LGPLv3 or higher.
from . import TrimeshReader
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("uranium")
def getMetaData():
return {
"mesh_reader": [
{
"extension": "ctm",
"description": i18n_catalog.i18nc("@item:inlistbox", "Open Compressed Triangle Mesh")
},
{
"extension": "dae",
"description": i18n_catalog.i18nc("@item:inlistbox", "COLLADA Digital Asset Exchange")
},
{
"extension": "glb",
"description": i18n_catalog.i18nc("@item:inlistbox", "glTF Binary")
},
{
"extension": "gltf",
"description": i18n_catalog.i18nc("@item:inlistbox", "glTF Embedded JSON")
},
# Trimesh seems to have a bug when reading OFF files.
#{
# "extension": "off",
# "description": i18n_catalog.i18nc("@item:inlistbox", "Geomview Object File Format")
#},
{
"extension": "ply",
"description": i18n_catalog.i18nc("@item:inlistbox", "Stanford Triangle Format")
},
{
"extension": "zae",
"description": i18n_catalog.i18nc("@item:inlistbox", "Compressed COLLADA Digital Asset Exchange")
}
]
}
def register(app):
return {"mesh_reader": TrimeshReader.TrimeshReader()}

View file

@ -0,0 +1,7 @@
{
"name": "Trimesh Reader",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for reading model files.",
"api": "6.0.0"
}

View file

@ -39,4 +39,4 @@ class UFPReader(MeshReader):
# Open the GCodeReader to parse the data # Open the GCodeReader to parse the data
gcode_reader = PluginRegistry.getInstance().getPluginObject("GCodeReader") # type: ignore gcode_reader = PluginRegistry.getInstance().getPluginObject("GCodeReader") # type: ignore
gcode_reader.preReadFromStream(gcode_stream) # type: ignore gcode_reader.preReadFromStream(gcode_stream) # type: ignore
return gcode_reader.readFromStream(gcode_stream) # type: ignore return gcode_reader.readFromStream(gcode_stream, file_name) # type: ignore

View file

@ -171,7 +171,11 @@ class CloudOutputDeviceManager:
machine.setName(device.name) machine.setName(device.name)
machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key) machine.setMetaDataEntry(self.META_CLUSTER_ID, device.key)
machine.setMetaDataEntry("group_name", device.name) machine.setMetaDataEntry("group_name", device.name)
device.connect()
machine.addConfiguredConnectionType(device.connectionType.value) machine.addConfiguredConnectionType(device.connectionType.value)
CuraApplication.getInstance().getOutputDeviceManager().addOutputDevice(device)
if not device.isConnected():
device.connect()
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
if device.key not in output_device_manager.getOutputDeviceIds():
output_device_manager.addOutputDevice(device)

View file

@ -1,5 +1,7 @@
# Copyright (c) 2019 Ultimaker B.V. # Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from .ClusterPrintCoreConfiguration import ClusterPrintCoreConfiguration from .ClusterPrintCoreConfiguration import ClusterPrintCoreConfiguration
@ -10,8 +12,11 @@ class ClusterPrinterMaterialStationSlot(ClusterPrintCoreConfiguration):
# \param slot_index: The index of the slot in the material station (ranging 0 to 5). # \param slot_index: The index of the slot in the material station (ranging 0 to 5).
# \param compatible: Whether the configuration is compatible with the print core. # \param compatible: Whether the configuration is compatible with the print core.
# \param material_remaining: How much material is remaining on the spool (between 0 and 1, or -1 for missing data). # \param material_remaining: How much material is remaining on the spool (between 0 and 1, or -1 for missing data).
def __init__(self, slot_index: int, compatible: bool, material_remaining: float, **kwargs): # \param material_empty: Whether the material spool is too empty to be used.
def __init__(self, slot_index: int, compatible: bool, material_remaining: float,
material_empty: Optional[bool] = False, **kwargs):
self.slot_index = slot_index self.slot_index = slot_index
self.compatible = compatible self.compatible = compatible
self.material_remaining = material_remaining self.material_remaining = material_remaining
self.material_empty = material_empty
super().__init__(**kwargs) super().__init__(**kwargs)

View file

@ -66,7 +66,11 @@ class ClusterPrinterStatus(BaseModel):
## Creates a new output model. ## Creates a new output model.
# \param controller - The controller of the model. # \param controller - The controller of the model.
def createOutputModel(self, controller: PrinterOutputController) -> PrinterOutputModel: def createOutputModel(self, controller: PrinterOutputController) -> PrinterOutputModel:
model = PrinterOutputModel(controller, len(self.configuration), firmware_version = self.firmware_version) # FIXME
# Note that we're using '2' here as extruder count. We have hardcoded this for now to prevent issues where the
# amount of extruders coming back from the API is actually lower (which it can be if a printer was just added
# to a cluster). This should be fixed in the future, probably also on the cluster API side.
model = PrinterOutputModel(controller, 2, firmware_version = self.firmware_version)
self.updateOutputModel(model) self.updateOutputModel(model)
return model return model
@ -80,6 +84,11 @@ class ClusterPrinterStatus(BaseModel):
model.updateBuildplate(self.build_plate.type if self.build_plate else "glass") model.updateBuildplate(self.build_plate.type if self.build_plate else "glass")
model.setCameraUrl(QUrl("http://{}:8080/?action=stream".format(self.ip_address))) model.setCameraUrl(QUrl("http://{}:8080/?action=stream".format(self.ip_address)))
if not model.printerConfiguration:
# Prevent accessing printer configuration when not available.
# This sometimes happens when a printer was just added to a group and Cura is connected to that group.
return
# Set the possible configurations based on whether a Material Station is present or not. # Set the possible configurations based on whether a Material Station is present or not.
if self.material_station and self.material_station.material_slots: if self.material_station and self.material_station.material_slots:
self._updateAvailableConfigurations(model) self._updateAvailableConfigurations(model)
@ -115,7 +124,7 @@ class ClusterPrinterStatus(BaseModel):
# We filter out any slot that is not supported by the extruder index, print core type or if the material is empty. # We filter out any slot that is not supported by the extruder index, print core type or if the material is empty.
@staticmethod @staticmethod
def _isSupportedConfiguration(slot: ClusterPrinterMaterialStationSlot, extruder_index: int) -> bool: def _isSupportedConfiguration(slot: ClusterPrinterMaterialStationSlot, extruder_index: int) -> bool:
return slot.extruder_index == extruder_index and slot.compatible return slot.extruder_index == extruder_index and slot.compatible and not slot.material_empty
## Create an empty material slot with a fake empty material. ## Create an empty material slot with a fake empty material.
@staticmethod @staticmethod

View file

@ -135,7 +135,7 @@ class ClusterApiClient:
result = model_class(**response) # type: ClusterApiClientModel result = model_class(**response) # type: ClusterApiClientModel
on_finished_item = cast(Callable[[ClusterApiClientModel], Any], on_finished) on_finished_item = cast(Callable[[ClusterApiClientModel], Any], on_finished)
on_finished_item(result) on_finished_item(result)
except JSONDecodeError: except (JSONDecodeError, TypeError):
Logger.log("e", "Could not parse response from network: %s", str(response)) Logger.log("e", "Could not parse response from network: %s", str(response))
## Creates a callback function so that it includes the parsing of the response into the correct model. ## Creates a callback function so that it includes the parsing of the response into the correct model.

View file

@ -122,9 +122,6 @@ class LocalClusterOutputDevice(UltimakerNetworkedPrinterOutputDevice):
self.writeStarted.emit(self) self.writeStarted.emit(self)
# Make sure the printer is aware of all new materials as the new print job might contain one.
self.sendMaterialProfiles()
# Export the scene to the correct file type. # Export the scene to the correct file type.
job = ExportFileJob(file_handler=file_handler, nodes=nodes, firmware_version=self.firmwareVersion) job = ExportFileJob(file_handler=file_handler, nodes=nodes, firmware_version=self.firmwareVersion)
job.finished.connect(self._onPrintJobCreated) job.finished.connect(self._onPrintJobCreated)

View file

@ -135,10 +135,13 @@ class LocalClusterOutputDeviceManager:
ultimaker_machines = container_registry.findContainersMetadata(type="machine", manufacturer="Ultimaker B.V.") ultimaker_machines = container_registry.findContainersMetadata(type="machine", manufacturer="Ultimaker B.V.")
found_machine_type_identifiers = {} # type: Dict[str, str] found_machine_type_identifiers = {} # type: Dict[str, str]
for machine in ultimaker_machines: for machine in ultimaker_machines:
machine_bom_number = machine.get("firmware_update_info", {}).get("id", None)
machine_type = machine.get("id", None) machine_type = machine.get("id", None)
if machine_bom_number and machine_type: machine_bom_numbers = machine.get("bom_numbers", [])
found_machine_type_identifiers[str(machine_bom_number)] = machine_type if machine_type and machine_bom_numbers:
for bom_number in machine_bom_numbers:
# This produces a n:1 mapping of bom numbers to machine types
# allowing the S5R1 and S5R2 hardware to use a single S5 definition.
found_machine_type_identifiers[str(bom_number)] = machine_type
return found_machine_type_identifiers return found_machine_type_identifiers
## Add a new device. ## Add a new device.
@ -236,7 +239,11 @@ class LocalClusterOutputDeviceManager:
machine.setName(device.name) machine.setName(device.name)
machine.setMetaDataEntry(self.META_NETWORK_KEY, device.key) machine.setMetaDataEntry(self.META_NETWORK_KEY, device.key)
machine.setMetaDataEntry("group_name", device.name) machine.setMetaDataEntry("group_name", device.name)
device.connect()
machine.addConfiguredConnectionType(device.connectionType.value) machine.addConfiguredConnectionType(device.connectionType.value)
CuraApplication.getInstance().getOutputDeviceManager().addOutputDevice(device)
if not device.isConnected():
device.connect()
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
if device.key not in output_device_manager.getOutputDeviceIds():
output_device_manager.addOutputDevice(device)

View file

@ -105,7 +105,6 @@ class SendMaterialJob(Job):
parts.append(self.device.createFormPart("name=\"signature_file\"; filename=\"{file_name}\"" parts.append(self.device.createFormPart("name=\"signature_file\"; filename=\"{file_name}\""
.format(file_name = signature_file_name), f.read())) .format(file_name = signature_file_name), f.read()))
Logger.log("d", "Syncing material %s with cluster.", material_id)
# FIXME: move form posting to API client # FIXME: move form posting to API client
self.device.postFormWithParts(target = "/cluster-api/v1/materials/", parts = parts, self.device.postFormWithParts(target = "/cluster-api/v1/materials/", parts = parts,
on_finished = self._sendingFinished) on_finished = self._sendingFinished)
@ -118,7 +117,6 @@ class SendMaterialJob(Job):
body = reply.readAll().data().decode('utf8') body = reply.readAll().data().decode('utf8')
if "not added" in body: if "not added" in body:
# For some reason the cluster returns a 200 sometimes even when syncing failed. # For some reason the cluster returns a 200 sometimes even when syncing failed.
Logger.log("w", "Error while syncing material: %s", body)
return return
# Inform the user that materials have been synced. This message only shows itself when not already visible. # Inform the user that materials have been synced. This message only shows itself when not already visible.
# Because of the guards above it is not shown when syncing failed (which is not always an actual problem). # Because of the guards above it is not shown when syncing failed (which is not always an actual problem).

View file

@ -1,5 +1,8 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import os.path
import sys
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
import configparser #To check whether the appropriate exceptions are raised. import configparser #To check whether the appropriate exceptions are raised.
import pytest #To register tests with. import pytest #To register tests with.

View file

@ -3,7 +3,9 @@
import configparser #To check whether the appropriate exceptions are raised. import configparser #To check whether the appropriate exceptions are raised.
import pytest #To register tests with. import pytest #To register tests with.
import os.path
import sys
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
import VersionUpgrade26to27 #The module we're testing. import VersionUpgrade26to27 #The module we're testing.
## Creates an instance of the upgrader to test with. ## Creates an instance of the upgrader to test with.

View file

@ -1,6 +1,8 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import os.path
import sys
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
import configparser #To parse the resulting config files. import configparser #To parse the resulting config files.
import pytest #To register tests with. import pytest #To register tests with.

View file

@ -1,6 +1,8 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import os.path
import sys
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
import configparser #To parse the resulting config files. import configparser #To parse the resulting config files.
import pytest #To register tests with. import pytest #To register tests with.

View file

@ -6,7 +6,7 @@ import io
import json #To parse the product-to-id mapping file. import json #To parse the product-to-id mapping file.
import os.path #To find the product-to-id mapping. import os.path #To find the product-to-id mapping.
import sys import sys
from typing import Any, Dict, List, Optional, Tuple, cast, Set from typing import Any, Dict, List, Optional, Tuple, cast, Set, Union
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from UM.Resources import Resources from UM.Resources import Resources
@ -20,7 +20,10 @@ from cura.CuraApplication import CuraApplication
from cura.Machines.ContainerTree import ContainerTree from cura.Machines.ContainerTree import ContainerTree
from cura.Machines.VariantType import VariantType from cura.Machines.VariantType import VariantType
from .XmlMaterialValidator import XmlMaterialValidator try:
from .XmlMaterialValidator import XmlMaterialValidator
except (ImportError, SystemError):
import XmlMaterialValidator # type: ignore # This fixes the tests not being able to import.
## Handles serializing and deserializing material containers from an XML file ## Handles serializing and deserializing material containers from an XML file
@ -41,11 +44,11 @@ class XmlMaterialProfile(InstanceContainer):
# #
# \param xml_version: The version number found in an XML file. # \param xml_version: The version number found in an XML file.
# \return The corresponding setting_version. # \return The corresponding setting_version.
@classmethod @staticmethod
def xmlVersionToSettingVersion(cls, xml_version: str) -> int: def xmlVersionToSettingVersion(xml_version: str) -> int:
if xml_version == "1.3": if xml_version == "1.3":
return CuraApplication.SettingVersion return CuraApplication.SettingVersion
return 0 #Older than 1.3. return 0 # Older than 1.3.
def getInheritedFiles(self): def getInheritedFiles(self):
return self._inherited_files return self._inherited_files
@ -407,7 +410,8 @@ class XmlMaterialProfile(InstanceContainer):
self._combineElement(self._expandMachinesXML(result), self._expandMachinesXML(second)) self._combineElement(self._expandMachinesXML(result), self._expandMachinesXML(second))
return result return result
def _createKey(self, element): @staticmethod
def _createKey(element):
key = element.tag.split("}")[-1] key = element.tag.split("}")[-1]
if "key" in element.attrib: if "key" in element.attrib:
key += " key:" + element.attrib["key"] key += " key:" + element.attrib["key"]
@ -423,15 +427,15 @@ class XmlMaterialProfile(InstanceContainer):
# Recursively merges XML elements. Updates either the text or children if another element is found in first. # Recursively merges XML elements. Updates either the text or children if another element is found in first.
# If it does not exist, copies it from second. # If it does not exist, copies it from second.
def _combineElement(self, first, second): @staticmethod
def _combineElement(first, second):
# Create a mapping from tag name to element. # Create a mapping from tag name to element.
mapping = {} mapping = {}
for element in first: for element in first:
key = self._createKey(element) key = XmlMaterialProfile._createKey(element)
mapping[key] = element mapping[key] = element
for element in second: for element in second:
key = self._createKey(element) key = XmlMaterialProfile._createKey(element)
if len(element): # Check if element has children. if len(element): # Check if element has children.
try: try:
if "setting" in element.tag and not "settings" in element.tag: if "setting" in element.tag and not "settings" in element.tag:
@ -441,7 +445,7 @@ class XmlMaterialProfile(InstanceContainer):
for child in element: for child in element:
mapping[key].append(child) mapping[key].append(child)
else: else:
self._combineElement(mapping[key], element) # Multiple elements, handle those. XmlMaterialProfile._combineElement(mapping[key], element) # Multiple elements, handle those.
except KeyError: except KeyError:
mapping[key] = element mapping[key] = element
first.append(element) first.append(element)
@ -742,9 +746,9 @@ class XmlMaterialProfile(InstanceContainer):
ContainerRegistry.getInstance().addContainer(container_to_add) ContainerRegistry.getInstance().addContainer(container_to_add)
@classmethod @classmethod
def _getSettingsDictForNode(cls, node) -> Tuple[dict, dict]: def _getSettingsDictForNode(cls, node) -> Tuple[Dict[str, Any], Dict[str, Any]]:
node_mapped_settings_dict = dict() node_mapped_settings_dict = dict() # type: Dict[str, Any]
node_unmapped_settings_dict = dict() node_unmapped_settings_dict = dict() # type: Dict[str, Any]
# Fetch settings in the "um" namespace # Fetch settings in the "um" namespace
um_settings = node.iterfind("./um:setting", cls.__namespaces) um_settings = node.iterfind("./um:setting", cls.__namespaces)
@ -1039,8 +1043,8 @@ class XmlMaterialProfile(InstanceContainer):
builder.data(data) builder.data(data)
builder.end(tag_name) builder.end(tag_name)
@classmethod @staticmethod
def _profile_name(cls, material_name, color_name): def _profile_name(material_name, color_name):
if material_name is None: if material_name is None:
return "Unknown Material" return "Unknown Material"
if color_name != "Generic": if color_name != "Generic":
@ -1048,8 +1052,8 @@ class XmlMaterialProfile(InstanceContainer):
else: else:
return material_name return material_name
@classmethod @staticmethod
def getPossibleDefinitionIDsFromName(cls, name): def getPossibleDefinitionIDsFromName(name):
name_parts = name.lower().split(" ") name_parts = name.lower().split(" ")
merged_name_parts = [] merged_name_parts = []
for part in name_parts: for part in name_parts:
@ -1087,8 +1091,8 @@ class XmlMaterialProfile(InstanceContainer):
return product_to_id_map return product_to_id_map
## Parse the value of the "material compatible" property. ## Parse the value of the "material compatible" property.
@classmethod @staticmethod
def _parseCompatibleValue(cls, value: str): def _parseCompatibleValue(value: str):
return value in {"yes", "unknown"} return value in {"yes", "unknown"}
## Small string representation for debugging. ## Small string representation for debugging.
@ -1117,7 +1121,7 @@ class XmlMaterialProfile(InstanceContainer):
"break position": "material_break_retracted_position", "break position": "material_break_retracted_position",
"break speed": "material_break_speed", "break speed": "material_break_speed",
"break temperature": "material_break_temperature" "break temperature": "material_break_temperature"
} } # type: Dict[str, str]
__unmapped_settings = [ __unmapped_settings = [
"hardware compatible", "hardware compatible",
"hardware recommended" "hardware recommended"

View file

@ -6,6 +6,7 @@
"Ultimaker 2+": "ultimaker2_plus", "Ultimaker 2+": "ultimaker2_plus",
"Ultimaker 3": "ultimaker3", "Ultimaker 3": "ultimaker3",
"Ultimaker 3 Extended": "ultimaker3_extended", "Ultimaker 3 Extended": "ultimaker3_extended",
"Ultimaker S3": "ultimaker_s3",
"Ultimaker S5": "ultimaker_s5", "Ultimaker S5": "ultimaker_s5",
"Ultimaker Original": "ultimaker_original", "Ultimaker Original": "ultimaker_original",
"Ultimaker Original+": "ultimaker_original_plus", "Ultimaker Original+": "ultimaker_original_plus",

View file

@ -0,0 +1,79 @@
from unittest.mock import patch, MagicMock
import sys
import os
# Prevents error: "PyCapsule_GetPointer called with incorrect name" with conflicting SIP configurations between Arcus and PyQt: Import Arcus and Savitar first!
import Savitar # Dont remove this line
import Arcus # No really. Don't. It needs to be there!
from UM.Qt.QtApplication import QtApplication # QtApplication import is required, even though it isn't used.
import pytest
import XmlMaterialProfile
def createXmlMaterialProfile(material_id):
try:
return XmlMaterialProfile.XmlMaterialProfile.XmlMaterialProfile(material_id)
except AttributeError:
return XmlMaterialProfile.XmlMaterialProfile(material_id)
def test_setName():
material_1 = createXmlMaterialProfile("herpderp")
material_2 = createXmlMaterialProfile("OMGZOMG")
material_1.getMetaData()["base_file"] = "herpderp"
material_2.getMetaData()["base_file"] = "herpderp"
container_registry = MagicMock()
container_registry.isReadOnly = MagicMock(return_value = False)
container_registry.findInstanceContainers = MagicMock(return_value = [material_1, material_2])
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
material_1.setName("beep!")
assert material_1.getName() == "beep!"
assert material_2.getName() == "beep!"
def test_setDirty():
material_1 = createXmlMaterialProfile("herpderp")
material_2 = createXmlMaterialProfile("OMGZOMG")
material_1.getMetaData()["base_file"] = "herpderp"
material_2.getMetaData()["base_file"] = "herpderp"
container_registry = MagicMock()
container_registry.isReadOnly = MagicMock(return_value=False)
container_registry.findContainers = MagicMock(return_value=[material_1, material_2])
# Sanity check. Since we did a hacky thing to set the metadata, the container should not be dirty.
# But this test assumes that it works like that, so we need to validate that.
assert not material_1.isDirty()
assert not material_2.isDirty()
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
material_2.setDirty(True)
assert material_1.isDirty()
assert material_2.isDirty()
# Setting the base material dirty does not set it's child as dirty.
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
material_1.setDirty(False)
assert not material_1.isDirty()
assert material_2.isDirty()
def test_serializeNonBaseMaterial():
material_1 = createXmlMaterialProfile("herpderp")
material_1.getMetaData()["base_file"] = "omgzomg"
container_registry = MagicMock()
container_registry.isReadOnly = MagicMock(return_value=False)
container_registry.findContainers = MagicMock(return_value=[material_1])
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
with pytest.raises(NotImplementedError):
# This material is not a base material, so it can't be serialized!
material_1.serialize()

View file

@ -2,146 +2,6 @@
"name": "Creawsome Base Printer", "name": "Creawsome Base Printer",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",
"overrides": {
"machine_name": { "default_value": "Creawsome Base Printer" },
"machine_start_gcode": { "default_value": "M201 X500.00 Y500.00 Z100.00 E5000.00 ;Setup machine max acceleration\nM203 X500.00 Y500.00 Z10.00 E50.00 ;Setup machine max feedrate\nM204 P500.00 R1000.00 T500.00 ;Setup Print/Retract/Travel acceleration\nM205 X8.00 Y8.00 Z0.40 E5.00 ;Setup Jerk\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\n\nG28 ;Home\n\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\n"},
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\n\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z\n" },
"machine_max_feedrate_x": { "value": 500 },
"machine_max_feedrate_y": { "value": 500 },
"machine_max_feedrate_z": { "value": 10 },
"machine_max_feedrate_e": { "value": 50 },
"machine_max_acceleration_x": { "value": 500 },
"machine_max_acceleration_y": { "value": 500 },
"machine_max_acceleration_z": { "value": 100 },
"machine_max_acceleration_e": { "value": 5000 },
"machine_acceleration": { "value": 500 },
"machine_max_jerk_xy": { "value": 10 },
"machine_max_jerk_z": { "value": 0.4 },
"machine_max_jerk_e": { "value": 5 },
"machine_heated_bed": { "default_value": true },
"material_diameter": { "default_value": 1.75 },
"acceleration_print": { "value": 500 },
"acceleration_travel": { "value": 500 },
"acceleration_travel_layer_0": { "value": "acceleration_travel" },
"acceleration_roofing": { "enabled": "acceleration_enabled and roofing_layer_count > 0 and top_layers > 0" },
"jerk_print": { "value": 8 },
"jerk_travel": { "value": "jerk_print" },
"jerk_travel_layer_0": { "value": "jerk_travel" },
"acceleration_enabled": { "value": false },
"jerk_enabled": { "value": false },
"speed_print": { "value": 50.0 } ,
"speed_infill": { "value": "speed_print" },
"speed_wall": { "value": "speed_print / 2" },
"speed_wall_0": { "value": "speed_wall" },
"speed_wall_x": { "value": "speed_wall" },
"speed_topbottom": { "value": "speed_print / 2" },
"speed_roofing": { "value": "speed_topbottom" },
"speed_travel": { "value": "150.0 if speed_print < 60 else 250.0 if speed_print > 100 else speed_print * 2.5" },
"speed_layer_0": { "value": 20.0 },
"speed_print_layer_0": { "value": "speed_layer_0" },
"speed_travel_layer_0": { "value": "100 if speed_layer_0 < 20 else 150 if speed_layer_0 > 30 else speed_layer_0 * 5" },
"speed_prime_tower": { "value": "speed_topbottom" },
"speed_support": { "value": "speed_wall_0" },
"speed_support_interface": { "value": "speed_topbottom" },
"speed_z_hop": {"value": 5},
"skirt_brim_speed": { "value": "speed_layer_0" },
"line_width": { "value": "machine_nozzle_size * 1.1"},
"material_initial_print_temperature": { "value": "material_print_temperature"},
"material_final_print_temperature": { "value": "material_print_temperature"},
"material_flow": { "value": 100},
"z_seam_type": { "value": "'back'"},
"z_seam_corner": { "value": "'z_seam_corner_none'"},
"infill_sparse_density": { "value": "20"},
"infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'"},
"infill_before_walls": { "value": false },
"infill_overlap": { "value": 30.0 },
"skin_overlap": { "value": 10.0 },
"infill_wipe_dist": { "value": 0.0 },
"wall_0_wipe_dist": { "value": 0.0 },
"fill_perimeter_gaps": { "value": "'everywhere'" },
"fill_outline_gaps": { "value": false },
"filter_out_tiny_gaps": { "value": false },
"retraction_speed": {
"maximum_value_warning": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value": 200
},
"retraction_retract_speed": {
"maximum_value_warning": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value": 200
},
"retraction_prime_speed": {
"maximum_value_warning": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value": 200
},
"retraction_hop_enabled": { "value": "support_enable" },
"retraction_hop": { "value": 0.2 },
"retraction_combing": { "value": "'off' if retraction_hop_enabled else 'infill'"},
"retraction_combing_max_distance": { "value": 30},
"travel_avoid_other_parts": { "value": true },
"travel_avoid_supports": { "value": true },
"travel_retract_before_outer_wall": { "value": true },
"retraction_enable": { "value": true },
"retraction_count_max": { "value": 100 },
"retraction_extrusion_window": { "value": 10 },
"retraction_min_travel": { "value": 1.5 },
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_fan_enabled": { "value": true },
"cool_min_layer_time": { "value": 10 },
"adhesion_type": { "value": "'skirt'" },
"brim_replaces_support": { "value": false },
"skirt_gap": { "value": 10.0 },
"skirt_line_count": { "value": 4 },
"adaptive_layer_height_variation": { "value": 0.04},
"adaptive_layer_height_variation_step": { "value": 0.04 },
"meshfix_maximum_resolution": { "value": "0.05" },
"meshfix_maximum_travel_resolution": { "value": "meshfix_maximum_resolution" },
"support_type": { "value": "'buildplate'"},
"support_angle": { "value": "math.floor(math.degrees(math.atan(line_width/2.0/layer_height)))" },
"support_pattern": { "value": "'zigzag'" },
"support_infill_rate": { "value": "0 if support_tree_enable else 20" },
"support_use_towers": { "value": false },
"support_xy_distance": { "value": "wall_line_width_0 * 2" },
"support_xy_distance_overhang": { "value": "wall_line_width_0" },
"support_z_distance": { "value": "layer_height if layer_height >= 0.16 else layer_height*2" },
"support_xy_overrides_z": { "value": "'xy_overrides_z'" },
"support_wall_count": { "value": 1},
"support_brim_enable": { "value": true},
"support_brim_width": { "value": 4},
"support_interface_enable": { "value": true },
"support_interface_height": { "value": "layer_height * 4" },
"support_interface_density": { "value": 33.333 },
"support_interface_pattern": { "value": "'grid'" },
"support_interface_skip_height": { "value": 0.2},
"minimum_support_area": { "value": 10},
"minimum_interface_area": { "value": 10},
"top_bottom_thickness": {"value": "layer_height_0 + layer_height * 3"},
"wall_thickness": {"value": "line_width * 2"}
},
"metadata": { "metadata": {
"visible": false, "visible": false,
"author": "trouch.com", "author": "trouch.com",
@ -261,5 +121,147 @@
"zyyx_pro_flex", "zyyx_pro_flex",
"zyyx_pro_pla" "zyyx_pro_pla"
] ]
},
"overrides": {
"machine_name": { "default_value": "Creawsome Base Printer" },
"machine_start_gcode": { "default_value": "M201 X500.00 Y500.00 Z100.00 E5000.00 ;Setup machine max acceleration\nM203 X500.00 Y500.00 Z10.00 E50.00 ;Setup machine max feedrate\nM204 P500.00 R1000.00 T500.00 ;Setup Print/Retract/Travel acceleration\nM205 X8.00 Y8.00 Z0.40 E5.00 ;Setup Jerk\nM220 S100 ;Reset Feedrate\nM221 S100 ;Reset Flowrate\n\nG28 ;Home\n\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\nG1 X10.1 Y20 Z0.28 F5000.0 ;Move to start position\nG1 X10.1 Y200.0 Z0.28 F1500.0 E15 ;Draw the first line\nG1 X10.4 Y200.0 Z0.28 F5000.0 ;Move to side a little\nG1 X10.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line\nG92 E0 ;Reset Extruder\nG1 Z2.0 F3000 ;Move Z Axis up\n" },
"machine_end_gcode": { "default_value": "G91 ;Relative positionning\nG1 E-2 F2700 ;Retract a bit\nG1 E-2 Z0.2 F2400 ;Retract and raise Z\nG1 X5 Y5 F3000 ;Wipe out\nG1 Z10 ;Raise Z more\nG90 ;Absolute positionning\n\nG1 X0 Y{machine_depth} ;Present print\nM106 S0 ;Turn-off fan\nM104 S0 ;Turn-off hotend\nM140 S0 ;Turn-off bed\n\nM84 X Y E ;Disable all steppers but Z\n" },
"machine_max_feedrate_x": { "value": 500 },
"machine_max_feedrate_y": { "value": 500 },
"machine_max_feedrate_z": { "value": 10 },
"machine_max_feedrate_e": { "value": 50 },
"machine_max_acceleration_x": { "value": 500 },
"machine_max_acceleration_y": { "value": 500 },
"machine_max_acceleration_z": { "value": 100 },
"machine_max_acceleration_e": { "value": 5000 },
"machine_acceleration": { "value": 500 },
"machine_max_jerk_xy": { "value": 10 },
"machine_max_jerk_z": { "value": 0.4 },
"machine_max_jerk_e": { "value": 5 },
"machine_heated_bed": { "default_value": true },
"material_diameter": { "default_value": 1.75 },
"acceleration_print": { "value": 500 },
"acceleration_travel": { "value": 500 },
"acceleration_travel_layer_0": { "value": "acceleration_travel" },
"acceleration_roofing": { "enabled": "acceleration_enabled and roofing_layer_count > 0 and top_layers > 0" },
"jerk_print": { "value": 8 },
"jerk_travel": { "value": "jerk_print" },
"jerk_travel_layer_0": { "value": "jerk_travel" },
"acceleration_enabled": { "value": false },
"jerk_enabled": { "value": false },
"speed_print": { "value": 50.0 } ,
"speed_infill": { "value": "speed_print" },
"speed_wall": { "value": "speed_print / 2" },
"speed_wall_0": { "value": "speed_wall" },
"speed_wall_x": { "value": "speed_wall" },
"speed_topbottom": { "value": "speed_print / 2" },
"speed_roofing": { "value": "speed_topbottom" },
"speed_travel": { "value": "150.0 if speed_print < 60 else 250.0 if speed_print > 100 else speed_print * 2.5" },
"speed_layer_0": { "value": 20.0 },
"speed_print_layer_0": { "value": "speed_layer_0" },
"speed_travel_layer_0": { "value": "100 if speed_layer_0 < 20 else 150 if speed_layer_0 > 30 else speed_layer_0 * 5" },
"speed_prime_tower": { "value": "speed_topbottom" },
"speed_support": { "value": "speed_wall_0" },
"speed_support_interface": { "value": "speed_topbottom" },
"speed_z_hop": { "value": 5 },
"skirt_brim_speed": { "value": "speed_layer_0" },
"line_width": { "value": "machine_nozzle_size" },
"optimize_wall_printing_order": { "value": "True" },
"material_initial_print_temperature": { "value": "material_print_temperature" },
"material_final_print_temperature": { "value": "material_print_temperature" },
"material_flow": { "value": 100 },
"travel_compensate_overlapping_walls_0_enabled": { "value": "False" },
"z_seam_type": { "value": "'back'" },
"z_seam_corner": { "value": "'z_seam_corner_weighted'" },
"infill_sparse_density": { "value": "20" },
"infill_pattern": { "value": "'lines' if infill_sparse_density > 50 else 'cubic'" },
"infill_before_walls": { "value": false },
"infill_overlap": { "value": 30.0 },
"skin_overlap": { "value": 10.0 },
"infill_wipe_dist": { "value": 0.0 },
"wall_0_wipe_dist": { "value": 0.0 },
"fill_perimeter_gaps": { "value": "'everywhere'" },
"fill_outline_gaps": { "value": false },
"filter_out_tiny_gaps": { "value": false },
"retraction_speed": {
"maximum_value_warning": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value": 200
},
"retraction_retract_speed": {
"maximum_value_warning": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value": 200
},
"retraction_prime_speed": {
"maximum_value_warning": "machine_max_feedrate_e if retraction_enable else float('inf')",
"maximum_value": 200
},
"retraction_hop_enabled": { "value": "False" },
"retraction_hop": { "value": 0.2 },
"retraction_combing": { "value": "'off' if retraction_hop_enabled else 'noskin'" },
"retraction_combing_max_distance": { "value": 30 },
"travel_avoid_other_parts": { "value": true },
"travel_avoid_supports": { "value": true },
"travel_retract_before_outer_wall": { "value": true },
"retraction_enable": { "value": true },
"retraction_count_max": { "value": 100 },
"retraction_extrusion_window": { "value": 10 },
"retraction_min_travel": { "value": 1.5 },
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
"cool_fan_enabled": { "value": true },
"cool_min_layer_time": { "value": 10 },
"adhesion_type": { "value": "'skirt'" },
"brim_replaces_support": { "value": false },
"skirt_gap": { "value": 10.0 },
"skirt_line_count": { "value": 3 },
"adaptive_layer_height_variation": { "value": 0.04 },
"adaptive_layer_height_variation_step": { "value": 0.04 },
"meshfix_maximum_resolution": { "value": "0.05" },
"meshfix_maximum_travel_resolution": { "value": "meshfix_maximum_resolution" },
"support_angle": { "value": "math.floor(math.degrees(math.atan(line_width/2.0/layer_height)))" },
"support_pattern": { "value": "'zigzag'" },
"support_infill_rate": { "value": "0 if support_tree_enable else 20" },
"support_use_towers": { "value": false },
"support_xy_distance": { "value": "wall_line_width_0 * 2" },
"support_xy_distance_overhang": { "value": "wall_line_width_0" },
"support_z_distance": { "value": "layer_height if layer_height >= 0.16 else layer_height*2" },
"support_xy_overrides_z": { "value": "'xy_overrides_z'" },
"support_wall_count": { "value": 1 },
"support_brim_enable": { "value": true },
"support_brim_width": { "value": 4 },
"support_interface_enable": { "value": true },
"support_interface_height": { "value": "layer_height * 4" },
"support_interface_density": { "value": 33.333 },
"support_interface_pattern": { "value": "'grid'" },
"support_interface_skip_height": { "value": 0.2 },
"minimum_support_area": { "value": 5 },
"minimum_interface_area": { "value": 10 },
"top_bottom_thickness": {"value": "layer_height_0 + layer_height * 3" },
"wall_thickness": {"value": "line_width * 2" }
} }
} }

View file

@ -13,10 +13,10 @@
"machine_depth": { "default_value": 220 }, "machine_depth": { "default_value": 220 },
"machine_height": { "default_value": 250 }, "machine_height": { "default_value": 250 },
"machine_head_polygon": { "default_value": [ "machine_head_polygon": { "default_value": [
[-26, 34], [-1, 1],
[-26, -32], [-1, -1],
[22, -32], [1, -1],
[22, 34] [1, 1]
] ]
}, },
"machine_head_with_fans_polygon": { "default_value": [ "machine_head_with_fans_polygon": { "default_value": [

View file

@ -2511,6 +2511,7 @@
"minimum_value": "5", "minimum_value": "5",
"minimum_value_warning": "50", "minimum_value_warning": "50",
"maximum_value_warning": "150", "maximum_value_warning": "150",
"enabled": "support_enable",
"limit_to_extruder": "support_infill_extruder_nr", "limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
@ -3891,7 +3892,7 @@
"value": "retraction_hop", "value": "retraction_hop",
"minimum_value_warning": "0", "minimum_value_warning": "0",
"maximum_value_warning": "10", "maximum_value_warning": "10",
"enabled": "retraction_enable and retraction_hop_after_extruder_switch", "enabled": "retraction_enable and retraction_hop_after_extruder_switch and extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
} }

View file

@ -5,7 +5,7 @@
"metadata": { "metadata": {
"visible": true, "visible": true,
"author": "Calvindog717", "author": "Calvindog717",
"manufacturer": "PrintrBot", "manufacturer": "Printrbot",
"platform": "printrbot_simple_metal_platform.stl", "platform": "printrbot_simple_metal_platform.stl",
"platform_offset": [0, -3.45, 0], "platform_offset": [0, -3.45, 0],
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",

View file

@ -5,7 +5,7 @@
"metadata": { "metadata": {
"visible": true, "visible": true,
"author": "samsector", "author": "samsector",
"manufacturer": "PrintrBot", "manufacturer": "Printrbot",
"platform": "printrbot_simple_metal_upgrade.stl", "platform": "printrbot_simple_metal_upgrade.stl",
"platform_offset": [0, -0.3, 0], "platform_offset": [0, -0.3, 0],
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",

View file

@ -33,7 +33,10 @@
"https://software.ultimaker.com/releases/firmware/9066/stable/um-update.swu.version" "https://software.ultimaker.com/releases/firmware/9066/stable/um-update.swu.version"
], ],
"update_url": "https://ultimaker.com/firmware" "update_url": "https://ultimaker.com/firmware"
} },
"bom_numbers": [
9066
]
}, },

View file

@ -30,7 +30,10 @@
"https://software.ultimaker.com/releases/firmware/9066/stable/um-update.swu.version" "https://software.ultimaker.com/releases/firmware/9066/stable/um-update.swu.version"
], ],
"update_url": "https://ultimaker.com/firmware" "update_url": "https://ultimaker.com/firmware"
} },
"bom_numbers": [
9511
]
}, },
"overrides": { "overrides": {

View file

@ -0,0 +1,168 @@
{
"id": "ultimaker_s3",
"version": 2,
"name": "Ultimaker S3",
"inherits": "ultimaker",
"metadata": {
"author": "Ultimaker",
"manufacturer": "Ultimaker B.V.",
"category": "Ultimaker",
"visible": true,
"file_formats": "application/x-ufp;text/x-gcode",
"platform": "ultimaker_s3_platform.obj",
"platform_texture": "UltimakerS3backplate.png",
"platform_offset": [0, 0, 0],
"has_machine_quality": true,
"has_materials": true,
"has_variant_buildplates": false,
"has_variants": true,
"exclude_materials": [ "generic_hips", "generic_petg", "structur3d_dap100silicone" ],
"preferred_variant_name": "AA 0.4",
"preferred_quality_type": "normal",
"variants_name": "Print core",
"nozzle_offsetting_for_disallowed_areas": false,
"machine_extruder_trains":
{
"0": "ultimaker_s3_extruder_left",
"1": "ultimaker_s3_extruder_right"
},
"first_start_actions": [ "DiscoverUM3Action" ],
"supported_actions": [ "DiscoverUM3Action" ],
"supports_usb_connection": false,
"weight": -1,
"firmware_update_info": {
"id": 213482,
"check_urls": ["https://software.ultimaker.com/releases/firmware/213482/stable/um-update.swu.version"],
"update_url": "https://ultimaker.com/firmware"
},
"bom_numbers": [
213482
]
},
"overrides": {
"machine_name": { "default_value": "Ultimaker S3" },
"machine_width": { "default_value": 230 },
"machine_depth": { "default_value": 190 },
"machine_height": { "default_value": 200 },
"machine_heated_bed": { "default_value": true },
"machine_nozzle_heat_up_speed": { "default_value": 1.4 },
"machine_nozzle_cool_down_speed": { "default_value": 0.8 },
"machine_head_with_fans_polygon":
{
"default_value":
[
[ -41.4, -45.8 ],
[ -41.4, 36.0 ],
[ 63.3, 36.0 ],
[ 63.3, -45.8 ]
]
},
"machine_gcode_flavor": { "default_value": "Griffin" },
"machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 },
"machine_acceleration": { "default_value": 3000 },
"gantry_height": { "value": "60" },
"machine_extruder_count": { "default_value": 2 },
"extruder_prime_pos_abs": { "default_value": true },
"machine_start_gcode": { "default_value": "" },
"machine_end_gcode": { "default_value": "" },
"prime_tower_position_x": { "default_value": 345 },
"prime_tower_position_y": { "default_value": 222.5 },
"prime_blob_enable": { "enabled": true, "default_value": false },
"speed_travel":
{
"maximum_value": "150",
"value": "150"
},
"acceleration_enabled": { "value": "True" },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
"acceleration_print": { "value": "4000" },
"acceleration_support": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
"acceleration_support_interface": { "value": "acceleration_topbottom" },
"acceleration_topbottom": { "value": "math.ceil(acceleration_print * 500 / 4000)" },
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 4000)" },
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 500 / 1000)" },
"brim_width": { "value": "3" },
"cool_fan_full_at_height": { "value": "layer_height_0 + 4 * layer_height" },
"cool_fan_speed": { "value": "50" },
"cool_fan_speed_max": { "value": "100" },
"cool_min_speed": { "value": "5" },
"infill_line_width": { "value": "round(line_width * 0.5 / 0.35, 2)" },
"infill_overlap": { "value": "0" },
"infill_pattern": { "value": "'triangles'" },
"infill_wipe_dist": { "value": "0" },
"jerk_enabled": { "value": "True" },
"jerk_layer_0": { "value": "jerk_topbottom" },
"jerk_prime_tower": { "value": "math.ceil(jerk_print * 15 / 25)" },
"jerk_print": { "value": "25" },
"jerk_support": { "value": "math.ceil(jerk_print * 15 / 25)" },
"jerk_support_interface": { "value": "jerk_topbottom" },
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
"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" },
"machine_min_cool_heat_time_window": { "value": "15" },
"default_material_print_temperature": { "value": "200" },
"material_standby_temperature": { "value": "100" },
"multiple_mesh_overlap": { "value": "0" },
"prime_tower_enable": { "value": "True" },
"raft_airgap": { "value": "0" },
"raft_base_speed": { "value": "20" },
"raft_base_thickness": { "value": "0.3" },
"raft_interface_line_spacing": { "value": "0.5" },
"raft_interface_line_width": { "value": "0.5" },
"raft_interface_speed": { "value": "20" },
"raft_interface_thickness": { "value": "0.2" },
"raft_jerk": { "value": "jerk_layer_0" },
"raft_margin": { "value": "10" },
"raft_speed": { "value": "25" },
"raft_surface_layers": { "value": "1" },
"retraction_amount": { "value": "6.5" },
"retraction_count_max": { "value": "10" },
"retraction_extrusion_window": { "value": "1" },
"retraction_hop": { "value": "2" },
"retraction_hop_enabled": { "value": "extruders_enabled_count > 1" },
"retraction_hop_only_when_collides": { "value": "True" },
"retraction_min_travel": { "value": "5" },
"retraction_prime_speed": { "value": "15" },
"skin_overlap": { "value": "10" },
"speed_equalize_flow_enabled": { "value": "True" },
"speed_layer_0": { "value": "20" },
"speed_prime_tower": { "value": "speed_topbottom" },
"speed_print": { "value": "35" },
"speed_support": { "value": "speed_wall_0" },
"speed_support_interface": { "value": "speed_topbottom" },
"speed_topbottom": { "value": "math.ceil(speed_print * 20 / 35)" },
"speed_wall": { "value": "math.ceil(speed_print * 30 / 35)" },
"speed_wall_0": { "value": "math.ceil(speed_wall * 20 / 30)" },
"speed_wall_x": { "value": "speed_wall" },
"support_angle": { "value": "45" },
"support_pattern": { "value": "'triangles'" },
"support_use_towers": { "value": "False" },
"support_xy_distance": { "value": "wall_line_width_0 * 2.5" },
"support_xy_distance_overhang": { "value": "wall_line_width_0" },
"support_z_distance": { "value": "0" },
"switch_extruder_prime_speed": { "value": "15" },
"switch_extruder_retraction_amount": { "value": "8" },
"top_bottom_thickness": { "value": "1" },
"travel_avoid_supports": { "value": "True" },
"travel_avoid_distance": { "value": "3 if extruders_enabled_count > 1 else machine_nozzle_tip_outer_diameter / 2 * 1.5" },
"wall_0_inset": { "value": "0" },
"wall_line_width_x": { "value": "round(line_width * 0.3 / 0.35, 2)" },
"wall_thickness": { "value": "1" },
"meshfix_maximum_resolution": { "value": "(speed_wall_0 + speed_wall_x) / 60" },
"meshfix_maximum_deviation": { "value": "layer_height / 2" },
"optimize_wall_printing_order": { "value": "True" },
"retraction_combing": { "default_value": "all" },
"initial_layer_line_width_factor": { "value": "120" },
"zig_zaggify_infill": { "value": "gradual_infill_steps == 0" }
}
}

View file

@ -35,7 +35,10 @@
"id": 9051, "id": 9051,
"check_urls": ["https://software.ultimaker.com/releases/firmware/9051/stable/um-update.swu.version"], "check_urls": ["https://software.ultimaker.com/releases/firmware/9051/stable/um-update.swu.version"],
"update_url": "https://ultimaker.com/firmware" "update_url": "https://ultimaker.com/firmware"
} },
"bom_numbers": [
9051, 214475
]
}, },
"overrides": { "overrides": {

View file

@ -0,0 +1,30 @@
{
"id": "ultimaker_s3_extruder_left",
"version": 2,
"name": "Extruder 1",
"inherits": "fdmextruder",
"metadata": {
"machine": "ultimaker_s3",
"position": "0"
},
"overrides": {
"extruder_nr": {
"default_value": 0,
"maximum_value": "1"
},
"machine_nozzle_offset_x": { "default_value": 0 },
"machine_nozzle_offset_y": { "default_value": 0 },
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "default_value": 180 },
"machine_extruder_start_pos_y": { "default_value": 180 },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "default_value": 180 },
"machine_extruder_end_pos_y": { "default_value": 180 },
"machine_nozzle_head_distance": { "default_value": 2.7 },
"extruder_prime_pos_x": { "default_value": -3 },
"extruder_prime_pos_y": { "default_value": 6 },
"extruder_prime_pos_z": { "default_value": 2 }
}
}

View file

@ -0,0 +1,30 @@
{
"id": "ultimaker_s3_extruder_right",
"version": 2,
"name": "Extruder 2",
"inherits": "fdmextruder",
"metadata": {
"machine": "ultimaker_s3",
"position": "1"
},
"overrides": {
"extruder_nr": {
"default_value": 1,
"maximum_value": "1"
},
"machine_nozzle_offset_x": { "default_value": 22 },
"machine_nozzle_offset_y": { "default_value": 0 },
"machine_extruder_start_pos_abs": { "default_value": true },
"machine_extruder_start_pos_x": { "default_value": 180 },
"machine_extruder_start_pos_y": { "default_value": 180 },
"machine_extruder_end_pos_abs": { "default_value": true },
"machine_extruder_end_pos_x": { "default_value": 180 },
"machine_extruder_end_pos_y": { "default_value": 180 },
"machine_nozzle_head_distance": { "default_value": 4.2 },
"extruder_prime_pos_x": { "default_value": 180 },
"extruder_prime_pos_y": { "default_value": 6 },
"extruder_prime_pos_z": { "default_value": 2 }
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -4,7 +4,7 @@ name = Smooth (TEST INTENT)
definition = ultimaker3 definition = ultimaker3
[metadata] [metadata]
setting_version = 8 setting_version = 9
type = intent type = intent
intent_category = smooth intent_category = smooth
quality_type = draft quality_type = draft

View file

@ -4,7 +4,7 @@ name = Strong (TEST INTENT)
definition = ultimaker3 definition = ultimaker3
[metadata] [metadata]
setting_version = 8 setting_version = 9
type = intent type = intent
intent_category = engineering intent_category = engineering
quality_type = draft quality_type = draft

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,65 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import UM 1.2 as UM
// The labelBar shows a set of labels that are evenly spaced from oneother.
// The first item is aligned to the left, the last is aligned to the right.
// It's intended to be used together with RadioCheckBar. As such, it needs
// to know what the used itemSize is, so it can ensure the labels are aligned correctly.
Item
{
id: base
property var model: null
property string modelKey: ""
property int itemSize: 14
height: childrenRect.height
RowLayout
{
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
Repeater
{
id: repeater
model: base.model
Item
{
Layout.fillWidth: true
Layout.maximumWidth: Math.round(index + 1 === repeater.count || repeater.count <= 1 ? itemSize : base.width / (repeater.count - 1))
height: label.height
Label
{
id: label
text: model[modelKey]
color: UM.Theme.getColor("text")
font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
height: contentHeight
anchors
{
// Some magic to ensure that the items are aligned properly.
// We want the following:
// First item should be aligned to the left, no margin.
// Last item should be aligned to the right, no margin.
// The middle item(s) should be aligned to the center of the "item" it's showing (hence half the itemsize as offset).
// We want the center of the label to align with the center of the item, so we negatively offset by half the contentWidth
right: index + 1 === repeater.count ? parent.right: undefined
left: index + 1 === repeater.count || index === 0 ? undefined: parent.left
leftMargin: Math.round((itemSize - contentWidth) * 0.5)
// For some reason, the last label in the row gets misaligned with Qt 5.10. This lines seems to
// fix it.
verticalCenter: parent.verticalCenter
}
}
}
}
}
}

View file

@ -1,36 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.4
import UM 1.2 as UM
import Cura 1.0 as Cura
Menu
{
id: menu
title: "Build plate"
property var buildPlateModel: CuraApplication.getBuildPlateModel()
Instantiator
{
model: menu.buildPlateModel
MenuItem {
text: model.name
checkable: true
checked: model.name == Cura.MachineManager.globalVariantName
exclusiveGroup: group
onTriggered: {
Cura.MachineManager.setGlobalVariant(model.container_node);
}
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
ExclusiveGroup { id: group }
}

View file

@ -1,8 +1,8 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 2.0 import QtQuick.Controls 2.3
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM import UM 1.2 as UM
@ -99,12 +99,14 @@ Cura.ExpandablePopup
left: extruderIcon.right left: extruderIcon.right
leftMargin: UM.Theme.getSize("default_margin").width leftMargin: UM.Theme.getSize("default_margin").width
top: typeAndBrandNameLabel.bottom top: typeAndBrandNameLabel.bottom
right: parent.right
rightMargin: UM.Theme.getSize("default_margin").width
} }
} }
} }
} }
//Placeholder text if there is a configuration to select but no materials (so we can't show the materials per extruder). // Placeholder text if there is a configuration to select but no materials (so we can't show the materials per extruder).
Label Label
{ {
text: catalog.i18nc("@label", "Select configuration") text: catalog.i18nc("@label", "Select configuration")

View file

@ -1,64 +0,0 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.4
import UM 1.2 as UM
import Cura 1.6 as Cura
Menu
{
id: menu
title: "Intent"
property int extruderIndex: 0
Cura.IntentCategoryModel
{
id: intentCategoryModel
}
Instantiator
{
model: intentCategoryModel
MenuItem //Section header.
{
text: model.name
enabled: false
checked: false
property var per_category_intents: Cura.IntentModel
{
id: intentModel
intentCategory: model.intent_category
}
property var intent_instantiator: Instantiator
{
model: intentModel
MenuItem
{
text: model.name
checkable: true
checked: false
Binding on checked
{
when: Cura.MachineManager.activeStack != null
value: Cura.MachineManager.activeStack.intent.metaData["intent_category"] == intentModel.intentCategory && Cura.MachineManager.activeStack.quality.metaData["quality_type"] == model.quality_type
}
exclusiveGroup: group
onTriggered: Cura.IntentManager.selectIntent(intentModel.intentCategory, model.quality_type)
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
ExclusiveGroup { id: group }
}

View file

@ -1,84 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.4
import UM 1.2 as UM
import Cura 1.0 as Cura
Menu
{
id: menu
Instantiator
{
model: Cura.QualityProfilesDropDownMenuModel
MenuItem
{
text:
{
var full_text = (model.layer_height != "") ? model.name + " - " + model.layer_height + model.layer_height_unit : model.name
full_text += model.is_experimental ? " - " + catalog.i18nc("@label", "Experimental") : ""
return full_text
}
checkable: true
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
exclusiveGroup: group
onTriggered: Cura.MachineManager.setQualityGroup(model.quality_group)
visible: model.available
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
MenuSeparator
{
id: customSeparator
visible: Cura.CustomQualityProfilesDropDownMenuModel.count > 0
}
Instantiator
{
id: customProfileInstantiator
model: Cura.CustomQualityProfilesDropDownMenuModel
Connections
{
target: Cura.CustomQualityProfilesDropDownMenuModel
onModelReset: customSeparator.visible = Cura.CustomQualityProfilesDropDownMenuModel.count > 0
}
MenuItem
{
text: model.name
checkable: true
checked: Cura.MachineManager.activeQualityOrQualityChangesName == model.name
exclusiveGroup: group
onTriggered: Cura.MachineManager.setQualityChangesGroup(model.quality_changes_group)
}
onObjectAdded:
{
customSeparator.visible = model.count > 0;
menu.insertItem(index, object);
}
onObjectRemoved:
{
customSeparator.visible = model.count > 0;
menu.removeItem(object);
}
}
ExclusiveGroup { id: group; }
MenuSeparator { id: profileMenuSeparator }
MenuItem { action: Cura.Actions.addProfile }
MenuItem { action: Cura.Actions.updateProfile }
MenuItem { action: Cura.Actions.resetProfile }
MenuSeparator { }
MenuItem { action: Cura.Actions.manageProfiles }
}

View file

@ -57,14 +57,6 @@ Menu
onObjectRemoved: base.removeItem(object) onObjectRemoved: base.removeItem(object)
} }
// TODO Only show in dev mode. Remove check when feature ready
BuildplateMenu
{
title: catalog.i18nc("@title:menu", "&Build plate")
visible: CuraSDKVersion == "dev" && Cura.MachineManager.hasVariantBuildplates
}
ProfileMenu { title: catalog.i18nc("@title:settings", "&Profile") }
MenuSeparator { } MenuSeparator { }
MenuItem { action: Cura.Actions.configureSettingVisibility } MenuItem { action: Cura.Actions.configureSettingVisibility }

View file

@ -1,4 +1,4 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2019 Ultimaker B.V.
// Uranium is released under the terms of the LGPLv3 or higher. // Uranium is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.7
@ -14,16 +14,17 @@ Item
{ {
id: base id: base
property QtObject qualityManager: CuraApplication.getQualityManager()
property var resetEnabled: false // Keep PreferencesDialog happy property var resetEnabled: false // Keep PreferencesDialog happy
property var extrudersModel: CuraApplication.getExtrudersModel() property var extrudersModel: CuraApplication.getExtrudersModel()
property var qualityManagementModel: CuraApplication.getQualityManagementModel() property var qualityManagementModel: CuraApplication.getQualityManagementModel()
UM.I18nCatalog { id: catalog; name: "cura"; } UM.I18nCatalog { id: catalog; name: "cura"; }
Label { Label
{
id: titleLabel id: titleLabel
anchors { anchors
{
top: parent.top top: parent.top
left: parent.left left: parent.left
right: parent.right right: parent.right
@ -35,28 +36,33 @@ Item
property var hasCurrentItem: base.currentItem != null property var hasCurrentItem: base.currentItem != null
property var currentItem: { property var currentItem:
{
var current_index = qualityListView.currentIndex; var current_index = qualityListView.currentIndex;
return (current_index == -1) ? null : base.qualityManagementModel.getItem(current_index); return (current_index == -1) ? null : base.qualityManagementModel.getItem(current_index);
} }
property var currentItemName: hasCurrentItem ? base.currentItem.name : "" property var currentItemName: hasCurrentItem ? base.currentItem.name : ""
property var isCurrentItemActivated: { property var isCurrentItemActivated:
if (!base.currentItem) { {
if (!base.currentItem)
{
return false; return false;
} }
return base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName; return base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName;
} }
property var canCreateProfile: { property var canCreateProfile:
{
return isCurrentItemActivated && Cura.MachineManager.hasUserSettings; return isCurrentItemActivated && Cura.MachineManager.hasUserSettings;
} }
Row // Button Row Row // Button Row
{ {
id: buttonRow id: buttonRow
anchors { anchors
{
left: parent.left left: parent.left
right: parent.right right: parent.right
top: titleLabel.bottom top: titleLabel.bottom
@ -70,10 +76,14 @@ Item
text: catalog.i18nc("@action:button", "Activate") text: catalog.i18nc("@action:button", "Activate")
iconName: "list-activate" iconName: "list-activate"
enabled: !isCurrentItemActivated enabled: !isCurrentItemActivated
onClicked: { onClicked:
if (base.currentItem.is_read_only) { {
if (base.currentItem.is_read_only)
{
Cura.MachineManager.setQualityGroup(base.currentItem.quality_group); Cura.MachineManager.setQualityGroup(base.currentItem.quality_group);
} else { }
else
{
Cura.MachineManager.setQualityChangesGroup(base.currentItem.quality_changes_group); Cura.MachineManager.setQualityChangesGroup(base.currentItem.quality_changes_group);
} }
} }
@ -88,7 +98,8 @@ Item
enabled: base.canCreateProfile && !Cura.MachineManager.stacksHaveErrors enabled: base.canCreateProfile && !Cura.MachineManager.stacksHaveErrors
visible: base.canCreateProfile visible: base.canCreateProfile
onClicked: { onClicked:
{
createQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name); createQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
createQualityDialog.open(); createQualityDialog.open();
createQualityDialog.selectText(); createQualityDialog.selectText();
@ -104,7 +115,8 @@ Item
enabled: !base.canCreateProfile enabled: !base.canCreateProfile
visible: !base.canCreateProfile visible: !base.canCreateProfile
onClicked: { onClicked:
{
duplicateQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name); duplicateQualityDialog.object = Cura.ContainerManager.makeUniqueName(base.currentItem.name);
duplicateQualityDialog.open(); duplicateQualityDialog.open();
duplicateQualityDialog.selectText(); duplicateQualityDialog.selectText();
@ -118,7 +130,8 @@ Item
text: catalog.i18nc("@action:button", "Remove") text: catalog.i18nc("@action:button", "Remove")
iconName: "list-remove" iconName: "list-remove"
enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated enabled: base.hasCurrentItem && !base.currentItem.is_read_only && !base.isCurrentItemActivated
onClicked: { onClicked:
{
forceActiveFocus(); forceActiveFocus();
confirmRemoveQualityDialog.open(); confirmRemoveQualityDialog.open();
} }
@ -131,7 +144,8 @@ Item
text: catalog.i18nc("@action:button", "Rename") text: catalog.i18nc("@action:button", "Rename")
iconName: "edit-rename" iconName: "edit-rename"
enabled: base.hasCurrentItem && !base.currentItem.is_read_only enabled: base.hasCurrentItem && !base.currentItem.is_read_only
onClicked: { onClicked:
{
renameQualityDialog.object = base.currentItem.name; renameQualityDialog.object = base.currentItem.name;
renameQualityDialog.open(); renameQualityDialog.open();
renameQualityDialog.selectText(); renameQualityDialog.selectText();
@ -144,7 +158,8 @@ Item
id: importMenuButton id: importMenuButton
text: catalog.i18nc("@action:button", "Import") text: catalog.i18nc("@action:button", "Import")
iconName: "document-import" iconName: "document-import"
onClicked: { onClicked:
{
importDialog.open(); importDialog.open();
} }
} }
@ -156,7 +171,8 @@ Item
text: catalog.i18nc("@action:button", "Export") text: catalog.i18nc("@action:button", "Export")
iconName: "document-export" iconName: "document-export"
enabled: base.hasCurrentItem && !base.currentItem.is_read_only enabled: base.hasCurrentItem && !base.currentItem.is_read_only
onClicked: { onClicked:
{
exportDialog.open(); exportDialog.open();
} }
} }
@ -237,7 +253,7 @@ Item
object: "<new name>" object: "<new name>"
onAccepted: onAccepted:
{ {
base.qualityManager.duplicateQualityChanges(newName, base.currentItem); base.qualityManagementModel.duplicateQualityChanges(newName, base.currentItem);
} }
} }
@ -268,7 +284,7 @@ Item
object: "<new name>" object: "<new name>"
onAccepted: onAccepted:
{ {
var actualNewName = base.qualityManager.renameQualityChangesGroup(base.currentItem.quality_changes_group, newName); var actualNewName = base.qualityManagementModel.renameQualityChangesGroup(base.currentItem.quality_changes_group, newName);
base.newQualityNameToSelect = actualNewName; // Select the new name after the model gets updated base.newQualityNameToSelect = actualNewName; // Select the new name after the model gets updated
} }
} }
@ -285,13 +301,16 @@ Item
{ {
var result = Cura.ContainerManager.importProfile(fileUrl); var result = Cura.ContainerManager.importProfile(fileUrl);
messageDialog.text = result.message; messageDialog.text = result.message;
if (result.status == "ok") { if (result.status == "ok")
{
messageDialog.icon = StandardIcon.Information; messageDialog.icon = StandardIcon.Information;
} }
else if (result.status == "duplicate") { else if (result.status == "duplicate")
{
messageDialog.icon = StandardIcon.Warning; messageDialog.icon = StandardIcon.Warning;
} }
else { else
{
messageDialog.icon = StandardIcon.Critical; messageDialog.icon = StandardIcon.Critical;
} }
messageDialog.open(); messageDialog.open();
@ -312,7 +331,8 @@ Item
var result = Cura.ContainerManager.exportQualityChangesGroup(base.currentItem.quality_changes_group, var result = Cura.ContainerManager.exportQualityChangesGroup(base.currentItem.quality_changes_group,
fileUrl, selectedNameFilter); fileUrl, selectedNameFilter);
if (result && result.status == "error") { if (result && result.status == "error")
{
messageDialog.icon = StandardIcon.Critical; messageDialog.icon = StandardIcon.Critical;
messageDialog.text = result.message; messageDialog.text = result.message;
messageDialog.open(); messageDialog.open();
@ -323,10 +343,12 @@ Item
} }
} }
Item { Item
{
id: contentsItem id: contentsItem
anchors { anchors
{
top: titleLabel.bottom top: titleLabel.bottom
left: parent.left left: parent.left
right: parent.right right: parent.right
@ -340,7 +362,8 @@ Item
Item Item
{ {
anchors { anchors
{
top: buttonRow.bottom top: buttonRow.bottom
topMargin: UM.Theme.getSize("default_margin").height topMargin: UM.Theme.getSize("default_margin").height
left: parent.left left: parent.left
@ -348,12 +371,16 @@ Item
bottom: parent.bottom bottom: parent.bottom
} }
SystemPalette { id: palette } SystemPalette
{
id: palette
}
Label Label
{ {
id: captionLabel id: captionLabel
anchors { anchors
{
top: parent.top top: parent.top
left: parent.left left: parent.left
} }
@ -366,14 +393,16 @@ Item
ScrollView ScrollView
{ {
id: profileScrollView id: profileScrollView
anchors { anchors
{
top: captionLabel.visible ? captionLabel.bottom : parent.top top: captionLabel.visible ? captionLabel.bottom : parent.top
topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0 topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0
bottom: parent.bottom bottom: parent.bottom
left: parent.left left: parent.left
} }
Rectangle { Rectangle
{
parent: viewport parent: viewport
anchors.fill: parent anchors.fill: parent
color: palette.light color: palette.light
@ -445,7 +474,8 @@ Item
MouseArea MouseArea
{ {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked:
{
parent.ListView.view.currentIndex = model.index; parent.ListView.view.currentIndex = model.index;
} }
} }
@ -458,7 +488,8 @@ Item
{ {
id: detailsPanel id: detailsPanel
anchors { anchors
{
left: profileScrollView.right left: profileScrollView.right
leftMargin: UM.Theme.getSize("default_margin").width leftMargin: UM.Theme.getSize("default_margin").width
top: parent.top top: parent.top
@ -478,13 +509,15 @@ Item
width: parent.width width: parent.width
height: childrenRect.height height: childrenRect.height
Label { Label
{
text: base.currentItemName text: base.currentItemName
font: UM.Theme.getFont("large_bold") font: UM.Theme.getFont("large_bold")
} }
} }
Flow { Flow
{
id: currentSettingsActions id: currentSettingsActions
visible: base.hasCurrentItem && base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName visible: base.hasCurrentItem && base.currentItem.name == Cura.MachineManager.activeQualityOrQualityChangesName
anchors.left: parent.left anchors.left: parent.left
@ -507,7 +540,8 @@ Item
} }
} }
Column { Column
{
id: profileNotices id: profileNotices
anchors.top: currentSettingsActions.visible ? currentSettingsActions.bottom : currentSettingsActions.anchors.top anchors.top: currentSettingsActions.visible ? currentSettingsActions.bottom : currentSettingsActions.anchors.top
anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.topMargin: UM.Theme.getSize("default_margin").height
@ -515,14 +549,16 @@ Item
anchors.right: parent.right anchors.right: parent.right
spacing: UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("default_margin").height
Label { Label
{
id: defaultsMessage id: defaultsMessage
visible: false visible: false
text: catalog.i18nc("@action:label", "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below.") text: catalog.i18nc("@action:label", "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below.")
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
} }
Label { Label
{
id: noCurrentSettingsMessage id: noCurrentSettingsMessage
visible: base.isCurrentItemActivated && !Cura.MachineManager.hasUserSettings visible: base.isCurrentItemActivated && !Cura.MachineManager.hasUserSettings
text: catalog.i18nc("@action:label", "Your current settings match the selected profile.") text: catalog.i18nc("@action:label", "Your current settings match the selected profile.")

View file

@ -1,12 +1,12 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 2.0 import QtQuick.Controls 2.3
import QtQuick.Controls 1.1 as OldControls import QtQuick.Controls 1.4 as OldControls
import UM 1.3 as UM import UM 1.3 as UM
import Cura 1.0 as Cura import Cura 1.6 as Cura
Item Item
@ -18,18 +18,6 @@ Item
property var extrudersModel: CuraApplication.getExtrudersModel() property var extrudersModel: CuraApplication.getExtrudersModel()
// Profile selector row
GlobalProfileSelector
{
id: globalProfileRow
anchors
{
top: parent.top
left: parent.left
right: parent.right
margins: parent.padding
}
}
Item Item
{ {
id: intent id: intent
@ -37,7 +25,7 @@ Item
anchors anchors
{ {
top: globalProfileRow.bottom top: parent.top
topMargin: UM.Theme.getSize("default_margin").height topMargin: UM.Theme.getSize("default_margin").height
left: parent.left left: parent.left
leftMargin: parent.padding leftMargin: parent.padding
@ -47,7 +35,7 @@ Item
Label Label
{ {
id: intentLabel id: profileLabel
anchors anchors
{ {
top: parent.top top: parent.top
@ -55,25 +43,130 @@ Item
left: parent.left left: parent.left
right: intentSelection.left right: intentSelection.left
} }
text: catalog.i18nc("@label", "Intent") text: catalog.i18nc("@label", "Profile")
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
renderType: Text.NativeRendering
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
OldControls.ToolButton
Button
{ {
id: intentSelection id: intentSelection
text: Cura.MachineManager.activeStack != null ? Cura.MachineManager.activeStack.intent.name : "" onClicked: menu.opened ? menu.close() : menu.open()
tooltip: text text: generateActiveQualityText()
height: UM.Theme.getSize("print_setup_big_item").height
width: UM.Theme.getSize("print_setup_big_item").width
anchors.right: parent.right
style: UM.Theme.styles.print_setup_header_button
activeFocusOnPress: true
menu: Cura.IntentMenu { extruderIndex: Cura.ExtruderManager.activeExtruderIndex } anchors.right: parent.right
width: UM.Theme.getSize("print_setup_big_item").width
height: textLabel.contentHeight + 2 * UM.Theme.getSize("narrow_margin").height
hoverEnabled: true
baselineOffset: null // If we don't do this, there is a binding loop. WHich is a bit weird, since we override the contentItem anyway...
contentItem: Label
{
id: textLabel
text: intentSelection.text
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: intentSelection.verticalCenter
height: contentHeight
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
}
background: Rectangle
{
id: backgroundItem
border.color: intentSelection.hovered ? UM.Theme.getColor("setting_control_border_highlight") : UM.Theme.getColor("setting_control_border")
border.width: UM.Theme.getSize("default_lining").width
radius: UM.Theme.getSize("default_radius").width
color: UM.Theme.getColor("main_background")
}
function generateActiveQualityText()
{
var result = ""
if(Cura.MachineManager.activeIntentCategory != "default")
{
result += Cura.MachineManager.activeIntentCategory + " - "
}
result += Cura.MachineManager.activeQualityOrQualityChangesName
if (Cura.MachineManager.isActiveQualityExperimental)
{
result += " (Experimental)"
}
if (Cura.MachineManager.isActiveQualitySupported)
{
if (Cura.MachineManager.activeQualityLayerHeight > 0)
{
result += " <font color=\"" + UM.Theme.getColor("text_detail") + "\">"
result += " - "
result += Cura.MachineManager.activeQualityLayerHeight + "mm"
result += "</font>"
}
}
return result
}
UM.SimpleButton
{
id: customisedSettings
visible: Cura.MachineManager.hasUserSettings
width: UM.Theme.getSize("print_setup_icon").width
height: UM.Theme.getSize("print_setup_icon").height
anchors.verticalCenter: parent.verticalCenter
anchors.right: downArrow.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button");
iconSource: UM.Theme.getIcon("star")
onClicked:
{
forceActiveFocus();
Cura.Actions.manageProfiles.trigger()
}
onEntered:
{
var content = catalog.i18nc("@tooltip", "Some setting/override values are different from the values stored in the profile.\n\nClick to open the profile manager.")
base.showTooltip(intent, Qt.point(-UM.Theme.getSize("default_margin").width, 0), content)
}
onExited: base.hideTooltip()
}
UM.RecolorImage
{
id: downArrow
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
anchors
{
right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: UM.Theme.getSize("default_margin").width
}
color: UM.Theme.getColor("setting_control_button")
}
} }
QualitiesWithIntentMenu
{
id: menu
y: intentSelection.y + intentSelection.height
x: intentSelection.x
width: intentSelection.width
}
} }
UM.TabRow UM.TabRow
@ -143,7 +236,7 @@ Item
{ {
anchors anchors
{ {
top: tabBar.visible ? tabBar.bottom : globalProfileRow.bottom top: tabBar.visible ? tabBar.bottom : intent.bottom
topMargin: -UM.Theme.getSize("default_lining").width topMargin: -UM.Theme.getSize("default_lining").width
left: parent.left left: parent.left
leftMargin: parent.padding leftMargin: parent.padding

View file

@ -1,100 +0,0 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.2
import UM 1.2 as UM
import Cura 1.0 as Cura
Item
{
id: globalProfileRow
height: childrenRect.height
Label
{
id: globalProfileLabel
anchors
{
top: parent.top
bottom: parent.bottom
left: parent.left
right: globalProfileSelection.left
}
text: catalog.i18nc("@label", "Profile")
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
verticalAlignment: Text.AlignVCenter
}
ToolButton
{
id: globalProfileSelection
text: generateActiveQualityText()
width: UM.Theme.getSize("print_setup_big_item").width
height: UM.Theme.getSize("print_setup_big_item").height
anchors
{
top: parent.top
right: parent.right
}
tooltip: Cura.MachineManager.activeQualityOrQualityChangesName
style: UM.Theme.styles.print_setup_header_button
activeFocusOnPress: true
menu: Cura.ProfileMenu { }
function generateActiveQualityText()
{
var result = Cura.MachineManager.activeQualityOrQualityChangesName
if (Cura.MachineManager.isActiveQualityExperimental)
{
result += " (Experimental)"
}
if (Cura.MachineManager.isActiveQualitySupported)
{
if (Cura.MachineManager.activeQualityLayerHeight > 0)
{
result += " <font color=\"" + UM.Theme.getColor("text_detail") + "\">"
result += " - "
result += Cura.MachineManager.activeQualityLayerHeight + "mm"
result += "</font>"
}
}
return result
}
UM.SimpleButton
{
id: customisedSettings
visible: Cura.MachineManager.hasUserSettings
width: UM.Theme.getSize("print_setup_icon").width
height: UM.Theme.getSize("print_setup_icon").height
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Math.round(UM.Theme.getSize("setting_preferences_button_margin").width - UM.Theme.getSize("thick_margin").width)
color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button");
iconSource: UM.Theme.getIcon("star")
onClicked:
{
forceActiveFocus();
Cura.Actions.manageProfiles.trigger()
}
onEntered:
{
var content = catalog.i18nc("@tooltip","Some setting/override values are different from the values stored in the profile.\n\nClick to open the profile manager.")
base.showTooltip(globalProfileRow, Qt.point(-UM.Theme.getSize("default_margin").width, 0), content)
}
onExited: base.hideTooltip()
}
}
}

View file

@ -0,0 +1,52 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import UM 1.2 as UM
import Cura 1.6 as Cura
Button
{
// This is a work around for a qml issue. Since the default button uses a private implementation for contentItem
// (the so called IconText), which handles the mnemonic conversion (aka; ensuring that &Button) text property
// is rendered with the B underlined. Since we're also forced to mix controls 1.0 and 2.0 actions together,
// we need a special property for the text of the label if we do want it to be rendered correclty, but don't want
// another shortcut to be added (which will cause for "QQuickAction::event: Ambiguous shortcut overload: " to
// happen.
property string labelText: ""
id: button
hoverEnabled: true
background: Rectangle
{
id: backgroundRectangle
border.width: 1
border.color: button.checked ? UM.Theme.getColor("setting_control_border_highlight") : "transparent"
color: button.hovered ? UM.Theme.getColor("action_button_hovered") : "transparent"
radius: UM.Theme.getSize("action_button_radius").width
}
// Workarround to ensure that the mnemonic highlighting happens correctly
function replaceText(txt)
{
var index = txt.indexOf("&")
if(index >= 0)
{
txt = txt.replace(txt.substr(index, 2), ("<u>" + txt.substr(index + 1, 1) + "</u>"))
}
return txt
}
contentItem: Label
{
id: textLabel
text: button.text != "" ? replaceText(button.text) : replaceText(button.labelText)
height: contentHeight
verticalAlignment: Text.AlignVCenter
anchors.left: button.left
anchors.leftMargin: UM.Theme.getSize("wide_margin").width
renderType: Text.NativeRendering
}
}

View file

@ -0,0 +1,228 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import UM 1.2 as UM
import Cura 1.6 as Cura
Popup
{
id: popup
implicitWidth: 400
property var dataModel: Cura.IntentCategoryModel {}
property int defaultMargin: UM.Theme.getSize("default_margin").width
property color backgroundColor: UM.Theme.getColor("main_background")
property color borderColor: UM.Theme.getColor("lining")
topPadding: UM.Theme.getSize("narrow_margin").height
rightPadding: UM.Theme.getSize("default_lining").width
leftPadding: UM.Theme.getSize("default_lining").width
padding: 0
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
background: Cura.RoundedRectangle
{
color: backgroundColor
border.width: UM.Theme.getSize("default_lining").width
border.color: borderColor
cornerSide: Cura.RoundedRectangle.Direction.Down
}
ButtonGroup
{
id: buttonGroup
exclusive: true
onClicked: popup.visible = false
}
contentItem: Column
{
// This repeater adds the intent labels
Repeater
{
model: dataModel
delegate: Item
{
// We need to set it like that, otherwise we'd have to set the sub model with model: model.qualities
// Which obviously won't work due to naming conflicts.
property variant subItemModel: model.qualities
height: childrenRect.height
anchors
{
left: parent.left
right: parent.right
}
Label
{
id: headerLabel
text: model.name
renderType: Text.NativeRendering
height: visible ? contentHeight: 0
enabled: false
visible: qualitiesList.visibleChildren.length > 0
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
}
Column
{
id: qualitiesList
anchors.top: headerLabel.bottom
anchors.left: parent.left
anchors.right: parent.right
// We set it by means of a binding, since then we can use the when condition, which we need to
// prevent a binding loop.
Binding
{
target: parent
property: "height"
value: parent.childrenRect.height
when: parent.visibleChildren.length > 0
}
// Add the qualities that belong to the intent
Repeater
{
visible: false
model: subItemModel
MenuButton
{
id: button
onClicked: Cura.IntentManager.selectIntent(model.intent_category, model.quality_type)
width: parent.width
checkable: true
visible: model.available
text: model.name + " - " + model.layer_height + " mm"
checked:
{
if(Cura.MachineManager.hasCustomQuality)
{
// When user created profile is active, no quality tickbox should be active.
return false
}
return Cura.MachineManager.activeQualityType == model.quality_type && Cura.MachineManager.activeIntentCategory == model.intent_category
}
ButtonGroup.group: buttonGroup
}
}
}
}
}
Rectangle
{
height: 1
anchors.left: parent.left
anchors.right: parent.right
color: borderColor
}
MenuButton
{
labelText: Cura.Actions.addProfile.text
anchors.left: parent.left
anchors.right: parent.right
enabled: Cura.Actions.addProfile.enabled
onClicked:
{
Cura.Actions.addProfile.trigger()
popup.visible = false
}
}
MenuButton
{
labelText: Cura.Actions.updateProfile.text
anchors.left: parent.left
anchors.right: parent.right
enabled: Cura.Actions.updateProfile.enabled
onClicked:
{
popup.visible = false
Cura.Actions.updateProfile.trigger()
}
}
MenuButton
{
text: catalog.i18nc("@action:button", "Discard current changes")
anchors.left: parent.left
anchors.right: parent.right
enabled: Cura.MachineManager.hasUserSettings
onClicked:
{
popup.visible = false
Cura.ContainerManager.clearUserContainers()
}
}
Rectangle
{
height: 1
anchors.left: parent.left
anchors.right: parent.right
color: borderColor
}
MenuButton
{
id: manageProfilesButton
text: Cura.Actions.manageProfiles.text
anchors
{
left: parent.left
right: parent.right
}
height: textLabel.contentHeight + 2 * UM.Theme.getSize("narrow_margin").height
contentItem: Item
{
width: manageProfilesButton.width
Label
{
id: textLabel
text: manageProfilesButton.text
height: contentHeight
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width + UM.Theme.getSize("narrow_margin").width
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
}
Label
{
id: shortcutLabel
text: Cura.Actions.manageProfiles.shortcut
height: contentHeight
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
}
}
onClicked:
{
popup.visible = false
Cura.Actions.manageProfiles.trigger()
}
}
// spacer
Item
{
width: 2
height: UM.Theme.getSize("default_radius").width
}
}
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 2.3 import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
@ -20,10 +20,16 @@ RowLayout
{ {
if (Cura.MachineManager.activeStack) if (Cura.MachineManager.activeStack)
{ {
var text = Cura.MachineManager.activeQualityOrQualityChangesName var text = ""
if(Cura.MachineManager.activeIntentCategory != "default")
{
text += Cura.MachineManager.activeIntentCategory + " - "
}
text += Cura.MachineManager.activeQualityOrQualityChangesName
if (!Cura.MachineManager.hasNotSupportedQuality) if (!Cura.MachineManager.hasNotSupportedQuality)
{ {
text += " " + layerHeight.properties.value + "mm" text += " - " + layerHeight.properties.value + "mm"
text += Cura.MachineManager.isActiveQualityExperimental ? " - " + catalog.i18nc("@label", "Experimental") : "" text += Cura.MachineManager.isActiveQualityExperimental ? " - " + catalog.i18nc("@label", "Experimental") : ""
} }
return text return text

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
@ -27,7 +27,6 @@ Item
Column Column
{ {
width: parent.width - 2 * parent.padding
spacing: UM.Theme.getSize("wide_margin").height spacing: UM.Theme.getSize("wide_margin").height
anchors anchors

View file

@ -1,17 +1,14 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7 import QtQuick 2.10
import QtQuick.Controls 1.4 import QtQuick.Controls 1.4
import QtQuick.Controls 2.3 as Controls2
import QtQuick.Controls.Styles 1.4 import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.6 as Cura
//
// Quality profile
//
Item Item
{ {
id: qualityRow id: qualityRow
@ -20,436 +17,111 @@ Item
property real labelColumnWidth: Math.round(width / 3) property real labelColumnWidth: Math.round(width / 3)
property real settingsColumnWidth: width - labelColumnWidth property real settingsColumnWidth: width - labelColumnWidth
Timer
{
id: qualitySliderChangeTimer
interval: 50
running: false
repeat: false
onTriggered:
{
var item = Cura.QualityProfilesDropDownMenuModel.getItem(qualitySlider.value);
Cura.MachineManager.activeQualityGroup = item.quality_group;
}
}
Component.onCompleted: qualityModel.update()
Connections
{
target: Cura.QualityProfilesDropDownMenuModel
onItemsChanged: qualityModel.update()
}
Connections {
target: base
onVisibleChanged:
{
// update needs to be called when the widgets are visible, otherwise the step width calculation
// will fail because the width of an invisible item is 0.
if (visible)
{
qualityModel.update();
}
}
}
ListModel
{
id: qualityModel
property var totalTicks: 0
property var availableTotalTicks: 0
property var existingQualityProfile: 0
property var qualitySliderActiveIndex: 0
property var qualitySliderStepWidth: 0
property var qualitySliderAvailableMin: 0
property var qualitySliderAvailableMax: 0
property var qualitySliderMarginRight: 0
function update ()
{
reset()
var availableMin = -1
var availableMax = -1
for (var i = 0; i < Cura.QualityProfilesDropDownMenuModel.rowCount(); i++)
{
var qualityItem = Cura.QualityProfilesDropDownMenuModel.getItem(i)
// Add each quality item to the UI quality model
qualityModel.append(qualityItem)
// Set selected value
if (Cura.MachineManager.activeQualityType == qualityItem.quality_type)
{
// set to -1 when switching to user created profile so all ticks are clickable
if (Cura.MachineManager.hasCustomQuality)
{
qualityModel.qualitySliderActiveIndex = -1
}
else
{
qualityModel.qualitySliderActiveIndex = i
}
qualityModel.existingQualityProfile = 1
}
// Set min available
if (qualityItem.available && availableMin == -1)
{
availableMin = i
}
// Set max available
if (qualityItem.available)
{
availableMax = i
}
}
// Set total available ticks for active slider part
if (availableMin != -1)
{
qualityModel.availableTotalTicks = availableMax - availableMin + 1
}
// Calculate slider values
calculateSliderStepWidth(qualityModel.totalTicks)
calculateSliderMargins(availableMin, availableMax, qualityModel.totalTicks)
qualityModel.qualitySliderAvailableMin = availableMin
qualityModel.qualitySliderAvailableMax = availableMax
}
function calculateSliderStepWidth (totalTicks)
{
// Do not use Math.round otherwise the tickmarks won't be aligned
qualityModel.qualitySliderStepWidth = totalTicks != 0 ?
((settingsColumnWidth - UM.Theme.getSize("print_setup_slider_handle").width) / (totalTicks)) : 0
}
function calculateSliderMargins (availableMin, availableMax, totalTicks)
{
if (availableMin == -1 || (availableMin == 0 && availableMax == 0))
{
// Do not use Math.round otherwise the tickmarks won't be aligned
qualityModel.qualitySliderMarginRight = settingsColumnWidth / 2
}
else if (availableMin == availableMax)
{
// Do not use Math.round otherwise the tickmarks won't be aligned
qualityModel.qualitySliderMarginRight = (totalTicks - availableMin) * qualitySliderStepWidth
}
else
{
// Do not use Math.round otherwise the tickmarks won't be aligned
qualityModel.qualitySliderMarginRight = (totalTicks - availableMax) * qualitySliderStepWidth
}
}
function reset () {
qualityModel.clear()
qualityModel.availableTotalTicks = 0
qualityModel.existingQualityProfile = 0
// check, the ticks count cannot be less than zero
qualityModel.totalTicks = Math.max(0, Cura.QualityProfilesDropDownMenuModel.rowCount() - 1)
}
}
// Here are the elements that are shown in the left column // Here are the elements that are shown in the left column
Item
{
id: titleRow
width: labelColumnWidth
height: childrenRect.height
Cura.IconWithText Column
{
anchors
{ {
id: qualityRowTitle left: parent.left
source: UM.Theme.getIcon("category_layer_height") right: parent.right
text: catalog.i18nc("@label", "Layer Height")
font: UM.Theme.getFont("medium")
anchors.left: parent.left
anchors.right: customisedSettings.left
} }
UM.SimpleButton spacing: UM.Theme.getSize("default_margin").height
{
id: customisedSettings
visible: Cura.SimpleModeSettingsManager.isProfileCustomized || Cura.MachineManager.hasCustomQuality Controls2.ButtonGroup
height: visible ? UM.Theme.getSize("print_setup_icon").height : 0 {
width: height id: activeProfileButtonGroup
exclusive: true
onClicked: Cura.IntentManager.selectIntent(button.modelData.intent_category, button.modelData.quality_type)
}
Item
{
height: childrenRect.height
anchors anchors
{ {
left: parent.left
right: parent.right right: parent.right
rightMargin: UM.Theme.getSize("default_margin").width }
leftMargin: UM.Theme.getSize("default_margin").width Cura.IconWithText
verticalCenter: parent.verticalCenter {
id: profileLabel
source: UM.Theme.getIcon("category_layer_height")
text: catalog.i18nc("@label", "Profiles")
font: UM.Theme.getFont("medium")
width: labelColumnWidth
} }
color: hovered ? UM.Theme.getColor("setting_control_button_hover") : UM.Theme.getColor("setting_control_button") Cura.LabelBar
iconSource: UM.Theme.getIcon("reset") {
id: labelbar
anchors
{
left: profileLabel.right
right: parent.right
}
onClicked: model: Cura.QualityProfilesDropDownMenuModel
{ modelKey: "layer_height"
// if the current profile is user-created, switch to a built-in quality
Cura.MachineManager.resetToUseDefaultQuality()
} }
onEntered:
{
var tooltipContent = catalog.i18nc("@tooltip","You have modified some profile settings. If you want to change these go to custom mode.")
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, 0), tooltipContent)
}
onExited: base.hideTooltip()
} }
}
// Show titles for the each quality slider ticks
Item
{
anchors.left: speedSlider.left
anchors.top: speedSlider.bottom
height: childrenRect.height
Repeater Repeater
{ {
model: qualityModel model: Cura.IntentCategoryModel {}
Item
Label
{ {
anchors.verticalCenter: parent.verticalCenter anchors
anchors.top: parent.top
// The height has to be set manually, otherwise it's not automatically calculated in the repeater
height: UM.Theme.getSize("default_margin").height
color: (Cura.MachineManager.activeMachine != null && Cura.QualityProfilesDropDownMenuModel.getItem(index).available) ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
text:
{ {
var result = "" left: parent.left
if(Cura.MachineManager.activeMachine != null) right: parent.right
{ }
result = Cura.QualityProfilesDropDownMenuModel.getItem(index).layer_height height: intentCategoryLabel.height
if(result == undefined) Label
{
id: intentCategoryLabel
text: model.name
width: labelColumnWidth - UM.Theme.getSize("section_icon").width
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("section_icon").width + UM.Theme.getSize("narrow_margin").width
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("text")
renderType: Text.NativeRendering
elide: Text.ElideRight
}
Cura.RadioCheckbar
{
anchors
{
left: intentCategoryLabel.right
right: parent.right
}
dataModel: model["qualities"]
buttonGroup: activeProfileButtonGroup
function checkedFunction(modelItem)
{
if(Cura.MachineManager.hasCustomQuality)
{ {
result = ""; // When user created profile is active, no quality tickbox should be active.
return false
} }
else
if(modelItem === null)
{ {
result = Number(Math.round(result + "e+2") + "e-2"); //Round to 2 decimals. Javascript makes this difficult... return false
if (result == undefined || result != result) //Parse failure.
{
result = "";
}
} }
return Cura.MachineManager.activeQualityType == modelItem.quality_type && Cura.MachineManager.activeIntentCategory == modelItem.intent_category
} }
return result
}
x: isCheckedFunction: checkedFunction
{
// Make sure the text aligns correctly with each tick
if (qualityModel.totalTicks == 0)
{
// If there is only one tick, align it centrally
return Math.round(((settingsColumnWidth) - width) / 2)
}
else if (index == 0)
{
return Math.round(settingsColumnWidth / qualityModel.totalTicks) * index
}
else if (index == qualityModel.totalTicks)
{
return Math.round(settingsColumnWidth / qualityModel.totalTicks) * index - width
}
else
{
return Math.round((settingsColumnWidth / qualityModel.totalTicks) * index - (width / 2))
}
}
font: UM.Theme.getFont("default")
}
}
}
// Print speed slider
// Two sliders are created, one at the bottom with the unavailable qualities
// and the other at the top with the available quality profiles and so the handle to select them.
Item
{
id: speedSlider
height: childrenRect.height
anchors
{
left: titleRow.right
right: parent.right
verticalCenter: titleRow.verticalCenter
}
// Draw unavailable slider
Slider
{
id: unavailableSlider
width: parent.width
height: qualitySlider.height // Same height as the slider that is on top
updateValueWhileDragging : false
tickmarksEnabled: true
minimumValue: 0
// maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly
// speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available)
maximumValue: qualityModel.totalTicks
stepSize: 1
style: SliderStyle
{
//Draw Unvailable line
groove: Item
{
Rectangle
{
height: UM.Theme.getSize("print_setup_slider_groove").height
width: control.width - UM.Theme.getSize("print_setup_slider_handle").width
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color: UM.Theme.getColor("quality_slider_unavailable")
}
}
handle: Item {}
tickmarks: Repeater
{
id: qualityRepeater
model: qualityModel.totalTicks > 0 ? qualityModel : 0
Rectangle
{
color: Cura.QualityProfilesDropDownMenuModel.getItem(index).available ? UM.Theme.getColor("quality_slider_available") : UM.Theme.getColor("quality_slider_unavailable")
implicitWidth: UM.Theme.getSize("print_setup_slider_tickmarks").width
implicitHeight: UM.Theme.getSize("print_setup_slider_tickmarks").height
anchors.verticalCenter: parent.verticalCenter
// Do not use Math.round otherwise the tickmarks won't be aligned
x: ((UM.Theme.getSize("print_setup_slider_handle").width / 2) - (implicitWidth / 2) + (qualityModel.qualitySliderStepWidth * index))
radius: Math.round(implicitWidth / 2)
}
} }
} }
// Create a mouse area on top of the unavailable profiles to show a specific tooltip
MouseArea
{
anchors.fill: parent
hoverEnabled: true
enabled: !Cura.MachineManager.hasCustomQuality
onEntered:
{
var tooltipContent = catalog.i18nc("@tooltip", "This quality profile is not available for your current material and nozzle configuration. Please change these to enable this quality profile.")
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, customisedSettings.height), tooltipContent)
}
onExited: base.hideTooltip()
}
}
// Draw available slider
Slider
{
id: qualitySlider
width: qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks - 1) + UM.Theme.getSize("print_setup_slider_handle").width
height: UM.Theme.getSize("print_setup_slider_handle").height // The handle is the widest element of the slider
enabled: qualityModel.totalTicks > 0 && !Cura.SimpleModeSettingsManager.isProfileCustomized
visible: qualityModel.availableTotalTicks > 0
updateValueWhileDragging : false
anchors
{
right: parent.right
rightMargin: qualityModel.qualitySliderMarginRight
}
minimumValue: qualityModel.qualitySliderAvailableMin >= 0 ? qualityModel.qualitySliderAvailableMin : 0
// maximumValue must be greater than minimumValue to be able to see the handle. While the value is strictly
// speaking not always correct, it seems to have the correct behavior (switching from 0 available to 1 available)
maximumValue: qualityModel.qualitySliderAvailableMax >= 1 ? qualityModel.qualitySliderAvailableMax : 1
stepSize: 1
value: qualityModel.qualitySliderActiveIndex
style: SliderStyle
{
// Draw Available line
groove: Item
{
Rectangle
{
height: UM.Theme.getSize("print_setup_slider_groove").height
width: control.width - UM.Theme.getSize("print_setup_slider_handle").width
anchors.verticalCenter: parent.verticalCenter
// Do not use Math.round otherwise the tickmarks won't be aligned
x: UM.Theme.getSize("print_setup_slider_handle").width / 2
color: UM.Theme.getColor("quality_slider_available")
}
}
handle: Rectangle
{
id: qualityhandleButton
color: UM.Theme.getColor("primary")
implicitWidth: UM.Theme.getSize("print_setup_slider_handle").width
implicitHeight: implicitWidth
radius: Math.round(implicitWidth / 2)
visible: !Cura.SimpleModeSettingsManager.isProfileCustomized && !Cura.MachineManager.hasCustomQuality && qualityModel.existingQualityProfile
}
}
onValueChanged:
{
// only change if an active machine is set and the slider is visible at all.
if (Cura.MachineManager.activeMachine != null && visible)
{
// prevent updating during view initializing. Trigger only if the value changed by user
if (qualitySlider.value != qualityModel.qualitySliderActiveIndex && qualityModel.qualitySliderActiveIndex != -1)
{
// start updating with short delay
qualitySliderChangeTimer.start()
}
}
}
// This mouse area is only used to capture the onHover state and don't propagate it to the unavailable mouse area
MouseArea
{
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
enabled: !Cura.MachineManager.hasCustomQuality
}
}
// This mouse area will only take the mouse events and show a tooltip when the profile in use is
// a user created profile
MouseArea
{
anchors.fill: parent
hoverEnabled: true
visible: Cura.MachineManager.hasCustomQuality
onEntered:
{
var tooltipContent = catalog.i18nc("@tooltip", "A custom profile is currently active. To enable the quality slider, choose a default quality profile in Custom tab")
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("thick_margin").width, customisedSettings.height), tooltipContent)
}
onExited: base.hideTooltip()
} }
} }
} }

View file

@ -0,0 +1,153 @@
// Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import UM 1.1 as UM
Item
{
id: base
property ButtonGroup buttonGroup: null
property color activeColor: UM.Theme.getColor("primary")
property color inactiveColor: UM.Theme.getColor("slider_groove")
property color defaultItemColor: UM.Theme.getColor("small_button_active")
property int checkboxSize: UM.Theme.getSize("radio_button").height * 0.75
property int inactiveMarkerSize: 2 * barSize
property int barSize: UM.Theme.getSize("slider_groove_radius").height
property var isCheckedFunction // Function that accepts the modelItem and returns if the item should be active.
implicitWidth: 200
implicitHeight: checkboxSize
property var dataModel: null
// The horizontal inactive bar that sits behind the buttons
Rectangle
{
id: inactiveLine
color: inactiveColor
height: barSize
anchors
{
left: buttonBar.left
right: buttonBar.right
leftMargin: (checkboxSize - inactiveMarkerSize) / 2
rightMargin: (checkboxSize - inactiveMarkerSize) / 2
verticalCenter: parent.verticalCenter
}
}
RowLayout
{
id: buttonBar
anchors.top: parent.top
height: checkboxSize
width: parent.width
spacing: 0
Repeater
{
id: repeater
model: base.dataModel
height: checkboxSize
Item
{
Layout.fillWidth: true
Layout.fillHeight: true
// The last item of the repeater needs to be shorter, as we don't need another part to fit
// the horizontal bar. The others should essentially not be limited.
Layout.maximumWidth: index + 1 === repeater.count ? activeComponent.width: 200000000
property bool isEnabled: model.available
// The horizontal bar between the checkable options.
// Note that the horizontal bar points towards the previous item.
Rectangle
{
property Item previousItem: repeater.itemAt(index - 1)
height: barSize
width: buttonBar.width / (repeater.count - 1) - activeComponent.width - 2
color: defaultItemColor
anchors
{
right: activeComponent.left
verticalCenter: parent.verticalCenter
}
visible: previousItem !== null && previousItem.isEnabled && isEnabled
}
Loader
{
id: activeComponent
sourceComponent: isEnabled? checkboxComponent : disabledComponent
width: checkboxSize
property var modelItem: model
}
}
}
}
Component
{
id: disabledComponent
Item
{
height: checkboxSize
width: checkboxSize
Rectangle
{
// This can (and should) be done wiht a verticalCenter. For some reason it does work in QtCreator
// but not when using the exact same QML in Cura.
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
height: inactiveMarkerSize
width: inactiveMarkerSize
radius: width / 2
color: inactiveColor
}
}
}
Component
{
id: checkboxComponent
CheckBox
{
id: checkbox
ButtonGroup.group: buttonGroup
width: checkboxSize
height: checkboxSize
property var modelData: modelItem
checked: isCheckedFunction(modelItem)
indicator: Rectangle
{
height: checkboxSize
width: checkboxSize
radius: width / 2
border.color: defaultItemColor
Rectangle
{
anchors
{
margins: 3
fill: parent
}
radius: width / 2
color: activeColor
visible: checkbox.checked
}
}
}
}
}

View file

@ -20,7 +20,6 @@ SettingItem
textRole: "value" textRole: "value"
anchors.fill: parent anchors.fill: parent
highlighted: base.hovered
onActivated: onActivated:
{ {

View file

@ -14,40 +14,34 @@ import Cura 1.1 as Cura
ComboBox ComboBox
{ {
id: control id: control
property bool highlighted: False
states: [
State
{
name: "disabled"
when: !control.enabled
PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_control_disabled_border")}
PropertyChanges { target: backgroundRectangle; color: UM.Theme.getColor("setting_control_disabled")}
PropertyChanges { target: contentLabel; color: UM.Theme.getColor("setting_control_disabled_text")}
},
State
{
name: "highlighted"
when: control.hovered || control.activeFocus
PropertyChanges { target: backgroundRectangle.border; color: UM.Theme.getColor("setting_control_border_highlight") }
PropertyChanges { target: backgroundRectangle; color: UM.Theme.getColor("setting_control_highlight")}
}
]
background: Rectangle background: Rectangle
{ {
color: id: backgroundRectangle
{ color: UM.Theme.getColor("setting_control")
if (!enabled)
{
return UM.Theme.getColor("setting_control_disabled")
}
if (control.hovered || control.activeFocus || control.highlighted)
{
return UM.Theme.getColor("setting_control_highlight")
}
return UM.Theme.getColor("setting_control")
}
radius: UM.Theme.getSize("setting_control_radius").width radius: UM.Theme.getSize("setting_control_radius").width
border.width: UM.Theme.getSize("default_lining").width border.width: UM.Theme.getSize("default_lining").width
border.color: border.color: UM.Theme.getColor("setting_control_border")
{
if (!enabled)
{
return UM.Theme.getColor("setting_control_disabled_border")
}
if (control.hovered || control.activeFocus || control.highlighted)
{
return UM.Theme.getColor("setting_control_border_highlight")
}
return UM.Theme.getColor("setting_control_border")
}
} }
indicator: UM.RecolorImage indicator: UM.RecolorImage
@ -67,6 +61,7 @@ ComboBox
contentItem: Label contentItem: Label
{ {
id: contentLabel
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width anchors.leftMargin: UM.Theme.getSize("setting_unit_margin").width
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -76,7 +71,7 @@ ComboBox
textFormat: Text.PlainText textFormat: Text.PlainText
renderType: Text.NativeRendering renderType: Text.NativeRendering
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") color: UM.Theme.getColor("setting_control_text")
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }

View file

@ -0,0 +1,20 @@
[general]
version = 4
name = Fine
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = normal
weight = 0
material = generic_abs
variant = AA 0.25
[values]
cool_fan_speed = 40
infill_overlap = 15
material_final_print_temperature = =material_print_temperature - 5
retraction_prime_speed = 25
speed_topbottom = =math.ceil(speed_print * 30 / 55)
wall_thickness = 0.92

View file

@ -0,0 +1,20 @@
[general]
version = 4
name = Fine
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = normal
weight = 0
material = generic_cpe
variant = AA 0.25
[values]
retraction_combing_max_distance = 50
retraction_extrusion_window = 0.5
speed_infill = =math.ceil(speed_print * 40 / 55)
speed_topbottom = =math.ceil(speed_print * 30 / 55)
top_bottom_thickness = 0.8
wall_thickness = 0.92

View file

@ -0,0 +1,35 @@
[general]
version = 4
name = Fine
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = normal
weight = 0
material = generic_nylon
variant = AA 0.25
[values]
cool_min_layer_time_fan_speed_max = 20
cool_min_speed = 12
infill_line_width = =round(line_width * 0.5 / 0.4, 2)
machine_nozzle_cool_down_speed = 0.9
machine_nozzle_heat_up_speed = 1.4
ooze_shield_angle = 40
raft_acceleration = =acceleration_layer_0
raft_airgap = =round(layer_height_0 * 0.85, 2)
raft_interface_thickness = =round(machine_nozzle_size * 0.3 / 0.4, 3)
raft_jerk = =jerk_layer_0
raft_margin = 10
raft_surface_thickness = =round(machine_nozzle_size * 0.2 / 0.4, 2)
retraction_min_travel = 5
skin_overlap = 50
speed_print = 70
speed_topbottom = =math.ceil(speed_print * 30 / 70)
speed_wall = =math.ceil(speed_print * 30 / 70)
switch_extruder_prime_speed = 30
switch_extruder_retraction_amount = 30
switch_extruder_retraction_speeds = 40
wall_line_width_x = =wall_line_width

View file

@ -0,0 +1,55 @@
[general]
version = 4
name = Fine - Experimental
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = normal
weight = 0
material = generic_pc
variant = AA 0.25
is_experimental = True
[values]
acceleration_enabled = True
acceleration_print = 4000
adhesion_type = brim
brim_width = 20
cool_fan_full_at_height = =layer_height_0 + layer_height
cool_fan_speed_max = 50
cool_min_layer_time_fan_speed_max = 5
cool_min_speed = 5
infill_line_width = =line_width
infill_pattern = triangles
infill_wipe_dist = 0.1
jerk_enabled = True
jerk_print = 25
machine_min_cool_heat_time_window = 15
multiple_mesh_overlap = 0
ooze_shield_angle = 40
prime_tower_enable = True
prime_tower_wipe_enabled = True
raft_airgap = 0.25
raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 15
skin_overlap = 30
speed_layer_0 = =math.ceil(speed_print * 25 / 50)
speed_print = 50
speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50)
speed_wall_0 = =math.ceil(speed_wall * 25 / 40)
support_bottom_distance = =support_z_distance
support_interface_density = 87.5
support_interface_pattern = lines
switch_extruder_prime_speed = 15
switch_extruder_retraction_amount = 20
switch_extruder_retraction_speeds = 35
wall_0_inset = 0
wall_line_width_x = =line_width
wall_thickness = 1.2

View file

@ -0,0 +1,36 @@
[general]
version = 4
name = Fine
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = normal
weight = 0
material = generic_pla
variant = AA 0.25
[values]
brim_width = 8
cool_fan_full_at_height = =layer_height_0
cool_min_speed = 10
infill_overlap = 10
infill_pattern = grid
machine_nozzle_cool_down_speed = 0.9
machine_nozzle_heat_up_speed = 1.4
material_final_print_temperature = =max(-273.15, material_print_temperature - 15)
material_initial_print_temperature = =max(-273.15, material_print_temperature - 10)
material_print_temperature = 190
retraction_hop = 0.2
skin_overlap = 5
speed_layer_0 = =speed_print
speed_print = 30
speed_travel_layer_0 = 120
speed_wall = =math.ceil(speed_print * 25 / 30)
speed_wall_0 = =math.ceil(speed_print * 20 / 30)
top_bottom_thickness = 0.72
travel_avoid_distance = 0.4
wall_0_inset = 0.015
wall_0_wipe_dist = 0.25
wall_thickness = 0.7

View file

@ -0,0 +1,59 @@
[general]
version = 4
name = Fine - Experimental
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = normal
weight = 0
material = generic_pp
variant = AA 0.25
is_experimental = True
[values]
acceleration_enabled = True
acceleration_print = 4000
brim_width = 10
cool_fan_speed_max = 100
cool_min_layer_time_fan_speed_max = 5
cool_min_speed = 2.5
infill_line_width = =round(line_width * 0.38 / 0.38, 2)
infill_pattern = tetrahedral
infill_wipe_dist = 0.1
jerk_enabled = True
jerk_print = 25
line_width = =machine_nozzle_size * 0.92
machine_min_cool_heat_time_window = 15
material_bed_temperature_layer_0 = =material_bed_temperature + 5
material_final_print_temperature = =material_print_temperature - 10
material_initial_print_temperature = =material_print_temperature - 5
material_print_temperature = =default_material_print_temperature - 15
material_print_temperature_layer_0 = =material_print_temperature + 3
multiple_mesh_overlap = 0
prime_tower_enable = False
prime_tower_size = 16
prime_tower_wipe_enabled = True
retraction_count_max = 6
retraction_extra_prime_amount = 0.2
retraction_extrusion_window = 6.5
retraction_hop = 2
retraction_hop_only_when_collides = True
retraction_min_travel = 0.8
retraction_prime_speed = 13
speed_equalize_flow_enabled = True
speed_layer_0 = =math.ceil(speed_print * 15 / 25)
speed_print = 25
speed_travel_layer_0 = 50
speed_wall = =math.ceil(speed_print * 25 / 25)
speed_wall_0 = =math.ceil(speed_wall * 25 / 25)
support_angle = 50
switch_extruder_prime_speed = 15
switch_extruder_retraction_amount = 20
switch_extruder_retraction_speeds = 35
top_bottom_thickness = 1
travel_avoid_distance = 3
wall_0_inset = 0
wall_line_width_x = =line_width
wall_thickness = =line_width * 3

View file

@ -0,0 +1,40 @@
[general]
version = 4
name = Fine
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = normal
weight = 0
material = generic_tough_pla
variant = AA 0.25
[values]
brim_width = 8
cool_fan_full_at_height = =layer_height_0
cool_min_speed = 7
infill_line_width = =line_width
infill_overlap = 10
infill_pattern = grid
line_width = =machine_nozzle_size * 0.92
machine_nozzle_cool_down_speed = 0.9
machine_nozzle_heat_up_speed = 1.4
material_final_print_temperature = =max(-273.15, material_print_temperature - 15)
material_initial_print_temperature = =max(-273.15, material_print_temperature - 10)
material_print_temperature = =default_material_print_temperature - 15
skin_overlap = 5
speed_layer_0 = =math.ceil(speed_print * 30 / 30)
speed_print = 30
speed_topbottom = =math.ceil(speed_print * 20 / 30)
speed_travel_layer_0 = 120
speed_wall = =math.ceil(speed_print * 25 / 30)
speed_wall_0 = =math.ceil(speed_print * 20 / 30)
top_bottom_thickness = 0.72
wall_0_inset = 0.015
wall_0_wipe_dist = 0.25
wall_line_width = =line_width
wall_line_width_x= =line_width
wall_thickness = 0.7

View file

@ -0,0 +1,31 @@
[general]
version = 4
name = Fast
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = draft
weight = -2
material = generic_abs
variant = AA 0.4
[values]
machine_nozzle_cool_down_speed = 0.85
machine_nozzle_heat_up_speed = 1.5
material_print_temperature = =default_material_print_temperature + 20
material_initial_print_temperature = =material_print_temperature - 15
material_final_print_temperature = =material_print_temperature - 20
prime_tower_enable = False
skin_overlap = 20
speed_print = 60
speed_layer_0 = =math.ceil(speed_print * 20 / 60)
speed_topbottom = =math.ceil(speed_print * 35 / 60)
speed_wall = =math.ceil(speed_print * 45 / 60)
speed_wall_0 = =math.ceil(speed_wall * 35 / 45)
wall_thickness = 1
infill_line_width = =round(line_width * 0.4 / 0.35, 2)
speed_infill = =math.ceil(speed_print * 50 / 60)

View file

@ -0,0 +1,30 @@
[general]
version = 4
name = Normal
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = fast
weight = -1
material = generic_abs
variant = AA 0.4
[values]
cool_min_speed = 7
machine_nozzle_cool_down_speed = 0.85
machine_nozzle_heat_up_speed = 1.5
material_print_temperature = =default_material_print_temperature + 15
material_initial_print_temperature = =material_print_temperature - 15
material_final_print_temperature = =material_print_temperature - 20
prime_tower_enable = False
speed_print = 60
speed_layer_0 = =math.ceil(speed_print * 20 / 60)
speed_topbottom = =math.ceil(speed_print * 30 / 60)
speed_wall = =math.ceil(speed_print * 40 / 60)
speed_wall_0 = =math.ceil(speed_wall * 30 / 40)
infill_line_width = =round(line_width * 0.4 / 0.35, 2)
speed_infill = =math.ceil(speed_print * 45 / 60)

View file

@ -0,0 +1,29 @@
[general]
version = 4
name = Extra Fine
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = high
weight = 1
material = generic_abs
variant = AA 0.4
[values]
cool_min_speed = 12
machine_nozzle_cool_down_speed = 0.8
machine_nozzle_heat_up_speed = 1.5
material_print_temperature = =default_material_print_temperature + 5
material_initial_print_temperature = =material_print_temperature - 15
material_final_print_temperature = =material_print_temperature - 20
prime_tower_enable = False
speed_print = 50
speed_layer_0 = =math.ceil(speed_print * 20 / 50)
speed_topbottom = =math.ceil(speed_print * 30 / 50)
speed_wall = =math.ceil(speed_print * 30 / 50)
infill_line_width = =round(line_width * 0.4 / 0.35, 2)
speed_infill = =math.ceil(speed_print * 40 / 50)

View file

@ -0,0 +1,27 @@
[general]
version = 4
name = Fine
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = normal
weight = 0
material = generic_abs
variant = AA 0.4
[values]
machine_nozzle_cool_down_speed = 0.85
machine_nozzle_heat_up_speed = 1.5
material_print_temperature = =default_material_print_temperature + 10
material_initial_print_temperature = =material_print_temperature - 15
material_final_print_temperature = =material_print_temperature - 20
prime_tower_enable = False
speed_print = 55
speed_layer_0 = =math.ceil(speed_print * 20 / 55)
speed_topbottom = =math.ceil(speed_print * 30 / 55)
speed_wall = =math.ceil(speed_print * 30 / 55)
infill_line_width = =round(line_width * 0.4 / 0.35, 2)
speed_infill = =math.ceil(speed_print * 40 / 55)

View file

@ -0,0 +1,40 @@
[general]
version = 4
name = Fast
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = draft
weight = -2
material = generic_bam
variant = AA 0.4
[values]
brim_replaces_support = False
cool_fan_full_at_height = =layer_height_0 + 2 * layer_height
cool_fan_speed_max = =cool_fan_speed
machine_nozzle_cool_down_speed = 0.75
machine_nozzle_heat_up_speed = 1.6
material_print_temperature = =default_material_print_temperature + 5
# prime_tower_enable: see CURA-4248
prime_tower_enable = =min(extruderValues('material_surface_energy')) < 100
skin_overlap = 20
speed_layer_0 = =math.ceil(speed_print * 20 / 70)
speed_topbottom = =math.ceil(speed_print * 35 / 70)
speed_wall = =math.ceil(speed_print * 50 / 70)
speed_wall_0 = =math.ceil(speed_wall * 35 / 50)
top_bottom_thickness = 1
wall_thickness = 1
support_brim_enable = True
support_interface_enable = True
support_interface_density = =min(extruderValues('material_surface_energy'))
support_interface_pattern = ='lines' if support_interface_density < 100 else 'concentric'
support_top_distance = =math.ceil(min(extruderValues('material_adhesion_tendency')) / 2) * layer_height
support_bottom_distance = =math.ceil(min(extruderValues('material_adhesion_tendency')) / 2) * layer_height
support_angle = 45
support_join_distance = 5
support_offset = 2
support_pattern = triangles
support_infill_rate = =10 if support_enable else 0 if support_tree_enable else 10

View file

@ -0,0 +1,39 @@
[general]
version = 4
name = Normal
definition = ultimaker_s3
[metadata]
setting_version = 9
type = quality
quality_type = fast
weight = -1
material = generic_bam
variant = AA 0.4
[values]
brim_replaces_support = False
cool_fan_full_at_height = =layer_height_0 + 2 * layer_height
cool_fan_speed_max = =cool_fan_speed
machine_nozzle_cool_down_speed = 0.75
machine_nozzle_heat_up_speed = 1.6
# prime_tower_enable: see CURA-4248
prime_tower_enable = =min(extruderValues('material_surface_energy')) < 100
speed_print = 80
speed_layer_0 = =math.ceil(speed_print * 20 / 80)
speed_topbottom = =math.ceil(speed_print * 30 / 80)
speed_wall = =math.ceil(speed_print * 40 / 80)
speed_wall_0 = =math.ceil(speed_wall * 30 / 40)
top_bottom_thickness = 1
wall_thickness = 1
support_brim_enable = True
support_interface_enable = True
support_interface_density = =min(extruderValues('material_surface_energy'))
support_interface_pattern = ='lines' if support_interface_density < 100 else 'concentric'
support_top_distance = =math.ceil(min(extruderValues('material_adhesion_tendency')) / 1) * layer_height
support_bottom_distance = =math.ceil(min(extruderValues('material_adhesion_tendency')) / 2) * layer_height
support_angle = 45
support_join_distance = 5
support_offset = 2
support_pattern = triangles
support_infill_rate = =10 if support_enable else 0 if support_tree_enable else 10

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