Merge branch 'master' into feature_extruder_per_feature

Conflict in FDMPrinter: The Cubic Subdivision Radius setting was removed. No need to limit that setting any more.
This commit is contained in:
Ghostkeeper 2017-05-17 14:24:00 +02:00
commit 9450453b42
No known key found for this signature in database
GPG Key ID: C5F96EE2BC0F7E75
414 changed files with 16128 additions and 4264 deletions

24
.gitignore vendored
View File

@ -11,6 +11,10 @@ resources/firmware
resources/materials
LC_MESSAGES
.cache
*.qmlc
#MacOS
.DS_Store
# Editors and IDEs.
*kdev*
@ -19,6 +23,7 @@ LC_MESSAGES
*~
*.qm
.idea
cura.desktop
# Eclipse+PyDev
.project
@ -33,4 +38,23 @@ plugins/Doodle3D-cura-plugin
plugins/GodMode
plugins/PostProcessingPlugin
plugins/X3GWriter
plugins/FlatProfileExporter
plugins/ProfileFlattener
plugins/cura-god-mode-plugin
plugins/cura-big-flame-graph
#Build stuff
CMakeCache.txt
CMakeFiles
CPackSourceConfig.cmake
Testing/
CTestTestfile.cmake
Makefile*
junit-pytest-*
CuraVersion.py
cmake_install.cmake
#Debug
*.gcode
run.sh

View File

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

View File

@ -114,7 +114,7 @@ class Arrange:
self._priority_unique_values.sort()
## Return the amount of "penalty points" for polygon, which is the sum of priority
# 999999 if occupied
# None if occupied
# \param x x-coordinate to check shape
# \param y y-coordinate
# \param shape_arr the ShapeArray object to place
@ -128,9 +128,9 @@ class Arrange:
offset_x:offset_x + shape_arr.arr.shape[1]]
try:
if numpy.any(occupied_slice[numpy.where(shape_arr.arr == 1)]):
return 999999
return None
except IndexError: # out of bounds if you try to place an object outside
return 999999
return None
prio_slice = self._priority[
offset_y:offset_y + shape_arr.arr.shape[0],
offset_x:offset_x + shape_arr.arr.shape[1]]
@ -157,7 +157,7 @@ class Arrange:
# array to "world" coordinates
penalty_points = self.checkShape(projected_x, projected_y, shape_arr)
if penalty_points != 999999:
if penalty_points is not None:
return LocationSuggestion(x = projected_x, y = projected_y, penalty_points = penalty_points, priority = priority)
return LocationSuggestion(x = None, y = None, penalty_points = None, priority = priority) # No suitable location found :-(

View File

@ -25,6 +25,8 @@ catalog = i18nCatalog("cura")
import numpy
import math
from typing import List
# Setting for clearance around the prime
PRIME_CLEARANCE = 6.5
@ -129,7 +131,7 @@ class BuildVolume(SceneNode):
## Updates the listeners that listen for changes in per-mesh stacks.
#
# \param node The node for which the decorators changed.
def _updateNodeListeners(self, node):
def _updateNodeListeners(self, node: SceneNode):
per_mesh_stack = node.callDecoration("getStack")
if per_mesh_stack:
per_mesh_stack.propertyChanged.connect(self._onSettingPropertyChanged)
@ -139,21 +141,25 @@ class BuildVolume(SceneNode):
self._updateDisallowedAreasAndRebuild()
def setWidth(self, width):
if width: self._width = width
if width is not None:
self._width = width
def setHeight(self, height):
if height: self._height = height
if height is not None:
self._height = height
def setDepth(self, depth):
if depth: self._depth = depth
if depth is not None:
self._depth = depth
def setShape(self, shape):
if shape: self._shape = shape
def setShape(self, shape: str):
if shape:
self._shape = shape
def getDisallowedAreas(self):
def getDisallowedAreas(self) -> List[Polygon]:
return self._disallowed_areas
def setDisallowedAreas(self, areas):
def setDisallowedAreas(self, areas: List[Polygon]):
self._disallowed_areas = areas
def render(self, renderer):
@ -196,7 +202,6 @@ class BuildVolume(SceneNode):
return
for node in nodes:
# Need to check group nodes later
if node.callDecoration("isGroup"):
group_nodes.append(node) # Keep list of affected group_nodes
@ -412,10 +417,10 @@ class BuildVolume(SceneNode):
self.updateNodeBoundaryCheck()
def getBoundingBox(self):
def getBoundingBox(self) -> AxisAlignedBox:
return self._volume_aabb
def getRaftThickness(self):
def getRaftThickness(self) -> float:
return self._raft_thickness
def _updateRaftThickness(self):
@ -428,7 +433,8 @@ class BuildVolume(SceneNode):
self._global_container_stack.getProperty("raft_interface_thickness", "value") +
self._global_container_stack.getProperty("raft_surface_layers", "value") *
self._global_container_stack.getProperty("raft_surface_thickness", "value") +
self._global_container_stack.getProperty("raft_airgap", "value"))
self._global_container_stack.getProperty("raft_airgap", "value") -
self._global_container_stack.getProperty("layer_0_z_overlap", "value"))
# Rounding errors do not matter, we check if raft_thickness has changed at all
if old_raft_thickness != self._raft_thickness:
@ -492,7 +498,7 @@ class BuildVolume(SceneNode):
self._engine_ready = True
self.rebuild()
def _onSettingPropertyChanged(self, setting_key, property_name):
def _onSettingPropertyChanged(self, setting_key: str, property_name: str):
if property_name != "value":
return
@ -525,7 +531,7 @@ class BuildVolume(SceneNode):
if rebuild_me:
self.rebuild()
def hasErrors(self):
def hasErrors(self) -> bool:
return self._has_errors
## Calls _updateDisallowedAreas and makes sure the changes appear in the
@ -557,7 +563,7 @@ class BuildVolume(SceneNode):
used_extruders = [self._global_container_stack]
result_areas = self._computeDisallowedAreasStatic(disallowed_border_size, used_extruders) #Normal machine disallowed areas can always be added.
prime_areas = self._computeDisallowedAreasPrime(disallowed_border_size, used_extruders)
prime_areas = self._computeDisallowedAreasPrimeBlob(disallowed_border_size, used_extruders)
prime_disallowed_areas = self._computeDisallowedAreasStatic(0, used_extruders) #Where the priming is not allowed to happen. This is not added to the result, just for collision checking.
#Check if prime positions intersect with disallowed areas.
@ -595,20 +601,21 @@ class BuildVolume(SceneNode):
result_areas[extruder_id].append(polygon) #Don't perform the offset on these.
# Add prime tower location as disallowed area.
prime_tower_collision = False
prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
for extruder_id in prime_tower_areas:
for prime_tower_area in prime_tower_areas[extruder_id]:
for area in result_areas[extruder_id]:
if prime_tower_area.intersectsPolygon(area) is not None:
prime_tower_collision = True
if len(used_extruders) > 1: #No prime tower in single-extrusion.
prime_tower_collision = False
prime_tower_areas = self._computeDisallowedAreasPrinted(used_extruders)
for extruder_id in prime_tower_areas:
for prime_tower_area in prime_tower_areas[extruder_id]:
for area in result_areas[extruder_id]:
if prime_tower_area.intersectsPolygon(area) is not None:
prime_tower_collision = True
break
if prime_tower_collision: #Already found a collision.
break
if prime_tower_collision: #Already found a collision.
break
if not prime_tower_collision:
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
else:
self._error_areas.extend(prime_tower_areas[extruder_id])
if not prime_tower_collision:
result_areas[extruder_id].extend(prime_tower_areas[extruder_id])
else:
self._error_areas.extend(prime_tower_areas[extruder_id])
self._has_errors = len(self._error_areas) > 0
@ -652,7 +659,7 @@ class BuildVolume(SceneNode):
return result
## Computes the disallowed areas for the prime locations.
## Computes the disallowed areas for the prime blobs.
#
# These are special because they are not subject to things like brim or
# travel avoidance. They do get a dilute with the border size though
@ -663,17 +670,18 @@ class BuildVolume(SceneNode):
# \param used_extruders The extruder stacks to generate disallowed areas
# for.
# \return A dictionary with for each used extruder ID the prime areas.
def _computeDisallowedAreasPrime(self, border_size, used_extruders):
def _computeDisallowedAreasPrimeBlob(self, border_size, used_extruders):
result = {}
machine_width = self._global_container_stack.getProperty("machine_width", "value")
machine_depth = self._global_container_stack.getProperty("machine_depth", "value")
for extruder in used_extruders:
prime_blob_enabled = extruder.getProperty("prime_blob_enable", "value")
prime_x = extruder.getProperty("extruder_prime_pos_x", "value")
prime_y = - extruder.getProperty("extruder_prime_pos_y", "value")
#Ignore extruder prime position if it is not set
if prime_x == 0 and prime_y == 0:
#Ignore extruder prime position if it is not set or if blob is disabled
if (prime_x == 0 and prime_y == 0) or not prime_blob_enabled:
result[extruder.getId()] = []
continue
@ -944,9 +952,9 @@ class BuildVolume(SceneNode):
return max(min(value, max_value), min_value)
_skirt_settings = ["adhesion_type", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "brim_width", "brim_line_count", "raft_margin", "draft_shield_enabled", "draft_shield_dist"]
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap"]
_raft_settings = ["adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "raft_surface_thickness", "raft_airgap", "layer_0_z_overlap"]
_extra_z_settings = ["retraction_hop_enabled", "retraction_hop"]
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z"]
_prime_settings = ["extruder_prime_pos_x", "extruder_prime_pos_y", "extruder_prime_pos_z", "prime_blob_enable"]
_tower_settings = ["prime_tower_enable", "prime_tower_size", "prime_tower_position_x", "prime_tower_position_y"]
_ooze_shield_settings = ["ooze_shield_enabled", "ooze_shield_dist"]
_distance_settings = ["infill_wipe_dist", "travel_avoid_distance", "support_offset", "support_enable", "travel_avoid_other_parts"]

View File

@ -59,7 +59,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
hull = self._compute2DConvexHull()
if self._global_stack and self._node:
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
# Parent can be None if node is just loaded.
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")):
hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32)))
hull = self._add2DAdhesionMargin(hull)
return hull
@ -79,7 +80,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
return None
if self._global_stack:
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")):
head_with_fans = self._compute2DConvexHeadMin()
head_with_fans_with_adhesion_margin = self._add2DAdhesionMargin(head_with_fans)
return head_with_fans_with_adhesion_margin
@ -93,8 +94,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
return None
if self._global_stack:
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and not self._node.getParent().callDecoration("isGroup"):
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")):
# Printing one at a time and it's not an object in a group
return self._compute2DConvexHull()
return None
@ -328,11 +328,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
return self.__isDescendant(root, node.getParent())
_affected_settings = [
"adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers",
"raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence",
"adhesion_type", "raft_margin", "print_sequence",
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
## Settings that change the convex hull.
#
# If these settings change, the convex hull should be recalculated.
_influencing_settings = {"xy_offset", "mold_enabled", "mold_width"}
_influencing_settings = {"xy_offset", "mold_enabled", "mold_width"}

View File

@ -48,35 +48,32 @@ def show(exception_type, value, tb):
dialog = QDialog()
dialog.setMinimumWidth(640)
dialog.setMinimumHeight(640)
dialog.setWindowTitle(catalog.i18nc("@title:window", "Oops!"))
dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))
layout = QVBoxLayout(dialog)
label = QLabel(dialog)
pixmap = QPixmap()
try:
data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read()
pixmap.loadFromData(data)
except:
try:
from UM.Resources import Resources
path = Resources.getPath(Resources.Images, "kitten.jpg")
pixmap.load(path)
except:
pass
pixmap = pixmap.scaled(150, 150)
label.setPixmap(pixmap)
label.setAlignment(Qt.AlignCenter)
layout.addWidget(label)
#label = QLabel(dialog)
#pixmap = QPixmap()
#try:
# data = urllib.request.urlopen("http://www.randomkittengenerator.com/cats/rotator.php").read()
# pixmap.loadFromData(data)
#except:
# try:
# from UM.Resources import Resources
# path = Resources.getPath(Resources.Images, "kitten.jpg")
# pixmap.load(path)
# except:
# pass
#pixmap = pixmap.scaled(150, 150)
#label.setPixmap(pixmap)
#label.setAlignment(Qt.AlignCenter)
#layout.addWidget(label)
label = QLabel(dialog)
layout.addWidget(label)
#label.setScaledContents(True)
label.setText(catalog.i18nc("@label", """<p>A fatal exception has occurred that we could not recover from!</p>
<p>We hope this picture of a kitten helps you recover from the shock.</p>
<p>Please use the information below to post a bug report at <a href=\"http://github.com/Ultimaker/Cura/issues\">http://github.com/Ultimaker/Cura/issues</a></p>
"""))

View File

@ -1,10 +1,23 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtGui import QDesktopServices
from UM.FlameProfiler import pyqtSlot
from UM.Event import CallFunctionEvent
from UM.Application import Application
from UM.Math.Vector import Vector
from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.SetTransformOperation import SetTransformOperation
from cura.SetParentOperation import SetParentOperation
from cura.MultiplyObjectsJob import MultiplyObjectsJob
from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation
from cura.Settings.ExtruderManager import ExtruderManager
class CuraActions(QObject):
def __init__(self, parent = None):
@ -23,5 +36,84 @@ class CuraActions(QObject):
event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {})
Application.getInstance().functionEvent(event)
## Center all objects in the selection
@pyqtSlot()
def centerSelection(self) -> None:
operation = GroupedOperation()
for node in Selection.getAllSelectedObjects():
current_node = node
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
current_node = current_node.getParent()
center_operation = SetTransformOperation(current_node, Vector())
operation.addOperation(center_operation)
operation.push()
## Multiply all objects in the selection
#
# \param count The number of times to multiply the selection.
@pyqtSlot(int)
def multiplySelection(self, count: int) -> None:
job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, 8)
job.start()
## Delete all selected objects.
@pyqtSlot()
def deleteSelection(self) -> None:
if not Application.getInstance().getController().getToolsEnabled():
return
removed_group_nodes = []
op = GroupedOperation()
nodes = Selection.getAllSelectedObjects()
for node in nodes:
op.addOperation(RemoveSceneNodeOperation(node))
group_node = node.getParent()
if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes:
remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes))
if len(remaining_nodes_in_group) == 1:
removed_group_nodes.append(group_node)
op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent()))
op.addOperation(RemoveSceneNodeOperation(group_node))
op.push()
## Set the extruder that should be used to print the selection.
#
# \param extruder_id The ID of the extruder stack to use for the selected objects.
@pyqtSlot(str)
def setExtruderForSelection(self, extruder_id: str) -> None:
operation = GroupedOperation()
nodes_to_change = []
for node in Selection.getAllSelectedObjects():
# Do not change any nodes that already have the right extruder set.
if node.callDecoration("getActiveExtruder") == extruder_id:
continue
# If the node is a group, apply the active extruder to all children of the group.
if node.callDecoration("isGroup"):
for grouped_node in BreadthFirstIterator(node):
if grouped_node.callDecoration("getActiveExtruder") == extruder_id:
continue
if grouped_node.callDecoration("isGroup"):
continue
nodes_to_change.append(grouped_node)
continue
nodes_to_change.append(node)
if not nodes_to_change:
# If there are no changes to make, we still need to reset the selected extruders.
# This is a workaround for checked menu items being deselected while still being
# selected.
ExtruderManager.getInstance().resetSelectedObjectExtruders()
return
for node in nodes_to_change:
operation.addOperation(SetObjectExtruderOperation(node, extruder_id))
operation.push()
def _openUrl(self, url):
QDesktopServices.openUrl(url)
QDesktopServices.openUrl(url)

View File

@ -26,6 +26,7 @@ from UM.Message import Message
from UM.i18n import i18nCatalog
from UM.Workspace.WorkspaceReader import WorkspaceReader
from UM.Platform import Platform
from UM.Decorators import deprecated
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
@ -68,6 +69,8 @@ from cura.Settings.ContainerSettingsModel import ContainerSettingsModel
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from cura.Settings.QualitySettingsModel import QualitySettingsModel
from cura.Settings.ContainerManager import ContainerManager
from cura.Settings.GlobalStack import GlobalStack
from cura.Settings.ExtruderStack import ExtruderStack
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from UM.FlameProfiler import pyqtSlot
@ -96,6 +99,11 @@ if not MYPY:
class CuraApplication(QtApplication):
# SettingVersion represents the set of settings available in the machine/extruder definitions.
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
# changes of the settings.
SettingVersion = 1
class ResourceTypes:
QmlFiles = Resources.UserType + 1
Firmware = Resources.UserType + 2
@ -105,10 +113,15 @@ class CuraApplication(QtApplication):
UserInstanceContainer = Resources.UserType + 6
MachineStack = Resources.UserType + 7
ExtruderStack = Resources.UserType + 8
DefinitionChangesContainer = Resources.UserType + 9
Q_ENUMS(ResourceTypes)
def __init__(self):
# this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
Resources.addExpectedDirNameInData(dir_name)
Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources"))
if not hasattr(sys, "frozen"):
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
@ -146,6 +159,7 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.UserInstanceContainer, "user")
Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer)
@ -153,17 +167,18 @@ class CuraApplication(QtApplication):
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack)
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer)
## Initialise the version upgrade manager with Cura's storage paths.
import UM.VersionUpgradeManager #Needs to be here to prevent circular dependencies.
UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions(
{
("quality", InstanceContainer.Version): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
("machine_stack", ContainerStack.Version): (self.ResourceTypes.MachineStack, "application/x-uranium-containerstack"),
("extruder_train", ContainerStack.Version): (self.ResourceTypes.ExtruderStack, "application/x-uranium-extruderstack"),
("preferences", Preferences.Version): (Resources.Preferences, "application/x-uranium-preferences"),
("user", InstanceContainer.Version): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer")
("user", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.UserInstanceContainer, "application/x-uranium-instancecontainer")
}
)
@ -214,6 +229,7 @@ class CuraApplication(QtApplication):
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)
self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware")
@ -283,6 +299,7 @@ class CuraApplication(QtApplication):
z_seam_y
infill
infill_sparse_density
gradual_infill_steps
material
material_print_temperature
material_bed_temperature
@ -320,6 +337,7 @@ class CuraApplication(QtApplication):
blackmagic
print_sequence
infill_mesh
cutting_mesh
experimental
""".replace("\n", ";").replace(" ", ""))
@ -407,7 +425,7 @@ class CuraApplication(QtApplication):
elif instance_type == "variant":
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
elif instance_type == "definition_changes":
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
path = Resources.getStoragePath(self.ResourceTypes.DefinitionChangesContainer, file_name)
if path:
instance.setPath(path)
@ -430,16 +448,18 @@ class CuraApplication(QtApplication):
mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack))
file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix
stack_type = stack.getMetaDataEntry("type", None)
path = None
if not stack_type or stack_type == "machine":
if isinstance(stack, GlobalStack):
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
elif stack_type == "extruder_train":
elif isinstance(stack, ExtruderStack):
path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name)
if path:
stack.setPath(path)
with SaveFile(path, "wt") as f:
f.write(data)
else:
path = Resources.getStoragePath(Resources.ContainerStacks, file_name)
stack.setPath(path)
with SaveFile(path, "wt") as f:
f.write(data)
@pyqtSlot(str, result = QUrl)
@ -803,6 +823,7 @@ class CuraApplication(QtApplication):
# Remove all selected objects from the scene.
@pyqtSlot()
@deprecated("Moved to CuraActions", "2.6")
def deleteSelection(self):
if not self.getController().getToolsEnabled():
return
@ -823,6 +844,7 @@ class CuraApplication(QtApplication):
## Remove an object from the scene.
# Note that this only removes an object if it is selected.
@pyqtSlot("quint64")
@deprecated("Use deleteSelection instead", "2.6")
def deleteObject(self, object_id):
if not self.getController().getToolsEnabled():
return
@ -850,13 +872,22 @@ class CuraApplication(QtApplication):
# \param count number of copies
# \param min_offset minimum offset to other objects.
@pyqtSlot("quint64", int)
@deprecated("Use CuraActions::multiplySelection", "2.6")
def multiplyObject(self, object_id, count, min_offset = 8):
job = MultiplyObjectsJob(object_id, count, min_offset)
node = self.getController().getScene().findObject(object_id)
if not node:
node = Selection.getSelectedObject(0)
while node.getParent() and node.getParent().callDecoration("isGroup"):
node = node.getParent()
job = MultiplyObjectsJob([node], count, min_offset)
job.start()
return
## Center object on platform.
@pyqtSlot("quint64")
@deprecated("Use CuraActions::centerSelection", "2.6")
def centerObject(self, object_id):
node = self.getController().getScene().findObject(object_id)
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
@ -984,7 +1015,9 @@ class CuraApplication(QtApplication):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
if not node.isSelectable():
continue # i.e. node with layer data
nodes.append(node)
# Skip nodes that are too big
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
nodes.append(node)
self.arrange(nodes, fixed_nodes = [])
## Arrange Selection
@ -1255,6 +1288,8 @@ class CuraApplication(QtApplication):
arranger = Arrange.create(scene_root = root)
min_offset = 8
self.fileLoaded.emit(filename)
for node in nodes:
node.setSelectable(True)
node.setName(os.path.basename(filename))
@ -1278,13 +1313,18 @@ class CuraApplication(QtApplication):
# If there is no convex hull for the node, start calculating it and continue.
if not node.getDecorator(ConvexHullDecorator):
node.addDecorator(ConvexHullDecorator())
for child in node.getAllChildren():
if not child.getDecorator(ConvexHullDecorator):
child.addDecorator(ConvexHullDecorator())
if node.callDecoration("isSliceable"):
# Find node location
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
# Only check position if it's not already blatantly obvious that it won't fit.
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
# Find node location
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = min_offset)
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
node,_ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
op = AddSceneNodeOperation(node, scene.getRoot())
op.push()
@ -1309,3 +1349,13 @@ class CuraApplication(QtApplication):
except Exception as e:
Logger.log("e", "Could not check file %s: %s", file_url, e)
return False
def _onContextMenuRequested(self, x: float, y: float) -> None:
# Ensure we select the object if we request a context menu over an object without having a selection.
if not Selection.hasSelection():
node = self.getController().getScene().findObject(self.getRenderer().getRenderPass("selection").getIdAtPosition(x, y))
if node:
while(node.getParent() and node.getParent().callDecoration("isGroup")):
node = node.getParent()
Selection.add(node)

View File

@ -24,9 +24,9 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
class MultiplyObjectsJob(Job):
def __init__(self, object_id, count, min_offset = 8):
def __init__(self, objects, count, min_offset = 8):
super().__init__()
self._object_id = object_id
self._objects = objects
self._count = count
self._min_offset = min_offset
@ -35,33 +35,42 @@ class MultiplyObjectsJob(Job):
dismissable=False, progress=0)
status_message.show()
scene = Application.getInstance().getController().getScene()
node = scene.findObject(self._object_id)
if not node and self._object_id != 0: # Workaround for tool handles overlapping the selected object
node = Selection.getSelectedObject(0)
# If object is part of a group, multiply group
current_node = node
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
current_node = current_node.getParent()
total_progress = len(self._objects) * self._count
current_progress = 0
root = scene.getRoot()
arranger = Arrange.create(scene_root=root)
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset)
nodes = []
found_solution_for_all = True
for i in range(self._count):
# We do place the nodes one by one, as we want to yield in between.
node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
if not solution_found:
found_solution_for_all = False
new_location = node.getPosition()
new_location = new_location.set(z = 100 - i * 20)
node.setPosition(new_location)
for node in self._objects:
# If object is part of a group, multiply group
current_node = node
while current_node.getParent() and current_node.getParent().callDecoration("isGroup"):
current_node = current_node.getParent()
node_too_big = False
if node.getBoundingBox().width < 300 or node.getBoundingBox().depth < 300:
offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset)
else:
node_too_big = True
found_solution_for_all = True
for i in range(self._count):
# We do place the nodes one by one, as we want to yield in between.
if not node_too_big:
node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr)
if node_too_big or not solution_found:
found_solution_for_all = False
new_location = node.getPosition()
new_location = new_location.set(z = 100 - i * 20)
node.setPosition(new_location)
nodes.append(node)
current_progress += 1
status_message.setProgress((current_progress / total_progress) * 100)
Job.yieldThread()
nodes.append(node)
Job.yieldThread()
status_message.setProgress((i + 1) / self._count * 100)
if nodes:
op = GroupedOperation()
@ -72,4 +81,4 @@ class MultiplyObjectsJob(Job):
if not found_solution_for_all:
no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"))
no_full_solution_message.show()
no_full_solution_message.show()

View File

@ -31,8 +31,8 @@ catalog = i18nCatalog("cura")
# - This triggers a new slice with the current settings - this is the "current settings pass".
# - When the slice is done, we update the current print time and material amount.
# - If the source of the slice was not a Setting change, we start the second slice pass, the "low quality settings pass". Otherwise we stop here.
# - When that is done, we update the minimum print time and start the final slice pass, the "high quality settings pass".
# - When the high quality pass is done, we update the maximum print time.
# - When that is done, we update the minimum print time and start the final slice pass, the "Extra Fine settings pass".
# - When the Extra Fine pass is done, we update the maximum print time.
#
# This class also mangles the current machine name and the filename of the first loaded mesh into a job name.
# This job name is requested by the JobSpecs qml file.
@ -52,6 +52,19 @@ class PrintInformation(QObject):
super().__init__(parent)
self._current_print_time = Duration(None, self)
self._print_times_per_feature = {
"none": Duration(None, self),
"inset_0": Duration(None, self),
"inset_x": Duration(None, self),
"skin": Duration(None, self),
"support": Duration(None, self),
"skirt": Duration(None, self),
"infill": Duration(None, self),
"support_infill": Duration(None, self),
"travel": Duration(None, self),
"retract": Duration(None, self),
"support_interface": Duration(None, self)
}
self._material_lengths = []
self._material_weights = []
@ -93,6 +106,10 @@ class PrintInformation(QObject):
def currentPrintTime(self):
return self._current_print_time
@pyqtProperty("QVariantMap", notify = currentPrintTimeChanged)
def printTimesPerFeature(self):
return self._print_times_per_feature
materialLengthsChanged = pyqtSignal()
@pyqtProperty("QVariantList", notify = materialLengthsChanged)
@ -111,12 +128,16 @@ class PrintInformation(QObject):
def materialCosts(self):
return self._material_costs
def _onPrintDurationMessage(self, total_time, material_amounts):
if total_time != total_time: # Check for NaN. Engine can sometimes give us weird values.
Logger.log("w", "Received NaN for print duration message")
self._current_print_time.setDuration(0)
else:
self._current_print_time.setDuration(total_time)
def _onPrintDurationMessage(self, time_per_feature, material_amounts):
total_time = 0
for feature, time in time_per_feature.items():
if time != time: # Check for NaN. Engine can sometimes give us weird values.
self._print_times_per_feature[feature].setDuration(0)
Logger.log("w", "Received NaN for print duration message")
continue
total_time += time
self._print_times_per_feature[feature].setDuration(time)
self._current_print_time.setDuration(total_time)
self.currentPrintTimeChanged.emit()
@ -183,7 +204,10 @@ class PrintInformation(QObject):
def _onActiveMaterialChanged(self):
if self._active_material_container:
self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged)
try:
self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged)
except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that is already disconnected.
pass
active_material_id = Application.getInstance().getMachineManager().activeMaterialId
active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id=active_material_id)

View File

@ -3,7 +3,7 @@
# This collects a lot of quality and quality changes related code which was split between ContainerManager
# and the MachineManager and really needs to usable from both.
from typing import List
from typing import List, Optional, Dict, TYPE_CHECKING
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
@ -11,14 +11,18 @@ from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer
from cura.Settings.ExtruderManager import ExtruderManager
if TYPE_CHECKING:
from cura.Settings.GlobalStack import GlobalStack
from cura.Settings.ExtruderStack import ExtruderStack
from UM.Settings.DefinitionContainer import DefinitionContainerInterface
class QualityManager:
## Get the singleton instance for this class.
@classmethod
def getInstance(cls):
def getInstance(cls) -> "QualityManager":
# Note: Explicit use of class name to prevent issues with inheritance.
if QualityManager.__instance is None:
if not QualityManager.__instance:
QualityManager.__instance = cls()
return QualityManager.__instance
@ -27,12 +31,12 @@ class QualityManager:
## Find a quality by name for a specific machine definition and materials.
#
# \param quality_name
# \param machine_definition (Optional) \type{ContainerInstance} If nothing is
# \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is
# specified then the currently selected machine definition is used.
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
# \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then
# the current set of selected materials is used.
# \return the matching quality container \type{ContainerInstance}
def findQualityByName(self, quality_name, machine_definition=None, material_containers=None):
# \return the matching quality container \type{InstanceContainer}
def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None) -> Optional[InstanceContainer]:
criteria = {"type": "quality", "name": quality_name}
result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria)
@ -46,12 +50,10 @@ class QualityManager:
## Find a quality changes container by name.
#
# \param quality_changes_name \type{str} the name of the quality changes container.
# \param machine_definition (Optional) \type{ContainerInstance} If nothing is
# specified then the currently selected machine definition is used.
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
# the current set of selected materials is used.
# \return the matching quality changes containers \type{List[ContainerInstance]}
def findQualityChangesByName(self, quality_changes_name, machine_definition=None):
# \param machine_definition (Optional) \type{DefinitionContainer} If nothing is
# specified then the currently selected machine definition is used..
# \return the matching quality changes containers \type{List[InstanceContainer]}
def findQualityChangesByName(self, quality_changes_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None):
criteria = {"type": "quality_changes", "name": quality_changes_name}
result = self._getFilteredContainersForStack(machine_definition, [], **criteria)
@ -62,7 +64,7 @@ class QualityManager:
# \param machine_definition \type{DefinitionContainer}
# \param material_containers \type{List[InstanceContainer]}
# \return \type{List[str]}
def findAllQualityTypesForMachineAndMaterials(self, machine_definition, material_containers):
def findAllQualityTypesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[str]:
# Determine the common set of quality types which can be
# applied to all of the materials for this machine.
quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
@ -76,9 +78,9 @@ class QualityManager:
## Fetches a dict of quality types names to quality profiles for a combination of machine and material.
#
# \param machine_definition \type{DefinitionContainer} the machine definition.
# \param material \type{ContainerInstance} the material.
# \return \type{Dict[str, ContainerInstance]} the dict of suitable quality type names mapping to qualities.
def __fetchQualityTypeDictForMaterial(self, machine_definition, material):
# \param material \type{InstanceContainer} the material.
# \return \type{Dict[str, InstanceContainer]} the dict of suitable quality type names mapping to qualities.
def __fetchQualityTypeDictForMaterial(self, machine_definition: "DefinitionContainerInterface", material: InstanceContainer) -> Dict[str, InstanceContainer]:
qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material)
quality_type_dict = {}
for quality in qualities:
@ -88,12 +90,12 @@ class QualityManager:
## Find a quality container by quality type.
#
# \param quality_type \type{str} the name of the quality type to search for.
# \param machine_definition (Optional) \type{ContainerInstance} If nothing is
# \param machine_definition (Optional) \type{InstanceContainer} If nothing is
# specified then the currently selected machine definition is used.
# \param material_containers (Optional) \type{List[ContainerInstance]} If nothing is specified then
# \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then
# the current set of selected materials is used.
# \return the matching quality container \type{ContainerInstance}
def findQualityByQualityType(self, quality_type, machine_definition=None, material_containers=None, **kwargs):
# \return the matching quality container \type{InstanceContainer}
def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None, **kwargs) -> InstanceContainer:
criteria = kwargs
criteria["type"] = "quality"
if quality_type:
@ -110,9 +112,9 @@ class QualityManager:
## Find all suitable qualities for a combination of machine and material.
#
# \param machine_definition \type{DefinitionContainer} the machine definition.
# \param material_container \type{ContainerInstance} the material.
# \return \type{List[ContainerInstance]} the list of suitable qualities.
def findAllQualitiesForMachineMaterial(self, machine_definition, material_container):
# \param material_container \type{InstanceContainer} the material.
# \return \type{List[InstanceContainer]} the list of suitable qualities.
def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]:
criteria = {"type": "quality" }
result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria)
if not result:
@ -125,7 +127,7 @@ class QualityManager:
#
# \param machine_definition \type{DefinitionContainer} the machine definition.
# \return \type{List[InstanceContainer]} the list of quality changes
def findAllQualityChangesForMachine(self, machine_definition: DefinitionContainer) -> List[InstanceContainer]:
def findAllQualityChangesForMachine(self, machine_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
if machine_definition.getMetaDataEntry("has_machine_quality"):
definition_id = machine_definition.getId()
else:
@ -141,19 +143,19 @@ class QualityManager:
# Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal
# then only one of then is returned (at random).
#
# \param global_container_stack \type{ContainerStack} the global machine definition
# \param extruder_stacks \type{List[ContainerStack]} the list of extruder stacks
# \param global_container_stack \type{GlobalStack} the global machine definition
# \param extruder_stacks \type{List[ExtruderStack]} the list of extruder stacks
# \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles
# return come from the first extruder in the given list of extruders.
def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack, extruder_stacks):
def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]:
global_machine_definition = global_container_stack.getBottom()
if extruder_stacks:
# Multi-extruder machine detected.
materials = [stack.findContainer(type="material") for stack in extruder_stacks]
materials = [stack.material for stack in extruder_stacks]
else:
# Machine with one extruder.
materials = [global_container_stack.findContainer(type="material")]
materials = [global_container_stack.material]
quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
@ -170,7 +172,7 @@ class QualityManager:
# This tries to find a generic or basic version of the given material.
# \param material_container \type{InstanceContainer} the material
# \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found.
def _getBasicMaterials(self, material_container):
def _getBasicMaterials(self, material_container: InstanceContainer):
base_material = material_container.getMetaDataEntry("material")
material_container_definition = material_container.getDefinition()
if material_container_definition and material_container_definition.getMetaDataEntry("has_machine_quality"):
@ -192,7 +194,7 @@ class QualityManager:
def _getFilteredContainers(self, **kwargs):
return self._getFilteredContainersForStack(None, None, **kwargs)
def _getFilteredContainersForStack(self, machine_definition=None, material_containers=None, **kwargs):
def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_containers: List[InstanceContainer] = None, **kwargs):
# Fill in any default values.
if machine_definition is None:
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
@ -202,7 +204,8 @@ class QualityManager:
if material_containers is None:
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
material_containers = [stack.findContainer(type="material") for stack in active_stacks]
if active_stacks:
material_containers = [stack.material for stack in active_stacks]
criteria = kwargs
filter_by_material = False
@ -216,12 +219,11 @@ class QualityManager:
filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials")
else:
criteria["definition"] = "fdmprinter"
material_ids = set()
# Stick the material IDs in a set
if material_containers is None or len(material_containers) == 0:
filter_by_material = False
else:
material_ids = set()
for material_instance in material_containers:
if material_instance is not None:
# Add the parent material too.
@ -245,7 +247,7 @@ class QualityManager:
# an extruder definition.
# \return \type{DefinitionContainer} the parent machine definition. If the given machine
# definition doesn't have a parent then it is simply returned.
def getParentMachineDefinition(self, machine_definition: DefinitionContainer) -> DefinitionContainer:
def getParentMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
container_registry = ContainerRegistry.getInstance()
machine_entry = machine_definition.getMetaDataEntry("machine")
@ -274,8 +276,8 @@ class QualityManager:
#
# \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
# an extruder definition.
# \return \type{DefinitionContainer}
def getWholeMachineDefinition(self, machine_definition):
# \return \type{DefinitionContainerInterface}
def getWholeMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
machine_entry = machine_definition.getMetaDataEntry("machine")
if machine_entry is None:
# This already is a 'global' machine definition.

View File

@ -1,13 +1,15 @@
# Copyright (c) 2016 Ultimaker B.V.
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import os.path
import urllib
import uuid
from typing import Dict, Union
from PyQt5.QtCore import QObject, QUrl, QVariant
from UM.FlameProfiler import pyqtSlot
from PyQt5.QtWidgets import QMessageBox
from UM.Util import parseBool
from UM.PluginRegistry import PluginRegistry
from UM.SaveFile import SaveFile
@ -429,7 +431,7 @@ class ContainerManager(QObject):
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
# Find the quality_changes container for this stack and merge the contents of the top container into it.
quality_changes = stack.findContainer(type = "quality_changes")
quality_changes = stack.qualityChanges
if not quality_changes or quality_changes.isReadOnly():
Logger.log("e", "Could not update quality of a nonexistant or read only quality profile in stack %s", stack.getId())
continue
@ -482,8 +484,8 @@ class ContainerManager(QObject):
# Go through the active stacks and create quality_changes containers from the user containers.
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
user_container = stack.getTop()
quality_container = stack.findContainer(type = "quality")
quality_changes_container = stack.findContainer(type = "quality_changes")
quality_container = stack.quality
quality_changes_container = stack.qualityChanges
if not quality_container or not quality_changes_container:
Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
continue
@ -604,7 +606,7 @@ class ContainerManager(QObject):
machine_definition = global_stack.getBottom()
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
material_containers = [stack.findContainer(type="material") for stack in active_stacks]
material_containers = [stack.material for stack in active_stacks]
result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name,
QualityManager.getInstance().getParentMachineDefinition(machine_definition),
@ -671,6 +673,9 @@ class ContainerManager(QObject):
return new_change_instances
## Create a duplicate of a material, which has the same GUID and base_file metadata
#
# \return \type{str} the id of the newly created container.
@pyqtSlot(str, result = str)
def duplicateMaterial(self, material_id: str) -> str:
containers = self._container_registry.findInstanceContainers(id=material_id)
@ -693,6 +698,104 @@ class ContainerManager(QObject):
duplicated_container.deserialize(f.read())
duplicated_container.setDirty(True)
self._container_registry.addContainer(duplicated_container)
return self._getMaterialContainerIdForActiveMachine(new_id)
## Create a new material by cloning Generic PLA and setting the GUID to something unqiue
#
# \return \type{str} the id of the newly created container.
@pyqtSlot(result = str)
def createMaterial(self) -> str:
# Ensure all settings are saved.
Application.getInstance().saveSettings()
containers = self._container_registry.findInstanceContainers(id="generic_pla")
if not containers:
Logger.log("d", "Unable to create a new material by cloning generic_pla, because it doesn't exist.")
return ""
# Create a new ID & container to hold the data.
new_id = self._container_registry.uniqueName("custom_material")
container_type = type(containers[0]) # Could be either a XMLMaterialProfile or a InstanceContainer
duplicated_container = container_type(new_id)
# Instead of duplicating we load the data from the basefile again.
# This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile
# are also correctly created.
with open(containers[0].getPath(), encoding="utf-8") as f:
duplicated_container.deserialize(f.read())
duplicated_container.setMetaDataEntry("GUID", str(uuid.uuid4()))
duplicated_container.setMetaDataEntry("brand", catalog.i18nc("@label", "Custom"))
duplicated_container.setMetaDataEntry("material", catalog.i18nc("@label", "Custom"))
duplicated_container.setName(catalog.i18nc("@label", "Custom Material"))
self._container_registry.addContainer(duplicated_container)
return self._getMaterialContainerIdForActiveMachine(new_id)
## Find the id of a material container based on the new material
# Utilty function that is shared between duplicateMaterial and createMaterial
#
# \param base_file \type{str} the id of the created container.
def _getMaterialContainerIdForActiveMachine(self, base_file):
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return base_file
has_machine_materials = parseBool(global_stack.getMetaDataEntry("has_machine_materials", default = False))
has_variant_materials = parseBool(global_stack.getMetaDataEntry("has_variant_materials", default = False))
has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False))
if has_machine_materials or has_variant_materials:
if has_variants:
materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
else:
materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
if materials:
return materials[0].getId()
Logger.log("w", "Unable to find a suitable container based on %s for the current machine .", base_file)
return "" # do not activate a new material if a container can not be found
return base_file
## Get a list of materials that have the same GUID as the reference material
#
# \param material_id \type{str} the id of the material for which to get the linked materials.
# \return \type{list} a list of names of materials with the same GUID
@pyqtSlot(str, result = "QStringList")
def getLinkedMaterials(self, material_id: str):
containers = self._container_registry.findInstanceContainers(id=material_id)
if not containers:
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
return []
material_container = containers[0]
material_base_file = material_container.getMetaDataEntry("base_file", "")
material_guid = material_container.getMetaDataEntry("GUID", "")
if not material_guid:
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id)
return []
containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_guid)
linked_material_names = []
for container in containers:
if container.getId() in [material_id, material_base_file] or container.getMetaDataEntry("base_file") != container.getId():
continue
linked_material_names.append(container.getName())
return linked_material_names
## Unlink a material from all other materials by creating a new GUID
# \param material_id \type{str} the id of the material to create a new GUID for.
@pyqtSlot(str)
def unlinkMaterial(self, material_id: str):
containers = self._container_registry.findInstanceContainers(id=material_id)
if not containers:
Logger.log("d", "Unable to make the material with id %s unique, because it doesn't exist.", material_id)
return ""
containers[0].setMetaDataEntry("GUID", str(uuid.uuid4()))
## Get the singleton instance for this class.
@classmethod
@ -815,6 +918,8 @@ class ContainerManager(QObject):
quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0])
else:
quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition))
from cura.CuraApplication import CuraApplication
quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
return quality_changes

View File

@ -1,4 +1,4 @@
# Copyright (c) 2016 Ultimaker B.V.
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import os
@ -6,6 +6,7 @@ import os.path
import re
from PyQt5.QtWidgets import QMessageBox
from UM.Decorators import override
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.InstanceContainer import InstanceContainer
@ -16,8 +17,12 @@ from UM.Platform import Platform
from UM.PluginRegistry import PluginRegistry #For getting the possible profile writers to write with.
from UM.Util import parseBool
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ContainerManager import ContainerManager
from . import ExtruderStack
from . import GlobalStack
from .ContainerManager import ContainerManager
from .ExtruderManager import ExtruderManager
from cura.CuraApplication import CuraApplication
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -26,6 +31,28 @@ class CuraContainerRegistry(ContainerRegistry):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
## Overridden from ContainerRegistry
#
# Adds a container to the registry.
#
# This will also try to convert a ContainerStack to either Extruder or
# Global stack based on metadata information.
@override(ContainerRegistry)
def addContainer(self, container):
# Note: Intentional check with type() because we want to ignore subclasses
if type(container) == ContainerStack:
container = self._convertContainerStack(container)
if isinstance(container, InstanceContainer) and type(container) != type(self.getEmptyInstanceContainer()):
#Check against setting version of the definition.
required_setting_version = CuraApplication.SettingVersion
actual_setting_version = int(container.getMetaDataEntry("setting_version", default = 0))
if required_setting_version != actual_setting_version:
Logger.log("w", "Instance container {container_id} is outdated. Its setting version is {actual_setting_version} but it should be {required_setting_version}.".format(container_id = container.getId(), actual_setting_version = actual_setting_version, required_setting_version = required_setting_version))
return #Don't add.
super().addContainer(container)
## Create a name that is not empty and unique
# \param container_type \type{string} Type of the container (machine, quality, ...)
# \param current_name \type{} Current name of the container, which may be an acceptable option
@ -212,6 +239,11 @@ class CuraContainerRegistry(ContainerRegistry):
# If it hasn't returned by now, none of the plugins loaded the profile successfully.
return {"status": "error", "message": catalog.i18nc("@info:status", "Profile {0} has an unknown file type or is corrupted.", file_name)}
@override(ContainerRegistry)
def load(self):
super().load()
self._fixupExtruders()
def _configureProfile(self, profile, id_seed, new_name):
profile.setReadOnly(False)
profile.setDirty(True) # Ensure the profiles are correctly saved
@ -284,3 +316,47 @@ class CuraContainerRegistry(ContainerRegistry):
if global_container_stack:
return parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", False))
return False
## Convert an "old-style" pure ContainerStack to either an Extruder or Global stack.
def _convertContainerStack(self, container):
assert type(container) == ContainerStack
container_type = container.getMetaDataEntry("type")
if container_type not in ("extruder_train", "machine"):
# It is not an extruder or machine, so do nothing with the stack
return container
Logger.log("d", "Converting ContainerStack {stack} to {type}", stack = container.getId(), type = container_type)
new_stack = None
if container_type == "extruder_train":
new_stack = ExtruderStack.ExtruderStack(container.getId())
else:
new_stack = GlobalStack.GlobalStack(container.getId())
container_contents = container.serialize()
new_stack.deserialize(container_contents)
# Delete the old configuration file so we do not get double stacks
if os.path.isfile(container.getPath()):
os.remove(container.getPath())
return new_stack
# Fix the extruders that were upgraded to ExtruderStack instances during addContainer.
# The stacks are now responsible for setting the next stack on deserialize. However,
# due to problems with loading order, some stacks may not have the proper next stack
# set after upgrading, because the proper global stack was not yet loaded. This method
# makes sure those extruders also get the right stack set.
def _fixupExtruders(self):
extruder_stacks = self.findContainers(ExtruderStack.ExtruderStack)
for extruder_stack in extruder_stacks:
if extruder_stack.getNextStack():
# Has the right next stack, so ignore it.
continue
machines = ContainerRegistry.getInstance().findContainerStacks(id=extruder_stack.getMetaDataEntry("machine", ""))
if machines:
extruder_stack.setNextStack(machines[0])
else:
Logger.log("w", "Could not find machine {machine} for extruder {extruder}", machine = extruder_stack.getMetaDataEntry("machine"), extruder = extruder_stack.getId())

View File

@ -0,0 +1,616 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import os.path
from typing import Any, Optional
from PyQt5.QtCore import pyqtProperty, pyqtSignal
from UM.FlameProfiler import pyqtSlot
from UM.Decorators import override
from UM.Logger import Logger
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import ContainerInterface
from . import Exceptions
## Base class for Cura related stacks that want to enforce certain containers are available.
#
# This class makes sure that the stack has the following containers set: user changes, quality
# changes, quality, material, variant, definition changes and finally definition. Initially,
# these will be equal to the empty instance container.
#
# The container types are determined based on the following criteria:
# - user: An InstanceContainer with the metadata entry "type" set to "user".
# - quality changes: An InstanceContainer with the metadata entry "type" set to "quality_changes".
# - quality: An InstanceContainer with the metadata entry "type" set to "quality".
# - material: An InstanceContainer with the metadata entry "type" set to "material".
# - variant: An InstanceContainer with the metadata entry "type" set to "variant".
# - definition changes: An InstanceContainer with the metadata entry "type" set to "definition_changes".
# - definition: A DefinitionContainer.
#
# Internally, this class ensures the mentioned containers are always there and kept in a specific order.
# This also means that operations on the stack that modifies the container ordering is prohibited and
# will raise an exception.
class CuraContainerStack(ContainerStack):
def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
self._empty_instance_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._containers = [self._empty_instance_container for i in range(len(_ContainerIndexes.IndexTypeMap))]
self.containersChanged.connect(self._onContainersChanged)
# This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted.
pyqtContainersChanged = pyqtSignal()
## Set the user changes container.
#
# \param new_user_changes The new user changes container. It is expected to have a "type" metadata entry with the value "user".
def setUserChanges(self, new_user_changes: InstanceContainer) -> None:
self.replaceContainer(_ContainerIndexes.UserChanges, new_user_changes)
## Get the user changes container.
#
# \return The user changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged)
def userChanges(self) -> InstanceContainer:
return self._containers[_ContainerIndexes.UserChanges]
## Set the quality changes container.
#
# \param new_quality_changes The new quality changes container. It is expected to have a "type" metadata entry with the value "quality_changes".
def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit)
## Set the quality changes container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
#
# \param new_quality_changes_id The ID of the new quality changes container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setQualityChangesById(self, new_quality_changes_id: str) -> None:
quality_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_changes_id)
if quality_changes:
self.setQualityChanges(quality_changes[0])
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_changes_id))
## Get the quality changes container.
#
# \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged)
def qualityChanges(self) -> InstanceContainer:
return self._containers[_ContainerIndexes.QualityChanges]
## Set the quality container.
#
# \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality".
def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
## Set the quality container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultQuality
# for details.
#
# \param new_quality_id The ID of the new quality container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setQualityById(self, new_quality_id: str) -> None:
quality = self._empty_instance_container
if new_quality_id == "default":
new_quality = self.findDefaultQuality()
if new_quality:
quality = new_quality
else:
qualities = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_id)
if qualities:
quality = qualities[0]
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_id))
self.setQuality(quality)
## Get the quality container.
#
# \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged)
def quality(self) -> InstanceContainer:
return self._containers[_ContainerIndexes.Quality]
## Set the material container.
#
# \param new_quality_changes The new material container. It is expected to have a "type" metadata entry with the value "quality_changes".
def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None:
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
## Set the material container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultMaterial
# for details.
#
# \param new_quality_changes_id The ID of the new material container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setMaterialById(self, new_material_id: str) -> None:
material = self._empty_instance_container
if new_material_id == "default":
new_material = self.findDefaultMaterial()
if new_material:
material = new_material
else:
materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id)
if materials:
material = materials[0]
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_material_id))
self.setMaterial(material)
## Get the material container.
#
# \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged)
def material(self) -> InstanceContainer:
return self._containers[_ContainerIndexes.Material]
## Set the variant container.
#
# \param new_quality_changes The new variant container. It is expected to have a "type" metadata entry with the value "quality_changes".
def setVariant(self, new_variant: InstanceContainer) -> None:
self.replaceContainer(_ContainerIndexes.Variant, new_variant)
## Set the variant container by an ID.
#
# This will search for the specified container and set it. If no container was found, an error will be raised.
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultVariant
# for details.
#
# \param new_quality_changes_id The ID of the new variant container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setVariantById(self, new_variant_id: str) -> None:
variant = self._empty_instance_container
if new_variant_id == "default":
new_variant = self.findDefaultVariant()
if new_variant:
variant = new_variant
else:
variants = ContainerRegistry.getInstance().findInstanceContainers(id = new_variant_id)
if variants:
variant = variants[0]
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_variant_id))
self.setVariant(variant)
## Get the variant container.
#
# \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged)
def variant(self) -> InstanceContainer:
return self._containers[_ContainerIndexes.Variant]
## Set the definition changes container.
#
# \param new_quality_changes The new definition changes container. It is expected to have a "type" metadata entry with the value "quality_changes".
def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None:
self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)
## Set the definition changes container by an ID.
#
# \param new_quality_changes_id The ID of the new definition changes container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setDefinitionChangesById(self, new_definition_changes_id: str) -> None:
new_definition_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_definition_changes_id)
if new_definition_changes:
self.setDefinitionChanges(new_definition_changes[0])
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_changes_id))
## Get the definition changes container.
#
# \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged)
def definitionChanges(self) -> InstanceContainer:
return self._containers[_ContainerIndexes.DefinitionChanges]
## Set the definition container.
#
# \param new_quality_changes The new definition container. It is expected to have a "type" metadata entry with the value "quality_changes".
def setDefinition(self, new_definition: DefinitionContainer) -> None:
self.replaceContainer(_ContainerIndexes.Definition, new_definition)
## Set the definition container by an ID.
#
# \param new_quality_changes_id The ID of the new definition container.
#
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
def setDefinitionById(self, new_definition_id: str) -> None:
new_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = new_definition_id)
if new_definition:
self.setDefinition(new_definition[0])
else:
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_id))
## Get the definition container.
#
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@pyqtProperty(DefinitionContainer, fset = setDefinition, notify = pyqtContainersChanged)
def definition(self) -> DefinitionContainer:
return self._containers[_ContainerIndexes.Definition]
@override(ContainerStack)
def getBottom(self) -> "DefinitionContainer":
return self.definition
@override(ContainerStack)
def getTop(self) -> "InstanceContainer":
return self.userChanges
## Check whether the specified setting has a 'user' value.
#
# A user value here is defined as the setting having a value in either
# the UserChanges or QualityChanges container.
#
# \return True if the setting has a user value, False if not.
@pyqtSlot(str, result = bool)
def hasUserValue(self, key: str) -> bool:
if self._containers[_ContainerIndexes.UserChanges].hasProperty(key, "value"):
return True
if self._containers[_ContainerIndexes.QualityChanges].hasProperty(key, "value"):
return True
return False
## Set a property of a setting.
#
# This will set a property of a specified setting. Since the container stack does not contain
# any settings itself, it is required to specify a container to set the property on. The target
# container is matched by container type.
#
# \param key The key of the setting to set.
# \param property_name The name of the property to set.
# \param new_value The new value to set the property to.
# \param target_container The type of the container to set the property of. Defaults to "user".
def setProperty(self, key: str, property_name: str, new_value: Any, target_container: str = "user") -> None:
container_index = _ContainerIndexes.TypeIndexMap.get(target_container, -1)
if container_index != -1:
self._containers[container_index].setProperty(key, property_name, new_value)
else:
raise IndexError("Invalid target container {type}".format(type = target_container))
## Overridden from ContainerStack
#
# Since we have a fixed order of containers in the stack and this method would modify the container
# ordering, we disallow this operation.
@override(ContainerStack)
def addContainer(self, container: ContainerInterface) -> None:
raise Exceptions.InvalidOperationError("Cannot add a container to Global stack")
## Overridden from ContainerStack
#
# Since we have a fixed order of containers in the stack and this method would modify the container
# ordering, we disallow this operation.
@override(ContainerStack)
def insertContainer(self, index: int, container: ContainerInterface) -> None:
raise Exceptions.InvalidOperationError("Cannot insert a container into Global stack")
## Overridden from ContainerStack
#
# Since we have a fixed order of containers in the stack and this method would modify the container
# ordering, we disallow this operation.
@override(ContainerStack)
def removeContainer(self, index: int = 0) -> None:
raise Exceptions.InvalidOperationError("Cannot remove a container from Global stack")
## Overridden from ContainerStack
#
# Replaces the container at the specified index with another container.
# This version performs checks to make sure the new container has the expected metadata and type.
#
# \throws Exception.InvalidContainerError Raised when trying to replace a container with a container that has an incorrect type.
@override(ContainerStack)
def replaceContainer(self, index: int, container: ContainerInterface, postpone_emit: bool = False) -> None:
expected_type = _ContainerIndexes.IndexTypeMap[index]
if expected_type == "definition":
if not isinstance(container, DefinitionContainer):
raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not a DefinitionContainer".format(index = index))
elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type:
raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type")))
super().replaceContainer(index, container, postpone_emit)
## Overridden from ContainerStack
#
# This deserialize will make sure the internal list of containers matches with what we expect.
# It will first check to see if the container at a certain index already matches with what we
# expect. If it does not, it will search for a matching container with the correct type. Should
# no container with the correct type be found, it will use the empty container.
#
# \throws InvalidContainerStackError Raised when no definition can be found for the stack.
@override(ContainerStack)
def deserialize(self, contents: str) -> None:
super().deserialize(contents)
new_containers = self._containers.copy()
while len(new_containers) < len(_ContainerIndexes.IndexTypeMap):
new_containers.append(self._empty_instance_container)
# Validate and ensure the list of containers matches with what we expect
for index, type_name in _ContainerIndexes.IndexTypeMap.items():
try:
container = new_containers[index]
except IndexError:
container = None
if type_name == "definition":
if not container or not isinstance(container, DefinitionContainer):
definition = self.findContainer(container_type = DefinitionContainer)
if not definition:
raise InvalidContainerStackError("Stack {id} does not have a definition!".format(id = self._id))
new_containers[index] = definition
continue
if not container or container.getMetaDataEntry("type") != type_name:
actual_container = self.findContainer(type = type_name)
if actual_container:
new_containers[index] = actual_container
else:
new_containers[index] = self._empty_instance_container
self._containers = new_containers
## Find the variant that should be used as "default" variant.
#
# This will search for variants that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# The following criteria are used to find the default variant:
# - If the machine definition does not have a metadata entry "has_variants" set to True, return None
# - The definition of the variant should be the same as the machine definition for this stack.
# - The container should have a metadata entry "type" with value "variant".
# - If the machine definition has a metadata entry "preferred_variant", filter the variant IDs based on that.
#
# \return The container that should be used as default, or None if nothing was found or the machine does not use variants.
#
# \note This method assumes the stack has a valid machine definition.
def findDefaultVariant(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
if not definition.getMetaDataEntry("has_variants"):
# If the machine does not use variants, we should never set a variant.
return None
# First add any variant. Later, overwrite with preference if the preference is valid.
variant = None
definition_id = self._findInstanceContainerDefinitionId(definition)
variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant")
if variants:
variant = variants[0]
preferred_variant_id = definition.getMetaDataEntry("preferred_variant")
if preferred_variant_id:
preferred_variants = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_id, definition = definition_id, type = "variant")
if preferred_variants:
variant = preferred_variants[0]
else:
Logger.log("w", "The preferred variant \"{variant}\" of stack {stack} does not exist or is not a variant.", variant = preferred_variant_id, stack = self.id)
# And leave it at the default variant.
if variant:
return variant
Logger.log("w", "Could not find a valid default variant for stack {stack}", stack = self.id)
return None
## Find the material that should be used as "default" material.
#
# This will search for materials that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# The following criteria are used to find the default material:
# - If the machine definition does not have a metadata entry "has_materials" set to True, return None
# - If the machine definition has a metadata entry "has_machine_materials", the definition of the material should
# be the same as the machine definition for this stack. Otherwise, the definition should be "fdmprinter".
# - The container should have a metadata entry "type" with value "material".
# - If the machine definition has a metadata entry "has_variants" and set to True, the "variant" metadata entry of
# the material should be the same as the ID of the variant in the stack. Only applies if "has_machine_materials" is also True.
# - If the stack currently has a material set, try to find a material that matches the current material by name.
# - Otherwise, if the machine definition has a metadata entry "preferred_material", try to find a material that matches the specified ID.
#
# \return The container that should be used as default, or None if nothing was found or the machine does not use materials.
def findDefaultMaterial(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
if not definition.getMetaDataEntry("has_materials"):
# Machine does not use materials, never try to set it.
return None
search_criteria = {"type": "material"}
if definition.getMetaDataEntry("has_machine_materials"):
search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
if definition.getMetaDataEntry("has_variants"):
search_criteria["variant"] = self.variant.id
else:
search_criteria["definition"] = "fdmprinter"
if self.material != self._empty_instance_container:
search_criteria["name"] = self.material.name
else:
preferred_material = definition.getMetaDataEntry("preferred_material")
if preferred_material:
search_criteria["id"] = preferred_material
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if not materials:
Logger.log("w", "The preferred material \"{material}\" could not be found for stack {stack}", material = preferred_material, stack = self.id)
# We failed to find any materials matching the specified criteria, drop some specific criteria and try to find
# a material that sort-of matches what we want.
search_criteria.pop("variant", None)
search_criteria.pop("id", None)
search_criteria.pop("name", None)
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if materials:
return materials[0]
Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id)
return None
## Find the quality that should be used as "default" quality.
#
# This will search for qualities that match the current definition and pick the preferred one,
# if specified by the machine definition.
#
# \return The container that should be used as default, or None if nothing was found.
def findDefaultQuality(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition()
registry = ContainerRegistry.getInstance()
material_container = self.material if self.material != self._empty_instance_container else None
search_criteria = {"type": "quality"}
if definition.getMetaDataEntry("has_machine_quality"):
search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
if definition.getMetaDataEntry("has_materials") and material_container:
search_criteria["material"] = material_container.id
else:
search_criteria["definition"] = "fdmprinter"
if self.quality != self._empty_instance_container:
search_criteria["name"] = self.quality.name
else:
preferred_quality = definition.getMetaDataEntry("preferred_quality")
if preferred_quality:
search_criteria["id"] = preferred_quality
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
if "material" in search_criteria:
# First check if we can solve our material not found problem by checking if we can find quality containers
# that are assigned to the parents of this material profile.
try:
inherited_files = material_container.getInheritedFiles()
except AttributeError: # Material_container does not support inheritance.
inherited_files = []
if inherited_files:
for inherited_file in inherited_files:
# Extract the ID from the path we used to load the file.
search_criteria["material"] = os.path.basename(inherited_file).split(".")[0]
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
# We still weren't able to find a quality for this specific material.
# Try to find qualities for a generic version of the material.
material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
if definition.getMetaDataEntry("has_machine_quality"):
if self.material != self._empty_instance_container:
material_search_criteria["definition"] = material_container.getDefinition().id
if definition.getMetaDataEntry("has_variants"):
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
else:
material_search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
if definition.getMetaDataEntry("has_variants") and self.variant != self._empty_instance_container:
material_search_criteria["variant"] = self.variant.id
else:
material_search_criteria["definition"] = "fdmprinter"
material_containers = registry.findInstanceContainers(**material_search_criteria)
# Try all materials to see if there is a quality profile available.
for material_container in material_containers:
search_criteria["material"] = material_container.getId()
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
if "name" in search_criteria or "id" in search_criteria:
# If a quality by this name can not be found, try a wider set of search criteria
search_criteria.pop("name", None)
search_criteria.pop("id", None)
containers = registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
return None
## protected:
# Helper to make sure we emit a PyQt signal on container changes.
def _onContainersChanged(self, container: Any) -> None:
self.pyqtContainersChanged.emit()
# Helper that can be overridden to get the "machine" definition, that is, the definition that defines the machine
# and its properties rather than, for example, the extruder. Defaults to simply returning the definition property.
def _getMachineDefinition(self) -> DefinitionContainer:
return self.definition
## Find the ID that should be used when searching for instance containers for a specified definition.
#
# This handles the situation where the definition specifies we should use a different definition when
# searching for instance containers.
#
# \param machine_definition The definition to find the "quality definition" for.
#
# \return The ID of the definition container to use when searching for instance containers.
@classmethod
def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainer) -> str:
quality_definition = machine_definition.getMetaDataEntry("quality_definition")
if not quality_definition:
return machine_definition.id
definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition)
if not definitions:
Logger.log("w", "Unable to find parent definition {parent} for machine {machine}", parent = quality_definition, machine = machine_definition.id)
return machine_definition.id
return cls._findInstanceContainerDefinitionId(definitions[0])
## private:
# Private helper class to keep track of container positions and their types.
class _ContainerIndexes:
UserChanges = 0
QualityChanges = 1
Quality = 2
Material = 3
Variant = 4
DefinitionChanges = 5
Definition = 6
# Simple hash map to map from index to "type" metadata entry
IndexTypeMap = {
UserChanges: "user",
QualityChanges: "quality_changes",
Quality: "quality",
Material: "material",
Variant: "variant",
DefinitionChanges: "definition_changes",
Definition: "definition",
}
# Reverse lookup: type -> index
TypeIndexMap = dict([(v, k) for k, v in IndexTypeMap.items()])

View File

@ -0,0 +1,155 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Logger import Logger
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from .GlobalStack import GlobalStack
from .ExtruderStack import ExtruderStack
from typing import Optional
## Contains helper functions to create new machines.
class CuraStackBuilder:
## Create a new instance of a machine.
#
# \param name The name of the new machine.
# \param definition_id The ID of the machine definition to use.
#
# \return The new global stack or None if an error occurred.
@classmethod
def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]:
registry = ContainerRegistry.getInstance()
definitions = registry.findDefinitionContainers(id = definition_id)
if not definitions:
Logger.log("w", "Definition {definition} was not found!", definition = definition_id)
return None
machine_definition = definitions[0]
name = registry.createUniqueName("machine", "", name, machine_definition.name)
new_global_stack = cls.createGlobalStack(
new_stack_id = name,
definition = machine_definition,
quality = "default",
material = "default",
variant = "default",
)
for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id):
position = extruder_definition.getMetaDataEntry("position", None)
if not position:
Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id)
new_extruder_id = registry.uniqueName(extruder_definition.id)
new_extruder = cls.createExtruderStack(
new_extruder_id,
definition = extruder_definition,
machine_definition = machine_definition,
quality = "default",
material = "default",
variant = "default",
next_stack = new_global_stack
)
return new_global_stack
## Create a new Extruder stack
#
# \param new_stack_id The ID of the new stack.
# \param definition The definition to base the new stack on.
# \param machine_definition The machine definition to use for the user container.
# \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
#
# \return A new Global stack instance with the specified parameters.
@classmethod
def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainer, machine_definition: DefinitionContainer, **kwargs) -> ExtruderStack:
stack = ExtruderStack(new_stack_id)
stack.setName(definition.getName())
stack.setDefinition(definition)
stack.addMetaDataEntry("position", definition.getMetaDataEntry("position"))
user_container = InstanceContainer(new_stack_id + "_user")
user_container.addMetaDataEntry("type", "user")
user_container.addMetaDataEntry("extruder", new_stack_id)
from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(machine_definition)
stack.setUserChanges(user_container)
if "next_stack" in kwargs:
stack.setNextStack(kwargs["next_stack"])
# Important! The order here matters, because that allows the stack to
# assume the material and variant have already been set.
if "definition_changes" in kwargs:
stack.setDefinitionChangesById(kwargs["definition_changes"])
if "variant" in kwargs:
stack.setVariantById(kwargs["variant"])
if "material" in kwargs:
stack.setMaterialById(kwargs["material"])
if "quality" in kwargs:
stack.setQualityById(kwargs["quality"])
if "quality_changes" in kwargs:
stack.setQualityChangesById(kwargs["quality_changes"])
# Only add the created containers to the registry after we have set all the other
# properties. This makes the create operation more transactional, since any problems
# setting properties will not result in incomplete containers being added.
registry = ContainerRegistry.getInstance()
registry.addContainer(stack)
registry.addContainer(user_container)
return stack
## Create a new Global stack
#
# \param new_stack_id The ID of the new stack.
# \param definition The definition to base the new stack on.
# \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
#
# \return A new Global stack instance with the specified parameters.
@classmethod
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainer, **kwargs) -> GlobalStack:
stack = GlobalStack(new_stack_id)
stack.setDefinition(definition)
user_container = InstanceContainer(new_stack_id + "_user")
user_container.addMetaDataEntry("type", "user")
user_container.addMetaDataEntry("machine", new_stack_id)
from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(definition)
stack.setUserChanges(user_container)
# Important! The order here matters, because that allows the stack to
# assume the material and variant have already been set.
if "definition_changes" in kwargs:
stack.setDefinitionChangesById(kwargs["definition_changes"])
if "variant" in kwargs:
stack.setVariantById(kwargs["variant"])
if "material" in kwargs:
stack.setMaterialById(kwargs["material"])
if "quality" in kwargs:
stack.setQualityById(kwargs["quality"])
if "quality_changes" in kwargs:
stack.setQualityChangesById(kwargs["quality_changes"])
registry = ContainerRegistry.getInstance()
registry.addContainer(stack)
registry.addContainer(user_container)
return stack

View File

@ -0,0 +1,22 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
## Raised when trying to perform an operation like add on a stack that does not allow that.
class InvalidOperationError(Exception):
pass
## Raised when trying to replace a container with a container that does not have the expected type.
class InvalidContainerError(Exception):
pass
## Raised when trying to add an extruder to a Global stack that already has the maximum number of extruders.
class TooManyExtrudersError(Exception):
pass
## Raised when an extruder has no next stack set.
class NoGlobalStackError(Exception):
pass

View File

@ -1,4 +1,4 @@
# Copyright (c) 2016 Ultimaker B.V.
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant #For communicating data and events to Qt.
@ -6,14 +6,22 @@ from UM.FlameProfiler import pyqtSlot
from UM.Application import Application #To get the global container stack to find the current machine.
from UM.Logger import Logger
from UM.Decorators import deprecated
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.SceneNode import SceneNode
from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers by ID.
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer
from typing import Optional
from typing import Optional, List, TYPE_CHECKING, Union
if TYPE_CHECKING:
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack
## Manages all existing extruder stacks.
#
@ -32,12 +40,15 @@ class ExtruderManager(QObject):
## Registers listeners and such to listen to changes to the extruders.
def __init__(self, parent = None):
super().__init__(parent)
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs.
self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
self._active_extruder_index = 0
self._selected_object_extruders = []
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
self._global_container_stack_definition_id = None
self._addCurrentMachineExtruders()
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
## Gets the unique identifier of the currently active extruder stack.
#
# The currently active extruder stack is the stack that is currently being
@ -75,7 +86,7 @@ class ExtruderManager(QObject):
for position in self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()]:
extruder = self._extruder_trains[Application.getInstance().getGlobalContainerStack().getId()][position]
if extruder.getId() == id:
return extruder.findContainer(type = "quality_changes").getId()
return extruder.qualityChanges.getId()
## The instance of the singleton pattern.
#
@ -117,7 +128,50 @@ class ExtruderManager(QObject):
except IndexError:
return ""
def getActiveExtruderStack(self) -> ContainerStack:
## Emitted whenever the selectedObjectExtruders property changes.
selectedObjectExtrudersChanged = pyqtSignal()
## Provides a list of extruder IDs used by the current selected objects.
@pyqtProperty("QVariantList", notify = selectedObjectExtrudersChanged)
def selectedObjectExtruders(self) -> List[str]:
if not self._selected_object_extruders:
object_extruders = set()
# First, build a list of the actual selected objects (including children of groups, excluding group nodes)
selected_nodes = []
for node in Selection.getAllSelectedObjects():
if node.callDecoration("isGroup"):
for grouped_node in BreadthFirstIterator(node):
if grouped_node.callDecoration("isGroup"):
continue
selected_nodes.append(grouped_node)
else:
selected_nodes.append(node)
# Then, figure out which nodes are used by those selected nodes.
for node in selected_nodes:
extruder = node.callDecoration("getActiveExtruder")
if extruder:
object_extruders.add(extruder)
else:
global_stack = Application.getInstance().getGlobalContainerStack()
if global_stack.getId() in self._extruder_trains:
object_extruders.add(self._extruder_trains[global_stack.getId()]["0"].getId())
self._selected_object_extruders = list(object_extruders)
return self._selected_object_extruders
## Reset the internal list used for the selectedObjectExtruders property
#
# This will trigger a recalculation of the extruders used for the
# selection.
def resetSelectedObjectExtruders(self) -> None:
self._selected_object_extruders = []
self.selectedObjectExtrudersChanged.emit()
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
@ -127,7 +181,7 @@ class ExtruderManager(QObject):
return None
## Get an extruder stack by index
def getExtruderStack(self, index):
def getExtruderStack(self, index) -> Optional["ExtruderStack"]:
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
if global_container_stack.getId() in self._extruder_trains:
@ -136,7 +190,7 @@ class ExtruderManager(QObject):
return None
## Get all extruder stacks
def getExtruderStacks(self):
def getExtruderStacks(self) -> List["ExtruderStack"]:
result = []
for i in range(self.extruderCount):
result.append(self.getExtruderStack(i))
@ -147,6 +201,7 @@ class ExtruderManager(QObject):
#
# \param machine_definition The machine definition to add the extruders for.
# \param machine_id The machine_id to add the extruders for.
@deprecated("Use CuraStackBuilder", "2.6")
def addMachineExtruders(self, machine_definition: DefinitionContainer, machine_id: str) -> None:
changed = False
machine_definition_id = machine_definition.getId()
@ -199,6 +254,7 @@ class ExtruderManager(QObject):
# \param machine_definition The machine that the extruder train belongs to.
# \param position The position of this extruder train in the extruder slots of the machine.
# \param machine_id The id of the "global" stack this extruder is linked to.
@deprecated("Use CuraStackBuilder::createExtruderStack", "2.6")
def createExtruderTrain(self, extruder_definition: DefinitionContainer, machine_definition: DefinitionContainer,
position, machine_id: str) -> None:
# Cache some things.
@ -244,7 +300,13 @@ class ExtruderManager(QObject):
material = materials[0]
preferred_material_id = machine_definition.getMetaDataEntry("preferred_material")
if preferred_material_id:
search_criteria = { "type": "material", "id": preferred_material_id}
global_stack = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
if global_stack:
approximate_material_diameter = round(global_stack[0].getProperty("material_diameter", "value"))
else:
approximate_material_diameter = round(machine_definition.getProperty("material_diameter", "value"))
search_criteria = { "type": "material", "id": preferred_material_id, "approximate_diameter": approximate_material_diameter}
if machine_definition.getMetaDataEntry("has_machine_materials"):
search_criteria["definition"] = machine_definition_id
@ -301,6 +363,8 @@ class ExtruderManager(QObject):
user_profile = InstanceContainer(extruder_stack_id + "_current_settings") # Add an empty user profile.
user_profile.addMetaDataEntry("type", "user")
user_profile.addMetaDataEntry("extruder", extruder_stack_id)
from cura.CuraApplication import CuraApplication
user_profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_profile.setDefinition(machine_definition)
container_registry.addContainer(user_profile)
container_stack.addContainer(user_profile)
@ -340,7 +404,7 @@ class ExtruderManager(QObject):
# list.
#
# \return A list of extruder stacks.
def getUsedExtruderStacks(self):
def getUsedExtruderStacks(self) -> List["ContainerStack"]:
global_stack = Application.getInstance().getGlobalContainerStack()
container_registry = ContainerRegistry.getInstance()
@ -394,19 +458,16 @@ class ExtruderManager(QObject):
## Removes the container stack and user profile for the extruders for a specific machine.
#
# \param machine_id The machine to remove the extruders for.
def removeMachineExtruders(self, machine_id):
def removeMachineExtruders(self, machine_id: str):
for extruder in self.getMachineExtruders(machine_id):
containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", extruder = extruder.getId())
for container in containers:
ContainerRegistry.getInstance().removeContainer(container.getId())
ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId())
ContainerRegistry.getInstance().removeContainer(extruder.getId())
## Returns extruders for a specific machine.
#
# \param machine_id The machine to get the extruders of.
def getMachineExtruders(self, machine_id):
def getMachineExtruders(self, machine_id: str):
if machine_id not in self._extruder_trains:
Logger.log("w", "Tried to get the extruder trains for machine %s, which doesn't exist.", machine_id)
return []
return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
@ -414,7 +475,7 @@ class ExtruderManager(QObject):
#
# The first element is the global container stack, followed by any extruder stacks.
# \return \type{List[ContainerStack]}
def getActiveGlobalAndExtruderStacks(self):
def getActiveGlobalAndExtruderStacks(self) -> Optional[List[Union["ExtruderStack", "GlobalStack"]]]:
global_stack = Application.getInstance().getGlobalContainerStack()
if not global_stack:
return None
@ -426,23 +487,24 @@ class ExtruderManager(QObject):
## Returns the list of active extruder stacks.
#
# \return \type{List[ContainerStack]} a list of
def getActiveExtruderStacks(self):
def getActiveExtruderStacks(self) -> List["ExtruderStack"]:
global_stack = Application.getInstance().getGlobalContainerStack()
result = []
if global_stack:
if global_stack and global_stack.getId() in self._extruder_trains:
for extruder in sorted(self._extruder_trains[global_stack.getId()]):
result.append(self._extruder_trains[global_stack.getId()][extruder])
return result
def __globalContainerStackChanged(self) -> None:
self._addCurrentMachineExtruders()
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id:
self._global_container_stack_definition_id = global_container_stack.getBottom().getId()
self.globalContainerStackDefinitionChanged.emit()
self.activeExtruderChanged.emit()
self.resetSelectedObjectExtruders()
## Adds the extruders of the currently active machine.
def _addCurrentMachineExtruders(self) -> None:
global_stack = Application.getInstance().getGlobalContainerStack()
@ -463,6 +525,10 @@ class ExtruderManager(QObject):
result = []
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
# only include values from extruders that are "active" for the current machine instance
if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"):
continue
value = extruder.getRawProperty(key, "value")
if value is None:
@ -521,18 +587,6 @@ class ExtruderManager(QObject):
@staticmethod
def getResolveOrValue(key):
global_stack = Application.getInstance().getGlobalContainerStack()
resolved_value = global_stack.getProperty(key, "value")
resolved_value = global_stack.getProperty(key, "resolve")
if resolved_value is not None:
user_container = global_stack.findContainer({"type": "user"})
quality_changes_container = global_stack.findContainer({"type": "quality_changes"})
if user_container.hasProperty(key, "value") or quality_changes_container.hasProperty(key, "value"):
# Normal case
value = global_stack.getProperty(key, "value")
else:
# We have a resolved value and we're using it because of no user and quality_changes value
value = resolved_value
else:
value = global_stack.getRawProperty(key, "value")
return value
return resolved_value

View File

@ -0,0 +1,85 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from typing import Any
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.Interfaces import ContainerInterface
from . import Exceptions
from .CuraContainerStack import CuraContainerStack
from .ExtruderManager import ExtruderManager
## Represents an Extruder and its related containers.
#
#
class ExtruderStack(CuraContainerStack):
def __init__(self, container_id, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
self.addMetaDataEntry("type", "extruder_train") # For backward compatibility
## Overridden from ContainerStack
#
# This will set the next stack and ensure that we register this stack as an extruder.
@override(ContainerStack)
def setNextStack(self, stack: ContainerStack) -> None:
super().setNextStack(stack)
stack.addExtruder(self)
self.addMetaDataEntry("machine", stack.id)
# For backward compatibility: Register the extruder with the Extruder Manager
ExtruderManager.getInstance().registerExtruder(self, stack.id)
@classmethod
def getLoadingPriority(cls) -> int:
return 3
## Overridden from ContainerStack
#
# It will perform a few extra checks when trying to get properties.
#
# The two extra checks it currently does is to ensure a next stack is set and to bypass
# the extruder when the property is not settable per extruder.
#
# \throws Exceptions.NoGlobalStackError Raised when trying to get a property from an extruder without
# having a next stack set.
@override(ContainerStack)
def getProperty(self, key: str, property_name: str) -> Any:
if not self._next_stack:
raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id))
if not super().getProperty(key, "settable_per_extruder"):
return self.getNextStack().getProperty(key, property_name)
return super().getProperty(key, property_name)
@override(CuraContainerStack)
def _getMachineDefinition(self) -> ContainerInterface:
if not self.getNextStack():
raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id))
return self.getNextStack()._getMachineDefinition()
@override(CuraContainerStack)
def deserialize(self, contents: str) -> None:
super().deserialize(contents)
stacks = ContainerRegistry.getInstance().findContainerStacks(id=self.getMetaDataEntry("machine", ""))
if stacks:
self.setNextStack(stacks[0])
extruder_stack_mime = MimeType(
name = "application/x-cura-extruderstack",
comment = "Cura Extruder Stack",
suffixes = ["extruder.cfg"]
)
MimeTypeDatabase.addMimeType(extruder_stack_mime)
ContainerRegistry.addContainerTypeByName(ExtruderStack, "extruder_stack", extruder_stack_mime.name)

View File

@ -1,11 +1,11 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer
import UM.Qt.ListModel
from UM.Application import Application
import UM.FlameProfiler
from cura.Settings.ExtruderManager import ExtruderManager
## Model that holds extruders.
@ -33,6 +33,12 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
# The ID of the definition of the extruder.
DefinitionRole = Qt.UserRole + 5
# The material of the extruder.
MaterialRole = Qt.UserRole + 6
# The variant of the extruder.
VariantRole = Qt.UserRole + 7
## List of colours to display if there is no material or the material has no known
# colour.
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
@ -49,6 +55,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
self.addRoleName(self.ColorRole, "color")
self.addRoleName(self.IndexRole, "index")
self.addRoleName(self.DefinitionRole, "definition")
self.addRoleName(self.MaterialRole, "material")
self.addRoleName(self.VariantRole, "variant")
self._update_extruder_timer = QTimer()
self._update_extruder_timer.setInterval(100)
self._update_extruder_timer.setSingleShot(True)
self._update_extruder_timer.timeout.connect(self.__updateExtruders)
self._add_global = False
self._simple_names = False
@ -103,28 +116,33 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
active_extruder_stack.containersChanged.connect(self._onExtruderStackContainersChanged)
self._active_extruder_stack = active_extruder_stack
def _onExtruderStackContainersChanged(self, container):
# The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name
self._updateExtruders()
# Update when there is an empty container or material change
if container.getMetaDataEntry("type") == "material" or container.getMetaDataEntry("type") is None:
# The ExtrudersModel needs to be updated when the material-name or -color changes, because the user identifies extruders by material-name
self._updateExtruders()
modelChanged = pyqtSignal()
def _updateExtruders(self):
self._update_extruder_timer.start()
## Update the list of extruders.
#
# This should be called whenever the list of extruders changes.
def _updateExtruders(self):
@UM.FlameProfiler.profile
def __updateExtruders(self):
changed = False
if self.rowCount() != 0:
changed = True
items = []
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
if self._add_global:
material = global_container_stack.findContainer({ "type": "material" })
material = global_container_stack.material
color = material.getMetaDataEntry("color_code", default = self.defaultColors[0]) if material else self.defaultColors[0]
item = {
"id": global_container_stack.getId(),
@ -136,15 +154,20 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
items.append(item)
changed = True
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
manager = ExtruderManager.getInstance()
for extruder in manager.getMachineExtruders(global_container_stack.getId()):
extruder_name = extruder.getName()
material = extruder.findContainer({ "type": "material" })
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
try:
position = int(position)
except ValueError: #Not a proper int.
position = -1
if position >= machine_extruder_count:
continue
extruder_name = extruder.getName()
material = extruder.material
variant = extruder.variant
default_color = self.defaultColors[position] if position >= 0 and position < len(self.defaultColors) else self.defaultColors[0]
color = material.getMetaDataEntry("color_code", default = default_color) if material else default_color
item = { #Construct an item with only the relevant information.
@ -152,7 +175,9 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
"name": extruder_name,
"color": color,
"index": position,
"definition": extruder.getBottom().getId()
"definition": extruder.getBottom().getId(),
"material": material.getName() if material else "",
"variant": variant.getName() if variant else "",
}
items.append(item)
changed = True

123
cura/Settings/GlobalStack.py Executable file
View File

@ -0,0 +1,123 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from typing import Any
from PyQt5.QtCore import pyqtProperty
from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.SettingInstance import InstanceState
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Logger import Logger
from . import Exceptions
from .CuraContainerStack import CuraContainerStack
## Represents the Global or Machine stack and its related containers.
#
class GlobalStack(CuraContainerStack):
def __init__(self, container_id: str, *args, **kwargs):
super().__init__(container_id, *args, **kwargs)
self.addMetaDataEntry("type", "machine") # For backward compatibility
self._extruders = []
# This property is used to track which settings we are calculating the "resolve" for
# and if so, to bypass the resolve to prevent an infinite recursion that would occur
# if the resolve function tried to access the same property it is a resolve for.
self._resolving_settings = set()
## Get the list of extruders of this stack.
#
# \return The extruders registered with this stack.
@pyqtProperty("QVariantList")
def extruders(self) -> list:
return self._extruders
@classmethod
def getLoadingPriority(cls) -> int:
return 2
## Add an extruder to the list of extruders of this stack.
#
# \param extruder The extruder to add.
#
# \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we
# already have the maximum number of extruders.
def addExtruder(self, extruder: ContainerStack) -> None:
extruder_count = self.getProperty("machine_extruder_count", "value")
if extruder_count and len(self._extruders) + 1 > extruder_count:
Logger.log("w", "Adding extruder {meta} to {id} but its extruder count is {count}".format(id = self.id, count = extruder_count, meta = str(extruder.getMetaData())))
self._extruders.append(extruder)
## Overridden from ContainerStack
#
# This will return the value of the specified property for the specified setting,
# unless the property is "value" and that setting has a "resolve" function set.
# When a resolve is set, it will instead try and execute the resolve first and
# then fall back to the normal "value" property.
#
# \param key The setting key to get the property of.
# \param property_name The property to get the value of.
#
# \return The value of the property for the specified setting, or None if not found.
@override(ContainerStack)
def getProperty(self, key: str, property_name: str) -> Any:
if not self.definition.findDefinitions(key = key):
return None
if self._shouldResolve(key, property_name):
self._resolving_settings.add(key)
resolve = super().getProperty(key, "resolve")
self._resolving_settings.remove(key)
if resolve is not None:
return resolve
return super().getProperty(key, property_name)
## Overridden from ContainerStack
#
# This will simply raise an exception since the Global stack cannot have a next stack.
@override(ContainerStack)
def setNextStack(self, next_stack: ContainerStack) -> None:
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
# protected:
# Determine whether or not we should try to get the "resolve" property instead of the
# requested property.
def _shouldResolve(self, key: str, property_name: str) -> bool:
if property_name is not "value":
# Do not try to resolve anything but the "value" property
return False
if key in self._resolving_settings:
# To prevent infinite recursion, if getProperty is called with the same key as
# we are already trying to resolve, we should not try to resolve again. Since
# this can happen multiple times when trying to resolve a value, we need to
# track all settings that are being resolved.
return False
setting_state = super().getProperty(key, "state")
if setting_state is not None and setting_state != InstanceState.Default:
# When the user has explicitly set a value, we should ignore any resolve and
# just return that value.
return False
return True
## private:
global_stack_mime = MimeType(
name = "application/x-cura-globalstack",
comment = "Cura Global Stack",
suffixes = ["global.cfg"]
)
MimeTypeDatabase.addMimeType(global_stack_mime)
ContainerRegistry.addContainerTypeByName(GlobalStack, "global_stack", global_stack_mime.name)

View File

@ -15,26 +15,45 @@ from UM.Message import Message
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingDefinition import SettingDefinition
from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.Validator import ValidatorState
from UM.Signal import postponeSignals
import UM.FlameProfiler
from cura.QualityManager import QualityManager
from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.Settings.ExtruderManager import ExtruderManager
from .CuraStackBuilder import CuraStackBuilder
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from UM.Settings.DefinitionContainer import DefinitionContainer
from cura.Settings.CuraContainerStack import CuraContainerStack
from cura.Settings.GlobalStack import GlobalStack
import os
class MachineManager(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._active_container_stack = None # type: ContainerStack
self._global_container_stack = None # type: ContainerStack
self._active_container_stack = None # type: CuraContainerStack
self._global_container_stack = None # type: GlobalStack
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
self._instance_container_timer = QTimer()
self._instance_container_timer.setInterval(250)
self._instance_container_timer.setSingleShot(True)
self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged)
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
## When the global container is changed, active material probably needs to be updated.
@ -43,10 +62,12 @@ class MachineManager(QObject):
self.globalContainerChanged.connect(self.activeQualityChanged)
self._stacks_have_errors = None
self._empty_variant_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_variant")[0]
self._empty_material_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_material")[0]
self._empty_quality_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality")[0]
self._empty_quality_changes_container = ContainerRegistry.getInstance().findInstanceContainers(id="empty_quality_changes")[0]
self._empty_variant_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_material_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_quality_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._empty_quality_changes_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
self._onGlobalContainerChanged()
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
@ -83,11 +104,6 @@ class MachineManager(QObject):
self._material_incompatible_message = Message(catalog.i18nc("@info:status",
"The selected material is incompatible with the selected machine or configuration."))
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value)
activeMaterialChanged = pyqtSignal()
activeVariantChanged = pyqtSignal()
@ -123,7 +139,7 @@ class MachineManager(QObject):
return self._printer_output_devices
@pyqtProperty(int, constant=True)
def totalNumberOfSettings(self):
def totalNumberOfSettings(self) -> int:
return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())
def _onHotendIdChanged(self, index: Union[str, int], hotend_id: str) -> None:
@ -147,7 +163,7 @@ class MachineManager(QObject):
else:
Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.getBottom().getId(), hotend_id))
def _onMaterialIdChanged(self, index, material_id):
def _onMaterialIdChanged(self, index: Union[str, int], material_id: str):
if not self._global_container_stack:
return
@ -223,14 +239,22 @@ class MachineManager(QObject):
def _onGlobalContainerChanged(self):
if self._global_container_stack:
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
material = self._global_container_stack.findContainer({"type": "material"})
try:
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that was already disconnected.
pass
try:
self._global_container_stack.containersChanged.disconnect(self._onInstanceContainersChanged)
except TypeError:
pass
try:
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
except TypeError:
pass
material = self._global_container_stack.material
material.nameChanged.disconnect(self._onMaterialNameChanged)
quality = self._global_container_stack.findContainer({"type": "quality"})
quality = self._global_container_stack.quality
quality.nameChanged.disconnect(self._onQualityNameChanged)
if self._global_container_stack.getProperty("machine_extruder_count", "value") > 1:
@ -253,26 +277,25 @@ class MachineManager(QObject):
# For multi-extrusion machines, we do not want variant or material profiles in the stack,
# because these are extruder specific and may cause wrong values to be used for extruders
# that did not specify a value in the extruder.
global_variant = self._global_container_stack.findContainer(type = "variant")
global_variant = self._global_container_stack.variant
if global_variant != self._empty_variant_container:
self._global_container_stack.replaceContainer(self._global_container_stack.getContainerIndex(global_variant), self._empty_variant_container)
self._global_container_stack.setVariant(self._empty_variant_container)
global_material = self._global_container_stack.findContainer(type = "material")
global_material = self._global_container_stack.material
if global_material != self._empty_material_container:
self._global_container_stack.replaceContainer(self._global_container_stack.getContainerIndex(global_material), self._empty_material_container)
self._global_container_stack.setMaterial(self._empty_material_container)
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): #Listen for changes on all extruder stacks.
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
extruder_stack.containersChanged.connect(self._onInstanceContainersChanged)
else:
material = self._global_container_stack.findContainer({"type": "material"})
material = self._global_container_stack.material
material.nameChanged.connect(self._onMaterialNameChanged)
quality = self._global_container_stack.findContainer({"type": "quality"})
quality = self._global_container_stack.quality
quality.nameChanged.connect(self._onQualityNameChanged)
self._updateStacksHaveErrors()
self._error_check_timer.start()
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
def _updateStacksHaveErrors(self):
@ -289,23 +312,23 @@ class MachineManager(QObject):
if not self._active_container_stack:
self._active_container_stack = self._global_container_stack
self._updateStacksHaveErrors()
self._error_check_timer.start()
if old_active_container_stack != self._active_container_stack:
# Many methods and properties related to the active quality actually depend
# on _active_container_stack. If it changes, then the properties change.
self.activeQualityChanged.emit()
def _onInstanceContainersChanged(self, container):
container_type = container.getMetaDataEntry("type")
def __onInstanceContainersChanged(self):
self.activeQualityChanged.emit()
self.activeVariantChanged.emit()
self.activeMaterialChanged.emit()
self.activeQualityChanged.emit()
self._error_check_timer.start()
self._updateStacksHaveErrors()
def _onInstanceContainersChanged(self, container):
self._instance_container_timer.start()
def _onPropertyChanged(self, key, property_name):
def _onPropertyChanged(self, key: str, property_name: str):
if property_name == "value":
# Notify UI items, such as the "changed" star in profile pull down menu.
self.activeStackValueChanged.emit()
@ -322,40 +345,11 @@ class MachineManager(QObject):
@pyqtSlot(str, str)
def addMachine(self, name: str, definition_id: str) -> None:
container_registry = ContainerRegistry.getInstance()
definitions = container_registry.findDefinitionContainers(id = definition_id)
if definitions:
definition = definitions[0]
name = self._createUniqueName("machine", "", name, definition.getName())
new_global_stack = ContainerStack(name)
new_global_stack.addMetaDataEntry("type", "machine")
container_registry.addContainer(new_global_stack)
variant_instance_container = self._updateVariantContainer(definition)
material_instance_container = self._updateMaterialContainer(definition, variant_instance_container)
quality_instance_container = self._updateQualityContainer(definition, variant_instance_container, material_instance_container)
current_settings_instance_container = InstanceContainer(name + "_current_settings")
current_settings_instance_container.addMetaDataEntry("machine", name)
current_settings_instance_container.addMetaDataEntry("type", "user")
current_settings_instance_container.setDefinition(definitions[0])
container_registry.addContainer(current_settings_instance_container)
new_global_stack.addContainer(definition)
if variant_instance_container:
new_global_stack.addContainer(variant_instance_container)
if material_instance_container:
new_global_stack.addContainer(material_instance_container)
if quality_instance_container:
new_global_stack.addContainer(quality_instance_container)
new_global_stack.addContainer(self._empty_quality_changes_container)
new_global_stack.addContainer(current_settings_instance_container)
ExtruderManager.getInstance().addMachineExtruders(definition, new_global_stack.getId())
Application.getInstance().setGlobalContainerStack(new_global_stack)
new_stack = CuraStackBuilder.createMachine(name, definition_id)
if new_stack:
Application.getInstance().setGlobalContainerStack(new_stack)
else:
Logger.log("w", "Failed creating a new machine!")
## Create a name that is not empty and unique
# \param container_type \type{string} Type of the container (machine, quality, ...)
@ -418,7 +412,7 @@ class MachineManager(QObject):
## Delete a user setting from the global stack and all extruder stacks.
# \param key \type{str} the name of the key to delete
@pyqtSlot(str)
def clearUserSettingAllCurrentStacks(self, key):
def clearUserSettingAllCurrentStacks(self, key: str):
if not self._global_container_stack:
return
@ -474,6 +468,10 @@ class MachineManager(QObject):
return ""
@pyqtProperty("QObject", notify = globalContainerChanged)
def activeMachine(self) -> "GlobalStack":
return self._global_container_stack
@pyqtProperty(str, notify = activeStackChanged)
def activeStackId(self) -> str:
if self._active_container_stack:
@ -484,7 +482,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = activeMaterialChanged)
def activeMaterialName(self) -> str:
if self._active_container_stack:
material = self._active_container_stack.findContainer({"type":"material"})
material = self._active_container_stack.material
if material:
return material.getName()
@ -495,18 +493,29 @@ class MachineManager(QObject):
result = []
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
variant_container = stack.findContainer({"type": "variant"})
variant_container = stack.variant
if variant_container and variant_container != self._empty_variant_container:
result.append(variant_container.getName())
return result
@pyqtProperty("QVariantList", notify = activeVariantChanged)
def activeMaterialIds(self):
result = []
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
variant_container = stack.findContainer({"type": "variant"})
if variant_container and variant_container != self._empty_variant_container:
result.append(variant_container.getId())
return result
@pyqtProperty("QVariantList", notify = activeMaterialChanged)
def activeMaterialNames(self):
result = []
if ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() is not None:
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
material_container = stack.findContainer(type="material")
material_container = stack.material
if material_container and material_container != self._empty_material_container:
result.append(material_container.getName())
return result
@ -514,12 +523,28 @@ class MachineManager(QObject):
@pyqtProperty(str, notify=activeMaterialChanged)
def activeMaterialId(self) -> str:
if self._active_container_stack:
material = self._active_container_stack.findContainer({"type": "material"})
material = self._active_container_stack.material
if material:
return material.getId()
return ""
@pyqtProperty("QVariantMap", notify = activeVariantChanged)
def allActiveVariantIds(self):
if not self._global_container_stack:
return {}
result = {}
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
variant_container = stack.variant
if not variant_container:
continue
result[stack.getId()] = variant_container.getId()
return result
@pyqtProperty("QVariantMap", notify = activeMaterialChanged)
def allActiveMaterialIds(self):
if not self._global_container_stack:
@ -528,7 +553,7 @@ class MachineManager(QObject):
result = {}
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
material_container = stack.findContainer(type = "material")
material_container = stack.material
if not material_container:
continue
@ -543,31 +568,31 @@ class MachineManager(QObject):
# \return The layer height of the currently active quality profile. If
# there is no quality profile, this returns 0.
@pyqtProperty(float, notify=activeQualityChanged)
def activeQualityLayerHeight(self):
def activeQualityLayerHeight(self) -> float:
if not self._global_container_stack:
return 0
quality_changes = self._global_container_stack.findContainer({"type": "quality_changes"})
quality_changes = self._global_container_stack.qualityChanges
if quality_changes:
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality_changes.getId())
if isinstance(value, SettingFunction):
value = value(self._global_container_stack)
return value
quality = self._global_container_stack.findContainer({"type": "quality"})
quality = self._global_container_stack.quality
if quality:
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = quality.getId())
if isinstance(value, SettingFunction):
value = value(self._global_container_stack)
return value
return 0 #No quality profile.
return 0 # No quality profile.
## Get the Material ID associated with the currently active material
# \returns MaterialID (string) if found, empty string otherwise
@pyqtProperty(str, notify=activeQualityChanged)
def activeQualityMaterialId(self) -> str:
if self._active_container_stack:
quality = self._active_container_stack.findContainer({"type": "quality"})
quality = self._active_container_stack.quality
if quality:
material_id = quality.getMetaDataEntry("material")
if material_id:
@ -582,50 +607,50 @@ class MachineManager(QObject):
return ""
@pyqtProperty(str, notify=activeQualityChanged)
def activeQualityName(self):
def activeQualityName(self) -> str:
if self._active_container_stack and self._global_container_stack:
quality = self._global_container_stack.findContainer({"type": "quality_changes"})
if quality and quality != self._empty_quality_changes_container:
quality = self._global_container_stack.qualityChanges
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
return quality.getName()
quality = self._active_container_stack.findContainer({"type": "quality"})
quality = self._active_container_stack.quality
if quality:
return quality.getName()
return ""
@pyqtProperty(str, notify=activeQualityChanged)
def activeQualityId(self):
def activeQualityId(self) -> str:
if self._active_container_stack:
quality = self._active_container_stack.findContainer({"type": "quality_changes"})
if quality and quality != self._empty_quality_changes_container:
quality = self._active_container_stack.qualityChanges
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
return quality.getId()
quality = self._active_container_stack.findContainer({"type": "quality"})
quality = self._active_container_stack.quality
if quality:
return quality.getId()
return ""
@pyqtProperty(str, notify=activeQualityChanged)
def globalQualityId(self):
def globalQualityId(self) -> str:
if self._global_container_stack:
quality = self._global_container_stack.findContainer({"type": "quality_changes"})
if quality and quality != self._empty_quality_changes_container:
quality = self._global_container_stack.qualityChanges
if quality and not isinstance(quality, type(self._empty_quality_changes_container)):
return quality.getId()
quality = self._global_container_stack.findContainer({"type": "quality"})
quality = self._global_container_stack.quality
if quality:
return quality.getId()
return ""
@pyqtProperty(str, notify = activeQualityChanged)
def activeQualityType(self):
def activeQualityType(self) -> str:
if self._active_container_stack:
quality = self._active_container_stack.findContainer(type = "quality")
quality = self._active_container_stack.quality
if quality:
return quality.getMetaDataEntry("quality_type")
return ""
@pyqtProperty(bool, notify = activeQualityChanged)
def isActiveQualitySupported(self):
def isActiveQualitySupported(self) -> bool:
if self._active_container_stack:
quality = self._active_container_stack.findContainer(type = "quality")
quality = self._active_container_stack.quality
if quality:
return Util.parseBool(quality.getMetaDataEntry("supported", True))
return False
@ -637,25 +662,25 @@ class MachineManager(QObject):
# \todo Ideally, this method would be named activeQualityId(), and the other one
# would be named something like activeQualityOrQualityChanges() for consistency
@pyqtProperty(str, notify = activeQualityChanged)
def activeQualityContainerId(self):
def activeQualityContainerId(self) -> str:
# We're using the active stack instead of the global stack in case the list of qualities differs per extruder
if self._global_container_stack:
quality = self._active_container_stack.findContainer(type = "quality")
quality = self._active_container_stack.quality
if quality:
return quality.getId()
return ""
@pyqtProperty(str, notify = activeQualityChanged)
def activeQualityChangesId(self):
def activeQualityChangesId(self) -> str:
if self._active_container_stack:
changes = self._active_container_stack.findContainer(type = "quality_changes")
if changes:
changes = self._active_container_stack.qualityChanges
if changes and changes.getId() != "empty":
return changes.getId()
return ""
## Check if a container is read_only
@pyqtSlot(str, result = bool)
def isReadOnly(self, container_id) -> bool:
def isReadOnly(self, container_id: str) -> bool:
containers = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
if not containers or not self._active_container_stack:
return True
@ -663,7 +688,7 @@ class MachineManager(QObject):
## Copy the value of the setting of the current extruder to all other extruders as well as the global container.
@pyqtSlot(str)
def copyValueToExtruders(self, key):
def copyValueToExtruders(self, key: str):
if not self._active_container_stack or self._global_container_stack.getProperty("machine_extruder_count", "value") <= 1:
return
@ -677,7 +702,7 @@ class MachineManager(QObject):
## Set the active material by switching out a container
# Depending on from/to material+current variant, a quality profile is chosen and set.
@pyqtSlot(str)
def setActiveMaterial(self, material_id):
def setActiveMaterial(self, material_id: str):
with postponeSignals(*self._getContainerChangedSignals(), compress = True):
containers = ContainerRegistry.getInstance().findInstanceContainers(id = material_id)
if not containers or not self._active_container_stack:
@ -686,21 +711,20 @@ class MachineManager(QObject):
Logger.log("d", "Attempting to change the active material to %s", material_id)
old_material = self._active_container_stack.findContainer({"type": "material"})
old_quality = self._active_container_stack.findContainer({"type": "quality"})
old_quality_changes = self._active_container_stack.findContainer({"type": "quality_changes"})
old_material = self._active_container_stack.material
old_quality = self._active_container_stack.quality
old_quality_changes = self._active_container_stack.qualityChanges
if not old_material:
Logger.log("w", "While trying to set the active material, no material was found to replace it.")
return
if old_quality_changes.getId() == "empty_quality_changes":
if old_quality_changes and isinstance(old_quality_changes, type(self._empty_quality_changes_container)):
old_quality_changes = None
self.blurSettings.emit()
old_material.nameChanged.disconnect(self._onMaterialNameChanged)
material_index = self._active_container_stack.getContainerIndex(old_material)
self._active_container_stack.replaceContainer(material_index, material_container)
self._active_container_stack.material = material_container
Logger.log("d", "Active material changed")
material_container.nameChanged.connect(self._onMaterialNameChanged)
@ -727,7 +751,7 @@ class MachineManager(QObject):
candidate_quality = quality_manager.findQualityByQualityType(quality_type,
quality_manager.getWholeMachineDefinition(machine_definition),
[material_container])
if not candidate_quality or candidate_quality.getId() == "empty_quality":
if not candidate_quality or isinstance(candidate_quality, type(self._empty_quality_changes_container)):
# Fall back to a quality (which must be compatible with all other extruders)
new_qualities = quality_manager.findAllUsableQualitiesForMachineAndExtruders(
self._global_container_stack, ExtruderManager.getInstance().getExtruderStacks())
@ -743,31 +767,30 @@ class MachineManager(QObject):
self.setActiveQuality(new_quality_id)
@pyqtSlot(str)
def setActiveVariant(self, variant_id):
def setActiveVariant(self, variant_id: str):
with postponeSignals(*self._getContainerChangedSignals(), compress = True):
containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_id)
if not containers or not self._active_container_stack:
return
Logger.log("d", "Attempting to change the active variant to %s", variant_id)
old_variant = self._active_container_stack.findContainer({"type": "variant"})
old_material = self._active_container_stack.findContainer({"type": "material"})
old_variant = self._active_container_stack.variant
old_material = self._active_container_stack.material
if old_variant:
self.blurSettings.emit()
variant_index = self._active_container_stack.getContainerIndex(old_variant)
self._active_container_stack.replaceContainer(variant_index, containers[0])
Logger.log("d", "Active variant changed")
self._active_container_stack.variant = containers[0]
Logger.log("d", "Active variant changed to {active_variant_id}".format(active_variant_id = containers[0].getId()))
preferred_material = None
if old_material:
preferred_material_name = old_material.getName()
self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), containers[0], preferred_material_name).id)
self.setActiveMaterial(self._updateMaterialContainer(self._global_container_stack.getBottom(), self._global_container_stack, containers[0], preferred_material_name).id)
else:
Logger.log("w", "While trying to set the active variant, no variant was found to replace.")
## set the active quality
# \param quality_id The quality_id of either a quality or a quality_changes
@pyqtSlot(str)
def setActiveQuality(self, quality_id):
def setActiveQuality(self, quality_id: str):
with postponeSignals(*self._getContainerChangedSignals(), compress = True):
self.blurSettings.emit()
@ -804,8 +827,8 @@ class MachineManager(QObject):
name_changed_connect_stacks.append(stack_quality)
name_changed_connect_stacks.append(stack_quality_changes)
self._replaceQualityOrQualityChangesInStack(stack, stack_quality)
self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes)
self._replaceQualityOrQualityChangesInStack(stack, stack_quality, postpone_emit=True)
self._replaceQualityOrQualityChangesInStack(stack, stack_quality_changes, postpone_emit=True)
# Send emits that are postponed in replaceContainer.
# Here the stacks are finished replacing and every value can be resolved based on the current state.
@ -825,7 +848,8 @@ class MachineManager(QObject):
#
# \param quality_name \type{str} the name of the quality.
# \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
def determineQualityAndQualityChangesForQualityType(self, quality_type):
@UM.FlameProfiler.profile
def determineQualityAndQualityChangesForQualityType(self, quality_type: str):
quality_manager = QualityManager.getInstance()
result = []
empty_quality_changes = self._empty_quality_changes_container
@ -841,7 +865,7 @@ class MachineManager(QObject):
stacks = [global_container_stack]
for stack in stacks:
material = stack.findContainer(type="material")
material = stack.material
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
if not quality: #No quality profile is found for this quality type.
quality = self._empty_quality_container
@ -862,7 +886,7 @@ class MachineManager(QObject):
#
# \param quality_changes_name \type{str} the name of the quality changes.
# \return \type{List[Dict]} with keys "stack", "quality" and "quality_changes".
def _determineQualityAndQualityChangesForQualityChanges(self, quality_changes_name):
def _determineQualityAndQualityChangesForQualityChanges(self, quality_changes_name: str):
result = []
quality_manager = QualityManager.getInstance()
@ -878,7 +902,7 @@ class MachineManager(QObject):
else:
Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name)
return None
material = global_container_stack.findContainer(type="material")
material = global_container_stack.material
# For the global stack, find a quality which matches the quality_type in
# the quality changes profile and also satisfies any material constraints.
@ -901,7 +925,7 @@ class MachineManager(QObject):
else:
quality_changes = global_quality_changes
material = stack.findContainer(type="material")
material = stack.material
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material])
if not quality: #No quality profile found for this quality type.
quality = self._empty_quality_container
@ -918,44 +942,44 @@ class MachineManager(QObject):
return result
def _replaceQualityOrQualityChangesInStack(self, stack, container, postpone_emit = False):
def _replaceQualityOrQualityChangesInStack(self, stack: "CuraContainerStack", container: "InstanceContainer", postpone_emit = False):
# Disconnect the signal handling from the old container.
old_container = stack.findContainer(type=container.getMetaDataEntry("type"))
if old_container:
old_container.nameChanged.disconnect(self._onQualityNameChanged)
else:
Logger.log("e", "Could not find container of type %s in stack %s while replacing quality (changes) with container %s", container.getMetaDataEntry("type"), stack.getId(), container.getId())
return
# Swap in the new container into the stack.
stack.replaceContainer(stack.getContainerIndex(old_container), container, postpone_emit = postpone_emit)
# Attach the needed signal handling.
container.nameChanged.connect(self._onQualityNameChanged)
container_type = container.getMetaDataEntry("type")
if container_type == "quality":
stack.quality.nameChanged.disconnect(self._onQualityNameChanged)
stack.setQuality(container, postpone_emit = postpone_emit)
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
elif container_type == "quality_changes" or container_type is None:
# If the container is an empty container, we need to change the quality_changes.
# Quality can never be set to empty.
stack.qualityChanges.nameChanged.disconnect(self._onQualityNameChanged)
stack.setQualityChanges(container, postpone_emit = postpone_emit)
stack.qualityChanges.nameChanged.connect(self._onQualityNameChanged)
self._onQualityNameChanged()
def _askUserToKeepOrClearCurrentSettings(self):
Application.getInstance().discardOrKeepProfileChanges()
@pyqtProperty(str, notify = activeVariantChanged)
def activeVariantName(self):
def activeVariantName(self) -> str:
if self._active_container_stack:
variant = self._active_container_stack.findContainer({"type": "variant"})
variant = self._active_container_stack.variant
if variant:
return variant.getName()
return ""
@pyqtProperty(str, notify = activeVariantChanged)
def activeVariantId(self):
def activeVariantId(self) -> str:
if self._active_container_stack:
variant = self._active_container_stack.findContainer({"type": "variant"})
variant = self._active_container_stack.variant
if variant:
return variant.getId()
return ""
@pyqtProperty(str, notify = globalContainerChanged)
def activeDefinitionId(self):
def activeDefinitionId(self) -> str:
if self._global_container_stack:
definition = self._global_container_stack.getBottom()
if definition:
@ -964,7 +988,7 @@ class MachineManager(QObject):
return ""
@pyqtProperty(str, notify=globalContainerChanged)
def activeDefinitionName(self):
def activeDefinitionName(self) -> str:
if self._global_container_stack:
definition = self._global_container_stack.getBottom()
if definition:
@ -976,7 +1000,7 @@ class MachineManager(QObject):
# \returns DefinitionID (string) if found, empty string otherwise
# \sa getQualityDefinitionId
@pyqtProperty(str, notify = globalContainerChanged)
def activeQualityDefinitionId(self):
def activeQualityDefinitionId(self) -> str:
if self._global_container_stack:
return self.getQualityDefinitionId(self._global_container_stack.getBottom())
return ""
@ -985,16 +1009,16 @@ class MachineManager(QObject):
# This is normally the id of the definition itself, but machines can specify a different definition to inherit qualities from
# \param definition (DefinitionContainer) machine definition
# \returns DefinitionID (string) if found, empty string otherwise
def getQualityDefinitionId(self, definition):
def getQualityDefinitionId(self, definition: "DefinitionContainer") -> str:
return QualityManager.getInstance().getParentMachineDefinition(definition).getId()
## Get the Variant ID to use to select quality profiles for the currently active variant
# \returns VariantID (string) if found, empty string otherwise
# \sa getQualityVariantId
@pyqtProperty(str, notify = activeVariantChanged)
def activeQualityVariantId(self):
def activeQualityVariantId(self) -> str:
if self._active_container_stack:
variant = self._active_container_stack.findContainer({"type": "variant"})
variant = self._active_container_stack.variant
if variant:
return self.getQualityVariantId(self._global_container_stack.getBottom(), variant)
return ""
@ -1003,9 +1027,9 @@ class MachineManager(QObject):
# This is normally the id of the variant itself, but machines can specify a different definition
# to inherit qualities from, which has consequences for the variant to use as well
# \param definition (DefinitionContainer) machine definition
# \param variant (DefinitionContainer) variant definition
# \param variant (InstanceContainer) variant definition
# \returns VariantID (string) if found, empty string otherwise
def getQualityVariantId(self, definition, variant):
def getQualityVariantId(self, definition: "DefinitionContainer", variant: "InstanceContainer") -> str:
variant_id = variant.getId()
definition_id = definition.getId()
quality_definition_id = self.getQualityDefinitionId(definition)
@ -1017,7 +1041,7 @@ class MachineManager(QObject):
## Gets how the active definition calls variants
# Caveat: per-definition-variant-title is currently not translated (though the fallback is)
@pyqtProperty(str, notify = globalContainerChanged)
def activeDefinitionVariantsName(self):
def activeDefinitionVariantsName(self) -> str:
fallback_title = catalog.i18nc("@label", "Nozzle")
if self._global_container_stack:
return self._global_container_stack.getBottom().getMetaDataEntry("variants_name", fallback_title)
@ -1025,7 +1049,7 @@ class MachineManager(QObject):
return fallback_title
@pyqtSlot(str, str)
def renameMachine(self, machine_id, new_name):
def renameMachine(self, machine_id: str, new_name: str):
containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
if containers:
new_name = self._createUniqueName("machine", containers[0].getName(), new_name, containers[0].getBottom().getName())
@ -1033,7 +1057,7 @@ class MachineManager(QObject):
self.globalContainerChanged.emit()
@pyqtSlot(str)
def removeMachine(self, machine_id):
def removeMachine(self, machine_id: str):
# If the machine that is being removed is the currently active machine, set another machine as the active machine.
activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id)
@ -1051,14 +1075,14 @@ class MachineManager(QObject):
@pyqtProperty(bool, notify = globalContainerChanged)
def hasMaterials(self):
def hasMaterials(self) -> bool:
if self._global_container_stack:
return bool(self._global_container_stack.getMetaDataEntry("has_materials", False))
return False
@pyqtProperty(bool, notify = globalContainerChanged)
def hasVariants(self):
def hasVariants(self) -> bool:
if self._global_container_stack:
return bool(self._global_container_stack.getMetaDataEntry("has_variants", False))
@ -1067,7 +1091,7 @@ class MachineManager(QObject):
## Property to indicate if a machine has "specialized" material profiles.
# Some machines have their own material profiles that "override" the default catch all profiles.
@pyqtProperty(bool, notify = globalContainerChanged)
def filterMaterialsByMachine(self):
def filterMaterialsByMachine(self) -> bool:
if self._global_container_stack:
return bool(self._global_container_stack.getMetaDataEntry("has_machine_materials", False))
@ -1076,7 +1100,7 @@ class MachineManager(QObject):
## Property to indicate if a machine has "specialized" quality profiles.
# Some machines have their own quality profiles that "override" the default catch all profiles.
@pyqtProperty(bool, notify = globalContainerChanged)
def filterQualityByMachine(self):
def filterQualityByMachine(self) -> bool:
if self._global_container_stack:
return bool(self._global_container_stack.getMetaDataEntry("has_machine_quality", False))
return False
@ -1085,7 +1109,7 @@ class MachineManager(QObject):
# \param machine_id string machine id to get the definition ID of
# \returns DefinitionID (string) if found, None otherwise
@pyqtSlot(str, result = str)
def getDefinitionByMachineId(self, machine_id):
def getDefinitionByMachineId(self, machine_id: str) -> str:
containers = ContainerRegistry.getInstance().findContainerStacks(id=machine_id)
if containers:
return containers[0].getBottom().getId()
@ -1094,27 +1118,12 @@ class MachineManager(QObject):
def createMachineManager(engine=None, script_engine=None):
return MachineManager()
def _updateVariantContainer(self, definition):
if not definition.getMetaDataEntry("has_variants"):
return self._empty_variant_container
machine_definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(definition)
containers = []
preferred_variant = definition.getMetaDataEntry("preferred_variant")
if preferred_variant:
containers = ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id, id = preferred_variant)
if not containers:
containers = ContainerRegistry.getInstance().findInstanceContainers(type = "variant", definition = machine_definition_id)
if containers:
return containers[0]
return self._empty_variant_container
def _updateMaterialContainer(self, definition, variant_container = None, preferred_material_name = None):
def _updateMaterialContainer(self, definition: "DefinitionContainer", stack: "ContainerStack", variant_container: Optional["InstanceContainer"] = None, preferred_material_name: Optional[str] = None):
if not definition.getMetaDataEntry("has_materials"):
return self._empty_material_container
search_criteria = { "type": "material" }
approximate_material_diameter = round(stack.getProperty("material_diameter", "value"))
search_criteria = { "type": "material", "approximate_diameter": approximate_material_diameter }
if definition.getMetaDataEntry("has_machine_materials"):
search_criteria["definition"] = self.getQualityDefinitionId(definition)
@ -1146,110 +1155,6 @@ class MachineManager(QObject):
Logger.log("w", "Unable to find a material container with provided criteria, returning an empty one instead.")
return self._empty_material_container
def _updateQualityContainer(self, definition, variant_container, material_container = None, preferred_quality_name = None):
container_registry = ContainerRegistry.getInstance()
search_criteria = { "type": "quality" }
if definition.getMetaDataEntry("has_machine_quality"):
search_criteria["definition"] = self.getQualityDefinitionId(definition)
if definition.getMetaDataEntry("has_materials") and material_container:
search_criteria["material"] = material_container.id
else:
search_criteria["definition"] = "fdmprinter"
if preferred_quality_name and preferred_quality_name != "empty":
search_criteria["name"] = preferred_quality_name
else:
preferred_quality = definition.getMetaDataEntry("preferred_quality")
if preferred_quality:
search_criteria["id"] = preferred_quality
containers = container_registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
if "material" in search_criteria:
# First check if we can solve our material not found problem by checking if we can find quality containers
# that are assigned to the parents of this material profile.
try:
inherited_files = material_container.getInheritedFiles()
except AttributeError: # Material_container does not support inheritance.
inherited_files = []
if inherited_files:
for inherited_file in inherited_files:
# Extract the ID from the path we used to load the file.
search_criteria["material"] = os.path.basename(inherited_file).split(".")[0]
containers = container_registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
# We still weren't able to find a quality for this specific material.
# Try to find qualities for a generic version of the material.
material_search_criteria = { "type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
if definition.getMetaDataEntry("has_machine_quality"):
if material_container:
material_search_criteria["definition"] = material_container.getDefinition().id
if definition.getMetaDataEntry("has_variants"):
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
else:
material_search_criteria["definition"] = self.getQualityDefinitionId(definition)
if definition.getMetaDataEntry("has_variants") and variant_container:
material_search_criteria["variant"] = self.getQualityVariantId(definition, variant_container)
else:
material_search_criteria["definition"] = "fdmprinter"
material_containers = container_registry.findInstanceContainers(**material_search_criteria)
# Try all materials to see if there is a quality profile available.
for material_container in material_containers:
search_criteria["material"] = material_container.getId()
containers = container_registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
if "name" in search_criteria or "id" in search_criteria:
# If a quality by this name can not be found, try a wider set of search criteria
search_criteria.pop("name", None)
search_criteria.pop("id", None)
containers = container_registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
# Notify user that we were unable to find a matching quality
message = Message(catalog.i18nc("@info:status", "Unable to find a quality profile for this combination. Default settings will be used instead."))
message.show()
return self._empty_quality_container
## Finds a quality-changes container to use if any other container
# changes.
#
# \param quality_type The quality type to find a quality-changes for.
# \param preferred_quality_changes_name The name of the quality-changes to
# pick, if any such quality-changes profile is available.
def _updateQualityChangesContainer(self, quality_type, preferred_quality_changes_name = None):
container_registry = ContainerRegistry.getInstance() # Cache.
search_criteria = { "type": "quality_changes" }
search_criteria["quality"] = quality_type
if preferred_quality_changes_name:
search_criteria["name"] = preferred_quality_changes_name
# Try to search with the name in the criteria first, since we prefer to have the correct name.
containers = container_registry.findInstanceContainers(**search_criteria)
if containers: # Found one!
return containers[0]
if "name" in search_criteria:
del search_criteria["name"] # Not found, then drop the name requirement (if we had one) and search again.
containers = container_registry.findInstanceContainers(**search_criteria)
if containers:
return containers[0]
return self._empty_quality_changes_container # Didn't find anything with the required quality_type.
def _onMachineNameChanged(self):
self.globalContainerChanged.emit()

View File

@ -32,9 +32,9 @@ class ProfilesModel(InstanceContainersModel):
## Get the singleton instance for this class.
@classmethod
def getInstance(cls):
def getInstance(cls) -> "ProfilesModel":
# Note: Explicit use of class name to prevent issues with inheritance.
if ProfilesModel.__instance is None:
if not ProfilesModel.__instance:
ProfilesModel.__instance = cls()
return ProfilesModel.__instance

View File

@ -0,0 +1,27 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Scene.SceneNode import SceneNode
from UM.Operations.Operation import Operation
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
## Simple operation to set the extruder a certain object should be printed with.
class SetObjectExtruderOperation(Operation):
def __init__(self, node: SceneNode, extruder_id: str) -> None:
self._node = node
self._extruder_id = extruder_id
self._previous_extruder_id = None
self._decorator_added = False
def undo(self):
if self._previous_extruder_id:
self._node.callDecoration("setActiveExtruder", self._previous_extruder_id)
def redo(self):
stack = self._node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway.
if not stack:
self._node.addDecorator(SettingOverrideDecorator())
self._previous_extruder_id = self._node.callDecoration("getActiveExtruder")
self._node.callDecoration("setActiveExtruder", self._extruder_id)

View File

@ -109,10 +109,13 @@ class SettingInheritanceManager(QObject):
self._settings_with_inheritance_warning.remove(key)
settings_with_inheritance_warning_changed = True
# Find the topmost parent (Assumed to be a category)
parent = definitions[0].parent
while parent.parent is not None:
parent = parent.parent
# Find the topmost parent (Assumed to be a category)
if parent is not None:
while parent.parent is not None:
parent = parent.parent
else:
parent = definitions[0] # Already at a category
if parent.key not in self._settings_with_inheritance_warning and has_overwritten_inheritance:
# Category was not in the list yet, so needs to be added now.

View File

@ -109,6 +109,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
def setActiveExtruder(self, extruder_stack_id):
self._extruder_stack = extruder_stack_id
self._updateNextStack()
ExtruderManager.getInstance().resetSelectedObjectExtruders()
self.activeExtruderChanged.emit()
def getStack(self):

View File

@ -43,8 +43,10 @@ class ShapeArray:
transform_x = transform._data[0][3]
transform_y = transform._data[2][3]
hull_verts = node.callDecoration("getConvexHull")
# For one_at_a_time printing you need the convex hull head.
hull_head_verts = node.callDecoration("getConvexHullHead") or hull_verts
offset_verts = hull_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
offset_verts = hull_head_verts.getMinkowskiHull(Polygon.approximatedCircle(min_offset))
offset_points = copy.deepcopy(offset_verts._points) # x, y
offset_points[:, 0] = numpy.add(offset_points[:, 0], -transform_x)
offset_points[:, 1] = numpy.add(offset_points[:, 1], -transform_y)

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

@ -1,3 +1,6 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from UM.Workspace.WorkspaceReader import WorkspaceReader
from UM.Application import Application
@ -15,7 +18,10 @@ from .WorkspaceDialog import WorkspaceDialog
import xml.etree.ElementTree as ET
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack
from configparser import ConfigParser
import zipfile
import io
import configparser
@ -31,10 +37,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._dialog = WorkspaceDialog()
self._3mf_mesh_reader = None
self._container_registry = ContainerRegistry.getInstance()
self._definition_container_suffix = ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix
# suffixes registered with the MineTypes don't start with a dot '.'
self._definition_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(DefinitionContainer).preferredSuffix
self._material_container_suffix = None # We have to wait until all other plugins are loaded before we can set it
self._instance_container_suffix = ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix
self._container_stack_suffix = ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix
self._instance_container_suffix = "." + ContainerRegistry.getMimeTypeForContainer(InstanceContainer).preferredSuffix
self._container_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ContainerStack).preferredSuffix
self._extruder_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(ExtruderStack).preferredSuffix
self._global_stack_suffix = "." + ContainerRegistry.getMimeTypeForContainer(GlobalStack).preferredSuffix
# Certain instance container types are ignored because we make the assumption that only we make those types
# of containers. They are:
# - quality
# - variant
self._ignored_instance_container_types = {"quality", "variant"}
self._resolve_strategies = {}
@ -47,6 +63,49 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
return self._id_mapping[old_id]
## Separates the given file list into a list of GlobalStack files and a list of ExtruderStack files.
#
# In old versions, extruder stack files have the same suffix as container stack files ".stack.cfg".
#
def _determineGlobalAndExtruderStackFiles(self, project_file_name, file_list):
archive = zipfile.ZipFile(project_file_name, "r")
global_stack_file_list = [name for name in file_list if name.endswith(self._global_stack_suffix)]
extruder_stack_file_list = [name for name in file_list if name.endswith(self._extruder_stack_suffix)]
# separate container stack files and extruder stack files
files_to_determine = [name for name in file_list if name.endswith(self._container_stack_suffix)]
for file_name in files_to_determine:
# FIXME: HACK!
# We need to know the type of the stack file, but we can only know it if we deserialize it.
# The default ContainerStack.deserialize() will connect signals, which is not desired in this case.
# Since we know that the stack files are INI files, so we directly use the ConfigParser to parse them.
serialized = archive.open(file_name).read().decode("utf-8")
stack_config = ConfigParser()
stack_config.read_string(serialized)
# sanity check
if not stack_config.has_option("metadata", "type"):
Logger.log("e", "%s in %s doesn't seem to be valid stack file", file_name, project_file_name)
continue
stack_type = stack_config.get("metadata", "type")
if stack_type == "extruder_train":
extruder_stack_file_list.append(file_name)
elif stack_type == "machine":
global_stack_file_list.append(file_name)
else:
Logger.log("w", "Unknown container stack type '%s' from %s in %s",
stack_type, file_name, project_file_name)
if len(global_stack_file_list) != 1:
raise RuntimeError("More than one global stack file found: [%s]" % str(global_stack_file_list))
return global_stack_file_list[0], extruder_stack_file_list
## read some info so we can make decisions
# \param file_name
# \param show_dialog In case we use preRead() to check if a file is a valid project file, we don't want to show a dialog.
def preRead(self, file_name, show_dialog=True, *args, **kwargs):
self._3mf_mesh_reader = Application.getInstance().getMeshFileHandler().getReaderForFile(file_name)
if self._3mf_mesh_reader and self._3mf_mesh_reader.preRead(file_name) == WorkspaceReader.PreReadResult.accepted:
@ -59,51 +118,52 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
machine_type = ""
variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")
num_extruders = 0
# Check if there are any conflicts, so we can ask the user.
archive = zipfile.ZipFile(file_name, "r")
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
machine_conflict = False
quality_changes_conflict = False
for container_stack_file in container_stack_files:
container_id = self._stripFileToId(container_stack_file)
serialized = archive.open(container_stack_file).read().decode("utf-8")
if machine_name == "":
machine_name = self._getMachineNameFromSerializedStack(serialized)
stacks = self._container_registry.findContainerStacks(id=container_id)
if stacks:
# Check if there are any changes at all in any of the container stacks.
id_list = self._getContainerIdListFromSerialized(serialized)
for index, container_id in enumerate(id_list):
if stacks[0].getContainer(index).getId() != container_id:
machine_conflict = True
Job.yieldThread()
# A few lists of containers in this project files.
# When loading the global stack file, it may be associated with those containers, which may or may not be
# in Cura already, so we need to provide them as alternative search lists.
definition_container_list = []
instance_container_list = []
material_container_list = []
#
# Read definition containers
#
machine_definition_container_count = 0
extruder_definition_container_count = 0
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
for definition_container_file in definition_container_files:
container_id = self._stripFileToId(definition_container_file)
for each_definition_container_file in definition_container_files:
container_id = self._stripFileToId(each_definition_container_file)
definitions = self._container_registry.findDefinitionContainers(id=container_id)
if not definitions:
definition_container = DefinitionContainer(container_id)
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"))
else:
definition_container = definitions[0]
definition_container_list.append(definition_container)
if definition_container.getMetaDataEntry("type") != "extruder":
definition_container_type = definition_container.getMetaDataEntry("type")
if definition_container_type == "machine":
machine_type = definition_container.getName()
variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name)
machine_definition_container_count += 1
elif definition_container_type == "extruder":
extruder_definition_container_count += 1
else:
num_extruders += 1
Logger.log("w", "Unknown definition container type %s for %s",
definition_container_type, each_definition_container_file)
Job.yieldThread()
if num_extruders == 0:
num_extruders = 1 # No extruder stacks found, which means there is one extruder
extruders = num_extruders * [""]
# sanity check
if machine_definition_container_count != 1:
msg = "Expecting one machine definition container but got %s" % machine_definition_container_count
Logger.log("e", msg)
raise RuntimeError(msg)
material_labels = []
material_conflict = False
@ -119,18 +179,25 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if materials and not materials[0].isReadOnly(): # Only non readonly materials can be in conflict
material_conflict = True
Job.yieldThread()
# Check if any quality_changes instance container is in conflict.
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
quality_name = ""
quality_type = ""
num_settings_overriden_by_quality_changes = 0 # How many settings are changed by the quality changes
num_settings_overriden_by_definition_changes = 0 # How many settings are changed by the definition changes
num_user_settings = 0
for instance_container_file in instance_container_files:
container_id = self._stripFileToId(instance_container_file)
quality_changes_conflict = False
definition_changes_conflict = False
for each_instance_container_file in instance_container_files:
container_id = self._stripFileToId(each_instance_container_file)
instance_container = InstanceContainer(container_id)
# Deserialize InstanceContainer by converting read data from bytes to string
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
instance_container.deserialize(archive.open(each_instance_container_file).read().decode("utf-8"))
instance_container_list.append(instance_container)
container_type = instance_container.getMetaDataEntry("type")
if container_type == "quality_changes":
quality_name = instance_container.getName()
@ -141,16 +208,41 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Check if there really is a conflict by comparing the values
if quality_changes[0] != instance_container:
quality_changes_conflict = True
elif container_type == "quality":
# If the quality name is not set (either by quality or changes, set it now)
# Quality changes should always override this (as they are "on top")
if quality_name == "":
quality_name = instance_container.getName()
quality_type = instance_container.getName()
elif container_type == "definition_changes":
definition_name = instance_container.getName()
num_settings_overriden_by_definition_changes += len(instance_container._instances)
definition_changes = self._container_registry.findDefinitionContainers(id = container_id)
if definition_changes:
if definition_changes[0] != instance_container:
definition_changes_conflict = True
elif container_type == "user":
num_user_settings += len(instance_container._instances)
elif container_type in self._ignored_instance_container_types:
# Ignore certain instance container types
Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
continue
Job.yieldThread()
# Load ContainerStack files and ExtruderStack files
global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(
file_name, cura_file_names)
self._resolve_strategies = {"machine": None, "quality_changes": None, "material": None}
machine_conflict = False
for container_stack_file in [global_stack_file] + extruder_stack_files:
container_id = self._stripFileToId(container_stack_file)
serialized = archive.open(container_stack_file).read().decode("utf-8")
if machine_name == "":
machine_name = self._getMachineNameFromSerializedStack(serialized)
stacks = self._container_registry.findContainerStacks(id = container_id)
if stacks:
# Check if there are any changes at all in any of the container stacks.
id_list = self._getContainerIdListFromSerialized(serialized)
for index, container_id in enumerate(id_list):
if stacks[0].getContainer(index).getId() != container_id:
machine_conflict = True
Job.yieldThread()
num_visible_settings = 0
try:
temp_preferences = Preferences()
@ -171,9 +263,17 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if not show_dialog:
return WorkspaceReader.PreReadResult.accepted
# prepare data for the dialog
num_extruders = extruder_definition_container_count
if num_extruders == 0:
num_extruders = 1 # No extruder stacks found, which means there is one extruder
extruders = num_extruders * [""]
# Show the dialog, informing the user what is about to happen.
self._dialog.setMachineConflict(machine_conflict)
self._dialog.setQualityChangesConflict(quality_changes_conflict)
self._dialog.setDefinitionChangesConflict(definition_changes_conflict)
self._dialog.setMaterialConflict(material_conflict)
self._dialog.setNumVisibleSettings(num_visible_settings)
self._dialog.setQualityName(quality_name)
@ -196,9 +296,58 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
return WorkspaceReader.PreReadResult.cancelled
self._resolve_strategies = self._dialog.getResult()
#
# There can be 3 resolve strategies coming from the dialog:
# - new: create a new container
# - override: override the existing container
# - None: There is no conflict, which means containers with the same IDs may or may not be there already.
# If they are there, there is no conflict between the them.
# In this case, you can either create a new one, or safely override the existing one.
#
# Default values
for k, v in self._resolve_strategies.items():
if v is None:
self._resolve_strategies[k] = "new"
return WorkspaceReader.PreReadResult.accepted
## Overrides an ExtruderStack in the given GlobalStack and returns the new ExtruderStack.
def _overrideExtruderStack(self, global_stack, extruder_index, extruder_file_content):
extruder_stack = global_stack.extruders[extruder_index]
machine_extruder_count = len(global_stack.extruders)
old_extruder_stack_id = extruder_stack.getId()
# HACK: There are two cases:
# - the new ExtruderStack has the same ID as the one we are overriding
# - they don't have the same ID
# In the second case, directly overriding the existing ExtruderStack will leave the old stack file
# in the Cura directory, and this will cause a problem when we restart Cura. So, we always delete
# the existing file first.
self._container_registry._deleteFiles(extruder_stack)
# override the given extruder stack
extruder_stack.deserialize(extruder_file_content)
# HACK: The deserialize() of ExtruderStack will add itself to the GlobalStack, which is redundant here.
# So we need to remove the new entries in the GlobalStack.
global_stack._extruders = global_stack._extruders[:machine_extruder_count]
# HACK: clean and fill the container query cache again
if old_extruder_stack_id in self._container_registry._id_container_cache:
del self._container_registry._id_container_cache[old_extruder_stack_id]
new_extruder_stack_id = extruder_stack.getId()
self._container_registry._id_container_cache[new_extruder_stack_id] = extruder_stack
# return the new ExtruderStack
return extruder_stack
## Read the project file
# Add all the definitions / materials / quality changes that do not exist yet. Then it loads
# all the stacks into the container registry. In some cases it will reuse the container for the global stack.
# It handles old style project files containing .stack.cfg as well as new style project files
# containing global.cfg / extruder.cfg
#
# \param file_name
def read(self, file_name):
archive = zipfile.ZipFile(file_name, "r")
@ -232,6 +381,24 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# We do this so that if something goes wrong, it's easier to clean up.
containers_to_add = []
global_stack_file, extruder_stack_files = self._determineGlobalAndExtruderStackFiles(file_name, cura_file_names)
global_stack = None
extruder_stacks = []
extruder_stacks_added = []
container_stacks_added = []
containers_added = []
global_stack_id_original = self._stripFileToId(global_stack_file)
global_stack_id_new = global_stack_id_original
global_stack_need_rename = False
if self._resolve_strategies["machine"] == "new":
# We need a new id if the id already exists
if self._container_registry.findContainerStacks(id = global_stack_id_original):
global_stack_id_new = self.getNewId(global_stack_id_original)
global_stack_need_rename = True
# TODO: For the moment we use pretty naive existence checking. If the ID is the same, we assume in quite a few
# TODO: cases that the container loaded is the same (most notable in materials & definitions).
# TODO: It might be possible that we need to add smarter checking in the future.
@ -240,7 +407,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
for definition_container_file in definition_container_files:
container_id = self._stripFileToId(definition_container_file)
definitions = self._container_registry.findDefinitionContainers(id=container_id)
definitions = self._container_registry.findDefinitionContainers(id = container_id)
if not definitions:
definition_container = DefinitionContainer(container_id)
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"))
@ -257,222 +424,379 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
for material_container_file in material_container_files:
container_id = self._stripFileToId(material_container_file)
materials = self._container_registry.findInstanceContainers(id=container_id)
materials = self._container_registry.findInstanceContainers(id = container_id)
if not materials:
material_container = xml_material_profile(container_id)
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
containers_to_add.append(material_container)
else:
if not materials[0].isReadOnly(): # Only create new materials if they are not read only.
material_container = materials[0]
if not material_container.isReadOnly(): # Only create new materials if they are not read only.
if self._resolve_strategies["material"] == "override":
materials[0].deserialize(archive.open(material_container_file).read().decode("utf-8"))
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
elif self._resolve_strategies["material"] == "new":
# Note that we *must* deserialize it with a new ID, as multiple containers will be
# auto created & added.
material_container = xml_material_profile(self.getNewId(container_id))
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"))
containers_to_add.append(material_container)
material_containers.append(material_container)
material_containers.append(material_container)
Job.yieldThread()
Logger.log("d", "Workspace loading is checking instance containers...")
# Get quality_changes and user profiles saved in the workspace
instance_container_files = [name for name in cura_file_names if name.endswith(self._instance_container_suffix)]
user_instance_containers = []
quality_changes_instance_containers = []
quality_and_definition_changes_instance_containers = []
for instance_container_file in instance_container_files:
container_id = self._stripFileToId(instance_container_file)
serialized = archive.open(instance_container_file).read().decode("utf-8")
# HACK! we ignore the "metadata/type = quality" instance containers!
parser = configparser.ConfigParser()
parser.read_string(serialized)
if not parser.has_option("metadata", "type"):
Logger.log("w", "Cannot find metadata/type in %s, ignoring it", instance_container_file)
continue
if parser.get("metadata", "type") == "quality":
continue
instance_container = InstanceContainer(container_id)
# Deserialize InstanceContainer by converting read data from bytes to string
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
instance_container.deserialize(serialized)
container_type = instance_container.getMetaDataEntry("type")
Job.yieldThread()
if container_type == "user":
if container_type in self._ignored_instance_container_types:
# Ignore certain instance container types
Logger.log("w", "Ignoring instance container [%s] with type [%s]", container_id, container_type)
continue
elif container_type == "user":
# Check if quality changes already exists.
user_containers = self._container_registry.findInstanceContainers(id=container_id)
user_containers = self._container_registry.findInstanceContainers(id = container_id)
if not user_containers:
containers_to_add.append(instance_container)
else:
if self._resolve_strategies["machine"] == "override" or self._resolve_strategies["machine"] is None:
user_containers[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
instance_container = user_containers[0]
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
instance_container.setDirty(True)
elif self._resolve_strategies["machine"] == "new":
# The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
extruder_id = instance_container.getMetaDataEntry("extruder", None)
if extruder_id:
new_id = self.getNewId(extruder_id) + "_current_settings"
new_extruder_id = self.getNewId(extruder_id)
new_id = new_extruder_id + "_current_settings"
instance_container._id = new_id
instance_container.setName(new_id)
instance_container.setMetaDataEntry("extruder", self.getNewId(extruder_id))
instance_container.setMetaDataEntry("extruder", new_extruder_id)
containers_to_add.append(instance_container)
machine_id = instance_container.getMetaDataEntry("machine", None)
if machine_id:
new_id = self.getNewId(machine_id) + "_current_settings"
new_machine_id = self.getNewId(machine_id)
new_id = new_machine_id + "_current_settings"
instance_container._id = new_id
instance_container.setName(new_id)
instance_container.setMetaDataEntry("machine", self.getNewId(machine_id))
instance_container.setMetaDataEntry("machine", new_machine_id)
containers_to_add.append(instance_container)
user_instance_containers.append(instance_container)
elif container_type == "quality_changes":
elif container_type in ("quality_changes", "definition_changes"):
# Check if quality changes already exists.
quality_changes = self._container_registry.findInstanceContainers(id = container_id)
if not quality_changes:
changes_containers = self._container_registry.findInstanceContainers(id = container_id)
if not changes_containers:
# no existing containers with the same ID, so we can safely add the new one
containers_to_add.append(instance_container)
else:
if self._resolve_strategies["quality_changes"] == "override":
quality_changes[0].deserialize(archive.open(instance_container_file).read().decode("utf-8"))
elif self._resolve_strategies["quality_changes"] is None:
# we have found existing container with the same ID, so we need to resolve according to the
# selected strategy.
if self._resolve_strategies[container_type] == "override":
instance_container = changes_containers[0]
instance_container.deserialize(archive.open(instance_container_file).read().decode("utf-8"))
instance_container.setDirty(True)
elif self._resolve_strategies[container_type] == "new":
# TODO: how should we handle the case "new" for quality_changes and definition_changes?
new_changes_container_id = self.getNewId(instance_container.getId())
instance_container._id = new_changes_container_id
instance_container.setName(new_changes_container_id)
# TODO: we don't know the following is correct or not, need to verify
# AND REFACTOR!!!
if self._resolve_strategies["machine"] == "new":
# The machine is going to get a spiffy new name, so ensure that the id's of user settings match.
extruder_id = instance_container.getMetaDataEntry("extruder", None)
if extruder_id:
new_extruder_id = self.getNewId(extruder_id)
instance_container.setMetaDataEntry("extruder", new_extruder_id)
machine_id = instance_container.getMetaDataEntry("machine", None)
if machine_id:
new_machine_id = self.getNewId(machine_id)
instance_container.setMetaDataEntry("machine", new_machine_id)
containers_to_add.append(instance_container)
elif self._resolve_strategies[container_type] is None:
# The ID already exists, but nothing in the values changed, so do nothing.
pass
quality_changes_instance_containers.append(instance_container)
quality_and_definition_changes_instance_containers.append(instance_container)
else:
continue
existing_container = self._container_registry.findInstanceContainers(id = container_id)
if not existing_container:
containers_to_add.append(instance_container)
if global_stack_need_rename:
if instance_container.getMetaDataEntry("machine"):
instance_container.setMetaDataEntry("machine", global_stack_id_new)
# Add all the containers right before we try to add / serialize the stack
for container in containers_to_add:
self._container_registry.addContainer(container)
container.setDirty(True)
containers_added.append(container)
# Get the stack(s) saved in the workspace.
Logger.log("d", "Workspace loading is checking stacks containers...")
container_stack_files = [name for name in cura_file_names if name.endswith(self._container_stack_suffix)]
global_stack = None
extruder_stacks = []
container_stacks_added = []
try:
for container_stack_file in container_stack_files:
container_id = self._stripFileToId(container_stack_file)
# Check if a stack by this ID already exists;
container_stacks = self._container_registry.findContainerStacks(id=container_id)
# --
# load global stack file
try:
# Check if a stack by this ID already exists;
container_stacks = self._container_registry.findContainerStacks(id = global_stack_id_original)
if container_stacks:
stack = container_stacks[0]
if self._resolve_strategies["machine"] == "override":
# TODO: HACK
# There is a machine, check if it has authentication data. If so, keep that data.
network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"))
if network_authentication_id:
container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
if network_authentication_key:
container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
elif self._resolve_strategies["machine"] == "new":
stack = GlobalStack(global_stack_id_new)
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
# Ensure a unique ID and name
stack._id = global_stack_id_new
# Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
# bound machine also needs to change.
if stack.getMetaDataEntry("machine", None):
stack.setMetaDataEntry("machine", global_stack_id_new)
# Only machines need a new name, stacks may be non-unique
stack.setName(self._container_registry.uniqueName(stack.getName()))
container_stacks_added.append(stack)
self._container_registry.addContainer(stack)
else:
Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
else:
# no existing container stack, so we create a new one
stack = GlobalStack(global_stack_id_new)
# Deserialize stack by converting read data from bytes to string
stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"))
container_stacks_added.append(stack)
self._container_registry.addContainer(stack)
containers_added.append(stack)
global_stack = stack
Job.yieldThread()
except:
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
# Something went really wrong. Try to remove any data that we added.
for container in containers_added:
self._container_registry.removeContainer(container.getId())
return
# --
# load extruder stack files
try:
for index, extruder_stack_file in enumerate(extruder_stack_files):
container_id = self._stripFileToId(extruder_stack_file)
extruder_file_content = archive.open(extruder_stack_file, "r").read().decode("utf-8")
container_stacks = self._container_registry.findContainerStacks(id = container_id)
if container_stacks:
# this container stack already exists, try to resolve
stack = container_stacks[0]
if self._resolve_strategies["machine"] == "override":
# TODO: HACK
# There is a machine, check if it has authenticationd data. If so, keep that data.
network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id")
network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key")
container_stacks[0].deserialize(archive.open(container_stack_file).read().decode("utf-8"))
if network_authentication_id:
container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id)
if network_authentication_key:
container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key)
# NOTE: This is the same code as those in the lower part
# deserialize new extruder stack over the current ones
stack = self._overrideExtruderStack(global_stack, index, extruder_file_content)
elif self._resolve_strategies["machine"] == "new":
# create a new extruder stack from this one
new_id = self.getNewId(container_id)
stack = ContainerStack(new_id)
stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))
stack = ExtruderStack(new_id)
stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8"))
# Ensure a unique ID and name
stack._id = new_id
# Extruder stacks are "bound" to a machine. If we add the machine as a new one, the id of the
# bound machine also needs to change.
if stack.getMetaDataEntry("machine", None):
stack.setMetaDataEntry("machine", self.getNewId(stack.getMetaDataEntry("machine")))
if stack.getMetaDataEntry("type") != "extruder_train":
# Only machines need a new name, stacks may be non-unique
stack.setName(self._container_registry.uniqueName(stack.getName()))
container_stacks_added.append(stack)
self._container_registry.addContainer(stack)
else:
Logger.log("w", "Resolve strategy of %s for machine is not supported", self._resolve_strategies["machine"])
extruder_stacks_added.append(stack)
containers_added.append(stack)
else:
stack = ContainerStack(container_id)
# Deserialize stack by converting read data from bytes to string
stack.deserialize(archive.open(container_stack_file).read().decode("utf-8"))
container_stacks_added.append(stack)
self._container_registry.addContainer(stack)
# No extruder stack with the same ID can be found
if self._resolve_strategies["machine"] == "override":
# deserialize new extruder stack over the current ones
stack = self._overrideExtruderStack(global_stack, index, extruder_file_content)
if stack.getMetaDataEntry("type") == "extruder_train":
extruder_stacks.append(stack)
else:
global_stack = stack
Job.yieldThread()
elif self._resolve_strategies["machine"] == "new":
# container not found, create a new one
stack = ExtruderStack(container_id)
stack.deserialize(archive.open(extruder_stack_file).read().decode("utf-8"))
self._container_registry.addContainer(stack)
extruder_stacks_added.append(stack)
containers_added.append(stack)
else:
Logger.log("w", "Unknown resolve strategy: %s" % str(self._resolve_strategies["machine"]))
if global_stack_need_rename:
if stack.getMetaDataEntry("machine"):
stack.setMetaDataEntry("machine", global_stack_id_new)
extruder_stacks.append(stack)
except:
Logger.logException("w", "We failed to serialize the stack. Trying to clean up.")
# Something went really wrong. Try to remove any data that we added.
for container in containers_to_add:
self._container_registry.getInstance().removeContainer(container.getId())
for container in container_stacks_added:
self._container_registry.getInstance().removeContainer(container.getId())
return None
for container in containers_added:
self._container_registry.removeContainer(container.getId())
return
#
# Replacing the old containers if resolve is "new".
# When resolve is "new", some containers will get renamed, so all the other containers that reference to those
# MUST get updated too.
#
if self._resolve_strategies["machine"] == "new":
# A new machine was made, but it was serialized with the wrong user container. Fix that now.
for container in user_instance_containers:
# replacing the container ID for user instance containers for the extruders
extruder_id = container.getMetaDataEntry("extruder", None)
if extruder_id:
for extruder in extruder_stacks:
if extruder.getId() == extruder_id:
extruder.replaceContainer(0, container)
extruder.userChanges = container
continue
# replacing the container ID for user instance containers for the machine
machine_id = container.getMetaDataEntry("machine", None)
if machine_id:
if global_stack.getId() == machine_id:
global_stack.replaceContainer(0, container)
global_stack.userChanges = container
continue
if self._resolve_strategies["quality_changes"] == "new":
# Quality changes needs to get a new ID, added to registry and to the right stacks
for container in quality_changes_instance_containers:
old_id = container.getId()
container.setName(self._container_registry.uniqueName(container.getName()))
# We're not really supposed to change the ID in normal cases, but this is an exception.
container._id = self.getNewId(container.getId())
for changes_container_type in ("quality_changes", "definition_changes"):
if self._resolve_strategies[changes_container_type] == "new":
# Quality changes needs to get a new ID, added to registry and to the right stacks
for each_changes_container in quality_and_definition_changes_instance_containers:
old_id = each_changes_container.getId()
each_changes_container.setName(self._container_registry.uniqueName(each_changes_container.getName()))
# We're not really supposed to change the ID in normal cases, but this is an exception.
each_changes_container._id = self.getNewId(each_changes_container.getId())
# The container was not added yet, as it didn't have an unique ID. It does now, so add it.
self._container_registry.addContainer(container)
# The container was not added yet, as it didn't have an unique ID. It does now, so add it.
self._container_registry.addContainer(each_changes_container)
# Replace the quality changes container
old_container = global_stack.findContainer({"type": "quality_changes"})
if old_container.getId() == old_id:
quality_changes_index = global_stack.getContainerIndex(old_container)
global_stack.replaceContainer(quality_changes_index, container)
continue
# Find the old (current) changes container in the global stack
if changes_container_type == "quality_changes":
old_container = global_stack.qualityChanges
elif changes_container_type == "definition_changes":
old_container = global_stack.definitionChanges
for stack in extruder_stacks:
old_container = stack.findContainer({"type": "quality_changes"})
# sanity checks
# NOTE: The following cases SHOULD NOT happen!!!!
if not old_container:
Logger.log("e", "We try to get [%s] from the global stack [%s] but we got None instead!",
changes_container_type, global_stack.getId())
# Replace the quality/definition changes container if it's in the GlobalStack
# NOTE: we can get an empty container here, but the IDs will not match,
# so this comparison is fine.
if old_container.getId() == old_id:
quality_changes_index = stack.getContainerIndex(old_container)
stack.replaceContainer(quality_changes_index, container)
if changes_container_type == "quality_changes":
global_stack.qualityChanges = each_changes_container
elif changes_container_type == "definition_changes":
global_stack.definitionChanges = each_changes_container
continue
# Replace the quality/definition changes container if it's in one of the ExtruderStacks
for each_extruder_stack in extruder_stacks:
changes_container = None
if changes_container_type == "quality_changes":
changes_container = each_extruder_stack.qualityChanges
elif changes_container_type == "definition_changes":
changes_container = each_extruder_stack.definitionChanges
# sanity checks
# NOTE: The following cases SHOULD NOT happen!!!!
if not changes_container:
Logger.log("e", "We try to get [%s] from the extruder stack [%s] but we got None instead!",
changes_container_type, each_extruder_stack.getId())
# NOTE: we can get an empty container here, but the IDs will not match,
# so this comparison is fine.
if changes_container.getId() == old_id:
if changes_container_type == "quality_changes":
each_extruder_stack.qualityChanges = each_changes_container
elif changes_container_type == "definition_changes":
each_extruder_stack.definitionChanges = each_changes_container
if self._resolve_strategies["material"] == "new":
for material in material_containers:
old_material = global_stack.findContainer({"type": "material"})
if old_material.getId() in self._id_mapping:
material_index = global_stack.getContainerIndex(old_material)
global_stack.replaceContainer(material_index, material)
for each_material in material_containers:
old_material = global_stack.material
# check if the old material container has been renamed to this material container ID
# if the container hasn't been renamed, we do nothing.
new_id = self._id_mapping.get(old_material.getId())
if new_id is None or new_id != each_material.getId():
continue
for stack in extruder_stacks:
old_material = stack.findContainer({"type": "material"})
if old_material.getId() in self._id_mapping:
material_index = stack.getContainerIndex(old_material)
stack.replaceContainer(material_index, material)
if old_material.getId() in self._id_mapping:
global_stack.material = each_material
for each_extruder_stack in extruder_stacks:
old_material = each_extruder_stack.material
# check if the old material container has been renamed to this material container ID
# if the container hasn't been renamed, we do nothing.
new_id = self._id_mapping.get(old_material.getId())
if new_id is None or new_id != each_material.getId():
continue
for stack in extruder_stacks:
ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
if old_material.getId() in self._id_mapping:
each_extruder_stack.material = each_material
if extruder_stacks:
for stack in extruder_stacks:
ExtruderManager.getInstance().registerExtruder(stack, global_stack.getId())
else:
# Machine has no extruders, but it needs to be registered with the extruder manager.
ExtruderManager.getInstance().registerExtruder(None, global_stack.getId())
Logger.log("d", "Workspace loading is notifying rest of the code of changes...")
# Notify everything/one that is to notify about changes.
global_stack.containersChanged.emit(global_stack.getTop())
for stack in extruder_stacks:
stack.setNextStack(global_stack)
stack.containersChanged.emit(stack.getTop())
if self._resolve_strategies["machine"] == "new":
for stack in extruder_stacks:
stack.setNextStack(global_stack)
stack.containersChanged.emit(stack.getTop())
# Actually change the active machine.
Application.getInstance().setGlobalContainerStack(global_stack)
# Notify everything/one that is to notify about changes.
global_stack.containersChanged.emit(global_stack.getTop())
# Load all the nodes / meshdata of the workspace
nodes = self._3mf_mesh_reader.read(file_name)
if nodes is None:

View File

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

View File

@ -7,6 +7,7 @@ from cura.Settings.ExtruderManager import ExtruderManager
import zipfile
from io import StringIO
import copy
import configparser
class ThreeMFWorkspaceWriter(WorkspaceWriter):
@ -48,6 +49,16 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
Preferences.getInstance().writeToFile(preferences_string)
archive.writestr(preferences_file, preferences_string.getvalue())
# Save Cura version
version_file = zipfile.ZipInfo("Cura/version.ini")
version_config_parser = configparser.ConfigParser()
version_config_parser.add_section("versions")
version_config_parser.set("versions", "cura_version", Application.getStaticVersion())
version_file_string = StringIO()
version_config_parser.write(version_file_string)
archive.writestr(version_file, version_file_string.getvalue())
# Close the archive & reset states.
archive.close()
mesh_writer.setStoreArchive(False)

View File

@ -79,7 +79,7 @@ The initial and final printing temperatures reduce the amount of oozing during P
Initial and final printing temperature settings have been tuned for higher quality results. For all materials the initial print temperature is 5 degrees above the default value.
*Printing temperature of the materials
The printing temperature of the materials in the material profiles is now the same as the printing temperature for the Normal Quality profile.
The printing temperature of the materials in the material profiles is now the same as the printing temperature for the Fine profile.
*Improved PLA-PVA layer adhesion
The PVA jerk and acceleration have been optimized to improve the layer adhesion between PVA and PLA.

View File

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

View File

@ -13,9 +13,9 @@ from UM.Resources import Resources
from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then.
from UM.Platform import Platform
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Qt.Duration import DurationFormat
from PyQt5.QtCore import QObject, pyqtSlot
from cura.Settings.ExtruderManager import ExtruderManager
from . import ProcessSlicedLayersJob
from . import StartSliceJob
@ -187,7 +187,19 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("w", "Slice unnecessary, nothing has changed that needs reslicing.")
return
self.printDurationMessage.emit(0, [0])
self.printDurationMessage.emit({
"none": 0,
"inset_0": 0,
"inset_x": 0,
"skin": 0,
"support": 0,
"skirt": 0,
"infill": 0,
"support_infill": 0,
"travel": 0,
"retract": 0,
"support_interface": 0
}, [0])
self._stored_layer_data = []
self._stored_optimized_layer_data = []
@ -273,9 +285,15 @@ class CuraEngineBackend(QObject, Backend):
if not extruders:
error_keys = self._global_container_stack.getErrorKeys()
error_labels = set()
definition_container = self._global_container_stack.getBottom()
for key in error_keys:
error_labels.add(definition_container.findDefinitions(key = key)[0].label)
for stack in [self._global_container_stack] + extruders: #Search all container stacks for the definition of this setting. Some are only in an extruder stack.
definitions = stack.getBottom().findDefinitions(key = key)
if definitions:
break #Found it! No need to continue search.
else: #No stack has a definition for this setting.
Logger.log("w", "When checking settings for errors, unable to find definition for key: {key}".format(key = key))
continue
error_labels.add(definitions[0].label)
error_labels = ", ".join(error_labels)
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}".format(error_labels)))
@ -442,6 +460,15 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.Done)
self.processingProgress.emit(1.0)
for line in self._scene.gcode_list:
replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths))
replaced = replaced.replace("{filament_weight}", str(Application.getInstance().getPrintInformation().materialWeights))
replaced = replaced.replace("{filament_cost}", str(Application.getInstance().getPrintInformation().materialCosts))
replaced = replaced.replace("{jobname}", str(Application.getInstance().getPrintInformation().jobName))
self._scene.gcode_list[self._scene.gcode_list.index(line)] = replaced
self._slicing = False
self._need_slicing = False
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
@ -466,13 +493,26 @@ class CuraEngineBackend(QObject, Backend):
## Called when a print time message is received from the engine.
#
# \param message The protobuff message containing the print time and
# \param message The protobuf message containing the print time per feature and
# material amount per extruder
def _onPrintTimeMaterialEstimates(self, message):
material_amounts = []
for index in range(message.repeatedMessageCount("materialEstimates")):
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
self.printDurationMessage.emit(message.time, material_amounts)
feature_times = {
"none": message.time_none,
"inset_0": message.time_inset_0,
"inset_x": message.time_inset_x,
"skin": message.time_skin,
"support": message.time_support,
"skirt": message.time_skirt,
"infill": message.time_infill,
"support_infill": message.time_support_infill,
"travel": message.time_travel,
"retract": message.time_retract,
"support_interface": message.time_support_interface
}
self.printDurationMessage.emit(feature_times, material_amounts)
## Creates a new socket connection.
def _createSocket(self):

View File

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

View File

@ -4,6 +4,7 @@
import numpy
from string import Formatter
from enum import IntEnum
import time
from UM.Job import Job
from UM.Application import Application
@ -230,25 +231,22 @@ class StartSliceJob(Job):
keys = stack.getAllKeys()
settings = {}
for key in keys:
# Use resolvement value if available, or take the value
resolved_value = stack.getProperty(key, "resolve")
if resolved_value is not None:
# There is a resolvement value. Check if we need to use it.
user_container = stack.findContainer({"type": "user"})
quality_changes_container = stack.findContainer({"type": "quality_changes"})
if user_container.hasProperty(key,"value") or quality_changes_container.hasProperty(key,"value"):
# Normal case
settings[key] = stack.getProperty(key, "value")
else:
settings[key] = resolved_value
else:
# Normal case
settings[key] = stack.getProperty(key, "value")
settings[key] = stack.getProperty(key, "value")
Job.yieldThread()
start_gcode = settings["machine_start_gcode"]
settings["material_bed_temp_prepend"] = "{material_bed_temperature}" not in start_gcode #Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
settings["material_print_temp_prepend"] = "{material_print_temperature}" not in start_gcode
#Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
bed_temperature_settings = {"material_bed_temperature", "material_bed_temperature_layer_0"}
settings["material_bed_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in bed_temperature_settings))
print_temperature_settings = {"material_print_temperature", "material_print_temperature_layer_0", "default_material_print_temperature", "material_initial_print_temperature", "material_final_print_temperature", "material_standby_temperature"}
settings["material_print_temp_prepend"] = all(("{" + setting + "}" not in start_gcode for setting in print_temperature_settings))
settings["print_bed_temperature"] = settings["material_bed_temperature"]
settings["print_temperature"] = settings["material_print_temperature"]
settings["time"] = time.strftime('%H:%M:%S')
settings["date"] = time.strftime('%d-%m-%Y')
settings["day"] = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][int(time.strftime('%w'))]
for key, value in settings.items(): #Add all submessages for each individual setting.
setting_message = self._slice_message.getMessage("global_settings").addRepeatedMessage("settings")

View File

@ -56,7 +56,7 @@ class GCodeProfileReader(ProfileReader):
# TODO: Consider moving settings to the start?
serialized = "" # Will be filled with the serialized profile.
try:
with open(file_name) as f:
with open(file_name, "r") as f:
for line in f:
if line.startswith(prefix):
# Remove the prefix and the newline from the line and add it to the rest.
@ -66,9 +66,13 @@ class GCodeProfileReader(ProfileReader):
return None
serialized = unescapeGcodeComment(serialized)
Logger.log("i", "Serialized the following from %s: %s" %(file_name, repr(serialized)))
json_data = json.loads(serialized)
# serialized data can be invalid JSON
try:
json_data = json.loads(serialized)
except Exception as e:
Logger.log("e", "Could not parse serialized JSON data from GCode %s, error: %s", file_name, e)
return None
profiles = []
global_profile = readQualityProfileFromString(json_data["global_quality"])

View File

@ -179,6 +179,7 @@ class GCodeReader(MeshReader):
def _processGCode(self, G, line, position, path):
func = getattr(self, "_gCode%s" % G, None)
line = line.split(";", 1)[0] # Remove comments (if any)
if func is not None:
s = line.upper().split(" ")
x, y, z, e = None, None, None, None

View File

@ -100,7 +100,7 @@ class GCodeWriter(MeshWriter):
prefix = ";SETTING_" + str(GCodeWriter.version) + " " # The prefix to put before each line.
prefix_length = len(prefix)
container_with_profile = stack.findContainer({"type": "quality_changes"})
container_with_profile = stack.qualityChanges
if not container_with_profile:
Logger.log("e", "No valid quality profile found, not writing settings to GCode!")
return ""
@ -115,7 +115,7 @@ class GCodeWriter(MeshWriter):
data = {"global_quality": serialized}
for extruder in sorted(ExtruderManager.getInstance().getMachineExtruders(stack.getId()), key = lambda k: k.getMetaDataEntry("position")):
extruder_quality = extruder.findContainer({"type": "quality_changes"})
extruder_quality = extruder.qualityChanges
if not extruder_quality:
Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
continue

262
plugins/LayerView/LayerView.qml Normal file → Executable file
View File

@ -7,6 +7,7 @@ import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
import UM 1.0 as UM
import Cura 1.0 as Cura
Item
{
@ -42,7 +43,8 @@ Item
property bool show_helpers: UM.Preferences.getValue("layerview/show_helpers")
property bool show_skin: UM.Preferences.getValue("layerview/show_skin")
property bool show_infill: UM.Preferences.getValue("layerview/show_infill")
property bool show_legend: UM.LayerView.compatibilityMode || UM.Preferences.getValue("layerview/layer_view_type") == 1
// if we are in compatibility mode, we only show the "line type"
property bool show_legend: UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type") == 1
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
@ -58,6 +60,7 @@ Item
anchors.left: parent.left
text: catalog.i18nc("@label","View Mode: Layers")
font.bold: true
color: UM.Theme.getColor("text")
}
Label
@ -75,6 +78,7 @@ Item
text: catalog.i18nc("@label","Color scheme")
visible: !UM.LayerView.compatibilityMode
Layout.fillWidth: true
color: UM.Theme.getColor("text")
}
ListModel // matches LayerView.py
@ -102,28 +106,25 @@ Item
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
model: layerViewTypes
visible: !UM.LayerView.compatibilityMode
style: UM.Theme.styles.combobox
property int layer_view_type: UM.Preferences.getValue("layerview/layer_view_type")
currentIndex: layer_view_type // index matches type_id
onActivated: {
// Combobox selection
var type_id = index;
UM.Preferences.setValue("layerview/layer_view_type", type_id);
updateLegend(type_id);
}
onModelChanged: {
updateLegend(UM.Preferences.getValue("layerview/layer_view_type"));
onActivated:
{
UM.Preferences.setValue("layerview/layer_view_type", index);
}
// Update visibility of legend.
function updateLegend(type_id) {
if (UM.LayerView.compatibilityMode || (type_id == 1)) {
// Line type
view_settings.show_legend = true;
} else {
view_settings.show_legend = false;
}
Component.onCompleted:
{
currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
updateLegends(currentIndex);
}
function updateLegends(type_id)
{
// update visibility of legends
view_settings.show_legend = UM.LayerView.compatibilityMode || (type_id == 1);
}
}
Label
@ -149,7 +150,8 @@ Item
target: UM.Preferences
onPreferenceChanged:
{
layerTypeCombobox.layer_view_type = UM.Preferences.getValue("layerview/layer_view_type");
layerTypeCombobox.currentIndex = UM.LayerView.compatibilityMode ? 1 : UM.Preferences.getValue("layerview/layer_view_type");
layerTypeCombobox.updateLegends(layerTypeCombobox.currentIndex);
view_settings.extruder_opacities = UM.Preferences.getValue("layerview/extruder_opacities").split("|");
view_settings.show_travel_moves = UM.Preferences.getValue("layerview/show_travel_moves");
view_settings.show_helpers = UM.Preferences.getValue("layerview/show_helpers");
@ -161,106 +163,88 @@ Item
}
Repeater {
model: UM.LayerView.extruderCount
model: Cura.ExtrudersModel{}
CheckBox {
checked: view_settings.extruder_opacities[index] > 0.5 || view_settings.extruder_opacities[index] == undefined || view_settings.extruder_opacities[index] == ""
onClicked: {
view_settings.extruder_opacities[index] = checked ? 1.0 : 0.0
UM.Preferences.setValue("layerview/extruder_opacities", view_settings.extruder_opacities.join("|"));
}
text: catalog.i18nc("@label", "Extruder %1").arg(index + 1)
text: model.name
visible: !UM.LayerView.compatibilityMode
enabled: index + 1 <= 4
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: model.color
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: !view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
}
}
CheckBox {
checked: view_settings.show_travel_moves
onClicked: {
UM.Preferences.setValue("layerview/show_travel_moves", checked);
Repeater {
model: ListModel {
id: typesLegenModel
Component.onCompleted:
{
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Travels"),
initialValue: view_settings.show_travel_moves,
preference: "layerview/show_travel_moves",
colorId: "layerview_move_combing"
});
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Helpers"),
initialValue: view_settings.show_helpers,
preference: "layerview/show_helpers",
colorId: "layerview_support"
});
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Shell"),
initialValue: view_settings.show_skin,
preference: "layerview/show_skin",
colorId: "layerview_inset_0"
});
typesLegenModel.append({
label: catalog.i18nc("@label", "Show Infill"),
initialValue: view_settings.show_infill,
preference: "layerview/show_infill",
colorId: "layerview_infill"
});
}
}
text: catalog.i18nc("@label", "Show Travels")
Rectangle {
anchors.top: parent.top
anchors.topMargin: 2
anchors.right: parent.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor("layerview_move_combing")
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
CheckBox {
checked: model.initialValue
onClicked: {
UM.Preferences.setValue(model.preference, checked);
}
text: label
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
style: UM.Theme.styles.checkbox
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
}
CheckBox {
checked: view_settings.show_helpers
onClicked: {
UM.Preferences.setValue("layerview/show_helpers", checked);
}
text: catalog.i18nc("@label", "Show Helpers")
Rectangle {
anchors.top: parent.top
anchors.topMargin: 2
anchors.right: parent.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor("layerview_support")
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
}
CheckBox {
checked: view_settings.show_skin
onClicked: {
UM.Preferences.setValue("layerview/show_skin", checked);
}
text: catalog.i18nc("@label", "Show Shell")
Rectangle {
anchors.top: parent.top
anchors.topMargin: 2
anchors.right: parent.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor("layerview_inset_0")
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
}
CheckBox {
checked: view_settings.show_infill
onClicked: {
UM.Preferences.setValue("layerview/show_infill", checked);
}
text: catalog.i18nc("@label", "Show Infill")
Rectangle {
anchors.top: parent.top
anchors.topMargin: 2
anchors.right: parent.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor("layerview_infill")
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
}
CheckBox {
checked: view_settings.only_show_top_layers
onClicked: {
@ -268,6 +252,7 @@ Item
}
text: catalog.i18nc("@label", "Only Show Top Layers")
visible: UM.LayerView.compatibilityMode
style: UM.Theme.styles.checkbox
}
CheckBox {
checked: view_settings.top_layer_count == 5
@ -276,51 +261,44 @@ Item
}
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
visible: UM.LayerView.compatibilityMode
style: UM.Theme.styles.checkbox
}
Label
{
id: topBottomLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Top / Bottom")
Rectangle {
anchors.top: parent.top
anchors.topMargin: 2
anchors.right: parent.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor("layerview_skin")
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
Repeater {
model: ListModel {
id: typesLegenModelNoCheck
Component.onCompleted:
{
typesLegenModelNoCheck.append({
label: catalog.i18nc("@label", "Top / Bottom"),
colorId: "layerview_skin",
});
typesLegenModelNoCheck.append({
label: catalog.i18nc("@label", "Inner Wall"),
colorId: "layerview_inset_x",
});
}
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
visible: view_settings.show_legend
}
Label
{
id: innerWallLabel
anchors.left: parent.left
text: catalog.i18nc("@label","Inner Wall")
Rectangle {
anchors.top: parent.top
anchors.topMargin: 2
anchors.right: parent.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor("layerview_inset_x")
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
Label {
text: label
visible: view_settings.show_legend
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
width: UM.Theme.getSize("layerview_legend_size").width
height: UM.Theme.getSize("layerview_legend_size").height
color: UM.Theme.getColor(model.colorId)
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
visible: view_settings.show_legend
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
color: UM.Theme.getColor("text")
}
Layout.fillWidth: true
Layout.preferredHeight: UM.Theme.getSize("layerview_row").height
Layout.preferredWidth: UM.Theme.getSize("layerview_row").width
visible: view_settings.show_legend
}
}
Item

252
plugins/MachineSettingsAction/MachineSettingsAction.py Normal file → Executable file
View File

@ -1,4 +1,4 @@
# Copyright (c) 2016 Ultimaker B.V.
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, pyqtSignal
@ -7,12 +7,15 @@ from UM.FlameProfiler import pyqtSlot
from cura.MachineAction import MachineAction
from UM.Application import Application
from UM.Preferences import Preferences
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Logger import Logger
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager
import UM.i18n
catalog = UM.i18n.i18nCatalog("cura")
@ -25,36 +28,81 @@ class MachineSettingsAction(MachineAction):
super().__init__("MachineSettingsAction", catalog.i18nc("@action", "Machine Settings"))
self._qml_url = "MachineSettingsAction.qml"
self._global_container_stack = None
self._container_index = 0
self._extruder_container_index = 0
self._container_registry = ContainerRegistry.getInstance()
self._container_registry.containerAdded.connect(self._onContainerAdded)
self._container_registry.containerRemoved.connect(self._onContainerRemoved)
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
self._backend = Application.getInstance().getBackend()
def _onContainerAdded(self, container):
# Add this action as a supported action to all machine definitions
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
def _onContainerRemoved(self, container):
# Remove definition_changes containers when a stack is removed
if container.getMetaDataEntry("type") in ["machine", "extruder_train"]:
definition_changes_container = container.findContainer({"type": "definition_changes"})
if not definition_changes_container:
return
self._container_registry.removeContainer(definition_changes_container.getId())
def _reset(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack:
if not self._global_container_stack:
return
# Make sure there is a definition_changes container to store the machine settings
definition_changes_container = global_container_stack.findContainer({"type": "definition_changes"})
definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"})
if not definition_changes_container:
definition_changes_container = self._createDefinitionChangesContainer(global_container_stack)
definition_changes_container = self._createDefinitionChangesContainer(self._global_container_stack, self._global_container_stack.getName() + "_settings")
# Notify the UI in which container to store the machine settings data
container_index = global_container_stack.getContainerIndex(definition_changes_container)
container_index = self._global_container_stack.getContainerIndex(definition_changes_container)
if container_index != self._container_index:
self._container_index = container_index
self.containerIndexChanged.emit()
def _createDefinitionChangesContainer(self, global_container_stack, container_index = None):
definition_changes_container = InstanceContainer(global_container_stack.getName() + "_settings")
definition = global_container_stack.getBottom()
# Disable autoslicing while the machineaction is showing
self._backend.disableTimer()
@pyqtSlot()
def onFinishAction(self):
# Restore autoslicing when the machineaction is dismissed
if self._backend.determineAutoSlicing():
self._backend.tickle()
def _onActiveExtruderStackChanged(self):
extruder_container_stack = ExtruderManager.getInstance().getActiveExtruderStack()
if not self._global_container_stack or not extruder_container_stack:
return
# Make sure there is a definition_changes container to store the machine settings
definition_changes_container = extruder_container_stack.findContainer({"type": "definition_changes"})
if not definition_changes_container:
definition_changes_container = self._createDefinitionChangesContainer(extruder_container_stack, extruder_container_stack.getId() + "_settings")
# Notify the UI in which container to store the machine settings data
container_index = extruder_container_stack.getContainerIndex(definition_changes_container)
if container_index != self._extruder_container_index:
self._extruder_container_index = container_index
self.extruderContainerIndexChanged.emit()
def _createDefinitionChangesContainer(self, container_stack, container_name, container_index = None):
definition_changes_container = InstanceContainer(container_name)
definition = container_stack.getBottom()
definition_changes_container.setDefinition(definition)
definition_changes_container.addMetaDataEntry("type", "definition_changes")
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
self._container_registry.addContainer(definition_changes_container)
# Insert definition_changes between the definition and the variant
global_container_stack.insertContainer(-1, definition_changes_container)
container_stack.definitionChanges = definition_changes_container
return definition_changes_container
@ -64,15 +112,129 @@ class MachineSettingsAction(MachineAction):
def containerIndex(self):
return self._container_index
def _onContainerAdded(self, container):
# Add this action as a supported action to all machine definitions
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine":
if container.getProperty("machine_extruder_count", "value") > 1:
# Multiextruder printers are not currently supported
Logger.log("d", "Not attaching MachineSettingsAction to %s; Multi-extrusion printers are not supported", container.getId())
return
extruderContainerIndexChanged = pyqtSignal()
@pyqtProperty(int, notify = extruderContainerIndexChanged)
def extruderContainerIndex(self):
return self._extruder_container_index
def _onGlobalContainerChanged(self):
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
# This additional emit is needed because we cannot connect a UM.Signal directly to a pyqtSignal
self.globalContainerChanged.emit()
globalContainerChanged = pyqtSignal()
@pyqtProperty(int, notify = globalContainerChanged)
def definedExtruderCount(self):
if not self._global_container_stack:
return 0
return len(self._global_container_stack.getMetaDataEntry("machine_extruder_trains"))
@pyqtSlot(int)
def setMachineExtruderCount(self, extruder_count):
machine_manager = Application.getInstance().getMachineManager()
extruder_manager = ExtruderManager.getInstance()
definition_changes_container = self._global_container_stack.findContainer({"type": "definition_changes"})
if not self._global_container_stack or not definition_changes_container:
return
previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
if extruder_count == previous_extruder_count:
return
extruder_material_id = None
extruder_variant_id = None
if extruder_count == 1:
# Get the material and variant of the first extruder before setting the number extruders to 1
if machine_manager.hasMaterials:
extruder_material_id = machine_manager.allActiveMaterialIds[extruder_manager.extruderIds["0"]]
if machine_manager.hasVariants:
extruder_variant_id = machine_manager.allActiveVariantIds[extruder_manager.extruderIds["0"]]
# Copy any settable_per_extruder setting value from the extruders to the global stack
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
extruder_stacks.reverse() # make sure the first extruder is done last, so its settings override any higher extruder settings
global_user_container = self._global_container_stack.getTop()
for extruder_stack in extruder_stacks:
extruder_index = extruder_stack.getMetaDataEntry("position")
extruder_user_container = extruder_stack.getTop()
for setting_instance in extruder_user_container.findInstances():
setting_key = setting_instance.definition.key
settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
if settable_per_extruder:
limit_to_extruder = self._global_container_stack.getProperty(setting_key, "limit_to_extruder")
if limit_to_extruder == "-1" or limit_to_extruder == extruder_index:
global_user_container.setProperty(setting_key, "value", extruder_user_container.getProperty(setting_key, "value"))
extruder_user_container.removeInstance(setting_key)
# Check to see if any features are set to print with an extruder that will no longer exist
for setting_key in ["adhesion_extruder_nr", "support_extruder_nr", "support_extruder_nr_layer_0", "support_infill_extruder_nr", "support_interface_extruder_nr"]:
if int(self._global_container_stack.getProperty(setting_key, "value")) > extruder_count - 1:
Logger.log("i", "Lowering %s setting to match number of extruders", setting_key)
self._global_container_stack.getTop().setProperty(setting_key, "value", extruder_count - 1)
# Check to see if any objects are set to print with an extruder that will no longer exist
root_node = Application.getInstance().getController().getScene().getRoot()
for node in DepthFirstIterator(root_node):
if node.getMeshData():
extruder_nr = node.callDecoration("getActiveExtruderPosition")
if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId())
definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
self.forceUpdate()
if extruder_count > 1:
# Multiextrusion
# Make sure one of the extruder stacks is active
if extruder_manager.activeExtruderIndex == -1:
extruder_manager.setActiveExtruderIndex(0)
# Move settable_per_extruder values out of the global container
if previous_extruder_count == 1:
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
global_user_container = self._global_container_stack.getTop()
for setting_instance in global_user_container.findInstances():
setting_key = setting_instance.definition.key
settable_per_extruder = self._global_container_stack.getProperty(setting_key, "settable_per_extruder")
if settable_per_extruder:
limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
extruder_stack = extruder_stacks[max(0, limit_to_extruder)]
extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
global_user_container.removeInstance(setting_key)
else:
# Single extrusion
# Make sure the machine stack is active
if extruder_manager.activeExtruderIndex > -1:
extruder_manager.setActiveExtruderIndex(-1)
# Restore material and variant on global stack
# MachineManager._onGlobalContainerChanged removes the global material and variant of multiextruder machines
if extruder_material_id or extruder_variant_id:
# Prevent the DiscardOrKeepProfileChangesDialog from popping up (twice) if there are user changes
# The dialog is not relevant here, since we're restoring the previous situation as good as possible
preferences = Preferences.getInstance()
choice_on_profile_override = preferences.getValue("cura/choice_on_profile_override")
preferences.setValue("cura/choice_on_profile_override", "always_keep")
if extruder_material_id:
machine_manager.setActiveMaterial(extruder_material_id)
if extruder_variant_id:
machine_manager.setActiveVariant(extruder_variant_id)
preferences.setValue("cura/choice_on_profile_override", choice_on_profile_override)
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
@pyqtSlot()
def forceUpdate(self):
@ -83,34 +245,34 @@ class MachineSettingsAction(MachineAction):
@pyqtSlot()
def updateHasMaterialsMetadata(self):
# Updates the has_materials metadata flag after switching gcode flavor
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
definition = global_container_stack.getBottom()
if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False):
has_materials = global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
if not self._global_container_stack:
return
material_container = global_container_stack.findContainer({"type": "material"})
material_index = global_container_stack.getContainerIndex(material_container)
definition = self._global_container_stack.getBottom()
if definition.getProperty("machine_gcode_flavor", "value") == "UltiGCode" and not definition.getMetaDataEntry("has_materials", False):
has_materials = self._global_container_stack.getProperty("machine_gcode_flavor", "value") != "UltiGCode"
if has_materials:
if "has_materials" in global_container_stack.getMetaData():
global_container_stack.setMetaDataEntry("has_materials", True)
else:
global_container_stack.addMetaDataEntry("has_materials", True)
material_container = self._global_container_stack.material
material_index = self._global_container_stack.getContainerIndex(material_container)
# Set the material container to a sane default
if material_container.getId() == "empty_material":
search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*" }
containers = self._container_registry.findInstanceContainers(**search_criteria)
if containers:
global_container_stack.replaceContainer(material_index, containers[0])
if has_materials:
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.setMetaDataEntry("has_materials", True)
else:
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
if "has_materials" in global_container_stack.getMetaData():
global_container_stack.removeMetaDataEntry("has_materials")
self._global_container_stack.addMetaDataEntry("has_materials", True)
empty_material = self._container_registry.findInstanceContainers(id = "empty_material")[0]
global_container_stack.replaceContainer(material_index, empty_material)
# Set the material container to a sane default
if material_container.getId() == "empty_material":
search_criteria = { "type": "material", "definition": "fdmprinter", "id": "*pla*"}
containers = self._container_registry.findInstanceContainers(**search_criteria)
if containers:
self._global_container_stack.replaceContainer(material_index, containers[0])
else:
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
if "has_materials" in self._global_container_stack.getMetaData():
self._global_container_stack.removeMetaDataEntry("has_materials")
Application.getInstance().globalContainerStackChanged.emit()
self._global_container_stack.material = ContainerRegistry.getInstance().getEmptyInstanceContainer()
Application.getInstance().globalContainerStackChanged.emit()

File diff suppressed because it is too large Load Diff

View File

@ -63,17 +63,20 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
stack_nr = -1
stack = None
# Check from what stack we should copy the raw property of the setting from.
if definition.limit_to_extruder != "-1" and self._stack.getProperty("machine_extruder_count", "value") > 1:
# A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use.
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder")))))
if self._stack.getProperty("machine_extruder_count", "value") > 1:
if definition.limit_to_extruder != "-1":
# A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use.
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder")))))
# Check if the found stack_number is in the extruder list of extruders.
if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value") is not None:
stack_nr = -1
# Check if the found stack_number is in the extruder list of extruders.
if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value") is not None:
stack_nr = -1
# Use the found stack number to get the right stack to copy the value from.
if stack_nr in ExtruderManager.getInstance().extruderIds:
stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
# Use the found stack number to get the right stack to copy the value from.
if stack_nr in ExtruderManager.getInstance().extruderIds:
stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
else:
stack = self._stack
# Use the raw property to set the value (so the inheritance doesn't break)
if stack is not None:

View File

@ -26,129 +26,6 @@ Item {
spacing: UM.Theme.getSize("default_margin").height
Row
{
spacing: UM.Theme.getSize("default_margin").width
Label
{
text: catalog.i18nc("@label Followed by extruder selection drop-down.", "Print model with")
anchors.verticalCenter: extruderSelector.verticalCenter
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
visible: extruderSelector.visible
}
ComboBox
{
id: extruderSelector
model: Cura.ExtrudersModel
{
id: extrudersModel
onModelChanged: extruderSelector.color = extrudersModel.getItem(extruderSelector.currentIndex).color
}
property string color: extrudersModel.getItem(extruderSelector.currentIndex).color
visible: machineExtruderCount.properties.value > 1
textRole: "name"
width: UM.Theme.getSize("setting_control").width
height: UM.Theme.getSize("section").height
MouseArea
{
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: wheel.accepted = true;
}
style: ComboBoxStyle
{
background: Rectangle
{
color:
{
if(extruderSelector.hovered || base.activeFocus)
{
return UM.Theme.getColor("setting_control_highlight");
}
else
{
return UM.Theme.getColor("setting_control");
}
}
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("setting_control_border")
}
label: Item
{
Rectangle
{
id: swatch
height: UM.Theme.getSize("setting_control").height / 2
width: height
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_lining").width
anchors.verticalCenter: parent.verticalCenter
color: extruderSelector.color
border.width: UM.Theme.getSize("default_lining").width
border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : UM.Theme.getColor("setting_control_border")
}
Label
{
anchors.left: swatch.right
anchors.leftMargin: UM.Theme.getSize("default_lining").width
anchors.right: downArrow.left
anchors.rightMargin: UM.Theme.getSize("default_lining").width
anchors.verticalCenter: parent.verticalCenter
text: extruderSelector.currentText
font: UM.Theme.getFont("default")
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
UM.RecolorImage
{
id: downArrow
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_lining").width * 2
anchors.verticalCenter: parent.verticalCenter
source: UM.Theme.getIcon("arrow_bottom")
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width + 5
sourceSize.height: width + 5
color: UM.Theme.getColor("setting_control_text")
}
}
}
onActivated:
{
UM.ActiveTool.setProperty("SelectedActiveExtruder", extrudersModel.getItem(index).id);
extruderSelector.color = extrudersModel.getItem(index).color;
}
onModelChanged: updateCurrentIndex();
function updateCurrentIndex()
{
for(var i = 0; i < extrudersModel.rowCount(); ++i)
{
if(extrudersModel.getItem(i).id == UM.ActiveTool.properties.getValue("SelectedActiveExtruder"))
{
extruderSelector.currentIndex = i;
extruderSelector.color = extrudersModel.getItem(i).color;
return;
}
}
extruderSelector.currentIndex = -1;
}
}
}
Column
{
// This is to ensure that the panel is first increasing in size up to 200 and then shows a scrollbar.
@ -264,14 +141,6 @@ Item {
storeIndex: 0
removeUnusedValue: false
}
// If the extruder by which the object needs to be printed is changed, ensure that the
// display is also notified of the fact.
Connections
{
target: extruderSelector
onActivated: provider.forcePropertiesChanged()
}
}
}
}

View File

@ -112,4 +112,4 @@ class PerObjectSettingsTool(Tool):
self._single_model_selected = False # Group is selected, so tool needs to be disabled
else:
self._single_model_selected = True
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, (self._advanced_mode or self._multi_extrusion) and self._single_model_selected)
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected)

View File

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

View File

@ -200,7 +200,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
def _onAuthenticationRequired(self, reply, authenticator):
if self._authentication_id is not None and self._authentication_key is not None:
Logger.log("d", "Authentication was required. Setting up authenticator with ID %s and key", self._authentication_id, self._getSafeAuthKey())
Logger.log("d", "Authentication was required. Setting up authenticator with ID %s and key %s", self._authentication_id, self._getSafeAuthKey())
authenticator.setUser(self._authentication_id)
authenticator.setPassword(self._authentication_key)
else:
@ -283,10 +283,8 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
#
# /param temperature The new target temperature of the bed.
def _setTargetBedTemperature(self, temperature):
if self._target_bed_temperature == temperature:
if not self._updateTargetBedTemperature(temperature):
return
self._target_bed_temperature = temperature
self.targetBedTemperatureChanged.emit()
url = QUrl("http://" + self._address + self._api_prefix + "printer/bed/temperature/target")
data = str(temperature)
@ -294,6 +292,17 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
self._manager.put(put_request, data.encode())
## Updates the target bed temperature from the printer, and emit a signal if it was changed.
#
# /param temperature The new target temperature of the bed.
# /return boolean, True if the temperature was changed, false if the new temperature has the same value as the already stored temperature
def _updateTargetBedTemperature(self, temperature):
if self._target_bed_temperature == temperature:
return False
self._target_bed_temperature = temperature
self.targetBedTemperatureChanged.emit()
return True
def _stopCamera(self):
self._camera_timer.stop()
if self._image_reply:
@ -528,7 +537,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
bed_temperature = self._json_printer_state["bed"]["temperature"]["current"]
self._setBedTemperature(bed_temperature)
target_bed_temperature = self._json_printer_state["bed"]["temperature"]["target"]
self._setTargetBedTemperature(target_bed_temperature)
self._updateTargetBedTemperature(target_bed_temperature)
head_x = self._json_printer_state["heads"][0]["position"]["x"]
head_y = self._json_printer_state["heads"][0]["position"]["y"]
@ -629,7 +638,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
if self._json_printer_state["heads"][0]["extruders"][index]["hotend"]["id"] == "":
Logger.log("e", "No cartridge loaded in slot %s, unable to start print", index + 1)
self._error_message = Message(
i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No PrinterCore loaded in slot {0}".format(index + 1)))
i18n_catalog.i18nc("@info:status", "Unable to start a new print job. No Printcore loaded in slot {0}".format(index + 1)))
self._error_message.show()
return
if self._json_printer_state["heads"][0]["extruders"][index]["active_material"]["guid"] == "":
@ -1089,7 +1098,7 @@ class NetworkPrinterOutputDevice(PrinterOutputDevice):
self._authentication_key = data["key"]
self._authentication_id = data["id"]
Logger.log("i", "Got a new authentication ID (%s) and KEY (%S). Waiting for authorization.", self._authentication_id, self._getSafeAuthKey())
Logger.log("i", "Got a new authentication ID (%s) and KEY (%s). Waiting for authorization.", self._authentication_id, self._getSafeAuthKey())
# Check if the authentication is accepted.
self._checkAuthentication()

View File

@ -164,8 +164,8 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
else:
if self._printers[key].isConnected():
Logger.log("d", "Closing connection [%s]..." % key)
self._printers[key].connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
self._printers[key].close()
self._printers[key].connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
def addPrinter(self, name, address, properties):
@ -183,9 +183,9 @@ class NetworkPrinterOutputDevicePlugin(OutputDevicePlugin):
printer = self._printers.pop(name, None)
if printer:
if printer.isConnected():
printer.disconnect()
printer.connectionStateChanged.disconnect(self._onPrinterConnectionStateChanged)
Logger.log("d", "removePrinter, disconnecting [%s]..." % name)
printer.disconnect()
self.printerListChanged.emit()
## Handler for when the connection state of one of the detected printers changes

View File

@ -148,6 +148,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
## Start a print based on a g-code.
# \param gcode_list List with gcode (strings).
def printGCode(self, gcode_list):
Logger.log("d", "Started printing g-code")
if self._progress or self._connection_state != ConnectionState.connected:
self._error_message = Message(catalog.i18nc("@info:status", "Unable to start a new job because the printer is busy or not connected."))
self._error_message.show()
@ -183,6 +184,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
## Private function (threaded) that actually uploads the firmware.
def _updateFirmware(self):
Logger.log("d", "Attempting to update firmware")
self._error_code = 0
self.setProgress(0, 100)
self._firmware_update_finished = False
@ -536,6 +538,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._sendNextGcodeLine()
elif b"resend" in line.lower() or b"rs" in line: # Because a resend can be asked with "resend" and "rs"
try:
Logger.log("d", "Got a resend response")
self._gcode_position = int(line.replace(b"N:",b" ").replace(b"N",b" ").replace(b":",b" ").split()[-1])
except:
if b"rs" in line:
@ -563,19 +566,19 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
line = line[:line.find(";")]
line = line.strip()
# Don't send empty lines. But we do have to send something, so send m105 instead.
if line == "":
# Don't send empty lines. But we do have to send something, so send
# m105 instead.
# Don't send the M0 or M1 to the machine, as M0 and M1 are handled as
# an LCD menu pause.
if line == "" or line == "M0" or line == "M1":
line = "M105"
try:
if line == "M0" or line == "M1":
line = "M105" # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
if ("G0" in line or "G1" in line) and "Z" in line:
z = float(re.search("Z([0-9\.]*)", line).group(1))
if self._current_z != z:
self._current_z = z
except Exception as e:
Logger.log("e", "Unexpected error with printer connection: %s" % e)
Logger.log("e", "Unexpected error with printer connection, could not parse current Z: %s: %s" % (e, line))
self._setErrorState("Unexpected error: %s" %e)
checksum = functools.reduce(lambda x,y: x^y, map(ord, "N%d%s" % (self._gcode_position, line)))
@ -674,4 +677,4 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
def cancelPreheatBed(self):
Logger.log("i", "Cancelling pre-heating of the bed.")
self._setTargetBedTemperature(0)
self.preheatBedRemainingTimeChanged.emit()
self.preheatBedRemainingTimeChanged.emit()

View File

@ -259,7 +259,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension):
i = 0
while True:
values = winreg.EnumValue(key, i)
if not only_list_usb or "USBSER" in values[0]:
if not only_list_usb or "USBSER" or "VCP" in values[0]:
base_list += [values[1]]
i += 1
except Exception as e:

View File

@ -0,0 +1,65 @@
# Copyright (c) 2017 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.InstanceContainer import InstanceContainer
from cura.MachineAction import MachineAction
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
from UM.i18n import i18nCatalog
from UM.Application import Application
from UM.Util import parseBool
catalog = i18nCatalog("cura")
import UM.Settings.InstanceContainer
## The Ultimaker 2 can have a few revisions & upgrades.
class UM2UpgradeSelection(MachineAction):
def __init__(self):
super().__init__("UM2UpgradeSelection", catalog.i18nc("@action", "Select upgrades"))
self._qml_url = "UM2UpgradeSelectionMachineAction.qml"
self._container_registry = ContainerRegistry.getInstance()
def _reset(self):
self.hasVariantsChanged.emit()
hasVariantsChanged = pyqtSignal()
@pyqtProperty(bool, notify = hasVariantsChanged)
def hasVariants(self):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
return parseBool(global_container_stack.getMetaDataEntry("has_variants", "false"))
@pyqtSlot(bool)
def setHasVariants(self, has_variants = True):
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
variant_container = global_container_stack.variant
variant_index = global_container_stack.getContainerIndex(variant_container)
if has_variants:
if "has_variants" in global_container_stack.getMetaData():
global_container_stack.setMetaDataEntry("has_variants", True)
else:
global_container_stack.addMetaDataEntry("has_variants", True)
# Set the variant container to a sane default
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
if type(variant_container) == type(empty_container):
search_criteria = { "type": "variant", "definition": "ultimaker2", "id": "*0.4*" }
containers = self._container_registry.findInstanceContainers(**search_criteria)
if containers:
global_container_stack.variant = containers[0]
else:
# The metadata entry is stored in an ini, and ini files are parsed as strings only.
# Because any non-empty string evaluates to a boolean True, we have to remove the entry to make it False.
if "has_variants" in global_container_stack.getMetaData():
global_container_stack.removeMetaDataEntry("has_variants")
# Set the variant container to an empty variant
global_container_stack.variant = ContainerRegistry.getInstance().getEmptyInstanceContainer()
Application.getInstance().globalContainerStackChanged.emit()

View File

@ -0,0 +1,52 @@
// Copyright (c) 2016 Ultimaker B.V.
// Cura is released under the terms of the AGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
import UM 1.2 as UM
import Cura 1.0 as Cura
Cura.MachineAction
{
anchors.fill: parent;
Item
{
id: upgradeSelectionMachineAction
anchors.fill: parent
Label
{
id: pageTitle
width: parent.width
text: catalog.i18nc("@title", "Select Printer Upgrades")
wrapMode: Text.WordWrap
font.pointSize: 18;
}
Label
{
id: pageDescription
anchors.top: pageTitle.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
width: parent.width
wrapMode: Text.WordWrap
text: catalog.i18nc("@label","Please select any upgrades made to this Ultimaker 2.");
}
CheckBox
{
anchors.top: pageDescription.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
text: catalog.i18nc("@label", "Olsson Block")
checked: manager.hasVariants
onClicked: manager.setHasVariants(checked)
}
UM.I18nCatalog { id: catalog; name: "cura"; }
}
}

View File

@ -11,7 +11,7 @@ from UM.Application import Application
catalog = i18nCatalog("cura")
import UM.Settings.InstanceContainer
from cura.CuraApplication import CuraApplication
## The Ultimaker Original can have a few revisions & upgrades. This action helps with selecting them, so they are added
# as a variant.
@ -49,6 +49,7 @@ class UMOUpgradeSelection(MachineAction):
definition = global_container_stack.getBottom()
definition_changes_container.setDefinition(definition)
definition_changes_container.addMetaDataEntry("type", "definition_changes")
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().addContainer(definition_changes_container)
# Insert definition_changes between the definition and the variant

View File

@ -5,6 +5,7 @@ from . import BedLevelMachineAction
from . import UpgradeFirmwareMachineAction
from . import UMOCheckupMachineAction
from . import UMOUpgradeSelection
from . import UM2UpgradeSelection
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -21,4 +22,10 @@ def getMetaData():
}
def register(app):
return { "machine_action": [BedLevelMachineAction.BedLevelMachineAction(), UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(), UMOCheckupMachineAction.UMOCheckupMachineAction(), UMOUpgradeSelection.UMOUpgradeSelection()]}
return { "machine_action": [
BedLevelMachineAction.BedLevelMachineAction(),
UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(),
UMOCheckupMachineAction.UMOCheckupMachineAction(),
UMOUpgradeSelection.UMOUpgradeSelection(),
UM2UpgradeSelection.UM2UpgradeSelection()
]}

View File

@ -1,4 +1,4 @@
# Copyright (c) 2016 Ultimaker B.V.
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import configparser #To get version numbers from config files.
@ -70,8 +70,8 @@ _printer_translations_profiles = {
# as a set for which profiles were built-in.
_profile_translations = {
"Low Quality": "low",
"Normal Quality": "normal",
"High Quality": "high",
"Fine": "normal",
"Extra Fine": "high",
"Ulti Quality": "high", #This one doesn't have an equivalent. Map it to high.
"abs_0.25_normal": "um2p_abs_0.25_normal",
"abs_0.4_fast": "um2p_abs_0.4_fast",
@ -249,7 +249,9 @@ class VersionUpgrade21to22(VersionUpgrade):
def getCfgVersion(self, serialised):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
return int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
return format_version * 1000000 + setting_version
## Gets the fallback quality to use for a specific machine-variant-material
# combination.

View File

@ -18,10 +18,10 @@ def getMetaData():
"api": 3
},
"version_upgrade": {
# From To Upgrade function
("profile", 1): ("quality", 2, upgrade.upgradeProfile),
("machine_instance", 1): ("machine_stack", 2, upgrade.upgradeMachineInstance),
("preferences", 2): ("preferences", 3, upgrade.upgradePreferences)
# From To Upgrade function
("profile", 1000000): ("quality", 2000000, upgrade.upgradeProfile),
("machine_instance", 1000000): ("machine_stack", 2000000, upgrade.upgradeMachineInstance),
("preferences", 2000000): ("preferences", 3000000, upgrade.upgradePreferences)
},
"sources": {
"profile": {

View File

@ -1,4 +1,4 @@
# Copyright (c) 2016 Ultimaker B.V.
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
import configparser #To get version numbers from config files.
@ -77,6 +77,7 @@ class VersionUpgrade22to24(VersionUpgrade):
with open(variant_path, "r") as fhandle:
variant_config.read_file(fhandle)
config_name = "Unknown Variant"
if variant_config.has_section("general") and variant_config.has_option("general", "name"):
config_name = variant_config.get("general", "name")
if config_name.endswith("_variant"):
@ -141,7 +142,19 @@ class VersionUpgrade22to24(VersionUpgrade):
config.write(output)
return [filename], [output.getvalue()]
def upgradeQuality(self, serialised, filename):
config = configparser.ConfigParser(interpolation = None)
config.read_string(serialised) # Read the input string as config file.
config.set("metadata", "type", "quality_changes") # Update metadata/type to quality_changes
config.set("general", "version", "2") # Just bump the version number. That is all we need for now.
output = io.StringIO()
config.write(output)
return [filename], [output.getvalue()]
def getCfgVersion(self, serialised):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
return int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
return format_version * 1000000 + setting_version

View File

@ -18,12 +18,12 @@ def getMetaData():
"api": 3
},
"version_upgrade": {
# From To Upgrade function
("machine_instance", 2): ("machine_stack", 3, upgrade.upgradeMachineInstance),
("extruder_train", 2): ("extruder_train", 3, upgrade.upgradeExtruderTrain),
("preferences", 3): ("preferences", 4, upgrade.upgradePreferences)
},
# From To Upgrade function
("machine_instance", 2000000): ("machine_stack", 3000000, upgrade.upgradeMachineInstance),
("extruder_train", 2000000): ("extruder_train", 3000000, upgrade.upgradeExtruderTrain),
("preferences", 3000000): ("preferences", 4000000, upgrade.upgradePreferences),
("quality", 2000000): ("quality_changes", 2000000, upgrade.upgradeQuality),
},
"sources": {
"machine_stack": {
"get_version": upgrade.getCfgVersion,

View File

@ -1,45 +0,0 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from . import VersionUpgrade24to25
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
upgrade = VersionUpgrade24to25.VersionUpgrade24to25()
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Version Upgrade 2.4 to 2.5"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.4 to Cura 2.5."),
"api": 3
},
"version_upgrade": {
# From To Upgrade function
("preferences", 4): ("preferences", 5, upgrade.upgradePreferences),
("quality", 2): ("quality", 3, upgrade.upgradeInstanceContainer),
("variant", 2): ("variant", 3, upgrade.upgradeInstanceContainer), #We can re-use upgradeContainerStack since there is nothing specific to quality, variant or user profiles being changed.
("user", 2): ("user", 3, upgrade.upgradeInstanceContainer)
},
"sources": {
"quality": {
"get_version": upgrade.getCfgVersion,
"location": {"./quality"}
},
"preferences": {
"get_version": upgrade.getCfgVersion,
"location": {"."}
},
"user": {
"get_version": upgrade.getCfgVersion,
"location": {"./user"}
}
}
}
def register(app):
return {}
return { "version_upgrade": upgrade }

View File

@ -5,19 +5,25 @@ import configparser #To parse the files we need to upgrade and write the new fil
import io #To serialise configparser output to a string.
from UM.VersionUpgrade import VersionUpgrade
from cura.CuraApplication import CuraApplication
_removed_settings = { #Settings that were removed in 2.5.
"start_layers_at_same_position"
"start_layers_at_same_position",
"sub_div_rad_mult"
}
_split_settings = { #These settings should be copied to all settings it was split into.
"support_interface_line_distance": {"support_roof_line_distance", "support_bottom_line_distance"}
}
## A collection of functions that convert the configuration of the user in Cura
# 2.4 to a configuration for Cura 2.5.
# 2.5 to a configuration for Cura 2.6.
#
# All of these methods are essentially stateless.
class VersionUpgrade24to25(VersionUpgrade):
## Gets the version number from a CFG file in Uranium's 2.4 format.
class VersionUpgrade25to26(VersionUpgrade):
## Gets the version number from a CFG file in Uranium's 2.5 format.
#
# Since the format may change, this is implemented for the 2.4 format only
# Since the format may change, this is implemented for the 2.5 format only
# and needs to be included in the version upgrade system rather than
# globally in Uranium.
#
@ -29,9 +35,11 @@ class VersionUpgrade24to25(VersionUpgrade):
def getCfgVersion(self, serialised):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialised)
return int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
return format_version * 1000000 + setting_version
## Upgrades the preferences file from version 2.4 to 2.5.
## Upgrades the preferences file from version 2.5 to 2.6.
#
# \param serialised The serialised form of a preferences file.
# \param filename The name of the file to upgrade.
@ -42,8 +50,16 @@ class VersionUpgrade24to25(VersionUpgrade):
#Remove settings from the visible_settings.
if parser.has_section("general") and "visible_settings" in parser["general"]:
visible_settings = parser["general"]["visible_settings"].split(";")
visible_settings = filter(lambda setting: setting not in _removed_settings, visible_settings)
parser["general"]["visible_settings"] = ";".join(visible_settings)
new_visible_settings = []
for setting in visible_settings:
if setting in _removed_settings:
continue #Skip.
if setting in _split_settings:
for replaced_setting in _split_settings[setting]:
new_visible_settings.append(replaced_setting)
continue #Don't add the original.
new_visible_settings.append(setting) #No special handling, so just add the original visible setting back.
parser["general"]["visible_settings"] = ";".join(new_visible_settings)
#Change the version number in the file.
if parser.has_section("general"): #It better have!
@ -54,7 +70,7 @@ class VersionUpgrade24to25(VersionUpgrade):
parser.write(output)
return [filename], [output.getvalue()]
## Upgrades an instance container from version 2.4 to 2.5.
## Upgrades an instance container from version 2.5 to 2.6.
#
# \param serialised The serialised form of a quality profile.
# \param filename The name of the file to upgrade.
@ -66,12 +82,22 @@ class VersionUpgrade24to25(VersionUpgrade):
if parser.has_section("values"):
for removed_setting in (_removed_settings & parser["values"].keys()): #Both in keys that need to be removed and in keys present in the file.
del parser["values"][removed_setting]
for replaced_setting in (_split_settings.keys() & parser["values"].keys()):
for replacement in _split_settings[replaced_setting]:
parser["values"][replacement] = parser["values"][replaced_setting] #Copy to replacement before removing the original!
del replaced_setting
#Change the version number in the file.
if parser.has_section("general"):
parser["general"]["version"] = "3"
for each_section in ("general", "metadata"):
if not parser.has_section(each_section):
parser.add_section(each_section)
# Change the version number in the file.
parser["metadata"]["setting_version"] = str(CuraApplication.SettingVersion)
# Update version
parser["general"]["version"] = "2"
#Re-serialise the file.
output = io.StringIO()
parser.write(output)
return [filename], [output.getvalue()]
return [filename], [output.getvalue()]

View File

@ -0,0 +1,46 @@
# Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the AGPLv3 or higher.
from . import VersionUpgrade25to26
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
upgrade = VersionUpgrade25to26.VersionUpgrade25to26()
def getMetaData():
return {
"plugin": {
"name": catalog.i18nc("@label", "Version Upgrade 2.5 to 2.6"),
"author": "Ultimaker",
"version": "1.0",
"description": catalog.i18nc("@info:whatsthis", "Upgrades configurations from Cura 2.5 to Cura 2.6."),
"api": 3
},
"version_upgrade": {
# From To Upgrade function
("preferences", 4000000): ("preferences", 4000001, upgrade.upgradePreferences),
# NOTE: All the instance containers share the same general/version, so we have to update all of them
# if any is updated.
("quality_changes", 2000000): ("quality_changes", 2000001, upgrade.upgradeInstanceContainer),
("user", 2000000): ("user", 2000001, upgrade.upgradeInstanceContainer),
("quality", 2000000): ("quality", 2000001, upgrade.upgradeInstanceContainer),
},
"sources": {
"quality_changes": {
"get_version": upgrade.getCfgVersion,
"location": {"./quality"}
},
"preferences": {
"get_version": upgrade.getCfgVersion,
"location": {"."}
},
"user": {
"get_version": upgrade.getCfgVersion,
"location": {"./user"}
},
}
}
def register(app):
return { "version_upgrade": upgrade }

View File

@ -4,12 +4,12 @@
import configparser #To check whether the appropriate exceptions are raised.
import pytest #To register tests with.
import VersionUpgrade24to25 #The module we're testing.
import VersionUpgrade25to26 #The module we're testing.
## Creates an instance of the upgrader to test with.
@pytest.fixture
def upgrader():
return VersionUpgrade24to25.VersionUpgrade24to25()
return VersionUpgrade25to26.VersionUpgrade25to26()
test_cfg_version_good_data = [
{
@ -17,7 +17,7 @@ test_cfg_version_good_data = [
"file_data": """[general]
version = 1
""",
"version": 1
"version": 1000000
},
{
"test_name": "Other Data Around",
@ -31,14 +31,32 @@ version = 3
layer_height = 0.12
infill_sparse_density = 42
""",
"version": 3
"version": 3000000
},
{
"test_name": "Negative Version", #Why not?
"file_data": """[general]
version = -20
""",
"version": -20
"version": -20000000
},
{
"test_name": "Setting Version",
"file_data": """[general]
version = 1
[metadata]
setting_version = 1
""",
"version": 1000001
},
{
"test_name": "Negative Setting Version",
"file_data": """[general]
version = 1
[metadata]
setting_version = -3
""",
"version": 999997
}
]
@ -77,6 +95,22 @@ true = false
"test_name": "Not a Number",
"file_data": """[general]
version = not-a-text-version-number
""",
"exception": ValueError
},
{
"test_name": "Setting Value NaN",
"file_data": """[general]
version = 4
[metadata]
setting_version = latest_or_something
""",
"exception": ValueError
},
{
"test_name": "Major-Minor",
"file_data": """[general]
version = 1.2
""",
"exception": ValueError
}
@ -121,7 +155,7 @@ foo = bar
}
]
## Tests whether the settings that should be removed are removed for the 2.5
## Tests whether the settings that should be removed are removed for the 2.6
# version of preferences.
@pytest.mark.parametrize("data", test_upgrade_preferences_removed_settings_data)
def test_upgradePreferencesRemovedSettings(data, upgrader):
@ -137,7 +171,7 @@ def test_upgradePreferencesRemovedSettings(data, upgrader):
upgraded_preferences = upgraded_preferences[0]
#Find whether the removed setting is removed from the file now.
settings -= VersionUpgrade24to25._removed_settings
settings -= VersionUpgrade25to26._removed_settings
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(upgraded_preferences)
assert (parser.has_section("general") and "visible_settings" in parser["general"]) == (len(settings) > 0) #If there are settings, there must also be a preference.
@ -166,7 +200,7 @@ type = instance_container
}
]
## Tests whether the settings that should be removed are removed for the 2.5
## Tests whether the settings that should be removed are removed for the 2.6
# version of instance containers.
@pytest.mark.parametrize("data", test_upgrade_instance_container_removed_settings_data)
def test_upgradeInstanceContainerRemovedSettings(data, upgrader):
@ -182,7 +216,7 @@ def test_upgradeInstanceContainerRemovedSettings(data, upgrader):
upgraded_container = upgraded_container[0]
#Find whether the forbidden setting is still in the container.
settings -= VersionUpgrade24to25._removed_settings
settings -= VersionUpgrade25to26._removed_settings
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(upgraded_container)
assert parser.has_section("values") == (len(settings) > 0) #If there are settings, there must also be the values category.

View File

@ -3,6 +3,7 @@
import copy
import io
from typing import Optional
import xml.etree.ElementTree as ET
from UM.Resources import Resources
@ -11,7 +12,7 @@ from UM.Util import parseBool
from cura.CuraApplication import CuraApplication
import UM.Dictionary
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.InstanceContainer import InstanceContainer, InvalidInstanceError
from UM.Settings.ContainerRegistry import ContainerRegistry
## Handles serializing and deserializing material containers from an XML file
@ -20,6 +21,20 @@ class XmlMaterialProfile(InstanceContainer):
super().__init__(container_id, *args, **kwargs)
self._inherited_files = []
## Translates the version number in the XML files to the setting_version
# metadata entry.
#
# Since the two may increment independently we need a way to say which
# versions of the XML specification are compatible with our setting data
# version numbers.
#
# \param xml_version: The version number found in an XML file.
# \return The corresponding setting_version.
def xmlVersionToSettingVersion(self, xml_version: str) -> int:
if xml_version == "1.3":
return 1
return 0 #Older than 1.3.
def getInheritedFiles(self):
return self._inherited_files
@ -118,6 +133,7 @@ class XmlMaterialProfile(InstanceContainer):
metadata.pop("variant", "")
metadata.pop("type", "")
metadata.pop("base_file", "")
metadata.pop("approximate_diameter", "")
## Begin Name Block
builder.start("name")
@ -143,10 +159,10 @@ class XmlMaterialProfile(InstanceContainer):
for key, value in metadata.items():
builder.start(key)
# Normally value is a string.
# Nones get handled well.
if isinstance(value, bool):
value = str(value) # parseBool in deserialize expects 'True'.
if value is not None: #Nones get handled well by the builder.
#Otherwise the builder always expects a string.
#Deserialize expects the stringified version.
value = str(value)
builder.data(value)
builder.end(key)
@ -369,8 +385,30 @@ class XmlMaterialProfile(InstanceContainer):
self._dirty = False
self._path = ""
def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]:
return "material"
def getVersionFromSerialized(self, serialized: str) -> Optional[int]:
version = None
data = ET.fromstring(serialized)
metadata = data.iterfind("./um:metadata/*", self.__namespaces)
for entry in metadata:
tag_name = _tag_without_namespace(entry)
if tag_name == "version":
try:
version = int(entry.text)
except Exception as e:
raise InvalidInstanceError("Invalid version string '%s': %s" % (entry.text, e))
break
if version is None:
raise InvalidInstanceError("Missing version in metadata")
return version
## Overridden from InstanceContainer
def deserialize(self, serialized):
# update the serialized data first
from UM.Settings.Interfaces import ContainerInterface
serialized = ContainerInterface.deserialize(self, serialized)
data = ET.fromstring(serialized)
# Reset previous metadata
@ -385,6 +423,10 @@ class XmlMaterialProfile(InstanceContainer):
inherited = self._resolveInheritance(inherits.text)
data = self._mergeXML(inherited, data)
if "version" in data.attrib:
meta_data["setting_version"] = self.xmlVersionToSettingVersion(data.attrib["version"])
else:
meta_data["setting_version"] = self.xmlVersionToSettingVersion("1.2") #1.2 and lower didn't have that version number there yet.
metadata = data.iterfind("./um:metadata/*", self.__namespaces)
for entry in metadata:
tag_name = _tag_without_namespace(entry)
@ -405,10 +447,10 @@ class XmlMaterialProfile(InstanceContainer):
continue
meta_data[tag_name] = entry.text
if not "description" in meta_data:
if "description" not in meta_data:
meta_data["description"] = ""
if not "adhesion_info" in meta_data:
if "adhesion_info" not in meta_data:
meta_data["adhesion_info"] = ""
property_values = {}
@ -417,8 +459,7 @@ class XmlMaterialProfile(InstanceContainer):
tag_name = _tag_without_namespace(entry)
property_values[tag_name] = entry.text
diameter = float(property_values.get("diameter", 2.85)) # In mm
density = float(property_values.get("density", 1.3)) # In g/cm3
meta_data["approximate_diameter"] = round(float(property_values.get("diameter", 2.85))) # In mm
meta_data["properties"] = property_values
self.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0])
@ -581,7 +622,8 @@ class XmlMaterialProfile(InstanceContainer):
"Ultimaker 2 Extended": "ultimaker2_extended",
"Ultimaker 2 Extended+": "ultimaker2_extended_plus",
"Ultimaker Original": "ultimaker_original",
"Ultimaker Original+": "ultimaker_original_plus"
"Ultimaker Original+": "ultimaker_original_plus",
"IMADE3D JellyBOX": "imade3d_jellybox"
}
# Map of recognised namespaces with a proper prefix.

View File

@ -0,0 +1,55 @@
{
"id": "alya3dp",
"name": "ALYA",
"version": 2,
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "ALYA",
"manufacturer": "ALYA",
"category": "Other",
"file_formats": "text/x-gcode"
},
"overrides": {
"machine_width": {
"default_value": 100
},
"machine_height": {
"default_value": 133
},
"machine_depth": {
"default_value": 100
},
"machine_center_is_zero": {
"default_value": false
},
"machine_nozzle_size": {
"default_value": 0.4
},
"machine_head_shape_min_x": {
"default_value": 75
},
"machine_head_shape_min_y": {
"default_value": 18
},
"machine_head_shape_max_x": {
"default_value": 18
},
"machine_head_shape_max_y": {
"default_value": 35
},
"machine_nozzle_gantry_distance": {
"default_value": 55
},
"machine_gcode_flavor": {
"default_value": "RepRap"
},
"machine_start_gcode": {
"default_value": ";Sliced at: {day} {date} {time}\n;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}\n;Print time: {print_time}\n;Filament used: {filament_amount}m {filament_weight}g\n;Filament cost: {filament_cost}\n;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line\n;M109 S{print_temperature} ;Uncomment to add your own temperature line\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to max endstops\nG1 Z115.0 F{travel_speed} ;move th e platform up 20mm\nG28 Z0 ;move Z to max endstop\nG1 Z15.0 F{travel_speed} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{travel_speed}\nM301 H1 P26.38 I2.57 D67.78\n;Put printing message on LCD screen\nM117 Printing..."
},
"machine_end_gcode": {
"default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG28 Z0\nM84 ;steppers off\nG90 ;absolute positioning\n;{profile_string}"
}
}
}

View File

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

View File

@ -10,6 +10,17 @@
"category": "Custom",
"file_formats": "text/x-gcode",
"has_materials": true,
"machine_extruder_trains":
{
"0": "custom_extruder_1",
"1": "custom_extruder_2",
"2": "custom_extruder_3",
"3": "custom_extruder_4",
"4": "custom_extruder_5",
"5": "custom_extruder_6",
"6": "custom_extruder_7",
"7": "custom_extruder_8"
},
"first_start_actions": ["MachineSettingsAction"]
}
}

View File

@ -14,23 +14,29 @@
},
"overrides": {
"machine_name": { "default_value": "Delta Go" },
"material_diameter": { "default_value": 1.75 },
"material_diameter": { "default_value": 1.75 },
"default_material_print_temperature": { "default_value": 210 },
"speed_travel": { "default_value": 150 },
"prime_tower_size": { "default_value": 8.66 },
"infill_sparse_density": { "default_value": 10 },
"prime_tower_size": { "default_value": 8.66 },
"infill_sparse_density": { "default_value": 10 },
"speed_wall_x": { "default_value": 30 },
"speed_wall_0": { "default_value": 30 },
"speed_topbottom": { "default_value": 20 },
"layer_height": { "default_value": 0.2 },
"layer_height": { "default_value": 0.15 },
"speed_print": { "default_value": 30 },
"machine_heated_bed": { "default_value": false },
"machine_center_is_zero": { "default_value": true },
"machine_height": { "default_value": 127 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_heated_bed": { "default_value": false },
"machine_center_is_zero": { "default_value": true },
"machine_height": { "default_value": 154 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_depth": { "default_value": 115 },
"machine_width": { "default_value": 115 },
"retraction_amount": { "default_value": 4.2 },
"retraction_speed": { "default_value": 400 },
"machine_shape": { "default_value": "elliptic"}
"raft_airgap": { "default_value": 0.15 },
"retraction_hop_enabled": { "value": "True" },
"retraction_amount": { "default_value": 4.1 },
"retraction_speed": { "default_value": 500 },
"retraction_hop": { "value": "0.2" },
"retraction_hop_only_when_collides": { "value": "True" },
"brim_width": { "value": "5" },
"machine_shape": { "default_value": "elliptic"}
}
}

View File

@ -7,6 +7,7 @@
"type": "extruder",
"author": "Ultimaker B.V.",
"manufacturer": "Ultimaker",
"setting_version": 1,
"visible": false
},
"settings":
@ -25,10 +26,22 @@
"type": "extruder",
"default_value": "0",
"settable_per_mesh": true,
"settable_per_extruder": false,
"settable_per_extruder": true,
"settable_per_meshgroup": false,
"settable_globally": false
},
"machine_nozzle_size":
{
"label": "Nozzle Diameter",
"description": "The inner diameter of the nozzle. Change this setting when using a non-standard nozzle size.",
"unit": "mm",
"type": "float",
"default_value": 0.4,
"minimum_value": "0.001",
"maximum_value_warning": "10",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_nozzle_offset_x":
{
"label": "Nozzle X Offset",

View File

@ -8,6 +8,7 @@
"author": "Ultimaker B.V.",
"category": "Ultimaker",
"manufacturer": "Ultimaker",
"setting_version": 1,
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
"visible": false,
"has_materials": true,
@ -940,7 +941,7 @@
"unit": "mm",
"default_value": 0.8,
"minimum_value": "0",
"minimum_value_warning": "3 * resolveOrValue('layer_height')",
"minimum_value_warning": "0.2 + resolveOrValue('layer_height')",
"maximum_value": "machine_height",
"type": "float",
"value": "top_bottom_thickness",
@ -956,7 +957,7 @@
"minimum_value": "0",
"maximum_value_warning": "100",
"type": "int",
"minimum_value_warning": "4",
"minimum_value_warning": "2",
"value": "0 if infill_sparse_density == 100 else math.ceil(round(top_thickness / resolveOrValue('layer_height'), 4))",
"limit_to_extruder": "top_bottom_extruder_nr",
"settable_per_mesh": true
@ -970,7 +971,7 @@
"unit": "mm",
"default_value": 0.6,
"minimum_value": "0",
"minimum_value_warning": "3 * resolveOrValue('layer_height')",
"minimum_value_warning": "0.2 + resolveOrValue('layer_height')",
"type": "float",
"value": "top_bottom_thickness",
"maximum_value": "machine_height",
@ -983,7 +984,7 @@
"label": "Bottom Layers",
"description": "The number of bottom layers. When calculated by the bottom thickness, this value is rounded to a whole number.",
"minimum_value": "0",
"minimum_value_warning": "4",
"minimum_value_warning": "2",
"default_value": 6,
"type": "int",
"value": "999999 if infill_sparse_density == 100 else math.ceil(round(bottom_thickness / resolveOrValue('layer_height'), 4))",
@ -1317,20 +1318,6 @@
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"sub_div_rad_mult":
{
"label": "Cubic Subdivision Radius",
"description": "A multiplier on the radius from the center of each cube to check for the boundary of the model, as to decide whether this cube should be subdivided. Larger values lead to more subdivisions, i.e. more small cubes.",
"unit": "%",
"type": "float",
"default_value": 100,
"minimum_value": "0",
"minimum_value_warning": "100",
"maximum_value_warning": "200",
"enabled": "infill_sparse_density > 0 and infill_pattern == 'cubicsubdiv'",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
},
"sub_div_rad_add":
{
"label": "Cubic Subdivision Shell",
@ -1441,8 +1428,8 @@
"default_value": 0,
"type": "int",
"minimum_value": "0",
"maximum_value_warning": "4",
"maximum_value": "(20 - math.log(infill_line_distance) / math.log(2)) if infill_line_distance > 0 and not spaghetti_infill_enabled else 0",
"maximum_value_warning": "5",
"maximum_value": "0 if spaghetti_infill_enabled else (999999 if infill_line_distance == 0 else (20 - math.log(infill_line_distance) / math.log(2)))",
"enabled": "infill_sparse_density > 0 and infill_pattern != 'cubicsubdiv' and not spaghetti_infill_enabled",
"limit_to_extruder": "infill_extruder_nr",
"settable_per_mesh": true
@ -1453,7 +1440,7 @@
"description": "The height of infill of a given density before switching to half the density.",
"unit": "mm",
"type": "float",
"default_value": 5.0,
"default_value": 1.5,
"minimum_value": "0.0001",
"minimum_value_warning": "3 * resolveOrValue('layer_height')",
"maximum_value_warning": "100",
@ -1493,8 +1480,8 @@
{
"expand_upper_skins":
{
"label": "Expand Upper Skins",
"description": "Expand upper skin areas (areas with air above) so that they support infill above.",
"label": "Expand Top Skins Into Infill",
"description": "Expand the top skin areas (areas with air above) so that they support infill above.",
"type": "bool",
"default_value": false,
"value": "expand_skins_into_infill",
@ -1503,8 +1490,8 @@
},
"expand_lower_skins":
{
"label": "Expand Lower Skins",
"description": "Expand lower skin areas (areas with air below) so that they are anchored by the infill layers above and below.",
"label": "Expand Bottom Skins Into Infill",
"description": "Expand the bottom skin areas (areas with air below) so that they are anchored by the infill layers above and below.",
"type": "bool",
"default_value": false,
"limit_to_extruder": "top_bottom_extruder_nr",
@ -2562,7 +2549,6 @@
"unit": "mm/s",
"type": "float",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"default_value": 20,
"enabled": "resolveOrValue('jerk_enabled')",
@ -2576,7 +2562,6 @@
"unit": "mm/s",
"type": "float",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"default_value": 20,
"value": "jerk_print",
@ -2591,7 +2576,6 @@
"unit": "mm/s",
"type": "float",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"default_value": 20,
"value": "jerk_print",
@ -2607,7 +2591,6 @@
"unit": "mm/s",
"type": "float",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"default_value": 20,
"value": "jerk_wall",
@ -2622,7 +2605,6 @@
"unit": "mm/s",
"type": "float",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"default_value": 20,
"value": "jerk_wall",
@ -2639,7 +2621,6 @@
"unit": "mm/s",
"type": "float",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"default_value": 20,
"value": "jerk_print",
@ -2654,7 +2635,6 @@
"unit": "mm/s",
"type": "float",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"default_value": 20,
"value": "jerk_print",
@ -2673,7 +2653,6 @@
"default_value": 20,
"value": "jerk_support",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "resolveOrValue('jerk_enabled') and support_enable",
"limit_to_extruder": "support_infill_extruder_nr",
@ -2689,7 +2668,6 @@
"default_value": 20,
"value": "jerk_support",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
"limit_to_extruder": "support_interface_extruder_nr",
@ -2706,7 +2684,6 @@
"default_value": 20,
"value": "jerk_support_interface",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_roof_extruder_nr, 'support_roof_enable') and support_enable",
"limit_to_extruder": "support_roof_extruder_nr",
@ -2722,7 +2699,6 @@
"default_value": 20,
"value": "jerk_support_interface",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "resolveOrValue('jerk_enabled') and extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable",
"limit_to_extruder": "support_bottom_extruder_nr",
@ -2740,7 +2716,6 @@
"unit": "mm/s",
"type": "float",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"default_value": 20,
"value": "jerk_print",
@ -2757,7 +2732,6 @@
"type": "float",
"default_value": 30,
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"value": "jerk_print if magic_spiralize else 30",
"enabled": "resolveOrValue('jerk_enabled')",
@ -2772,7 +2746,6 @@
"default_value": 20,
"value": "jerk_print",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "resolveOrValue('jerk_enabled')",
"settable_per_mesh": true,
@ -2787,7 +2760,6 @@
"default_value": 20,
"value": "jerk_layer_0",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "resolveOrValue('jerk_enabled')",
"settable_per_mesh": true
@ -2801,7 +2773,6 @@
"default_value": 20,
"value": "jerk_layer_0 * jerk_travel / jerk_print",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "resolveOrValue('jerk_enabled')",
"settable_per_extruder": true,
@ -2817,7 +2788,6 @@
"type": "float",
"default_value": 20,
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"value": "jerk_layer_0",
"enabled": "resolveOrValue('jerk_enabled')",
@ -3121,8 +3091,8 @@
{
"support_enable":
{
"label": "Enable Support",
"description": "Enable support structures. These structures support parts of the model with severe overhangs.",
"label": "Generate Support",
"description": "Generate structures to support parts of the model which have overhangs. Without these structures, such parts would collapse during printing.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true,
@ -3380,7 +3350,7 @@
"support_bottom_stair_step_height":
{
"label": "Support Stair Step Height",
"description": "The height of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures.",
"description": "The height of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures. Set to zero to turn off the stair-like behaviour.",
"unit": "mm",
"type": "float",
"default_value": 0.3,
@ -3390,6 +3360,19 @@
"enabled": "support_enable",
"settable_per_mesh": true
},
"support_bottom_stair_step_width":
{
"label": "Support Stair Step Maximum Width",
"description": "The maximum width of the steps of the stair-like bottom of support resting on the model. A low value makes the support harder to remove, but too high values can lead to unstable support structures.",
"unit": "mm",
"type": "float",
"default_value": 5.0,
"limit_to_extruder": "support_interface_extruder_nr if support_interface_enable else support_infill_extruder_nr",
"minimum_value": "0",
"maximum_value_warning": "10.0",
"enabled": "support_enable",
"settable_per_mesh": true
},
"support_join_distance":
{
"label": "Support Join Distance",
@ -3459,7 +3442,7 @@
"type": "float",
"default_value": 1,
"minimum_value": "0",
"minimum_value_warning": "3 * resolveOrValue('layer_height')",
"minimum_value_warning": "0.2 + resolveOrValue('layer_height')",
"maximum_value_warning": "10",
"limit_to_extruder": "support_interface_extruder_nr",
"enabled": "extruderValue(support_interface_extruder_nr, 'support_interface_enable') and support_enable",
@ -3474,7 +3457,7 @@
"type": "float",
"default_value": 1,
"minimum_value": "0",
"minimum_value_warning": "3 * resolveOrValue('layer_height')",
"minimum_value_warning": "0.2 + resolveOrValue('layer_height')",
"maximum_value_warning": "10",
"value": "extruderValue(support_roof_extruder_nr, 'support_interface_height')",
"limit_to_extruder": "support_roof_extruder_nr",
@ -3490,7 +3473,7 @@
"default_value": 1,
"value": "extruderValue(support_bottom_extruder_nr, 'support_interface_height')",
"minimum_value": "0",
"minimum_value_warning": "min(3 * resolveOrValue('layer_height'), extruderValue(support_bottom_extruder_nr, 'support_bottom_stair_step_height'))",
"minimum_value_warning": "min(0.2 + resolveOrValue('layer_height'), extruderValue(support_bottom_extruder_nr, 'support_bottom_stair_step_height'))",
"maximum_value_warning": "10",
"limit_to_extruder": "support_bottom_extruder_nr",
"enabled": "extruderValue(support_bottom_extruder_nr, 'support_bottom_enable') and support_enable",
@ -3718,6 +3701,17 @@
"description": "Adhesion",
"children":
{
"prime_blob_enable":
{
"label": "Enable Prime Blob",
"description": "Whether to prime the filament with a blob before printing. Turning this setting on will ensure that the extruder will have material ready at the nozzle before printing. Printing Brim or Skirt can act like priming too, in which case turning this setting off saves some time.",
"type": "bool",
"resolve": "any(extruderValues('prime_blob_enable'))",
"default_value": true,
"settable_per_mesh": false,
"settable_per_extruder": true,
"enabled": false
},
"extruder_prime_pos_x":
{
"label": "Extruder Prime X Position",
@ -3757,7 +3751,7 @@
"none": "None"
},
"default_value": "brim",
"resolve": "'raft' if 'raft' in extruderValues('adhesion_type') else ('brim' if 'brim' in extruderValues('adhesion_type') else 'skirt')",
"resolve": "extruderValue(adhesion_extruder_nr, 'adhesion_type')",
"settable_per_mesh": false,
"settable_per_extruder": false
},
@ -3968,7 +3962,7 @@
"value": "resolveOrValue('layer_height') * 1.5",
"minimum_value": "0.001",
"minimum_value_warning": "0.04",
"maximum_value_warning": "0.75 * extruderValue(adhesion_extruder_nr, 'raft_interface_line_width')",
"maximum_value_warning": "0.75 * extruderValue(adhesion_extruder_nr, 'machine_nozzle_size')",
"enabled": "resolveOrValue('adhesion_type') == 'raft'",
"settable_per_mesh": false,
"settable_per_extruder": true,
@ -4337,7 +4331,7 @@
"type": "float",
"unit": "mm",
"enabled": "resolveOrValue('prime_tower_enable')",
"default_value": 15,
"default_value": 20,
"resolve": "max(extruderValues('prime_tower_size'))",
"minimum_value": "0",
"maximum_value": "min(0.5 * machine_width, 0.5 * machine_depth)",
@ -4370,7 +4364,7 @@
"value": "round(max(2 * min(extruderValues('prime_tower_line_width')), 0.5 * (resolveOrValue('prime_tower_size') - math.sqrt(max(0, resolveOrValue('prime_tower_size') ** 2 - max(extruderValues('prime_tower_min_volume')) / resolveOrValue('layer_height'))))), 3)",
"resolve": "max(extruderValues('prime_tower_wall_thickness'))",
"minimum_value": "0.001",
"minimum_value_warning": "2 * min(extruderValues('prime_tower_line_width'))",
"minimum_value_warning": "2 * min(extruderValues('prime_tower_line_width')) - 0.0001",
"maximum_value_warning": "resolveOrValue('prime_tower_size') / 2",
"enabled": "resolveOrValue('prime_tower_enable')",
"settable_per_mesh": false,
@ -4595,6 +4589,31 @@
"settable_per_meshgroup": false,
"settable_globally": false
},
"infill_mesh_order":
{
"label": "Infill Mesh Order",
"description": "Determines which infill mesh is inside the infill of another infill mesh. An infill mesh with a higher order will modify the infill of infill meshes with lower order and normal meshes.",
"default_value": 0,
"value": "1 if infill_mesh else 0",
"minimum_value_warning": "1",
"maximum_value_warning": "50",
"type": "int",
"settable_per_mesh": true,
"settable_per_extruder": false,
"settable_per_meshgroup": false,
"settable_globally": false
},
"cutting_mesh":
{
"label": "Cutting Mesh",
"description": "Limit the volume of this mesh to within other meshes. You can use this to make certain areas of one mesh print with different settings and with a whole different extruder.",
"type": "bool",
"default_value": false,
"settable_per_mesh": true,
"settable_per_extruder": false,
"settable_per_meshgroup": false,
"settable_globally": false
},
"mold_enabled":
{
"label": "Mold",
@ -4629,20 +4648,6 @@
"settable_per_mesh": true,
"enabled": "mold_enabled"
},
"infill_mesh_order":
{
"label": "Infill Mesh Order",
"description": "Determines which infill mesh is inside the infill of another infill mesh. An infill mesh with a higher order will modify the infill of infill meshes with lower order and normal meshes.",
"default_value": 0,
"value": "1 if infill_mesh else 0",
"minimum_value_warning": "1",
"maximum_value_warning": "50",
"type": "int",
"settable_per_mesh": true,
"settable_per_extruder": false,
"settable_per_meshgroup": false,
"settable_globally": false
},
"support_mesh":
{
"label": "Support Mesh",
@ -4654,6 +4659,18 @@
"settable_per_meshgroup": false,
"settable_globally": false
},
"support_mesh_drop_down":
{
"label": "Drop Down Support Mesh",
"description": "Make support everywhere below the support mesh, so that there's no overhang in the support mesh.",
"type": "bool",
"default_value": true,
"enabled": "support_mesh",
"settable_per_mesh": true,
"settable_per_extruder": false,
"settable_per_meshgroup": false,
"settable_globally": false
},
"anti_overhang_mesh":
{
"label": "Anti Overhang Mesh",
@ -4682,11 +4699,21 @@
"magic_spiralize":
{
"label": "Spiralize Outer Contour",
"description": "Spiralize smooths out the Z move of the outer edge. This will create a steady Z increase over the whole print. This feature turns a solid model into a single walled print with a solid bottom. This feature used to be called Joris in older versions.",
"description": "Spiralize smooths out the Z move of the outer edge. This will create a steady Z increase over the whole print. This feature turns a solid model into a single walled print with a solid bottom. This feature should only be enabled when each layer only contains a single part.",
"type": "bool",
"default_value": false,
"settable_per_mesh": false,
"settable_per_extruder": false
},
"smooth_spiralized_contours":
{
"label": "Smooth Spiralized Contours",
"description": "Smooth the spiralized contours to reduce the visibility of the Z seam (the Z-seam should be barely visible on the print but will still be visible in the layer view). Note that smoothing will tend to blur fine surface details.",
"type": "bool",
"default_value": true,
"enabled": "magic_spiralize",
"settable_per_mesh": false,
"settable_per_extruder": false
}
}
},

View File

@ -32,7 +32,7 @@
"machine_center_is_zero": { "default_value": false },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default_value": ";---------------------------------------\n; ; ; Jellybox Start Script Begin ; ; ;\n;_______________________________________\n; M92 E140 ;optionally adjust steps per mm for your filament\n\n; Print Settings Summary\n; (leave these alone: this is only a list of the slicing settings)\n; (overwriting these values will NOT change your printer's behavior)\n; sliced for : {machine_name}\n; nozzle diameter : {machine_nozzle_size}\n; filament diameter : {material_diameter}\n; layer height : {layer_height}\n; 1st layer height : {layer_height_0}\n; line width : {line_width}\n; outer wall wipe dist. : {wall_0_wipe_dist}\n; infill line width : {infill_line_width}\n; wall thickness : {wall_thickness}\n; top thickness : {top_thickness}\n; bottom thickness : {bottom_thickness}\n; infill density : {infill_sparse_density}\n; infill pattern : {infill_pattern}\n; print temperature : {material_print_temperature}\n; 1st layer print temp. : {material_print_temperature_layer_0}\n; heated bed temperature : {material_bed_temperature}\n; 1st layer bed temp. : {material_bed_temperature_layer_0}\n; regular fan speed : {cool_fan_speed_min}\n; max fan speed : {cool_fan_speed_max}\n; retraction amount : {retraction_amount}\n; retr. retract speed : {retraction_retract_speed}\n; retr. prime speed : {retraction_prime_speed}\n; build plate adhesion : {adhesion_type}\n; support ? {support_enable}\n; spiralized ? {magic_spiralize}\n\nM117 Preparing ;write Preparing\nM140 S{material_bed_temperature_layer_0} ;set bed temperature and move on\nM104 S{material_print_temperature_layer_0} ;set extruder temperature and move on\nM206 X10.0 Y0.0 ;set x homing offset for default bed leveling\nG21 ;metric values\nG90 ;absolute positioning\nM107 ;start with the fan off\nM82 ;set extruder to absolute mode\nG28 ;home all axes\nM203 Z4 ;slow Z speed down for greater accuracy when probing\nG29 ;auto bed leveling procedure\nM203 Z7 ;pick up z speed again for printing\nM190 S{material_bed_temperature_layer_0} ;wait for the bed to reach desired temperature\nM109 S{material_print_temperature_layer_0} ;wait for the extruder to reach desired temperature\nG92 E0 ;reset the extruder position\nG1 F1500 E15 ;extrude 15mm of feed stock\nG92 E0 ;reset the extruder position again\nM117 Print starting ;write Print starting\n;---------------------------------------------\n; ; ; Jellybox Printer Start Script End ; ; ;\n;_____________________________________________\n"
"default_value": ";---------------------------------------\n; ; ; Jellybox Start Script Begin ; ; ;\n;_______________________________________\n; M92 E140 ;optionally adjust steps per mm for your filament\n\n; Print Settings Summary\n; (leave these alone: this is only a list of the slicing settings)\n; (overwriting these values will NOT change your printer's behavior)\n; sliced for : {machine_name}\n; nozzle diameter : {machine_nozzle_size}\n; filament diameter : {material_diameter}\n; layer height : {layer_height}\n; 1st layer height : {layer_height_0}\n; line width : {line_width}\n; outer wall wipe dist. : {wall_0_wipe_dist}\n; infill line width : {infill_line_width}\n; wall thickness : {wall_thickness}\n; top thickness : {top_thickness}\n; bottom thickness : {bottom_thickness}\n; infill density : {infill_sparse_density}\n; infill pattern : {infill_pattern}\n; print temperature : {material_print_temperature}\n; 1st layer print temp. : {material_print_temperature_layer_0}\n; heated bed temperature : {material_bed_temperature}\n; 1st layer bed temp. : {material_bed_temperature_layer_0}\n; regular fan speed : {cool_fan_speed_min}\n; max fan speed : {cool_fan_speed_max}\n; retraction amount : {retraction_amount}\n; retr. retract speed : {retraction_retract_speed}\n; retr. prime speed : {retraction_prime_speed}\n; build plate adhesion : {adhesion_type}\n; support ? {support_enable}\n; spiralized ? {magic_spiralize}\n\nM117 Preparing ;write Preparing\nM140 S{material_bed_temperature_layer_0} ;set bed temperature and move on\nM109 S{material_print_temperature} ; wait for the extruder to reach desired temperature\nM206 X10.0 Y0.0 ;set x homing offset for default bed leveling\nG21 ;metric values\nG90 ;absolute positioning\nM107 ;start with the fan off\nM82 ;set extruder to absolute mode\nG28 ;home all axes\nM203 Z4 ;slow Z speed down for greater accuracy when probing\nG29 ;auto bed leveling procedure\nM203 Z7 ;pick up z speed again for printing\nM190 S{material_bed_temperature_layer_0} ;wait for the bed to reach desired temperature\nM109 S{material_print_temperature_layer_0} ;wait for the extruder to reach desired temperature\nG92 E0 ;reset the extruder position\nG1 F1500 E15 ;extrude 15mm of feed stock\nG92 E0 ;reset the extruder position again\nM117 Print starting ;write Print starting\n;---------------------------------------------\n; ; ; Jellybox Printer Start Script End ; ; ;\n;_____________________________________________\n"
},
"machine_end_gcode": {
"default_value": "\n;---------------------------------\n;;; Jellybox End Script Begin ;;;\n;_________________________________\nM117 Finishing Up ;write Finishing Up\n\nM104 S0 ;extruder heater off\nM140 S0 ;bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG90 ;absolute positioning\nG28 X ;home x, so the head is out of the way\nG1 Y100 ;move Y forward, so the print is more accessible\nM84 ;steppers off\n\nM117 Print finished ;write Print finished\n;---------------------------------------\n;;; Jellybox End Script End ;;;\n;_______________________________________"

View File

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

View File

@ -0,0 +1,70 @@
{
"id": "makeR_pegasus",
"version": 2,
"name": "makeR Pegasus",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "makeR",
"manufacturer": "makeR",
"category": "Other",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "makeR_pegasus_platform.stl",
"platform_offset": [-200,-10,200]
},
"overrides": {
"machine_name": { "default_value": " makeR Pegasus" },
"machine_heated_bed": {
"default_value": true
},
"machine_width": {
"default_value": 400
},
"machine_height": {
"default_value": 400
},
"machine_depth": {
"default_value": 400
},
"machine_center_is_zero": {
"default_value": false
},
"machine_nozzle_size": {
"default_value": 0.4
},
"material_diameter": {
"default_value": 2.85
},
"machine_nozzle_heat_up_speed": {
"default_value": 2
},
"machine_nozzle_cool_down_speed": {
"default_value": 2
},
"machine_head_polygon": {
"default_value": [
[-75, -18],
[-75, 35],
[18, 35],
[18, -18]
]
},
"gantry_height": {
"default_value": -25
},
"machine_platform_offset":{
"default_value":-25
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
"default_value": "G1 Z15;\nG28;Home\nG29;Auto Level\nG1 Z5 F5000;Move the platform down 15mm"
},
"machine_end_gcode": {
"default_value": "M104 S0;Turn off temperature\nG28 X0; Home X\nM84; Disable Motors"
}
}
}

View File

@ -0,0 +1,67 @@
{
"id": "makeR_prusa_tairona_i3",
"version": 2,
"name": "makeR Prusa Tairona i3",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "makeR",
"manufacturer": "makeR",
"category": "Other",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "makeR_prusa_tairona_i3_platform.stl",
"platform_offset": [-2,0,0]
},
"overrides": {
"machine_name": { "default_value": "makeR Prusa Tairona I3" },
"machine_heated_bed": {
"default_value": true
},
"machine_width": {
"default_value": 200
},
"machine_height": {
"default_value": 200
},
"machine_depth": {
"default_value": 200
},
"machine_center_is_zero": {
"default_value": false
},
"machine_nozzle_size": {
"default_value": 0.4
},
"material_diameter": {
"default_value": 1.75
},
"machine_nozzle_heat_up_speed": {
"default_value": 2
},
"machine_nozzle_cool_down_speed": {
"default_value": 2
},
"machine_head_polygon": {
"default_value": [
[-75, -18],
[-75, 35],
[18, 35],
[18, -18]
]
},
"gantry_height": {
"default_value": 55
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
"default_value": "G1 Z15;\nG28;Home\nG29;Auto Level\nG1 Z5 F5000;Move the platform down 15mm"
},
"machine_end_gcode": {
"default_value": "M104 S0;Turn off temperature\nG28 X0; Home X\nM84; Disable Motors"
}
}
}

View File

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

View File

@ -0,0 +1,162 @@
{
"id": "peopoly_moai",
"version": 2,
"name": "Peopoly Moai",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "fieldOfView",
"manufacturer": "Peopoly",
"category": "Other",
"file_formats": "text/x-gcode",
"has_machine_quality": true,
"has_materials": false
},
"overrides": {
"machine_name": {
"default_value": "Moai"
},
"machine_width": {
"default_value": 130
},
"machine_height": {
"default_value": 180
},
"machine_depth": {
"default_value": 130
},
"machine_nozzle_size": {
"default_value": 0.067
},
"machine_head_with_fans_polygon":
{
"default_value": [
[ -20, 10 ],
[ -20, -10 ],
[ 10, 10 ],
[ 10, -10 ]
]
},
"machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)"
},
"machine_start_gcode": {
"default_value": "G28 ;Home"
},
"machine_end_gcode": {
"default_value": "M104 S0\nM140 S0\nG28 X0 Y0\nM84"
},
"line_width": {
"minimum_value_warning": "machine_nozzle_size"
},
"wall_line_width": {
"minimum_value_warning": "machine_nozzle_size"
},
"wall_line_width_x": {
"minimum_value_warning": "machine_nozzle_size"
},
"skin_line_width": {
"minimum_value_warning": "machine_nozzle_size"
},
"infill_line_width": {
"minimum_value_warning": "machine_nozzle_size"
},
"skirt_brim_line_width": {
"minimum_value_warning": "machine_nozzle_size"
},
"layer_height": {
"maximum_value_warning": "0.5",
"minimum_value_warning": "0.02"
},
"layer_height_0": {
"maximum_value_warning": "0.5",
"minimum_value_warning": "0.02",
"value": "0.1"
},
"top_bottom_thickness": {
"minimum_value_warning": "0.1"
},
"infill_sparse_thickness": {
"maximum_value_warning": "0.5"
},
"speed_print": {
"maximum_value_warning": "300"
},
"speed_infill": {
"maximum_value_warning": "300"
},
"speed_wall": {
"maximum_value_warning": "300",
"value": "speed_print"
},
"speed_wall_0": {
"maximum_value_warning": "300"
},
"speed_wall_x": {
"maximum_value_warning": "300",
"value": "speed_print"
},
"speed_topbottom": {
"maximum_value_warning": "300",
"value": "speed_print"
},
"speed_travel": {
"value": "300"
},
"speed_travel_layer_0": {
"value": "300"
},
"speed_layer_0": {
"value": "5"
},
"speed_slowdown_layers": {
"value": "2"
},
"acceleration_enabled": {
"value": "False"
},
"print_sequence": {
"enabled": false
},
"support_enable": {
"enabled": false
},
"machine_nozzle_temp_enabled": {
"value": "False"
},
"material_bed_temperature": {
"enabled": false
},
"material_diameter": {
"enabled": false,
"value": "1.75"
},
"cool_fan_enabled": {
"enabled": false,
"value": "False"
},
"retraction_enable": {
"enabled": false,
"value": "False"
},
"retraction_combing": {
"enabled": false,
"value": "'off'"
},
"retract_at_layer_change": {
"enabled": false
},
"cool_min_layer_time_fan_speed_max": {
"enabled": false
},
"cool_fan_full_at_height": {
"enabled": false
},
"cool_fan_full_layer": {
"enabled": false
}
}
}

View File

@ -0,0 +1,130 @@
{
"id": "rigid3d_zero2",
"name": "Rigid3D Zero2",
"version": 2,
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Rigid3D",
"manufacturer": "Rigid3D",
"category": "Other",
"has_materials": false,
"file_formats": "text/x-gcode",
"platform": "rigid3d_zero2_platform.stl",
"platform_offset": [ 5, 0, -35]
},
"overrides": {
"machine_name": { "default_value": "Rigid3D Zero2" },
"machine_head_with_fans_polygon": {
"default_value": [[ 30, 30], [ 30, 70], [ 30, 70], [ 30, 30]]
},
"z_seam_type": {
"default_value": "random"
},
"machine_heated_bed": {
"default_value": true
},
"layer_height": {
"default_value": 0.2
},
"layer_height_0": {
"default_value": 0.2
},
"wall_thickness": {
"default_value": 0.8
},
"top_bottom_thickness": {
"default_value": 0.8
},
"xy_offset": {
"default_value": -0.2
},
"material_print_temperature": {
"value": 235
},
"material_bed_temperature": {
"default_value": 100
},
"material_diameter": {
"default_value": 1.75
},
"speed_print": {
"default_value": 40
},
"speed_layer_0": {
"value": 15
},
"speed_travel": {
"value": 100
},
"support_enable": {
"default_value": false
},
"infill_sparse_density": {
"default_value": 15
},
"infill_pattern": {
"default_value": "lines",
"value": "lines"
},
"retraction_amount": {
"default_value": 1
},
"machine_width": {
"default_value": 200
},
"machine_height": {
"default_value": 200
},
"machine_depth": {
"default_value": 200
},
"machine_center_is_zero": {
"default_value": false
},
"machine_nozzle_size": {
"default_value": 0.4
},
"gantry_height": {
"default_value": 25
},
"machine_gcode_flavor": {
"default_value": "RepRap"
},
"cool_fan_enabled": {
"default_value": false
},
"cool_fan_speed": {
"default_value": 50,
"value": 50
},
"cool_fan_speed_min": {
"default_value": 0
},
"cool_fan_full_at_height": {
"default_value": 1.0,
"value": 1.0
},
"support_z_distance": {
"default_value": 0.2
},
"support_interface_enable": {
"default_value": true
},
"support_interface_height": {
"default_value": 0.8
},
"support_interface_density": {
"default_value": 70
},
"support_interface_pattern": {
"default_value": "grid"
},
"machine_start_gcode": {
"default_value": "G21\nG28 ; Home extruder\nM107 ; Turn off fan\nG91 ; Relative positioning\nG1 Z5 F180;\nG1 X100 Y100 F3000;\nG1 Z-5 F180;\nG90 ; Absolute positioning\nM82 ; Extruder in absolute mode\nG92 E0 ; Reset extruder position\n"
},
"machine_end_gcode": {
"default_value": "G1 X0 Y180 ; Get extruder out of way.\nM107 ; Turn off fan\nG91 ; Relative positioning\nG0 Z20 ; Lift extruder up\nT0\nG1 E-1 ; Reduce filament pressure\nM104 T0 S0 ; Turn extruder heater off\nG90 ; Absolute positioning\nG92 E0 ; Reset extruder position\nM140 S0 ; Disable heated bed\nM84 ; Turn steppers off\n"
}
}
}

View File

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

View File

@ -11,8 +11,7 @@
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2.png",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2Extendedbackplate.png",
"supported_actions": ["UpgradeFirmware"]
"platform_texture": "Ultimaker2Extendedbackplate.png"
},
"overrides": {

View File

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

View File

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

View File

@ -70,9 +70,11 @@
"machine_start_gcode": { "default_value": "" },
"machine_end_gcode": { "default_value": "" },
"prime_tower_position_x": { "default_value": 175 },
"prime_tower_position_y": { "default_value": 179 },
"prime_tower_position_y": { "default_value": 178 },
"prime_tower_wipe_enabled": { "default_value": false },
"prime_blob_enable": { "enabled": true },
"acceleration_enabled": { "value": "True" },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
@ -103,7 +105,7 @@
"layer_height_0": { "value": "round(machine_nozzle_size / 1.5, 2)" },
"layer_start_x": { "value": "sum(extruderValues('machine_extruder_start_pos_x')) / len(extruderValues('machine_extruder_start_pos_x'))" },
"layer_start_y": { "value": "sum(extruderValues('machine_extruder_start_pos_y')) / len(extruderValues('machine_extruder_start_pos_y'))" },
"line_width": { "value": "machine_nozzle_size * 0.875" },
"line_width": { "value": "round(machine_nozzle_size * 0.875, 3)" },
"machine_min_cool_heat_time_window": { "value": "15" },
"default_material_print_temperature": { "value": "200" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
@ -111,7 +113,7 @@
"material_bed_temperature_layer_0": { "maximum_value": "115" },
"material_standby_temperature": { "value": "100" },
"multiple_mesh_overlap": { "value": "0" },
"prime_tower_enable": { "value": "True" },
"prime_tower_enable": { "default_value": true },
"raft_airgap": { "value": "0" },
"raft_base_thickness": { "value": "0.3" },
"raft_interface_line_spacing": { "value": "0.5" },

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,17 @@
{
"id": "custom_extruder_1",
"version": 2,
"name": "Extruder 1",
"inherits": "fdmextruder",
"metadata": {
"machine": "custom",
"position": "0"
},
"overrides": {
"extruder_nr": {
"default_value": 0,
"maximum_value": "7"
}
}
}

View File

@ -0,0 +1,17 @@
{
"id": "custom_extruder_2",
"version": 2,
"name": "Extruder 2",
"inherits": "fdmextruder",
"metadata": {
"machine": "custom",
"position": "1"
},
"overrides": {
"extruder_nr": {
"default_value": 1,
"maximum_value": "7"
}
}
}

View File

@ -0,0 +1,17 @@
{
"id": "custom_extruder_3",
"version": 2,
"name": "Extruder 3",
"inherits": "fdmextruder",
"metadata": {
"machine": "custom",
"position": "2"
},
"overrides": {
"extruder_nr": {
"default_value": 2,
"maximum_value": "7"
}
}
}

View File

@ -0,0 +1,17 @@
{
"id": "custom_extruder_4",
"version": 2,
"name": "Extruder 4",
"inherits": "fdmextruder",
"metadata": {
"machine": "custom",
"position": "3"
},
"overrides": {
"extruder_nr": {
"default_value": 3,
"maximum_value": "7"
}
}
}

View File

@ -0,0 +1,17 @@
{
"id": "custom_extruder_5",
"version": 2,
"name": "Extruder 5",
"inherits": "fdmextruder",
"metadata": {
"machine": "custom",
"position": "4"
},
"overrides": {
"extruder_nr": {
"default_value": 4,
"maximum_value": "7"
}
}
}

View File

@ -0,0 +1,17 @@
{
"id": "custom_extruder_6",
"version": 2,
"name": "Extruder 6",
"inherits": "fdmextruder",
"metadata": {
"machine": "custom",
"position": "5"
},
"overrides": {
"extruder_nr": {
"default_value": 5,
"maximum_value": "7"
}
}
}

View File

@ -0,0 +1,17 @@
{
"id": "custom_extruder_7",
"version": 2,
"name": "Extruder 7",
"inherits": "fdmextruder",
"metadata": {
"machine": "custom",
"position": "6"
},
"overrides": {
"extruder_nr": {
"default_value": 6,
"maximum_value": "7"
}
}
}

View File

@ -0,0 +1,17 @@
{
"id": "custom_extruder_8",
"version": 2,
"name": "Extruder 8",
"inherits": "fdmextruder",
"metadata": {
"machine": "custom",
"position": "7"
},
"overrides": {
"extruder_nr": {
"default_value": 7,
"maximum_value": "7"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-27 17:27+0200\n"
"PO-Revision-Date: 2017-01-21 09:40+0200\n"
"PO-Revision-Date: 2017-04-09 18:00-0300\n"
"Last-Translator: Cláudio Sampaio <patola@makerlinux.com.br>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: ptbr\n"
@ -66,7 +66,7 @@ msgstr "Arquivo X3D"
#: /home/ruben/Projects/Cura/plugins/GCodeWriter/__init__.py:12
msgctxt "@label"
msgid "GCode Writer"
msgstr "Gravador de G-Code"
msgstr "Gerador de G-Code"
#: /home/ruben/Projects/Cura/plugins/GCodeWriter/__init__.py:15
msgctxt "@info:whatsthis"
@ -157,7 +157,7 @@ msgstr "Imprimir pela USB"
#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:30
msgctxt "@info:status"
msgid "Connected via USB"
msgstr "Conectado na USB"
msgstr "Conectado via USB"
#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:152
msgctxt "@info:status"
@ -167,7 +167,7 @@ msgstr "Incapaz de iniciar novo trabalho porque a impressora está ocupada ou n
#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:450
msgctxt "@info:status"
msgid "This printer does not support USB printing because it uses UltiGCode flavor."
msgstr ""
msgstr "Esta impressora não suporta impressão USB porque usa G-Code UltiGCode."
#: /home/ruben/Projects/Cura/plugins/USBPrinting/USBPrinterOutputDevice.py:454
msgctxt "@info:status"
@ -331,17 +331,17 @@ msgstr "Envia pedido de acesso à impressora"
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:336
msgctxt "@info:status"
msgid "Connected over the network. Please approve the access request on the printer."
msgstr ""
msgstr "Conectado pela rede. Por favor aprove a requisição de acesso na impressora."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:343
msgctxt "@info:status"
msgid "Connected over the network."
msgstr ""
msgstr "Conectado pela rede."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:356
msgctxt "@info:status"
msgid "Connected over the network. No access to control the printer."
msgstr ""
msgstr "Conectado pela rede. Sem acesso para controlar a impressora."
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkPrinterOutputDevice.py:361
msgctxt "@info:status"
@ -586,17 +586,17 @@ msgstr "Camadas"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.py:91
msgctxt "@info:status"
msgid "Cura does not accurately display layers when Wire Printing is enabled"
msgstr "O Cura não mostra as camadas corretamente quando Impressão de Arame estiver habilitada"
msgstr "O Cura não mostra as camadas corretamente quando Impressão em Arame estiver habilitada"
#: /home/ruben/Projects/Cura/plugins/VersionUpgrade/VersionUpgrade24to25/__init__.py:14
msgctxt "@label"
msgid "Version Upgrade 2.4 to 2.5"
msgstr ""
msgstr "Atualizar versão 2.4 para 2.5"
#: /home/ruben/Projects/Cura/plugins/VersionUpgrade/VersionUpgrade24to25/__init__.py:17
msgctxt "@info:whatsthis"
msgid "Upgrades configurations from Cura 2.4 to Cura 2.5."
msgstr ""
msgstr "Atualiza as configurações do Cura 2.4 para o Cura 2.5"
#: /home/ruben/Projects/Cura/plugins/VersionUpgrade/VersionUpgrade21to22/__init__.py:14
msgctxt "@label"
@ -763,22 +763,22 @@ msgstr "Sólido"
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:12
msgctxt "@label"
msgid "G-code Reader"
msgstr ""
msgstr "Leitor de G-Code"
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:15
msgctxt "@info:whatsthis"
msgid "Allows loading and displaying G-code files."
msgstr ""
msgstr "Permite carregar e mostrar arquivos G-Code."
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:25
msgctxt "@item:inlistbox"
msgid "G File"
msgstr ""
msgstr "Arquivo G"
#: /home/ruben/Projects/Cura/plugins/GCodeReader/GCodeReader.py:227
msgctxt "@info:status"
msgid "Parsing G-code"
msgstr ""
msgstr "Interpretando G-Code"
#: /home/ruben/Projects/Cura/plugins/CuraProfileWriter/__init__.py:12
msgctxt "@label"
@ -799,7 +799,7 @@ msgstr "Perfil do Cura"
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:13
msgctxt "@label"
msgid "3MF Writer"
msgstr "Gravador 3MF"
msgstr "Gerador 3MF"
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:16
msgctxt "@info:whatsthis"
@ -860,7 +860,7 @@ msgstr "Provê suporte para importar perfis do Cura."
#, python-brace-format
msgctxt "@label"
msgid "Pre-sliced file {0}"
msgstr ""
msgstr "Arquivo pré-fatiado {0}"
#: /home/ruben/Projects/Cura/cura/PrinterOutputDevice.py:376
msgctxt "@item:material"
@ -986,13 +986,13 @@ msgstr "%(width).1f x %(depth).1f x %(height).1f mm"
#, python-brace-format
msgctxt "@info:status"
msgid "Only one G-code file can be loaded at a time. Skipped importing {0}"
msgstr ""
msgstr "Somente um arquivo G-Code pode ser carregado por vez. Pulando importação de {0}"
#: /home/ruben/Projects/Cura/cura/CuraApplication.py:1201
#, python-brace-format
msgctxt "@info:status"
msgid "Can't open any other file if G-code is loading. Skipped importing {0}"
msgstr ""
msgstr "Não é possível abrir nenhum outro arquivo se G-Code estiver sendo carregado. Pulando importação de {0}"
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:27
msgctxt "@title"
@ -1359,72 +1359,72 @@ msgstr "Troca os scripts de pós-processamento ativos"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:59
msgctxt "@label"
msgid "View Mode: Layers"
msgstr ""
msgstr "Modo de Visão: Camadas"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:75
msgctxt "@label"
msgid "Color scheme"
msgstr ""
msgstr "Esquema de Cores"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:88
msgctxt "@label:listbox"
msgid "Material Color"
msgstr ""
msgstr "Cor do Material"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:92
msgctxt "@label:listbox"
msgid "Line Type"
msgstr ""
msgstr "Tipo de Linha"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:133
msgctxt "@label"
msgid "Compatibility Mode"
msgstr ""
msgstr "Modo de Compatibilidade"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:171
msgctxt "@label"
msgid "Extruder %1"
msgstr ""
msgstr "Extrusor %1"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:185
msgctxt "@label"
msgid "Show Travels"
msgstr ""
msgstr "Mostrar Viagens"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:206
msgctxt "@label"
msgid "Show Helpers"
msgstr ""
msgstr "Mostrar Assistentes"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:227
msgctxt "@label"
msgid "Show Shell"
msgstr ""
msgstr "Mostrar Perímetro"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:248
msgctxt "@label"
msgid "Show Infill"
msgstr ""
msgstr "Mostrar Preenchimento"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:269
msgctxt "@label"
msgid "Only Show Top Layers"
msgstr ""
msgstr "Somente Mostrar Camadas Superiores"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:277
msgctxt "@label"
msgid "Show 5 Detailed Layers On Top"
msgstr ""
msgstr "Mostrar 5 Camadas Superiores Detalhadas"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:285
msgctxt "@label"
msgid "Top / Bottom"
msgstr ""
msgstr "Topo / Base"
#: /home/ruben/Projects/Cura/plugins/LayerView/LayerView.qml:306
msgctxt "@label"
msgid "Inner Wall"
msgstr ""
msgstr "Parede Interna"
#: /home/ruben/Projects/Cura/plugins/ImageReader/ConfigUI.qml:19
msgctxt "@title:window"
@ -1598,8 +1598,8 @@ msgstr "Não no perfil"
msgctxt "@action:label"
msgid "%1 override"
msgid_plural "%1 overrides"
msgstr[0] "%1 sobreposição"
msgstr[1] "%1 sobreposições"
msgstr[0] "%1 sobrepujança"
msgstr[1] "%1 sobrepujanças"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:234
msgctxt "@action:label"
@ -1610,8 +1610,8 @@ msgstr "Derivado de"
msgctxt "@action:label"
msgid "%1, %2 override"
msgid_plural "%1, %2 overrides"
msgstr[0] "%1, %2 sobreposição"
msgstr[1] "%1, %2 sobreposições"
msgstr[0] "%1, %2 sobrepujança"
msgstr[1] "%1, %2 sobrepujanças"
#: /home/ruben/Projects/Cura/plugins/3MFReader/WorkspaceDialog.qml:255
msgctxt "@action:label"
@ -1891,7 +1891,7 @@ msgstr "Tem certeza que deseja abortar a impressão?"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:14
msgctxt "@title:window"
msgid "Discard or Keep changes"
msgstr ""
msgstr "Descartar ou Manter alterações"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:59
msgctxt "@text:window"
@ -1899,54 +1899,56 @@ msgid ""
"You have customized some profile settings.\n"
"Would you like to keep or discard those settings?"
msgstr ""
"Você personalizou alguns ajustes de perfil.\n"
"Gostaria de manter ou descartar estes ajustes?"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:108
msgctxt "@title:column"
msgid "Profile settings"
msgstr ""
msgstr "Ajustes de perfil"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:115
msgctxt "@title:column"
msgid "Default"
msgstr ""
msgstr "Default"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:122
msgctxt "@title:column"
msgid "Customized"
msgstr ""
msgstr "Personalizado"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:152
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:391
msgctxt "@option:discardOrKeep"
msgid "Always ask me this"
msgstr ""
msgstr "Sempre perguntar"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:153
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:392
msgctxt "@option:discardOrKeep"
msgid "Discard and never ask again"
msgstr ""
msgstr "Descartar e não perguntar novamente"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:154
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:393
msgctxt "@option:discardOrKeep"
msgid "Keep and never ask again"
msgstr ""
msgstr "Manter e não perguntar novamente"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:189
msgctxt "@action:button"
msgid "Discard"
msgstr ""
msgstr "Descartar"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:202
msgctxt "@action:button"
msgid "Keep"
msgstr ""
msgstr "Manter"
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:215
msgctxt "@action:button"
msgid "Create New Profile"
msgstr ""
msgstr "Criar Novo Perfil"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:29
msgctxt "@title"
@ -2006,7 +2008,7 @@ msgstr "Comprimento do Filamento"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:190
msgctxt "@label"
msgid "Cost per Meter"
msgstr ""
msgstr "Custo por Metro"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:201
msgctxt "@label"
@ -2072,7 +2074,7 @@ msgstr "Idioma:"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:157
msgctxt "@label"
msgid "Currency:"
msgstr ""
msgstr "Moeda:"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:173
msgctxt "@label"
@ -2082,12 +2084,12 @@ msgstr "A aplicação deverá ser reiniciada para que as alterações de idioma
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:190
msgctxt "@info:tooltip"
msgid "Slice automatically when changing settings."
msgstr ""
msgstr "Fatiar automaticamente quando mudar ajustes."
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:199
msgctxt "@option:check"
msgid "Slice automatically"
msgstr ""
msgstr "Fatiar automaticamente"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:213
msgctxt "@label"
@ -2137,17 +2139,17 @@ msgstr "Automaticamente fazer os modelos caírem na mesa de impressão."
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:278
msgctxt "@info:tooltip"
msgid "Should layer be forced into compatibility mode?"
msgstr ""
msgstr "A Visão de Camada deve ser forçada a ficar em modo de compatibilidade?"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:283
msgctxt "@option:check"
msgid "Force layer view compatibility mode (restart required)"
msgstr ""
msgstr "Forçar modo de compatibilidade da visão de camadas (requer reinício)"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:299
msgctxt "@label"
msgid "Opening and saving files"
msgstr ""
msgstr "Abrindo e salvando arquivos"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:305
msgctxt "@info:tooltip"
@ -2182,7 +2184,7 @@ msgstr "Adicionar prefixo de máquina ao nome do trabalho"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:347
msgctxt "@info:tooltip"
msgid "Should a summary be shown when saving a project file?"
msgstr "Um resumo deve ser exibido quando se estiver salvando um arquivo de projeto?"
msgstr "Um resumo deve ser exibido ao salvar um arquivo de projeto?"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:351
msgctxt "@option:check"
@ -2192,12 +2194,12 @@ msgstr "Mostrar diálogo de resumo ao salvar projeto"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:369
msgctxt "@info:tooltip"
msgid "When you have made changes to a profile and switched to a different one, a dialog will be shown asking whether you want to keep your modifications or not, or you can choose a default behaviour and never show that dialog again."
msgstr ""
msgstr "Quando você faz alterações em um perfil e troca para um diferent, um diálogo aparecerá perguntando se você quer manter ou aplicar suas modificações, ou você pode forçar um comportamento default e não ter o diálogo."
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:378
msgctxt "@label"
msgid "Override Profile"
msgstr ""
msgstr "Sobrepujar Perfil"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:427
msgctxt "@label"
@ -2330,7 +2332,7 @@ msgstr "Descartar ajustes atuais"
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:190
msgctxt "@action:label"
msgid "This profile uses the defaults specified by the printer, so it has no settings/overrides in the list below."
msgstr "Este perfil usa os defaults especificados pela impressora, portanto não tem ajustes e sobreposições na lista abaixo."
msgstr "Este perfil usa os defaults especificados pela impressora, portanto não tem ajustes e sobrepujanças na lista abaixo."
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:197
msgctxt "@action:label"
@ -2449,12 +2451,12 @@ msgstr "00h 00min"
#: /home/ruben/Projects/Cura/resources/qml/JobSpecs.qml:231
msgctxt "@label"
msgid "%1 m / ~ %2 g / ~ %4 %3"
msgstr ""
msgstr "%1 m / ~ %2 g / ~ %4 %3"
#: /home/ruben/Projects/Cura/resources/qml/JobSpecs.qml:236
msgctxt "@label"
msgid "%1 m / ~ %2 g"
msgstr "%1 m/~ %2 g"
msgstr "%1 m / ~ %2 g"
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:15
msgctxt "@title:window"
@ -2488,7 +2490,7 @@ msgstr "Framework de Aplicações"
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:120
msgctxt "@label"
msgid "GCode generator"
msgstr "Gravador de G-Code"
msgstr "Gerador de G-Code"
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:121
msgctxt "@label"
@ -2538,7 +2540,7 @@ msgstr "Biblioteca de suporte para manuseamento de arquivos STL"
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:131
msgctxt "@label"
msgid "Support library for handling 3MF files"
msgstr ""
msgstr "Biblioteca de suporte para manuseamento de arquivos 3MF"
#: /home/ruben/Projects/Cura/resources/qml/AboutDialog.qml:132
msgctxt "@label"
@ -2696,7 +2698,7 @@ msgstr "Abrir &Recente"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:33
msgctxt "@info:status"
msgid "No printer connected"
msgstr ""
msgstr "Nenhuma impressora conectada"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:90
msgctxt "@label"
@ -2706,22 +2708,22 @@ msgstr "Hotend"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:119
msgctxt "@tooltip"
msgid "The current temperature of this extruder."
msgstr ""
msgstr "A temperatura atual deste extrusor."
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:154
msgctxt "@tooltip"
msgid "The colour of the material in this extruder."
msgstr ""
msgstr "A cor do material neste extrusor."
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:186
msgctxt "@tooltip"
msgid "The material in this extruder."
msgstr ""
msgstr "O material neste extrusor."
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:218
msgctxt "@tooltip"
msgid "The nozzle inserted in this extruder."
msgstr ""
msgstr "O bico inserido neste extrusor."
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:249
msgctxt "@label"
@ -2731,32 +2733,32 @@ msgstr "Mesa de Impressão"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:278
msgctxt "@tooltip"
msgid "The target temperature of the heated bed. The bed will heat up or cool down towards this temperature. If this is 0, the bed heating is turned off."
msgstr ""
msgstr "A temperatura-alvo da mesa aquecida. A mesa aquecerá ou resfriará para esta temperatura. Se for zero, o aquecimento é desligado."
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:310
msgctxt "@tooltip"
msgid "The current temperature of the heated bed."
msgstr ""
msgstr "A temperatura atual da mesa aquecida."
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:379
msgctxt "@tooltip of temperature input"
msgid "The temperature to pre-heat the bed to."
msgstr ""
msgstr "A temperatura à qual pré-aquecer a mesa."
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:573
msgctxt "@button Cancel pre-heating"
msgid "Cancel"
msgstr ""
msgstr "Cancelar"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:573
msgctxt "@button"
msgid "Pre-heat"
msgstr ""
msgstr "Pré-aquecer"
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:600
msgctxt "@tooltip of pre-heat"
msgid "Heat the bed in advance before printing. You can continue adjusting your print while it is heating, and you won't have to wait for the bed to heat up when you're ready to print."
msgstr ""
msgstr "Aquecer a mesa antes de imprimir. Você pode continuar ajustando sua impressão enquanto ela está aquecendo, e não terá que esperar o aquecimento quando estiver pronto pra imprimir."
#: /home/ruben/Projects/Cura/resources/qml/PrintMonitor.qml:633
msgctxt "@label"
@ -2821,7 +2823,7 @@ msgstr "Administrar Materiais..."
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:126
msgctxt "@action:inmenu menubar:profile"
msgid "&Update profile with current settings/overrides"
msgstr "&Atualizar perfil com valores e sobreposições atuais"
msgstr "&Atualizar perfil com valores e sobrepujanças atuais"
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:134
msgctxt "@action:inmenu menubar:profile"
@ -2951,7 +2953,7 @@ msgstr "Por favor carregue um modelo 3D"
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:33
msgctxt "@label:PrintjobStatus"
msgid "Ready to slice"
msgstr ""
msgstr "Pronto para fatiar"
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:35
msgctxt "@label:PrintjobStatus"
@ -2971,17 +2973,17 @@ msgstr "Incapaz de Fatiar"
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:41
msgctxt "@label:PrintjobStatus"
msgid "Slicing unavailable"
msgstr ""
msgstr "Fatiamento indisponível"
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:136
msgctxt "@label:Printjob"
msgid "Prepare"
msgstr ""
msgstr "Preparar"
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:136
msgctxt "@label:Printjob"
msgid "Cancel"
msgstr ""
msgstr "Cancelar"
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:276
msgctxt "@info:tooltip"
@ -3213,7 +3215,7 @@ msgid ""
"\n"
"Click to open the profile manager."
msgstr ""
"Alguns ajustes/sobreposições têm valores diferentes dos que estão armazenados no perfil.\n"
"Alguns ajustes/sobrepujanças têm valores diferentes dos que estão armazenados no perfil.\n"
"\n"
"Clique para abrir o gerenciador de perfis."

View File

@ -4,7 +4,7 @@ msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2016-01-25 05:05-0300\n"
"PO-Revision-Date: 2017-04-10 09:05-0300\n"
"Last-Translator: Cláudio Sampaio <patola@makerlinux.com.br>\n"
"Language-Team: LANGUAGE\n"
"Language: ptbr\n"

View File

@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-01-24 01:00-0300\n"
"PO-Revision-Date: 2017-04-10 19:00-0300\n"
"Last-Translator: Cláudio Sampaio <patola@makerlinux.com.br>\n"
"Language-Team: LANGUAGE\n"
"Language: ptbr\n"
@ -30,7 +30,7 @@ msgstr "Tipo de Máquina"
#: fdmprinter.def.json
msgctxt "machine_name description"
msgid "The name of your 3D printer model."
msgstr "Nome do seu model de impressora 3D."
msgstr "Nome do seu modelo de impressora 3D."
#: fdmprinter.def.json
msgctxt "machine_show_variants label"
@ -254,12 +254,12 @@ msgstr "Distância da ponta do bico onde 'estacionar' o filamento quando seu ext
#: fdmprinter.def.json
msgctxt "machine_nozzle_temp_enabled label"
msgid "Enable Nozzle Temperature Control"
msgstr ""
msgstr "Habilitar Controle de Temperatura do Bico"
#: fdmprinter.def.json
msgctxt "machine_nozzle_temp_enabled description"
msgid "Whether to control temperature from Cura. Turn this off to control nozzle temperature from outside of Cura."
msgstr ""
msgstr "Se a temperatura deve ser controlada pelo Cura. Desligue para controlar a temperatura do bico fora do Cura."
#: fdmprinter.def.json
msgctxt "machine_nozzle_heat_up_speed label"
@ -579,7 +579,7 @@ msgstr "Altura de Camada"
#: fdmprinter.def.json
msgctxt "layer_height description"
msgid "The height of each layer in mm. Higher values produce faster prints in lower resolution, lower values produce slower prints in higher resolution."
msgstr "A altura das camadas em mm. Valores mais altos produzem impressões mais rápidas em resoluções baixas, valores mais baixos produzem impressão mais lentas em resolução mais alta. Recomenda-se não deixar a altura de camada maior que 80%% do diâmetro do bico."
msgstr "A altura das camadas em mm. Valores mais altos produzem impressões mais rápidas em resoluções baixas, valores mais baixos produzem impressão mais lentas em resolução mais alta. Recomenda-se não deixar a altura de camada maior que 80% do diâmetro do bico."
#: fdmprinter.def.json
msgctxt "layer_height_0 label"
@ -809,37 +809,37 @@ msgstr "Ziguezague"
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 label"
msgid "Bottom Pattern Initial Layer"
msgstr ""
msgstr "Camada Inicial do Padrão da Base"
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 description"
msgid "The pattern on the bottom of the print on the first layer."
msgstr ""
msgstr "O padrão na base da impressão na primeira camada."
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 option lines"
msgid "Lines"
msgstr ""
msgstr "Linhas"
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 option concentric"
msgid "Concentric"
msgstr ""
msgstr "Concêntrico"
#: fdmprinter.def.json
msgctxt "top_bottom_pattern_0 option zigzag"
msgid "Zig Zag"
msgstr ""
msgstr "Ziguezague"
#: fdmprinter.def.json
msgctxt "skin_angles label"
msgid "Top/Bottom Line Directions"
msgstr ""
msgstr "Direções de Linha Superior/Inferior"
#: fdmprinter.def.json
msgctxt "skin_angles description"
msgid "A list of integer line directions to use when the top/bottom layers use the lines or zig zag pattern. Elements from the list are used sequentially as the layers progress and when the end of the list is reached, it starts at the beginning again. The list items are separated by commas and the whole list is contained in square brackets. Default is an empty list which means use the traditional default angles (45 and 135 degrees)."
msgstr ""
msgstr "Uma lista de direções de linha inteiras para usar quando as camadas superiores e inferiores usarem os padrões de linha ou ziguezague. Elementos desta lista são usados sequencialmente à medida que as camadas progridem e quando o fim da lista é alcançado, ela inicia novamente. Os itens da lista são separados por vírgulas e a lita inteira é contida em colchetes. O default é uma lista vazia, o que significa usar os ângulos default (45 e 135 graus)."
#: fdmprinter.def.json
msgctxt "wall_0_inset label"
@ -984,7 +984,7 @@ msgstr "Ignorar Pequenos Vãos em Z"
#: fdmprinter.def.json
msgctxt "skin_no_small_gaps_heuristic description"
msgid "When the model has small vertical gaps, about 5% extra computation time can be spent on generating top and bottom skin in these narrow spaces. In such case, disable the setting."
msgstr "Quando o modelo tem pequenos vãos verticais, aproximadamente 5%% de tempo de computação adicional pode ser gasto ao gerar pele superior e inferior nestes espaços estreitos. Em tal caso, desabilite este ajuste."
msgstr "Quando o modelo tem pequenos vãos verticais, aproximadamente 5% de tempo de computação adicional pode ser gasto ao gerar camada externa superior e inferior nestes espaços estreitos. Em tal caso, desabilite este ajuste."
#: fdmprinter.def.json
msgctxt "infill label"
@ -1094,12 +1094,12 @@ msgstr "Um multiplicador do raio do centro de cada cubo para verificar a borda d
#: fdmprinter.def.json
msgctxt "sub_div_rad_add label"
msgid "Cubic Subdivision Shell"
msgstr "Casca de Subdivisão Cúbica"
msgstr "Cobertura de Subdivisão Cúbica"
#: fdmprinter.def.json
msgctxt "sub_div_rad_add description"
msgid "An addition to the radius from the center of each cube to check for the boundary of the model, as to decide whether this cube should be subdivided. Larger values lead to a thicker shell of small cubes near the boundary of the model."
msgstr "Um adicional ao raio do centro de cada cubo para verificar a borda do modelo, de modo a decidir se este cubo deve ser subdividido. Valores maiores levam a uma casca mais espessa de pequenos cubos perto da borda do modelo."
msgstr "Um adicional ao raio do centro de cada cubo para verificar a borda do modelo, de modo a decidir se este cubo deve ser subdividido. Valores maiores levam a uma cobertura mais espessa de pequenos cubos perto da borda do modelo."
#: fdmprinter.def.json
msgctxt "infill_overlap label"
@ -1124,22 +1124,22 @@ msgstr "Medida de sobreposição entre o preenchimento e as paredes. Uma leve so
#: fdmprinter.def.json
msgctxt "skin_overlap label"
msgid "Skin Overlap Percentage"
msgstr "Porcentagem de Sobreposição da Pele"
msgstr "Porcentagem de Sobreposição do Contorno"
#: fdmprinter.def.json
msgctxt "skin_overlap description"
msgid "The amount of overlap between the skin and the walls. A slight overlap allows the walls to connect firmly to the skin."
msgstr "Porcentagem de sobreposição entre a pele e as paredes. Uma ligeira sobreposição permite às paredes ficarem firmemente aderidas à pele."
msgstr "Porcentagem de sobreposição entre o contorno e as paredes. Uma ligeira sobreposição permite às paredes ficarem firmemente aderidas ao contorno."
#: fdmprinter.def.json
msgctxt "skin_overlap_mm label"
msgid "Skin Overlap"
msgstr "Sobreposição da Pele"
msgstr "Sobreposição do Contorno"
#: fdmprinter.def.json
msgctxt "skin_overlap_mm description"
msgid "The amount of overlap between the skin and the walls. A slight overlap allows the walls to connect firmly to the skin."
msgstr "Medida de sobreposição entre a pele e as paredes. Uma ligeira sobreposição permite às paredes ficarem firmemente aderidas à pele."
msgstr "Medida de sobreposição entre o contorno e as paredes. Uma ligeira sobreposição permite às paredes ficarem firmemente aderidas ao contorno."
#: fdmprinter.def.json
msgctxt "infill_wipe_dist label"
@ -1194,72 +1194,72 @@ msgstr "Imprime o preenchimento antes de imprimir as paredes. Imprimir as parede
#: fdmprinter.def.json
msgctxt "min_infill_area label"
msgid "Minimum Infill Area"
msgstr ""
msgstr "Área Mínima para Preenchimento"
#: fdmprinter.def.json
msgctxt "min_infill_area description"
msgid "Don't generate areas of infill smaller than this (use skin instead)."
msgstr ""
msgstr "Não gerar preenchimento para áreas menores que esta (usar contorno)."
#: fdmprinter.def.json
msgctxt "expand_skins_into_infill label"
msgid "Expand Skins Into Infill"
msgstr ""
msgstr "Expandir Contorno Para Preenchimento"
#: fdmprinter.def.json
msgctxt "expand_skins_into_infill description"
msgid "Expand skin areas of top and/or bottom skin of flat surfaces. By default, skins stop under the wall lines that surround infill but this can lead to holes appearing when the infill density is low. This setting extends the skins beyond the wall lines so that the infill on the next layer rests on skin."
msgstr ""
msgstr "Expandir áreas de perímetro das partes superiores e inferiores de superfícies chatas. Por default, o perímetro para sob as paredes que rodeiam o preenchimento mas isso pode fazer com que buracos apareçam caso a densidade de preenchimento seja baixa. Este ajuste estenda os perímetros além das linhas de parede de modo que o preenchimento da próxima camada fique em cima de perímetros."
#: fdmprinter.def.json
msgctxt "expand_upper_skins label"
msgid "Expand Upper Skins"
msgstr ""
msgstr "Expandir Contornos Superiores"
#: fdmprinter.def.json
msgctxt "expand_upper_skins description"
msgid "Expand upper skin areas (areas with air above) so that they support infill above."
msgstr ""
msgstr "Expandir as áreas de contorno superiores (áreas com ar acima) de modo que suportem o preenchimento acima."
#: fdmprinter.def.json
msgctxt "expand_lower_skins label"
msgid "Expand Lower Skins"
msgstr ""
msgstr "Expandir Contornos Inferiores"
#: fdmprinter.def.json
msgctxt "expand_lower_skins description"
msgid "Expand lower skin areas (areas with air below) so that they are anchored by the infill layers above and below."
msgstr ""
msgstr "Expandir as áreas de contorno inferiores (áreas com ar abaixo) de modo que fiquem ancoradas pelas camadas de preenchimento acima e abaixo."
#: fdmprinter.def.json
msgctxt "expand_skins_expand_distance label"
msgid "Skin Expand Distance"
msgstr ""
msgstr "Distância de Expansão do Contorno"
#: fdmprinter.def.json
msgctxt "expand_skins_expand_distance description"
msgid "The distance the skins are expanded into the infill. The default distance is enough to bridge the gap between the infill lines and will stop holes appearing in the skin where it meets the wall when the infill density is low. A smaller distance will often be sufficient."
msgstr ""
msgstr "A distância que os contornos são expandidos para dentro do preenchimento. A distância default é suficiente para ligar o vão entre as linhas de preenchimento e impedirá que buracos apareçam no contorno onde ele encontrar a parede em que a densidade de preenchimento é baixa. Uma distância menor pode ser suficiente."
#: fdmprinter.def.json
msgctxt "max_skin_angle_for_expansion label"
msgid "Maximum Skin Angle for Expansion"
msgstr ""
msgstr "Ângulo Máximo do Contorno para Expansão"
#: fdmprinter.def.json
msgctxt "max_skin_angle_for_expansion description"
msgid "Top and/or bottom surfaces of your object with an angle larger than this setting, won't have their top/bottom skin expanded. This avoids expanding the narrow skin areas that are created when the model surface has a near vertical slope. An angle of 0° is horizontal, while an angle of 90° is vertical."
msgstr ""
msgstr "Superfícies Superiores e/ou Inferiores de seu objeto com um ângulo maior que este ajuste não terão seus contornos superior/inferior expandidos. Isto evita que expandam as áreas estreitas de contorno que são criadas quando a superfície do modelo tem uma inclinação praticamente vertical. Um ângulo de 0° é horizontal, um ângulo de 90° é vertical."
#: fdmprinter.def.json
msgctxt "min_skin_width_for_expansion label"
msgid "Minimum Skin Width for Expansion"
msgstr ""
msgstr "Largura Mínima de Contorno para Expansão"
#: fdmprinter.def.json
msgctxt "min_skin_width_for_expansion description"
msgid "Skin areas narrower than this are not expanded. This avoids expanding the narrow skin areas that are created when the model surface has a slope close to the vertical."
msgstr ""
msgstr "Áreas de contorno mais estreitas que esta não são expandidas. Isto evita expandir as áreas estreitas que são criadas quando a superfície do modelo tem inclinações quase verticais."
#: fdmprinter.def.json
msgctxt "material label"
@ -1299,7 +1299,7 @@ msgstr "Temperatura de Impressão"
#: fdmprinter.def.json
msgctxt "material_print_temperature description"
msgid "The temperature used for printing."
msgstr ""
msgstr "A temperatura usada para impressão."
#: fdmprinter.def.json
msgctxt "material_print_temperature_layer_0 label"
@ -1359,7 +1359,7 @@ msgstr "Temperatura da Mesa de Impressão"
#: fdmprinter.def.json
msgctxt "material_bed_temperature description"
msgid "The temperature used for the heated build plate. If this is 0, the bed will not heat up for this print."
msgstr ""
msgstr "A temperatura usada pela mesa aquecida de impressão. Se for 0, a mesa não aquecerá para esta impressão."
#: fdmprinter.def.json
msgctxt "material_bed_temperature_layer_0 label"
@ -1589,7 +1589,7 @@ msgstr "Velocidade da Parede Exterior"
#: fdmprinter.def.json
msgctxt "speed_wall_0 description"
msgid "The speed at which the outermost walls are printed. Printing the outer wall at a lower speed improves the final skin quality. However, having a large difference between the inner wall speed and the outer wall speed will affect quality in a negative way."
msgstr "A velocidade em que as paredes mais externas são impressas. Imprimir a parede mais externa a uma velocidade menor melhora a qualidade final da pele. No entanto, ter uma diferença muito grande entre a velocidade da parede interna e a velocidade da parede externa afetará a qualidade de forma negativa."
msgstr "A velocidade em que as paredes mais externas são impressas. Imprimir a parede mais externa a uma velocidade menor melhora a qualidade final do contorno. No entanto, ter uma diferença muito grande entre a velocidade da parede interna e a velocidade da parede externa afetará a qualidade de forma negativa."
#: fdmprinter.def.json
msgctxt "speed_wall_x label"
@ -2079,7 +2079,7 @@ msgstr "Modo de Combing"
#: fdmprinter.def.json
msgctxt "retraction_combing description"
msgid "Combing keeps the nozzle within already printed areas when traveling. This results in slightly longer travel moves but reduces the need for retractions. If combing is off, the material will retract and the nozzle moves in a straight line to the next point. It is also possible to avoid combing over top/bottom skin areas by combing within the infill only."
msgstr "O Combing, ou penteamento, mantém o bico dentro de áreas já impressas quando viaja. Isso resulta em movimentos de viagem ligeiramente mais longos mas reduz a necessidade de retrações. Se o penteamento estiver desligado, o material sofrerá retração e o bico se moverá em linha reta para o próximo ponto. É também possível evitar o penteamento em área de paredes superiores e inferiores habilitando o penteamento no preenchimento somente."
msgstr "O Combing, ou penteamento, mantém o bico dentro de áreas já impressas quando viaja. Isso resulta em movimentos de viagem ligeiramente mais longos mas reduz a necessidade de retrações. Se o penteamento estiver desligado, o material sofrerá retração e o bico se moverá em linha reta para o próximo ponto. É também possível evitar o penteamento em área de contornos superiores e inferiores habilitando o penteamento no preenchimento somente."
#: fdmprinter.def.json
msgctxt "retraction_combing option off"
@ -2094,17 +2094,17 @@ msgstr "Tudo"
#: fdmprinter.def.json
msgctxt "retraction_combing option noskin"
msgid "No Skin"
msgstr "Somente Preenchimento"
msgstr "Evita Contornos"
#: fdmprinter.def.json
msgctxt "travel_retract_before_outer_wall label"
msgid "Retract Before Outer Wall"
msgstr ""
msgstr "Retrair Antes da Parede Externa"
#: fdmprinter.def.json
msgctxt "travel_retract_before_outer_wall description"
msgid "Always retract when moving to start an outer wall."
msgstr ""
msgstr "Sempre retrair quando se mover para iniciar uma parede externa."
#: fdmprinter.def.json
msgctxt "travel_avoid_other_parts label"
@ -2139,7 +2139,7 @@ msgstr "Em cada camada iniciar imprimindo o objeto próximo ao mesmo ponto, de m
#: fdmprinter.def.json
msgctxt "layer_start_x label"
msgid "Layer Start X"
msgstr "X do Início da Camada"
msgstr "X Inicial da Camada"
#: fdmprinter.def.json
msgctxt "layer_start_x description"
@ -2149,7 +2149,7 @@ msgstr "A coordenada X da posição próxima de onde achar a parte com que come
#: fdmprinter.def.json
msgctxt "layer_start_y label"
msgid "Layer Start Y"
msgstr "Y do Início da Camada"
msgstr "Y Inicial da Camada"
#: fdmprinter.def.json
msgctxt "layer_start_y description"
@ -2484,7 +2484,7 @@ msgstr "Distância em Z do Suporte"
#: fdmprinter.def.json
msgctxt "support_z_distance description"
msgid "Distance from the top/bottom of the support structure to the print. This gap provides clearance to remove the supports after the model is printed. This value is rounded up to a multiple of the layer height."
msgstr ""
msgstr "Distância do topo e base da estrutura de suporte para a impressão. Este vão provê um espaço para remover os suportes depois de o modelo ser impresso. O valor é arredondado para um múltiplo da altura de camada."
#: fdmprinter.def.json
msgctxt "support_top_distance label"
@ -2584,7 +2584,7 @@ msgstr "Habilitar Interface de Suporte"
#: fdmprinter.def.json
msgctxt "support_interface_enable description"
msgid "Generate a dense interface between the model and the support. This will create a skin at the top of the support on which the model is printed and at the bottom of the support, where it rests on the model."
msgstr "Gera uma interface densa entre o modelo e o suporte. Isto criará uma pele no topo do suporte em que o modelo é impresso e na base do suporte, onde ele fica sobre o modelo."
msgstr "Gera uma interface densa entre o modelo e o suporte. Isto criará um contorno no topo do suporte em que o modelo é impresso e na base do suporte, onde ele fica sobre o modelo."
#: fdmprinter.def.json
msgctxt "support_interface_height label"
@ -3583,7 +3583,7 @@ msgstr "Velocidade de Desengrenagem"
#: fdmprinter.def.json
msgctxt "coasting_speed description"
msgid "The speed by which to move during coasting, relative to the speed of the extrusion path. A value slightly under 100% is advised, since during the coasting move the pressure in the bowden tube drops."
msgstr "A velocidade pela qual se mover durante a desengrenagem, relativa à velocidade do caminho de extrusão. Um valor ligeiramente menor que 100%% é sugerido, já que durante a desengrenagem a pressão dentro do hotend cai."
msgstr "A velocidade pela qual se mover durante a desengrenagem, relativa à velocidade do caminho de extrusão. Um valor ligeiramente menor que 100% é sugerido, já que durante a desengrenagem a pressão dentro do hotend cai."
#: fdmprinter.def.json
msgctxt "skin_outline_count label"
@ -3648,7 +3648,7 @@ msgstr "Remove todo o preenchimento e torna o interior oco do objeto elegível a
#: fdmprinter.def.json
msgctxt "magic_fuzzy_skin_enabled label"
msgid "Fuzzy Skin"
msgstr "Pele Felpuda"
msgstr "Contorno Felpudo"
#: fdmprinter.def.json
msgctxt "magic_fuzzy_skin_enabled description"
@ -3658,7 +3658,7 @@ msgstr "Faz flutuações de movimento aleatório enquanto imprime a parede mais
#: fdmprinter.def.json
msgctxt "magic_fuzzy_skin_thickness label"
msgid "Fuzzy Skin Thickness"
msgstr "Espessura da Pele Felpuda"
msgstr "Espessura do Contorno Felpudo"
#: fdmprinter.def.json
msgctxt "magic_fuzzy_skin_thickness description"
@ -3668,7 +3668,7 @@ msgstr "A largura dentro da qual flutuar. É sugerido deixar este valor abaixo d
#: fdmprinter.def.json
msgctxt "magic_fuzzy_skin_point_density label"
msgid "Fuzzy Skin Density"
msgstr "Densidade da Pele Felpuda"
msgstr "Densidade do Contorno Felpudo"
#: fdmprinter.def.json
msgctxt "magic_fuzzy_skin_point_density description"
@ -3678,12 +3678,12 @@ msgstr "A densidade média dos pontos introduzidos em cada polígono de uma cama
#: fdmprinter.def.json
msgctxt "magic_fuzzy_skin_point_dist label"
msgid "Fuzzy Skin Point Distance"
msgstr "Distância de Pontos da Pele Felpuda"
msgstr "Distância de Pontos do Contorno Felpudo"
#: fdmprinter.def.json
msgctxt "magic_fuzzy_skin_point_dist description"
msgid "The average distance between the random points introduced on each line segment. Note that the original points of the polygon are discarded, so a high smoothness results in a reduction of the resolution. This value must be higher than half the Fuzzy Skin Thickness."
msgstr "A distância média entre os pontos aleatórios introduzidos em cada segmento de linha. Note que os pontos originais do polígono são descartados, portanto umo alto alisamento resulta em redução da resolução. Este valor deve ser maior que a metade da Espessura da Pele Felpuda."
msgstr "A distância média entre os pontos aleatórios introduzidos em cada segmento de linha. Note que os pontos originais do polígono são descartados, portanto umo alto alisamento resulta em redução da resolução. Este valor deve ser maior que a metade da Espessura do Contorno Felpudo."
#: fdmprinter.def.json
msgctxt "wireframe_enabled label"

View File

@ -1,16 +1,16 @@
# Cura
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: https://github.com/Ultimaker/Cura\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-27 17:27+0200\n"
"PO-Revision-Date: 2017-03-30 12:10+0300\n"
"Last-Translator: Ruslan Popov <ruslan.popov@gmail.com>\n"
"Language-Team: Ruslan Popov <ruslan.popov@gmail.com>\n"
"Language-Team: \n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"

View File

@ -1,17 +1,12 @@
# Cura JSON setting files
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-03-28 04:33+0300\n"
"Language-Team: Ruslan Popov <ruslan.popov@gmail.com>\n"
"Language: ru\n"
"PO-Revision-Date: 2017-01-08 04:33+0300\n"
"Last-Translator: Ruslan Popov <ruslan.popov@gmail.com>\n"
"Language-Team: \n"
"Language: ru_RU\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

View File

@ -1,17 +1,12 @@
# Cura JSON setting files
# Copyright (C) 2017 Ultimaker
# This file is distributed under the same license as the Cura package.
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Cura 2.5\n"
"Report-Msgid-Bugs-To: http://github.com/Ultimaker/Cura\n"
"Project-Id-Version: Uranium json setting files\n"
"Report-Msgid-Bugs-To: http://github.com/ultimaker/uranium\n"
"POT-Creation-Date: 2017-03-27 17:27+0000\n"
"PO-Revision-Date: 2017-03-28 04:41+0300\n"
"Language-Team: Ruslan Popov <ruslan.popov@gmail.com>\n"
"Language: ru\n"
"PO-Revision-Date: 2017-03-30 15:05+0300\n"
"Last-Translator: Ruslan Popov <ruslan.popov@gmail.com>\n"
"Language-Team: \n"
"Language: ru_RU\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

Binary file not shown.

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