Merge branch 'master' into feature_support_bottoms

This commit is contained in:
Tim Kuipers 2016-08-02 12:46:44 +02:00
commit 401e302ad5
43 changed files with 564 additions and 316 deletions

View file

@ -287,5 +287,5 @@ class ConvexHullDecorator(SceneNodeDecorator):
_affected_settings = [ _affected_settings = [
"adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", "adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers",
"raft_surface_thickness", "raft_airgap", "print_sequence", "raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence",
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance"] "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance"]

View file

@ -77,7 +77,7 @@ class ConvexHullNode(SceneNode):
convex_hull_head = self._node.callDecoration("getConvexHullHead") convex_hull_head = self._node.callDecoration("getConvexHullHead")
if convex_hull_head: if convex_hull_head:
convex_hull_head_builder = MeshBuilder() convex_hull_head_builder = MeshBuilder()
convex_hull_head_builder.addConvexPolygon(convex_hull_head.getPoints(), self._mesh_height-self._thickness) convex_hull_head_builder.addConvexPolygon(convex_hull_head.getPoints(), self._mesh_height - self._thickness)
self._convex_hull_head_mesh = convex_hull_head_builder.build() self._convex_hull_head_mesh = convex_hull_head_builder.build()
if not node: if not node:

View file

@ -203,7 +203,7 @@ class CuraApplication(QtApplication):
"dialog_profile_path", "dialog_profile_path",
"dialog_material_path"]: "dialog_material_path"]:
Preferences.getInstance().addPreference("local_file/%s" % key, "~/") Preferences.getInstance().addPreference("local_file/%s" % key, os.path.expanduser("~/"))
Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode") Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode")
@ -555,12 +555,18 @@ class CuraApplication(QtApplication):
def deleteSelection(self): def deleteSelection(self):
if not self.getController().getToolsEnabled(): if not self.getController().getToolsEnabled():
return return
removed_group_nodes = []
op = GroupedOperation() op = GroupedOperation()
nodes = Selection.getAllSelectedObjects() nodes = Selection.getAllSelectedObjects()
for node in nodes: for node in nodes:
op.addOperation(RemoveSceneNodeOperation(node)) 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() op.push()
pass pass
@ -586,8 +592,7 @@ class CuraApplication(QtApplication):
op.push() op.push()
if group_node: if group_node:
if len(group_node.getChildren()) == 1 and group_node.callDecoration("isGroup"): if len(group_node.getChildren()) == 1 and group_node.callDecoration("isGroup"):
group_node.getChildren()[0].translate(group_node.getPosition()) op.addOperation(SetParentOperation(group_node.getChildren()[0], group_node.getParent()))
group_node.getChildren()[0].setParent(group_node.getParent())
op = RemoveSceneNodeOperation(group_node) op = RemoveSceneNodeOperation(group_node)
op.push() op.push()
@ -629,6 +634,22 @@ class CuraApplication(QtApplication):
op = SetTransformOperation(node, Vector()) op = SetTransformOperation(node, Vector())
op.push() op.push()
## Select all nodes containing mesh data in the scene.
@pyqtSlot()
def selectAll(self):
if not self.getController().getToolsEnabled():
return
Selection.clear()
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if type(node) is not SceneNode:
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
Selection.add(node)
## Delete all nodes containing mesh data in the scene. ## Delete all nodes containing mesh data in the scene.
@pyqtSlot() @pyqtSlot()
def deleteAll(self): def deleteAll(self):
@ -652,6 +673,7 @@ class CuraApplication(QtApplication):
op.addOperation(RemoveSceneNodeOperation(node)) op.addOperation(RemoveSceneNodeOperation(node))
op.push() op.push()
Selection.clear()
## Reset all translation on nodes with mesh data. ## Reset all translation on nodes with mesh data.
@pyqtSlot() @pyqtSlot()

View file

@ -57,9 +57,10 @@ class MachineActionManager(QObject):
def addRequiredAction(self, definition_id, action_key): def addRequiredAction(self, definition_id, action_key):
if action_key in self._machine_actions: if action_key in self._machine_actions:
if definition_id in self._required_actions: if definition_id in self._required_actions:
self._required_actions[definition_id] |= {self._machine_actions[action_key]} if self._machine_actions[action_key] not in self._required_actions[definition_id]:
self._required_actions[definition_id].append(self._machine_actions[action_key])
else: else:
self._required_actions[definition_id] = {self._machine_actions[action_key]} self._required_actions[definition_id] = [self._machine_actions[action_key]]
else: else:
raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id)) raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id))
@ -67,9 +68,10 @@ class MachineActionManager(QObject):
def addSupportedAction(self, definition_id, action_key): def addSupportedAction(self, definition_id, action_key):
if action_key in self._machine_actions: if action_key in self._machine_actions:
if definition_id in self._supported_actions: if definition_id in self._supported_actions:
self._supported_actions[definition_id] |= {self._machine_actions[action_key]} if self._machine_actions[action_key] not in self._supported_actions[definition_id]:
self._supported_actions[definition_id].append(self._machine_actions[action_key])
else: else:
self._supported_actions[definition_id] = {self._machine_actions[action_key]} self._supported_actions[definition_id] = [self._machine_actions[action_key]]
else: else:
Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id) Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id)

View file

@ -244,6 +244,7 @@ class ContainerManager(QObject):
if not type_name or entry["type"] == type_name: if not type_name or entry["type"] == type_name:
filters.append(filter_string) filters.append(filter_string)
filters.append("All Files (*)")
return filters return filters
## Export a container to a file ## Export a container to a file
@ -280,6 +281,9 @@ class ContainerManager(QObject):
return { "status": "error", "message": "Container not found"} return { "status": "error", "message": "Container not found"}
container = containers[0] container = containers[0]
if UM.Platform.isOSX() and "." in file_url:
file_url = file_url[:file_url.rfind(".")]
for suffix in mime_type.suffixes: for suffix in mime_type.suffixes:
if file_url.endswith(suffix): if file_url.endswith(suffix):
break break
@ -301,7 +305,7 @@ class ContainerManager(QObject):
with UM.SaveFile(file_url, "w") as f: with UM.SaveFile(file_url, "w") as f:
f.write(contents) f.write(contents)
return { "status": "success", "message": "Succesfully exported container"} return { "status": "success", "message": "Succesfully exported container", "path": file_url}
## Imports a profile from a file ## Imports a profile from a file
# #
@ -371,11 +375,20 @@ class ContainerManager(QObject):
"container": container_type "container": container_type
} }
suffix_list = "*." + mime_type.preferredSuffix suffix = mime_type.preferredSuffix
if UM.Platform.isOSX() and "." in suffix:
# OSX's File dialog is stupid and does not allow selecting files with a . in its name
suffix = suffix[suffix.index(".") + 1:]
suffix_list = "*." + suffix
for suffix in mime_type.suffixes: for suffix in mime_type.suffixes:
if suffix == mime_type.preferredSuffix: if suffix == mime_type.preferredSuffix:
continue continue
if UM.Platform.isOSX() and "." in suffix:
# OSX's File dialog is stupid and does not allow selecting files with a . in its name
suffix = suffix[suffix.index("."):]
suffix_list += ", *." + suffix suffix_list += ", *." + suffix
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list) name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)

View file

@ -26,7 +26,7 @@ class MachineManager(QObject):
self._global_container_stack = None self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
self._global_stack_valid = None self._active_stack_valid = None
self._onGlobalContainerChanged() self._onGlobalContainerChanged()
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
@ -74,7 +74,7 @@ class MachineManager(QObject):
activeStackChanged = pyqtSignal() activeStackChanged = pyqtSignal()
globalValueChanged = pyqtSignal() # Emitted whenever a value inside global container is changed. globalValueChanged = pyqtSignal() # Emitted whenever a value inside global container is changed.
globalValidationChanged = pyqtSignal() # Emitted whenever a validation inside global container is changed activeValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed
blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
@ -281,16 +281,17 @@ class MachineManager(QObject):
self._global_container_stack.getTop().setProperty(key, "value", new_value) self._global_container_stack.getTop().setProperty(key, "value", new_value)
if property_name == "validationState": if property_name == "validationState":
if self._global_stack_valid: if self._active_stack_valid:
changed_validation_state = self._active_container_stack.getProperty(key, property_name) changed_validation_state = self._active_container_stack.getProperty(key, property_name)
if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError): if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError):
self._global_stack_valid = False self._active_stack_valid = False
self.globalValidationChanged.emit() self.activeValidationChanged.emit()
else: else:
has_errors = self._checkStackForErrors(self._active_container_stack) has_errors = self._checkStackForErrors(self._active_container_stack)
if not has_errors: if not has_errors:
self._global_stack_valid = True self._active_stack_valid = True
self.globalValidationChanged.emit() self.activeValidationChanged.emit()
def _onGlobalContainerChanged(self): def _onGlobalContainerChanged(self):
if self._global_container_stack: if self._global_container_stack:
self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged) self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged)
@ -313,8 +314,6 @@ class MachineManager(QObject):
self._global_container_stack.nameChanged.connect(self._onMachineNameChanged) self._global_container_stack.nameChanged.connect(self._onMachineNameChanged)
self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged) self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged)
self._global_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged) self._global_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
self._global_stack_valid = not self._checkStackForErrors(self._global_container_stack)
self.globalValidationChanged.emit()
material = self._global_container_stack.findContainer({"type": "material"}) material = self._global_container_stack.findContainer({"type": "material"})
material.nameChanged.connect(self._onMaterialNameChanged) material.nameChanged.connect(self._onMaterialNameChanged)
@ -332,6 +331,8 @@ class MachineManager(QObject):
self._active_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged) self._active_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged)
else: else:
self._active_container_stack = self._global_container_stack self._active_container_stack = self._global_container_stack
self._active_stack_valid = not self._checkStackForErrors(self._active_container_stack)
self.activeValidationChanged.emit()
def _onInstanceContainersChanged(self, container): def _onInstanceContainersChanged(self, container):
container_type = container.getMetaDataEntry("type") container_type = container.getMetaDataEntry("type")
@ -436,11 +437,11 @@ class MachineManager(QObject):
return len(user_settings) != 0 return len(user_settings) != 0
## Check if the global profile does not contain error states ## Check if the global profile does not contain error states
# Note that the _global_stack_valid is cached due to performance issues # Note that the _active_stack_valid is cached due to performance issues
# Calling _checkStackForErrors on every change is simply too expensive # Calling _checkStackForErrors on every change is simply too expensive
@pyqtProperty(bool, notify = globalValidationChanged) @pyqtProperty(bool, notify = activeValidationChanged)
def isGlobalStackValid(self): def isActiveStackValid(self):
return bool(self._global_stack_valid) return bool(self._active_stack_valid)
@pyqtProperty(str, notify = activeStackChanged) @pyqtProperty(str, notify = activeStackChanged)
def activeUserProfileId(self): def activeUserProfileId(self):
@ -552,7 +553,7 @@ class MachineManager(QObject):
return "" return ""
@pyqtSlot(str, str) @pyqtSlot(str, str)
def renameQualityContainer(self, container_id, nbalew_name): def renameQualityContainer(self, container_id, new_name):
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality") containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality")
if containers: if containers:
new_name = self._createUniqueName("quality", containers[0].getName(), new_name, new_name = self._createUniqueName("quality", containers[0].getName(), new_name,
@ -743,10 +744,7 @@ class MachineManager(QObject):
# If the machine that is being removed is the currently active machine, set another machine as the active machine. # 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) activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id)
stacks = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = machine_id) ExtruderManager.getInstance().removeMachineExtruders(machine_id)
if not stacks:
return
ExtruderManager.getInstance().removeMachineExtruders(stacks[0].getBottom().getId())
containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "user", machine = machine_id) containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "user", machine = machine_id)
for container in containers: for container in containers:

View file

@ -37,10 +37,15 @@ class SettingOverrideDecorator(SceneNodeDecorator):
self._updateNextStack() self._updateNextStack()
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
print("deepcopy settingoverridedecorator")
## Create a fresh decorator object ## Create a fresh decorator object
deep_copy = SettingOverrideDecorator() deep_copy = SettingOverrideDecorator()
## Copy the instance ## Copy the instance
deep_copy._instance = copy.deepcopy(self._instance, memo) deep_copy._instance = copy.deepcopy(self._instance, memo)
# Properly set the right extruder on the copy
deep_copy.setActiveExtruder(self._extruder_stack)
## Set the copied instance as the first (and only) instance container of the stack. ## Set the copied instance as the first (and only) instance container of the stack.
deep_copy._stack.replaceContainer(0, deep_copy._instance) deep_copy._stack.replaceContainer(0, deep_copy._instance)
return deep_copy return deep_copy
@ -75,6 +80,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# \param extruder_stack_id The new extruder stack to print with. # \param extruder_stack_id The new extruder stack to print with.
def setActiveExtruder(self, extruder_stack_id): def setActiveExtruder(self, extruder_stack_id):
self._extruder_stack = extruder_stack_id self._extruder_stack = extruder_stack_id
self._updateNextStack()
self.activeExtruderChanged.emit() self.activeExtruderChanged.emit()
def getStack(self): def getStack(self):

View file

@ -9,18 +9,20 @@ from UM.Math.Vector import Vector
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Scene.GroupDecorator import GroupDecorator from UM.Scene.GroupDecorator import GroupDecorator
from UM.Math.Quaternion import Quaternion from UM.Math.Quaternion import Quaternion
from UM.Job import Job from UM.Job import Job
import math import math
import zipfile import zipfile
import xml.etree.ElementTree as ET try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes! ## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes!
class ThreeMFReader(MeshReader): class ThreeMFReader(MeshReader):
def __init__(self): def __init__(self):
super(ThreeMFReader, self).__init__() super().__init__()
self._supported_extensions = [".3mf"] self._supported_extensions = [".3mf"]
self._namespaces = { self._namespaces = {
@ -116,4 +118,10 @@ class ThreeMFReader(MeshReader):
except Exception as e: except Exception as e:
Logger.log("e", "exception occured in 3mf reader: %s", e) Logger.log("e", "exception occured in 3mf reader: %s", e)
try: # Selftest - There might be more functions that should fail
boundingBox = result.getBoundingBox()
boundingBox.isValid()
except:
return None
return result return result

View file

@ -13,6 +13,7 @@ message Slice
repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another
SettingList global_settings = 2; // The global settings used for the whole print job SettingList global_settings = 2; // The global settings used for the whole print job
repeated Extruder extruders = 3; // The settings sent to each extruder object repeated Extruder extruders = 3; // The settings sent to each extruder object
repeated SettingExtruder global_inherits_stack = 4; //From which stack the setting would inherit if not defined in a stack.
} }
message Extruder message Extruder
@ -108,8 +109,14 @@ message Setting {
bytes value = 2; // The value of the setting bytes value = 2; // The value of the setting
} }
message SettingExtruder {
string name = 1; //The setting key.
int32 extruder = 2; //From which extruder stack the setting should inherit.
}
message GCodePrefix { message GCodePrefix {
bytes data = 2; // Header string to be prenpended before the rest of the gcode sent from the engine bytes data = 2; //Header string to be prepended before the rest of the g-code sent from the engine.
} }
message SlicingFinished { message SlicingFinished {

View file

@ -13,9 +13,11 @@ from UM.Resources import Resources
from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then. from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then.
from UM.Platform import Platform from UM.Platform import Platform
import cura.Settings import cura.Settings
from cura.OneAtATimeIterator import OneAtATimeIterator from cura.OneAtATimeIterator import OneAtATimeIterator
from cura.Settings.ExtruderManager import ExtruderManager
from . import ProcessSlicedLayersJob from . import ProcessSlicedLayersJob
from . import ProcessGCodeJob from . import ProcessGCodeJob
from . import StartSliceJob from . import StartSliceJob
@ -40,7 +42,8 @@ class CuraEngineBackend(Backend):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# Find out where the engine is located, and how it is called. This depends on how Cura is packaged and which OS we are running on. # Find out where the engine is located, and how it is called.
# This depends on how Cura is packaged and which OS we are running on.
default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", "CuraEngine") default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", "CuraEngine")
if hasattr(sys, "frozen"): if hasattr(sys, "frozen"):
default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "CuraEngine") default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "CuraEngine")
@ -59,7 +62,7 @@ class CuraEngineBackend(Backend):
self._stored_layer_data = [] self._stored_layer_data = []
self._stored_optimized_layer_data = [] self._stored_optimized_layer_data = []
#Triggers for when to (re)start slicing: # Triggers for when to (re)start slicing:
self._global_container_stack = None self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged() self._onGlobalStackChanged()
@ -68,15 +71,15 @@ class CuraEngineBackend(Backend):
cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
self._onActiveExtruderChanged() self._onActiveExtruderChanged()
#When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
#This timer will group them up, and only slice for the last setting changed signal. # This timer will group them up, and only slice for the last setting changed signal.
#TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction. # TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
self._change_timer = QTimer() self._change_timer = QTimer()
self._change_timer.setInterval(500) self._change_timer.setInterval(500)
self._change_timer.setSingleShot(True) self._change_timer.setSingleShot(True)
self._change_timer.timeout.connect(self.slice) self._change_timer.timeout.connect(self.slice)
#Listeners for receiving messages from the back-end. # Listeners for receiving messages from the back-end.
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
self._message_handlers["cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage self._message_handlers["cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage
self._message_handlers["cura.proto.Progress"] = self._onProgressMessage self._message_handlers["cura.proto.Progress"] = self._onProgressMessage
@ -86,19 +89,19 @@ class CuraEngineBackend(Backend):
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
self._start_slice_job = None self._start_slice_job = None
self._slicing = False #Are we currently slicing? self._slicing = False # Are we currently slicing?
self._restart = False #Back-end is currently restarting? self._restart = False # Back-end is currently restarting?
self._enabled = True #Should we be slicing? Slicing might be paused when, for instance, the user is dragging the mesh around. self._enabled = True # Should we be slicing? Slicing might be paused when, for instance, the user is dragging the mesh around.
self._always_restart = True #Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
self._process_layers_job = None #The currently active job to process layers, or None if it is not processing layers. self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers.
self._backend_log_max_lines = 20000 # Maximum number of lines to buffer self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
self._error_message = None #Pop-up message that shows errors. self._error_message = None # Pop-up message that shows errors.
self.backendQuit.connect(self._onBackendQuit) self.backendQuit.connect(self._onBackendQuit)
self.backendConnected.connect(self._onBackendConnected) self.backendConnected.connect(self._onBackendConnected)
#When a tool operation is in progress, don't slice. So we need to listen for tool operations. # When a tool operation is in progress, don't slice. So we need to listen for tool operations.
Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted) Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted)
Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped) Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped)
@ -117,9 +120,10 @@ class CuraEngineBackend(Backend):
# \return list of commands and args / parameters. # \return list of commands and args / parameters.
def getEngineCommand(self): def getEngineCommand(self):
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json") json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
return [Preferences.getInstance().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, "-vv"] return [Preferences.getInstance().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
## Emitted when we get a message containing print duration and material amount. This also implies the slicing has finished. ## Emitted when we get a message containing print duration and material amount.
# This also implies the slicing has finished.
# \param time The amount of time the print will take. # \param time The amount of time the print will take.
# \param material_amount The amount of material the print will use. # \param material_amount The amount of material the print will use.
printDurationMessage = Signal() printDurationMessage = Signal()
@ -133,7 +137,7 @@ class CuraEngineBackend(Backend):
## Perform a slice of the scene. ## Perform a slice of the scene.
def slice(self): def slice(self):
self._slice_start_time = time() self._slice_start_time = time()
if not self._enabled or not self._global_container_stack: #We shouldn't be slicing. if not self._enabled or not self._global_container_stack: # We shouldn't be slicing.
# try again in a short time # try again in a short time
self._change_timer.start() self._change_timer.start()
return return
@ -143,10 +147,10 @@ class CuraEngineBackend(Backend):
self._stored_layer_data = [] self._stored_layer_data = []
self._stored_optimized_layer_data = [] self._stored_optimized_layer_data = []
if self._slicing: #We were already slicing. Stop the old job. if self._slicing: # We were already slicing. Stop the old job.
self._terminate() self._terminate()
if self._process_layers_job: #We were processing layers. Stop that, the layers are going to change soon. if self._process_layers_job: # We were processing layers. Stop that, the layers are going to change soon.
self._process_layers_job.abort() self._process_layers_job.abort()
self._process_layers_job = None self._process_layers_job = None
@ -183,7 +187,7 @@ class CuraEngineBackend(Backend):
self._process.terminate() self._process.terminate()
Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait())
self._process = None self._process = None
except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this. except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this.
Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e)) Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e))
## Event handler to call when the job to initiate the slicing process is ## Event handler to call when the job to initiate the slicing process is
@ -204,7 +208,7 @@ class CuraEngineBackend(Backend):
if job.getResult() == StartSliceJob.StartJobResult.SettingError: if job.getResult() == StartSliceJob.StartJobResult.SettingError:
if Application.getInstance().getPlatformActivity: if Application.getInstance().getPlatformActivity:
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors."), lifetime = 10) self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors."))
self._error_message.show() self._error_message.show()
self.backendStateChange.emit(BackendState.Error) self.backendStateChange.emit(BackendState.Error)
else: else:
@ -213,7 +217,7 @@ class CuraEngineBackend(Backend):
if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice: if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice:
if Application.getInstance().getPlatformActivity: if Application.getInstance().getPlatformActivity:
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. No suitable objects found."), lifetime = 10) self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. No suitable objects found."))
self._error_message.show() self._error_message.show()
self.backendStateChange.emit(BackendState.Error) self.backendStateChange.emit(BackendState.Error)
else: else:
@ -265,7 +269,7 @@ class CuraEngineBackend(Backend):
# \param instance The setting instance that has changed. # \param instance The setting instance that has changed.
# \param property The property of the setting instance that has changed. # \param property The property of the setting instance that has changed.
def _onSettingChanged(self, instance, property): def _onSettingChanged(self, instance, property):
if property == "value": #Only reslice if the value has changed. if property == "value": # Only reslice if the value has changed.
self._onChanged() self._onChanged()
## Called when a sliced layer data message is received from the engine. ## Called when a sliced layer data message is received from the engine.
@ -316,7 +320,7 @@ class CuraEngineBackend(Backend):
## Called when a print time message is received from the engine. ## Called when a print time message is received from the engine.
# #
# \param message The protobuf message containing the print time and # \param message The protobuff message containing the print time and
# material amount per extruder # material amount per extruder
def _onPrintTimeMaterialEstimates(self, message): def _onPrintTimeMaterialEstimates(self, message):
material_amounts = [] material_amounts = []
@ -351,8 +355,8 @@ class CuraEngineBackend(Backend):
# #
# \param tool The tool that the user is using. # \param tool The tool that the user is using.
def _onToolOperationStarted(self, tool): def _onToolOperationStarted(self, tool):
self._terminate() # Do not continue slicing once a tool has started self._terminate() # Do not continue slicing once a tool has started
self._enabled = False # Do not reslice when a tool is doing it's 'thing' self._enabled = False # Do not reslice when a tool is doing it's 'thing'
## Called when the user stops using some tool. ## Called when the user stops using some tool.
# #
@ -360,13 +364,13 @@ class CuraEngineBackend(Backend):
# #
# \param tool The tool that the user was using. # \param tool The tool that the user was using.
def _onToolOperationStopped(self, tool): def _onToolOperationStopped(self, tool):
self._enabled = True # Tool stop, start listening for changes again. self._enabled = True # Tool stop, start listening for changes again.
## Called when the user changes the active view mode. ## Called when the user changes the active view mode.
def _onActiveViewChanged(self): def _onActiveViewChanged(self):
if Application.getInstance().getController().getActiveView(): if Application.getInstance().getController().getActiveView():
view = Application.getInstance().getController().getActiveView() view = Application.getInstance().getController().getActiveView()
if view.getPluginId() == "LayerView": #If switching to layer view, we should process the layers if that hasn't been done yet. if view.getPluginId() == "LayerView": # If switching to layer view, we should process the layers if that hasn't been done yet.
self._layer_view_active = True self._layer_view_active = True
# There is data and we're not slicing at the moment # There is data and we're not slicing at the moment
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment. # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
@ -391,22 +395,35 @@ class CuraEngineBackend(Backend):
if self._global_container_stack: if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged) self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged)
self._global_container_stack.containersChanged.disconnect(self._onChanged) self._global_container_stack.containersChanged.disconnect(self._onChanged)
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
if extruders:
for extruder in extruders:
extruder.propertyChanged.disconnect(self._onSettingChanged)
self._global_container_stack = Application.getInstance().getGlobalContainerStack() self._global_container_stack = Application.getInstance().getGlobalContainerStack()
if self._global_container_stack: if self._global_container_stack:
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) #Note: Only starts slicing when the value changed. self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
self._global_container_stack.containersChanged.connect(self._onChanged) self._global_container_stack.containersChanged.connect(self._onChanged)
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
if extruders:
for extruder in extruders:
extruder.propertyChanged.connect(self._onSettingChanged)
self._onActiveExtruderChanged() self._onActiveExtruderChanged()
self._onChanged() self._onChanged()
def _onActiveExtruderChanged(self): def _onActiveExtruderChanged(self):
if self._global_container_stack:
# Connect all extruders of the active machine. This might cause a few connects that have already happend,
# but that shouldn't cause issues as only new / unique connections are added.
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
if extruders:
for extruder in extruders:
extruder.propertyChanged.connect(self._onSettingChanged)
if self._active_extruder_stack: if self._active_extruder_stack:
self._active_extruder_stack.propertyChanged.disconnect(self._onSettingChanged)
self._active_extruder_stack.containersChanged.disconnect(self._onChanged) self._active_extruder_stack.containersChanged.disconnect(self._onChanged)
self._active_extruder_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack() self._active_extruder_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack()
if self._active_extruder_stack: if self._active_extruder_stack:
self._active_extruder_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
self._active_extruder_stack.containersChanged.connect(self._onChanged) self._active_extruder_stack.containersChanged.connect(self._onChanged)

View file

@ -56,10 +56,9 @@ class ProcessSlicedLayersJob(Job):
## Remove old layer data (if any) ## Remove old layer data (if any)
for node in DepthFirstIterator(self._scene.getRoot()): for node in DepthFirstIterator(self._scene.getRoot()):
if type(node) is SceneNode and node.getMeshData(): if node.callDecoration("getLayerData"):
if node.callDecoration("getLayerData"): node.getParent().removeChild(node)
self._scene.getRoot().removeChild(node) break
Job.yieldThread()
if self._abort_requested: if self._abort_requested:
if self._progress: if self._progress:
self._progress.hide() self._progress.hide()
@ -74,7 +73,7 @@ class ProcessSlicedLayersJob(Job):
# instead simply offset all other layers so the lowest layer is always 0. # instead simply offset all other layers so the lowest layer is always 0.
min_layer_number = 0 min_layer_number = 0
for layer in self._layers: for layer in self._layers:
if(layer.id < min_layer_number): if layer.id < min_layer_number:
min_layer_number = layer.id min_layer_number = layer.id
current_layer = 0 current_layer = 0
@ -98,7 +97,7 @@ class ProcessSlicedLayersJob(Job):
points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array
if polygon.point_type == 0: # Point2D if polygon.point_type == 0: # Point2D
points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly.
else: # Point3D else: # Point3D
points = points.reshape((-1,3)) points = points.reshape((-1,3))
line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array
@ -108,15 +107,14 @@ class ProcessSlicedLayersJob(Job):
# This uses manual array creation + copy rather than numpy.insert since this is # This uses manual array creation + copy rather than numpy.insert since this is
# faster. # faster.
new_points = numpy.empty((len(points), 3), numpy.float32) new_points = numpy.empty((len(points), 3), numpy.float32)
if polygon.point_type == 0: # Point2D if polygon.point_type == 0: # Point2D
new_points[:,0] = points[:,0] new_points[:, 0] = points[:, 0]
new_points[:,1] = layer.height/1000 # layer height value is in backend representation new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation
new_points[:,2] = -points[:,1] new_points[:, 2] = -points[:, 1]
else: # Point3D else: # Point3D
new_points[:,0] = points[:,0] new_points[:, 0] = points[:, 0]
new_points[:,1] = points[:,2] new_points[:, 1] = points[:, 2]
new_points[:,2] = -points[:,1] new_points[:, 2] = -points[:, 1]
this_poly = LayerPolygon.LayerPolygon(layer_data, extruder, line_types, new_points, line_widths) this_poly = LayerPolygon.LayerPolygon(layer_data, extruder, line_types, new_points, line_widths)
this_poly.buildCache() this_poly.buildCache()
@ -185,4 +183,3 @@ class ProcessSlicedLayersJob(Job):
else: else:
if self._progress: if self._progress:
self._progress.hide() self._progress.hide()

View file

@ -13,6 +13,7 @@ from UM.Scene.SceneNode import SceneNode
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Settings.Validator import ValidatorState from UM.Settings.Validator import ValidatorState
from UM.Settings.SettingRelation import RelationType
from cura.OneAtATimeIterator import OneAtATimeIterator from cura.OneAtATimeIterator import OneAtATimeIterator
@ -24,6 +25,7 @@ class StartJobResult(IntEnum):
SettingError = 3 SettingError = 3
NothingToSlice = 4 NothingToSlice = 4
## Formatter class that handles token expansion in start/end gcod ## Formatter class that handles token expansion in start/end gcod
class GcodeStartEndFormatter(Formatter): class GcodeStartEndFormatter(Formatter):
def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class] def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class]
@ -37,6 +39,7 @@ class GcodeStartEndFormatter(Formatter):
Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end gcode", key) Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end gcode", key)
return "{" + str(key) + "}" return "{" + str(key) + "}"
## Job class that builds up the message of scene data to send to CuraEngine. ## Job class that builds up the message of scene data to send to CuraEngine.
class StartSliceJob(Job): class StartSliceJob(Job):
def __init__(self, slice_message): def __init__(self, slice_message):
@ -71,7 +74,7 @@ class StartSliceJob(Job):
return return
# Don't slice if there is a setting with an error value. # Don't slice if there is a setting with an error value.
if self._checkStackForErrors(stack): if not Application.getInstance().getMachineManager().isActiveStackValid:
self.setResult(StartJobResult.SettingError) self.setResult(StartJobResult.SettingError)
return return
@ -123,11 +126,15 @@ class StartSliceJob(Job):
if temp_list: if temp_list:
object_groups.append(temp_list) object_groups.append(temp_list)
# There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
# able to find a possible sequence or because there are no objects on the build plate (or they are outside
# the build volume)
if not object_groups: if not object_groups:
self.setResult(StartJobResult.NothingToSlice) self.setResult(StartJobResult.NothingToSlice)
return return
self._buildGlobalSettingsMessage(stack) self._buildGlobalSettingsMessage(stack)
self._buildGlobalInheritsStackMessage(stack)
for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()): for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
self._buildExtruderMessage(extruder_stack) self._buildExtruderMessage(extruder_stack)
@ -171,6 +178,7 @@ class StartSliceJob(Job):
Logger.logException("w", "Unable to do token replacement on start/end gcode") Logger.logException("w", "Unable to do token replacement on start/end gcode")
return str(value).encode("utf-8") return str(value).encode("utf-8")
## Create extruder message from stack
def _buildExtruderMessage(self, stack): def _buildExtruderMessage(self, stack):
message = self._slice_message.addRepeatedMessage("extruders") message = self._slice_message.addRepeatedMessage("extruders")
message.id = int(stack.getMetaDataEntry("position")) message.id = int(stack.getMetaDataEntry("position"))
@ -209,11 +217,54 @@ class StartSliceJob(Job):
else: else:
setting_message.value = str(value).encode("utf-8") setting_message.value = str(value).encode("utf-8")
## Sends for some settings which extruder they should fallback to if not
# set.
#
# This is only set for settings that have the global_inherits_stack
# property.
#
# \param stack The global stack with all settings, from which to read the
# global_inherits_stack property.
def _buildGlobalInheritsStackMessage(self, stack):
for key in stack.getAllKeys():
extruder = int(stack.getProperty(key, "global_inherits_stack"))
if extruder >= 0: #Set to a specific extruder.
setting_extruder = self._slice_message.addRepeatedMessage("global_inherits_stack")
setting_extruder.name = key
setting_extruder.extruder = extruder
## Check if a node has per object settings and ensure that they are set correctly in the message
# \param node \type{SceneNode} Node to check.
# \param message object_lists message to put the per object settings in
def _handlePerObjectSettings(self, node, message): def _handlePerObjectSettings(self, node, message):
stack = node.callDecoration("getStack") stack = node.callDecoration("getStack")
# Check if the node has a stack attached to it and the stack has any settings in the top container.
if stack: if stack:
for key in stack.getAllKeys(): # Check all settings for relations, so we can also calculate the correct values for dependant settings.
changed_setting_keys = set(stack.getTop().getAllKeys())
for key in stack.getTop().getAllKeys():
instance = stack.getTop().getInstance(key)
self._addRelations(changed_setting_keys, instance.definition.relations)
Job.yieldThread()
# Ensure that the engine is aware what the build extruder is
if stack.getProperty("machine_extruder_count", "value") > 1:
changed_setting_keys.add("extruder_nr")
# Get values for all changed settings
for key in changed_setting_keys:
setting = message.addRepeatedMessage("settings") setting = message.addRepeatedMessage("settings")
setting.name = key setting.name = key
setting.value = str(stack.getProperty(key, "value")).encode("utf-8") setting.value = str(stack.getProperty(key, "value")).encode("utf-8")
Job.yieldThread() Job.yieldThread()
## Recursive function to put all settings that require eachother for value changes in a list
# \param relations_set \type{set} Set of keys (strings) of settings that are influenced
# \param relations list of relation objects that need to be checked.
def _addRelations(self, relations_set, relations):
for relation in filter(lambda r: r.role == "value", relations):
if relation.type == RelationType.RequiresTarget:
continue
relations_set.add(relation.target.key)
self._addRelations(relations_set, relation.target.relations)

View file

@ -9,6 +9,8 @@ import UM.Settings.ContainerRegistry
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from UM.Settings.InstanceContainer import InstanceContainer
import re #For escaping characters in the settings. import re #For escaping characters in the settings.
import json import json
@ -61,6 +63,20 @@ class GCodeWriter(MeshWriter):
return False return False
## Create a new container with container 2 as base and container 1 written over it.
def _createFlattenedContainerInstance(self, instance_container1, instance_container2):
flat_container = InstanceContainer(instance_container2.getName())
flat_container.setDefinition(instance_container2.getDefinition())
flat_container.setMetaData(instance_container2.getMetaData())
for key in instance_container2.getAllKeys():
flat_container.setProperty(key, "value", instance_container2.getProperty(key, "value"))
for key in instance_container1.getAllKeys():
flat_container.setProperty(key, "value", instance_container1.getProperty(key, "value"))
return flat_container
## Serialises a container stack to prepare it for writing at the end of the ## Serialises a container stack to prepare it for writing at the end of the
# g-code. # g-code.
# #
@ -74,38 +90,13 @@ class GCodeWriter(MeshWriter):
prefix_length = len(prefix) prefix_length = len(prefix)
container_with_profile = stack.findContainer({"type": "quality"}) container_with_profile = stack.findContainer({"type": "quality"})
machine_manager = CuraApplication.getInstance().getMachineManager() flat_global_container = self._createFlattenedContainerInstance(stack.getTop(),container_with_profile)
serialized = flat_global_container.serialize()
# Duplicate the current quality profile and update it with any user settings.
flat_quality_id = machine_manager.duplicateContainer(container_with_profile.getId())
flat_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = flat_quality_id)[0]
flat_quality._dirty = False
user_settings = stack.getTop()
# We don't want to send out any signals, so disconnect them.
flat_quality.propertyChanged.disconnectAll()
for key in user_settings.getAllKeys():
flat_quality.setProperty(key, "value", user_settings.getProperty(key, "value"))
serialized = flat_quality.serialize()
data = {"global_quality": serialized} data = {"global_quality": serialized}
manager = ExtruderManager.getInstance()
for extruder in manager.getMachineExtruders(stack.getId()): for extruder in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
extruder_quality = extruder.findContainer({"type": "quality"}) extruder_quality = extruder.findContainer({"type": "quality"})
flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
flat_extruder_quality_id = machine_manager.duplicateContainer(extruder_quality.getId())
flat_extruder_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id=flat_extruder_quality_id)[0]
flat_extruder_quality._dirty = False
extruder_user_settings = extruder.getTop()
# We don't want to send out any signals, so disconnect them.
flat_extruder_quality.propertyChanged.disconnectAll()
for key in extruder_user_settings.getAllKeys():
flat_extruder_quality.setProperty(key, "value", extruder_user_settings.getProperty(key, "value"))
extruder_serialized = flat_extruder_quality.serialize() extruder_serialized = flat_extruder_quality.serialize()
data.setdefault("extruder_quality", []).append(extruder_serialized) data.setdefault("extruder_quality", []).append(extruder_serialized)

View file

@ -11,6 +11,7 @@ from UM.Math.Color import Color
from UM.Mesh.MeshBuilder import MeshBuilder from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Job import Job from UM.Job import Job
from UM.Preferences import Preferences from UM.Preferences import Preferences
from UM.Logger import Logger
from UM.View.RenderBatch import RenderBatch from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL from UM.View.GL.OpenGL import OpenGL
@ -32,9 +33,9 @@ class LayerView(View):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._shader = None self._shader = None
self._selection_shader = None self._ghost_shader = None
self._num_layers = 0 self._num_layers = 0
self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100) self._layer_percentage = 0 # what percentage of layers need to be shown (Slider gives value between 0 - 100)
self._proxy = LayerViewProxy.LayerViewProxy() self._proxy = LayerViewProxy.LayerViewProxy()
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged) self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
self._max_layers = 0 self._max_layers = 0
@ -43,12 +44,14 @@ class LayerView(View):
self._current_layer_jumps = None self._current_layer_jumps = None
self._top_layers_job = None self._top_layers_job = None
self._activity = False self._activity = False
self._old_max_layers = 0
Preferences.getInstance().addPreference("view/top_layer_count", 5) Preferences.getInstance().addPreference("view/top_layer_count", 5)
Preferences.getInstance().addPreference("view/only_show_top_layers", False)
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count")) self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
self._busy = False self._busy = False
def getActivity(self): def getActivity(self):
@ -81,10 +84,9 @@ class LayerView(View):
scene = self.getController().getScene() scene = self.getController().getScene()
renderer = self.getRenderer() renderer = self.getRenderer()
if not self._selection_shader: if not self._ghost_shader:
self._selection_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader")) self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
self._selection_shader.setUniformValue("u_color", Color(32, 32, 32, 128)) self._ghost_shader.setUniformValue("u_color", Color(32, 32, 32, 96))
for node in DepthFirstIterator(scene.getRoot()): for node in DepthFirstIterator(scene.getRoot()):
# We do not want to render ConvexHullNode as it conflicts with the bottom layers. # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
# However, it is somewhat relevant when the node is selected, so do render it then. # However, it is somewhat relevant when the node is selected, so do render it then.
@ -93,14 +95,13 @@ class LayerView(View):
if not node.render(renderer): if not node.render(renderer):
if node.getMeshData() and node.isVisible(): if node.getMeshData() and node.isVisible():
if Selection.isSelected(node): renderer.queueNode(node, transparent = True, shader = self._ghost_shader)
renderer.queueNode(node, transparent = True, shader = self._selection_shader)
layer_data = node.callDecoration("getLayerData") layer_data = node.callDecoration("getLayerData")
if not layer_data: if not layer_data:
continue continue
# Render all layers below a certain number as line mesh instead of vertices. # Render all layers below a certain number as line mesh instead of vertices.
if self._current_layer_num - self._solid_layers > -1: if self._current_layer_num - self._solid_layers > -1 and not self._only_show_top_layers:
start = 0 start = 0
end = 0 end = 0
element_counts = layer_data.getElementCounts() element_counts = layer_data.getElementCounts()
@ -110,13 +111,13 @@ class LayerView(View):
end += counts end += counts
# This uses glDrawRangeElements internally to only draw a certain range of lines. # This uses glDrawRangeElements internally to only draw a certain range of lines.
renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, range = (start, end)) renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, overlay = True, range = (start, end))
if self._current_layer_mesh: if self._current_layer_mesh:
renderer.queueNode(node, mesh = self._current_layer_mesh) renderer.queueNode(node, mesh = self._current_layer_mesh, overlay = True)
if self._current_layer_jumps: if self._current_layer_jumps:
renderer.queueNode(node, mesh = self._current_layer_jumps) renderer.queueNode(node, mesh = self._current_layer_jumps, overlay = True)
def setLayer(self, value): def setLayer(self, value):
if self._current_layer_num != value: if self._current_layer_num != value:
@ -126,14 +127,12 @@ class LayerView(View):
if self._current_layer_num > self._max_layers: if self._current_layer_num > self._max_layers:
self._current_layer_num = self._max_layers self._current_layer_num = self._max_layers
self.resetLayerData()
self._startUpdateTopLayers() self._startUpdateTopLayers()
self.currentLayerNumChanged.emit() self.currentLayerNumChanged.emit()
def calculateMaxLayers(self): def calculateMaxLayers(self):
scene = self.getController().getScene() scene = self.getController().getScene()
renderer = self.getRenderer() # TODO: @UnusedVariable
self._activity = True self._activity = True
self._old_max_layers = self._max_layers self._old_max_layers = self._max_layers
@ -199,7 +198,7 @@ class LayerView(View):
if not job.getResult(): if not job.getResult():
return return
self.resetLayerData() # Reset the layer data only when job is done. Doing it now prevents "blinking" data.
self._current_layer_mesh = job.getResult().get("layers") self._current_layer_mesh = job.getResult().get("layers")
self._current_layer_jumps = job.getResult().get("jumps") self._current_layer_jumps = job.getResult().get("jumps")
self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot()) self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
@ -207,14 +206,15 @@ class LayerView(View):
self._top_layers_job = None self._top_layers_job = None
def _onPreferencesChanged(self, preference): def _onPreferencesChanged(self, preference):
if preference != "view/top_layer_count": if preference != "view/top_layer_count" and preference != "view/only_show_top_layers":
return return
self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count")) self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
self.resetLayerData()
self._startUpdateTopLayers() self._startUpdateTopLayers()
class _CreateTopLayersJob(Job): class _CreateTopLayersJob(Job):
def __init__(self, scene, layer_number, solid_layers): def __init__(self, scene, layer_number, solid_layers):
super().__init__() super().__init__()
@ -242,20 +242,20 @@ class _CreateTopLayersJob(Job):
try: try:
layer = layer_data.getLayer(layer_number).createMesh() layer = layer_data.getLayer(layer_number).createMesh()
except Exception as e: except Exception:
print(e) Logger.logException("w", "An exception occurred while creating layer mesh.")
return return
if not layer or layer.getVertices() is None: if not layer or layer.getVertices() is None:
continue continue
layer_mesh.addIndices(layer_mesh._vertex_count+layer.getIndices()) layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices())
layer_mesh.addVertices(layer.getVertices()) layer_mesh.addVertices(layer.getVertices())
# Scale layer color by a brightness factor based on the current layer number # Scale layer color by a brightness factor based on the current layer number
# This will result in a range of 0.5 - 1.0 to multiply colors by. # This will result in a range of 0.5 - 1.0 to multiply colors by.
brightness = numpy.ones((1,4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0 brightness = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
brightness[0, 3] = 1.0; brightness[0, 3] = 1.0
layer_mesh.addColors(layer.getColors() * brightness) layer_mesh.addColors(layer.getColors() * brightness)
if self._cancel: if self._cancel:
@ -271,7 +271,7 @@ class _CreateTopLayersJob(Job):
if not jump_mesh or jump_mesh.getVertices() is None: if not jump_mesh or jump_mesh.getVertices() is None:
jump_mesh = None jump_mesh = None
self.setResult({ "layers": layer_mesh.build(), "jumps": jump_mesh }) self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
def cancel(self): def cancel(self):
self._cancel = True self._cancel = True

View file

@ -3,6 +3,7 @@
from UM.View.View import View from UM.View.View import View
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.Selection import Selection
from UM.Resources import Resources from UM.Resources import Resources
from UM.Application import Application from UM.Application import Application
from UM.Preferences import Preferences from UM.Preferences import Preferences
@ -57,25 +58,28 @@ class SolidView(View):
# renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(),mode = Renderer.RenderLines) # renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(),mode = Renderer.RenderLines)
uniforms = {} uniforms = {}
if self._extruders_model.rowCount() > 0: if self._extruders_model.rowCount() == 0:
material = Application.getInstance().getGlobalContainerStack().findContainer({ "type": "material" })
material_color = material.getMetaDataEntry("color_code", default = self._extruders_model.defaultColours[0]) if material else self._extruders_model.defaultColours[0]
else:
# Get color to render this mesh in from ExtrudersModel # Get color to render this mesh in from ExtrudersModel
extruder_index = 0 extruder_index = 0
extruder_id = node.callDecoration("getActiveExtruder") extruder_id = node.callDecoration("getActiveExtruder")
if extruder_id: if extruder_id:
extruder_index = max(0, self._extruders_model.find("id", extruder_id)) extruder_index = max(0, self._extruders_model.find("id", extruder_id))
extruder_color = self._extruders_model.getItem(extruder_index)["colour"] material_color = self._extruders_model.getItem(extruder_index)["colour"]
try: try:
# Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
# an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0]) # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])
uniforms["diffuse_color"] = [ uniforms["diffuse_color"] = [
int(extruder_color[1:3], 16) / 255, int(material_color[1:3], 16) / 255,
int(extruder_color[3:5], 16) / 255, int(material_color[3:5], 16) / 255,
int(extruder_color[5:7], 16) / 255, int(material_color[5:7], 16) / 255,
1.0 1.0
] ]
except ValueError: except ValueError:
pass pass
if hasattr(node, "_outside_buildarea"): if hasattr(node, "_outside_buildarea"):
if node._outside_buildarea: if node._outside_buildarea:
@ -84,7 +88,7 @@ class SolidView(View):
renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms) renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms)
else: else:
renderer.queueNode(node, material = self._enabled_shader, uniforms = uniforms) renderer.queueNode(node, material = self._enabled_shader, uniforms = uniforms)
if node.callDecoration("isGroup"): if node.callDecoration("isGroup") and Selection.isSelected(node):
renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = Renderer.RenderLines) renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = Renderer.RenderLines)
def endRendering(self): def endRendering(self):

View file

@ -18,6 +18,11 @@ class BedLevelMachineAction(MachineAction):
pass pass
def _reset(self): def _reset(self):
self._bed_level_position = 0
pass
@pyqtSlot()
def startBedLeveling(self):
self._bed_level_position = 0 self._bed_level_position = 0
printer_output_devices = self._getPrinterOutputDevices() printer_output_devices = self._getPrinterOutputDevices()
if printer_output_devices: if printer_output_devices:
@ -52,4 +57,5 @@ class BedLevelMachineAction(MachineAction):
output_device.moveHead(0, 0, -3) output_device.moveHead(0, 0, -3)
self._bed_level_position += 1 self._bed_level_position += 1
elif self._bed_level_position >= 3: elif self._bed_level_position >= 3:
output_device.sendCommand("M18") # Turn off all motors so the user can move the axes
self.setFinished() self.setFinished()

View file

@ -47,37 +47,37 @@ Cura.MachineAction
text: catalog.i18nc("@label", "For every position; insert a piece of paper under the nozzle and adjust the print bed height. The print bed height is right when the paper is slightly gripped by the tip of the nozzle.") text: catalog.i18nc("@label", "For every position; insert a piece of paper under the nozzle and adjust the print bed height. The print bed height is right when the paper is slightly gripped by the tip of the nozzle.")
} }
Item Row
{ {
id: bedlevelingWrapper id: bedlevelingWrapper
anchors.top: bedlevelingText.bottom anchors.top: bedlevelingText.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
height: skipBedlevelingButton.height width: childrenRect.width
width: bedlevelingButton.width + skipBedlevelingButton.width + UM.Theme.getSize("default_margin").height < bedLevelMachineAction.width ? bedlevelingButton.width + skipBedlevelingButton.width + UM.Theme.getSize("default_margin").height : bedLevelMachineAction.width spacing: UM.Theme.getSize("default_margin").width
Button Button
{ {
id: bedlevelingButton id: startBedLevelingButton
anchors.top: parent.top text: catalog.i18nc("@action:button","Start Bed Leveling")
anchors.left: parent.left
text: catalog.i18nc("@action:button","Move to Next Position");
onClicked: onClicked:
{ {
manager.moveToNextLevelPosition() startBedLevelingButton.visible = false;
bedlevelingButton.visible = true;
checkupMachineAction.heatupHotendStarted = false;
checkupMachineAction.heatupBedStarted = false;
manager.startCheck();
} }
} }
Button Button
{ {
id: skipBedlevelingButton id: bedlevelingButton
anchors.top: parent.width < bedLevelMachineAction.width ? parent.top : bedlevelingButton.bottom text: catalog.i18nc("@action:button","Move to Next Position")
anchors.topMargin: parent.width < bedLevelMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height/2 visible: false
anchors.left: parent.width < bedLevelMachineAction.width ? bedlevelingButton.right : parent.left
anchors.leftMargin: parent.width < bedLevelMachineAction.width ? UM.Theme.getSize("default_margin").width : 0
text: catalog.i18nc("@action:button","Skip bed leveling");
onClicked: onClicked:
{ {
manager.setFinished() manager.moveToNextLevelPosition();
} }
} }
} }

View file

@ -155,6 +155,7 @@ class UMOCheckupMachineAction(MachineAction):
if output_devices: if output_devices:
self._output_device = output_devices[0] self._output_device = output_devices[0]
try: try:
self._output_device.sendCommand("M18") # Turn off all motors so the user can move the axes
self._output_device.startPollEndstop() self._output_device.startPollEndstop()
self._output_device.bedTemperatureChanged.connect(self.bedTemperatureChanged) self._output_device.bedTemperatureChanged.connect(self.bedTemperatureChanged)
self._output_device.hotendTemperaturesChanged.connect(self.hotendTemperatureChanged) self._output_device.hotendTemperaturesChanged.connect(self.hotendTemperatureChanged)

View file

@ -39,38 +39,26 @@ Cura.MachineAction
text: catalog.i18nc("@label", "It's a good idea to do a few sanity checks on your Ultimaker. You can skip this step if you know your machine is functional"); text: catalog.i18nc("@label", "It's a good idea to do a few sanity checks on your Ultimaker. You can skip this step if you know your machine is functional");
} }
Item Row
{ {
id: startStopButtons id: startStopButtons
anchors.top: pageDescription.bottom anchors.top: pageDescription.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
height: childrenRect.height width: childrenRect.width
width: startCheckButton.width + skipCheckButton.width + UM.Theme.getSize("default_margin").height < checkupMachineAction.width ? startCheckButton.width + skipCheckButton.width + UM.Theme.getSize("default_margin").height : checkupMachineAction.width spacing: UM.Theme.getSize("default_margin").width
Button Button
{ {
id: startCheckButton id: startCheckButton
anchors.top: parent.top
anchors.left: parent.left
text: catalog.i18nc("@action:button","Start Printer Check"); text: catalog.i18nc("@action:button","Start Printer Check");
onClicked: onClicked:
{ {
checkupMachineAction.heatupHotendStarted = false checkupMachineAction.heatupHotendStarted = false;
checkupMachineAction.heatupBedStarted = false checkupMachineAction.heatupBedStarted = false;
manager.startCheck() manager.startCheck();
startCheckButton.visible = false;
} }
} }
Button
{
id: skipCheckButton
anchors.top: parent.width < checkupMachineAction.width ? parent.top : startCheckButton.bottom
anchors.topMargin: parent.width < checkupMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height/2
anchors.left: parent.width < checkupMachineAction.width ? startCheckButton.right : parent.left
anchors.leftMargin: parent.width < checkupMachineAction.width ? UM.Theme.getSize("default_margin").width : 0
text: catalog.i18nc("@action:button", "Skip Printer Check");
onClicked: manager.setFinished()
}
} }
Item Item

View file

@ -56,29 +56,21 @@ Cura.MachineAction
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
text: catalog.i18nc("@label", "Cura requires these new features and thus your firmware will most likely need to be upgraded. You can do so now."); text: catalog.i18nc("@label", "Cura requires these new features and thus your firmware will most likely need to be upgraded. You can do so now.");
} }
Item Row
{ {
anchors.top: upgradeText2.bottom anchors.top: upgradeText2.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
width: upgradeButton.width + skipUpgradeButton.width + UM.Theme.getSize("default_margin").height < upgradeFirmwareMachineAction.width ? upgradeButton.width + skipUpgradeButton.width + UM.Theme.getSize("default_margin").height : upgradeFirmwareMachineAction.width width: childrenRect.width
spacing: UM.Theme.getSize("default_margin").width
Button Button
{ {
id: upgradeButton id: upgradeButton
anchors.top: parent.top
anchors.left: parent.left
text: catalog.i18nc("@action:button","Upgrade to Marlin Firmware"); text: catalog.i18nc("@action:button","Upgrade to Marlin Firmware");
onClicked: Cura.USBPrinterManager.updateAllFirmware() onClicked:
} {
Button Cura.USBPrinterManager.updateAllFirmware()
{ }
id: skipUpgradeButton
anchors.top: parent.width < upgradeFirmwareMachineAction.width ? parent.top : upgradeButton.bottom
anchors.topMargin: parent.width < upgradeFirmwareMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height / 2
anchors.left: parent.width < upgradeFirmwareMachineAction.width ? upgradeButton.right : parent.left
anchors.leftMargin: parent.width < upgradeFirmwareMachineAction.width ? UM.Theme.getSize("default_margin").width : 0
text: catalog.i18nc("@action:button", "Skip Upgrade");
onClicked: manager.setFinished()
} }
} }
} }

View file

@ -150,6 +150,8 @@
"label": "Number extruders", "label": "Number extruders",
"description": "Number of extruder trains. An extruder train is the combination of a feeder, bowden tube, and nozzle.", "description": "Number of extruder trains. An extruder train is the combination of a feeder, bowden tube, and nozzle.",
"default_value": 1, "default_value": 1,
"minimum_value": "1",
"maximum_value": "16",
"type": "int", "type": "int",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
@ -1064,6 +1066,7 @@
"unit": "[[mm³,°C]]", "unit": "[[mm³,°C]]",
"type": "str", "type": "str",
"default_value": "[[3.5,200],[7.0,240]]", "default_value": "[[3.5,200],[7.0,240]]",
"enabled": "False",
"comments": "old enabled function: material_flow_dependent_temperature", "comments": "old enabled function: material_flow_dependent_temperature",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
@ -1236,30 +1239,28 @@
"default_value": false, "default_value": false,
"enabled": "retraction_enable", "enabled": "retraction_enable",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true, "settable_per_extruder": true
"children": { },
"retraction_hop_only_when_collides": { "retraction_hop_only_when_collides": {
"label": "Z Hop Only Over Printed Parts", "label": "Z Hop Only Over Printed Parts",
"description": "Only perform a Z Hop when moving over printed parts which cannot be avoided by horizontal motion by Avoid Printed Parts when Traveling.", "description": "Only perform a Z Hop when moving over printed parts which cannot be avoided by horizontal motion by Avoid Printed Parts when Traveling.",
"type": "bool", "type": "bool",
"default_value": false, "default_value": false,
"enabled": "retraction_enable and retraction_hop_enabled and travel_avoid_other_parts", "enabled": "retraction_enable and retraction_hop_enabled and travel_avoid_other_parts",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
}, },
"retraction_hop": { "retraction_hop": {
"label": "Z Hop Height", "label": "Z Hop Height",
"description": "The height difference when performing a Z Hop.", "description": "The height difference when performing a Z Hop.",
"unit": "mm", "unit": "mm",
"type": "float", "type": "float",
"default_value": 1, "default_value": 1,
"minimum_value_warning": "-0.0001", "minimum_value_warning": "-0.0001",
"maximum_value_warning": "10", "maximum_value_warning": "10",
"enabled": "retraction_enable and retraction_hop_enabled", "enabled": "retraction_enable and retraction_hop_enabled",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
}
}
}, },
"material_standby_temperature": "material_standby_temperature":
{ {
@ -1511,14 +1512,44 @@
}, },
"speed_layer_0": { "speed_layer_0": {
"label": "Initial Layer Speed", "label": "Initial Layer Speed",
"description": "The print speed for the initial layer. A lower value is advised to improve adhesion to the build plate.", "description": "The speed for the initial layer. A lower value is advised to improve adhesion to the build plate.",
"unit": "mm/s", "unit": "mm/s",
"type": "float", "type": "float",
"default_value": 30, "default_value": 30,
"minimum_value": "0.1", "minimum_value": "0.1",
"maximum_value": "299792458000", "maximum_value": "299792458000",
"maximum_value_warning": "300", "maximum_value_warning": "300",
"settable_per_mesh": true "settable_per_mesh": true,
"children":
{
"speed_print_layer_0":
{
"label": "Initial Layer Print Speed",
"description": "The speed of printing for the initial layer. A lower value is advised to improve adhesion to the build plate.",
"unit": "mm/s",
"type": "float",
"default_value": 30,
"value": "speed_layer_0",
"minimum_value": "0.1",
"maximum_value": "299792458000",
"maximum_value_warning": "300",
"settable_per_mesh": true
},
"speed_travel_layer_0":
{
"label": "Initial Layer Travel Speed",
"description": "The speed of travel moves in the initial layer. A lower value is advised to prevent pulling previously printed parts away from the build plate.",
"unit": "mm/s",
"type": "float",
"default_value": 60,
"value": "speed_layer_0 * speed_travel / speed_print",
"minimum_value": "0.1",
"maximum_value": "299792458000",
"maximum_value_warning": "300",
"settable_per_extruder": true,
"settable_per_mesh": false
}
}
}, },
"skirt_brim_speed": { "skirt_brim_speed": {
"label": "Skirt/Brim Speed", "label": "Skirt/Brim Speed",
@ -1718,7 +1749,38 @@
"minimum_value_warning": "100", "minimum_value_warning": "100",
"maximum_value_warning": "10000", "maximum_value_warning": "10000",
"enabled": "acceleration_enabled", "enabled": "acceleration_enabled",
"settable_per_mesh": true "settable_per_mesh": true,
"children": {
"acceleration_print_layer_0":
{
"label": "Initial Layer Print Acceleration",
"description": "The acceleration during the printing of the initial layer.",
"unit": "mm/s",
"type": "float",
"default_value": 3000,
"value": "acceleration_layer_0",
"minimum_value": "0.1",
"minimum_value_warning": "100",
"maximum_value_warning": "10000",
"enabled": "acceleration_enabled",
"settable_per_mesh": true
},
"acceleration_travel_layer_0":
{
"label": "Initial Layer Travel Acceleration",
"description": "The acceleration for travel moves in the initial layer.",
"unit": "mm/s",
"type": "float",
"default_value": 3000,
"value": "acceleration_layer_0 * acceleration_travel / acceleration_print",
"minimum_value": "0.1",
"minimum_value_warning": "100",
"maximum_value_warning": "10000",
"enabled": "acceleration_enabled",
"settable_per_extruder": true,
"settable_per_mesh": false
}
}
}, },
"acceleration_skirt_brim": { "acceleration_skirt_brim": {
"label": "Skirt/Brim Acceleration", "label": "Skirt/Brim Acceleration",
@ -1906,7 +1968,38 @@
"minimum_value_warning": "5", "minimum_value_warning": "5",
"maximum_value_warning": "50", "maximum_value_warning": "50",
"enabled": "jerk_enabled", "enabled": "jerk_enabled",
"settable_per_mesh": true "settable_per_mesh": true,
"children": {
"jerk_print_layer_0":
{
"label": "Initial Layer Print Jerk",
"description": "The maximum instantaneous velocity change during the printing of the initial layer.",
"unit": "mm/s",
"type": "float",
"default_value": 20,
"value": "jerk_layer_0",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "jerk_enabled",
"settable_per_mesh": true
},
"jerk_travel_layer_0":
{
"label": "Initial Layer Travel Jerk",
"description": "The acceleration for travel moves in the initial layer.",
"unit": "mm/s",
"type": "float",
"default_value": 20,
"value": "jerk_layer_0 * jerk_travel / jerk_print",
"minimum_value": "0.1",
"minimum_value_warning": "5",
"maximum_value_warning": "50",
"enabled": "jerk_enabled",
"settable_per_extruder": true,
"settable_per_mesh": false
}
}
}, },
"jerk_skirt_brim": { "jerk_skirt_brim": {
"label": "Skirt/Brim Jerk", "label": "Skirt/Brim Jerk",
@ -3378,8 +3471,8 @@
"type": "float", "type": "float",
"minimum_value": "0", "minimum_value": "0",
"maximum_value_warning": "9999", "maximum_value_warning": "9999",
"default_value": 0, "default_value": 10,
"value": "9999 if draft_shield_height_limitation == 'full' and draft_shield_enabled else 0.0", "value": "10",
"enabled": "draft_shield_enabled and draft_shield_height_limitation == \"limited\"", "enabled": "draft_shield_enabled and draft_shield_height_limitation == \"limited\"",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false

View file

@ -54,13 +54,13 @@
"speed_travel": { "default_value": 150 }, "speed_travel": { "default_value": 150 },
"speed_layer_0": { "speed_layer_0": {
"minimum_value": "0.1", "minimum_value": "0.1",
"default": 15.0 "default_value": 15.0
}, },
"infill_overlap": { "default": 10 }, "infill_overlap": { "default_value": 10 },
"cool_fan_enabled": { "default": false }, "cool_fan_enabled": { "default_value": false },
"cool_fan_speed": { "default": 0 }, "cool_fan_speed": { "default_value": 0 },
"skirt_line_count": { "default": 3 }, "skirt_line_count": { "default_value": 3 },
"skirt_gap": { "default": 4 }, "skirt_gap": { "default_value": 4 },
"skirt_brim_minimal_length": { "default": 200 } "skirt_brim_minimal_length": { "default_value": 200 }
} }
} }

View file

@ -7,6 +7,7 @@
"visible": true, "visible": true,
"author": "Calvindog717", "author": "Calvindog717",
"manufacturer": "PrintrBot", "manufacturer": "PrintrBot",
"category": "Other",
"file_formats": "text/x-gcode" "file_formats": "text/x-gcode"
}, },

View file

@ -8,6 +8,7 @@
"author": "Ultimaker", "author": "Ultimaker",
"manufacturer": "Ultimaker", "manufacturer": "Ultimaker",
"category": "Ultimaker", "category": "Ultimaker",
"weight": 3,
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"icon": "icon_ultimaker2.png", "icon": "icon_ultimaker2.png",
"platform": "ultimaker2_platform.obj", "platform": "ultimaker2_platform.obj",
@ -82,10 +83,10 @@
"default_value": 45 "default_value": 45
}, },
"material_print_temperature": { "material_print_temperature": {
"enabled": "False" "enabled": "not (material_flow_dependent_temperature) and machine_gcode_flavor != \"UltiGCode\""
}, },
"material_bed_temperature": { "material_bed_temperature": {
"enabled": "False" "enabled": "machine_heated_bed and machine_gcode_flavor != \"UltiGCode\""
}, },
"machine_max_feedrate_x": { "machine_max_feedrate_x": {
"default_value": 300 "default_value": 300
@ -103,22 +104,22 @@
"default_value": 3000 "default_value": 3000
}, },
"material_diameter": { "material_diameter": {
"enabled": "False" "enabled": "machine_gcode_flavor != \"UltiGCode\""
}, },
"material_flow": { "material_flow": {
"enabled": "False" "enabled": "machine_gcode_flavor != \"UltiGCode\""
}, },
"retraction_amount": { "retraction_amount": {
"enabled": "False" "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\""
}, },
"retraction_speed": { "retraction_speed": {
"enabled": "False" "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\""
}, },
"retraction_retract_speed": { "retraction_retract_speed": {
"enabled": "False" "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\""
}, },
"retraction_prime_speed": { "retraction_prime_speed": {
"enabled": "False" "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\""
} }
} }
} }

View file

@ -7,6 +7,7 @@
"author": "Ultimaker", "author": "Ultimaker",
"manufacturer": "Ultimaker", "manufacturer": "Ultimaker",
"category": "Ultimaker", "category": "Ultimaker",
"weight": 3,
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"icon": "icon_ultimaker2.png", "icon": "icon_ultimaker2.png",
"platform": "ultimaker2_platform.obj", "platform": "ultimaker2_platform.obj",

View file

@ -7,6 +7,7 @@
"author": "Ultimaker", "author": "Ultimaker",
"manufacturer": "Ultimaker", "manufacturer": "Ultimaker",
"category": "Ultimaker", "category": "Ultimaker",
"weight": 2,
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"platform": "ultimaker2_platform.obj", "platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2ExtendedPlusbackplate.png", "platform_texture": "Ultimaker2ExtendedPlusbackplate.png",

View file

@ -7,6 +7,7 @@
"author": "Ultimaker", "author": "Ultimaker",
"manufacturer": "Ultimaker", "manufacturer": "Ultimaker",
"category": "Ultimaker", "category": "Ultimaker",
"weight": 3,
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"icon": "icon_ultimaker2.png", "icon": "icon_ultimaker2.png",
"platform": "ultimaker2go_platform.obj", "platform": "ultimaker2go_platform.obj",

View file

@ -7,6 +7,7 @@
"author": "Ultimaker", "author": "Ultimaker",
"manufacturer": "Ultimaker", "manufacturer": "Ultimaker",
"category": "Ultimaker", "category": "Ultimaker",
"weight": 1,
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"platform": "ultimaker2_platform.obj", "platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2Plusbackplate.png", "platform_texture": "Ultimaker2Plusbackplate.png",

View file

@ -8,6 +8,7 @@
"author": "Ultimaker", "author": "Ultimaker",
"manufacturer": "Ultimaker", "manufacturer": "Ultimaker",
"category": "Ultimaker", "category": "Ultimaker",
"weight": 4,
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"icon": "icon_ultimaker.png", "icon": "icon_ultimaker.png",
"platform": "ultimaker_platform.stl", "platform": "ultimaker_platform.stl",
@ -15,7 +16,7 @@
"preferred_material": "*pla*", "preferred_material": "*pla*",
"preferred_quality": "*normal*", "preferred_quality": "*normal*",
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"], "first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
"supported_actions": ["UMOCheckup", "UpgradeFirmware", "BedLevel", "UMOUpgradeSelection"] "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"]
}, },
"overrides": { "overrides": {

View file

@ -7,6 +7,7 @@
"author": "Ultimaker", "author": "Ultimaker",
"manufacturer": "Ultimaker", "manufacturer": "Ultimaker",
"category": "Ultimaker", "category": "Ultimaker",
"weight": 4,
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"icon": "icon_ultimaker.png", "icon": "icon_ultimaker.png",
"platform": "ultimaker2_platform.obj", "platform": "ultimaker2_platform.obj",

View file

@ -27,6 +27,7 @@ Item
property alias multiplyObject: multiplyObjectAction; property alias multiplyObject: multiplyObjectAction;
property alias selectAll: selectAllAction;
property alias deleteAll: deleteAllAction; property alias deleteAll: deleteAllAction;
property alias reloadAll: reloadAllAction; property alias reloadAll: reloadAllAction;
property alias resetAllTranslation: resetAllTranslationAction; property alias resetAllTranslation: resetAllTranslationAction;
@ -119,7 +120,7 @@ Item
Action Action
{ {
id: updateProfileAction; id: updateProfileAction;
enabled: Cura.MachineManager.isGlobalStackValid && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId) enabled: Cura.MachineManager.isActiveStackValid && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings"); text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings");
onTriggered: Cura.MachineManager.updateQualityContainerFromUserContainer() onTriggered: Cura.MachineManager.updateQualityContainerFromUserContainer()
} }
@ -135,7 +136,7 @@ Item
Action Action
{ {
id: addProfileAction; id: addProfileAction;
enabled: Cura.MachineManager.isGlobalStackValid && Cura.MachineManager.hasUserSettings enabled: Cura.MachineManager.isActiveStackValid && Cura.MachineManager.hasUserSettings
text: catalog.i18nc("@action:inmenu menubar:profile","&Create profile from current settings..."); text: catalog.i18nc("@action:inmenu menubar:profile","&Create profile from current settings...");
} }
@ -230,6 +231,16 @@ Item
iconName: "edit-duplicate" iconName: "edit-duplicate"
} }
Action
{
id: selectAllAction;
text: catalog.i18nc("@action:inmenu menubar:edit","&Select All Objects");
enabled: UM.Controller.toolsEnabled;
iconName: "edit-select-all";
shortcut: "Ctrl+A";
onTriggered: Printer.selectAll();
}
Action Action
{ {
id: deleteAllAction; id: deleteAllAction;

View file

@ -16,12 +16,24 @@ UM.Dialog
{ {
id: base id: base
title: catalog.i18nc("@title:window", "Add Printer") title: catalog.i18nc("@title:window", "Add Printer")
property string activeManufacturer: "Ultimaker"; property bool firstRun: false
property string preferredCategory: "Ultimaker"
property string activeCategory: preferredCategory
onVisibilityChanged:
{
// Reset selection and machine name
if (visible) {
activeCategory = preferredCategory;
machineList.currentIndex = 0;
machineName.text = getMachineName();
}
}
signal machineAdded(string id) signal machineAdded(string id)
function getMachineName() function getMachineName()
{ {
var name = machineList.model.getItem(machineList.currentIndex).name var name = machineList.model.getItem(machineList.currentIndex) != undefined ? machineList.model.getItem(machineList.currentIndex).name : ""
return name return name
} }
@ -36,6 +48,7 @@ UM.Dialog
right: parent.right; right: parent.right;
bottom: parent.bottom; bottom: parent.bottom;
} }
ListView ListView
{ {
id: machineList id: machineList
@ -43,9 +56,12 @@ UM.Dialog
model: UM.DefinitionContainersModel model: UM.DefinitionContainersModel
{ {
id: machineDefinitionsModel id: machineDefinitionsModel
filter: {"visible":true} filter: { "visible": true }
sectionProperty: "category"
preferredSectionValue: preferredCategory
} }
section.property: "manufacturer"
section.property: "section"
section.delegate: Button section.delegate: Button
{ {
text: section text: section
@ -76,16 +92,25 @@ UM.Dialog
sourceSize.width: width sourceSize.width: width
sourceSize.height: width sourceSize.height: width
color: palette.windowText color: palette.windowText
source: base.activeManufacturer == section ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right") source: base.activeCategory == section ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right")
} }
} }
} }
onClicked: onClicked:
{ {
base.activeManufacturer = section; base.activeCategory = section;
machineList.currentIndex = machineList.model.find("manufacturer", section) if (machineList.model.getItem(machineList.currentIndex).section != section) {
machineName.text = getMachineName() // Find the first machine from this section
for(var i = 0; i < sortedMachineDefinitionsModel.count; i++) {
var item = sortedMachineDefinitionsModel.getItem(i);
if (item.section == section) {
machineList.currentIndex = i;
break;
}
}
}
machineName.text = getMachineName();
} }
} }
@ -114,7 +139,7 @@ UM.Dialog
states: State states: State
{ {
name: "collapsed"; name: "collapsed";
when: base.activeManufacturer != model.manufacturer; when: base.activeCategory != model.section;
PropertyChanges { target: machineButton; opacity: 0; height: 0; } PropertyChanges { target: machineButton; opacity: 0; height: 0; }
} }

View file

@ -1,32 +0,0 @@
// Copyright (c) 2015 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.1 as UM
import Cura 1.0 as Cura
import "WizardPages"
UM.Wizard
{
id: base;
title: catalog.i18nc("@title:window", "Add Printer")
// This part is optional
// This part checks whether there is a printer -> if not: some of the functions (delete for example) are disabled
firstRun: false
Component.onCompleted: {
base.appendPage(Qt.resolvedUrl("WizardPages/AddMachine.qml"), catalog.i18nc("@title", "Add Printer"));
base.currentPage = 0;
}
Item {
UM.I18nCatalog { id: catalog; name: "cura"; }
}
}

View file

@ -54,10 +54,7 @@ UM.MainWindow
Keys.onPressed: { Keys.onPressed: {
if (event.key == Qt.Key_Backspace) if (event.key == Qt.Key_Backspace)
{ {
if(objectContextMenu.objectId != 0) Cura.Actions.deleteSelection.trigger()
{
Printer.deleteObject(objectContextMenu.objectId);
}
} }
} }
@ -121,6 +118,7 @@ UM.MainWindow
MenuItem { action: Cura.Actions.undo; } MenuItem { action: Cura.Actions.undo; }
MenuItem { action: Cura.Actions.redo; } MenuItem { action: Cura.Actions.redo; }
MenuSeparator { } MenuSeparator { }
MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.deleteSelection; } MenuItem { action: Cura.Actions.deleteSelection; }
MenuItem { action: Cura.Actions.deleteAll; } MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; } MenuItem { action: Cura.Actions.resetAllTranslation; }
@ -540,6 +538,7 @@ UM.MainWindow
MenuItem { action: Cura.Actions.deleteObject; } MenuItem { action: Cura.Actions.deleteObject; }
MenuItem { action: Cura.Actions.multiplyObject; } MenuItem { action: Cura.Actions.multiplyObject; }
MenuSeparator { } MenuSeparator { }
MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.deleteAll; } MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.reloadAll; } MenuItem { action: Cura.Actions.reloadAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; } MenuItem { action: Cura.Actions.resetAllTranslation; }
@ -592,6 +591,7 @@ UM.MainWindow
Menu Menu
{ {
id: contextMenu; id: contextMenu;
MenuItem { action: Cura.Actions.selectAll; }
MenuItem { action: Cura.Actions.deleteAll; } MenuItem { action: Cura.Actions.deleteAll; }
MenuItem { action: Cura.Actions.reloadAll; } MenuItem { action: Cura.Actions.reloadAll; }
MenuItem { action: Cura.Actions.resetAllTranslation; } MenuItem { action: Cura.Actions.resetAllTranslation; }
@ -677,6 +677,7 @@ UM.MainWindow
id: addMachineDialog id: addMachineDialog
onMachineAdded: onMachineAdded:
{ {
machineActionsWizard.firstRun = addMachineDialog.firstRun
machineActionsWizard.start(id) machineActionsWizard.start(id)
} }
} }

View file

@ -3,6 +3,10 @@ import QtQuick 2.2
Item Item
{ {
id: contentItem id: contentItem
// Point to the dialog containing the displayItem
property var dialog
// Connect the finished property change to completed signal. // Connect the finished property change to completed signal.
property var finished: manager.finished property var finished: manager.finished
onFinishedChanged: if(manager.finished) {completed()} onFinishedChanged: if(manager.finished) {completed()}

View file

@ -30,6 +30,8 @@ Rectangle
return UM.Theme.getColor("status_paused") return UM.Theme.getColor("status_paused")
else if (Cura.MachineManager.printerOutputDevices[0].jobState == "error") else if (Cura.MachineManager.printerOutputDevices[0].jobState == "error")
return UM.Theme.getColor("status_stopped") return UM.Theme.getColor("status_stopped")
else if (Cura.MachineManager.printerOutputDevices[0].jobState == "offline")
return UM.Theme.getColor("status_offline")
else else
return UM.Theme.getColor("text") return UM.Theme.getColor("text")
} }
@ -41,7 +43,10 @@ Rectangle
{ {
if(!printerConnected) if(!printerConnected)
{ {
return catalog.i18nc("@label:", "Please check your printer connections") return catalog.i18nc("@label:", "Not connected to a printer")
} else if(Cura.MachineManager.printerOutputDevices[0].jobState == "offline")
{
return catalog.i18nc("@label:", "Lost connection with the printer")
} else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing") } else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing")
{ {
return catalog.i18nc("@label:", "Printing...") return catalog.i18nc("@label:", "Printing...")

View file

@ -192,6 +192,7 @@ UM.PreferencesPage
} }
} }
UM.TooltipArea { UM.TooltipArea {
width: childrenRect.width; width: childrenRect.width;
height: childrenRect.height; height: childrenRect.height;
@ -215,6 +216,19 @@ UM.PreferencesPage
} }
} }
} }
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
text: catalog.i18nc("@info:tooltip", "Should only the top layers be displayed in layerview?")
CheckBox
{
id: topLayersOnlyCheckbox
text: catalog.i18nc("@option:check", "Only display top layer(s) in layer view")
checked: boolCheck(UM.Preferences.getValue("view/only_show_top_layers"))
onCheckedChanged: UM.Preferences.setValue("view/only_show_top_layers", checked)
}
}
Item Item
{ {

View file

@ -83,16 +83,17 @@ UM.ManagementPage
Repeater Repeater
{ {
id: machineActionRepeater id: machineActionRepeater
model: Cura.MachineActionManager.getSupportedActions(Cura.MachineManager.getDefinitionByMachineId(base.currentItem.id)) model: base.currentItem ? Cura.MachineActionManager.getSupportedActions(Cura.MachineManager.getDefinitionByMachineId(base.currentItem.id)) : null
Button Button
{ {
text: machineActionRepeater.model[index].label; text: machineActionRepeater.model[index].label
onClicked: onClicked:
{ {
actionDialog.content = machineActionRepeater.model[index].displayItem actionDialog.content = machineActionRepeater.model[index].displayItem;
machineActionRepeater.model[index].displayItem.reset() machineActionRepeater.model[index].displayItem.reset();
actionDialog.show() actionDialog.title = machineActionRepeater.model[index].label;
actionDialog.show();
} }
} }
} }
@ -106,6 +107,13 @@ UM.ManagementPage
{ {
contents = content; contents = content;
content.onCompleted.connect(hide) content.onCompleted.connect(hide)
content.dialog = actionDialog
}
rightButtons: Button
{
text: catalog.i18nc("@action:button", "Close")
iconName: "dialog-close"
onClicked: actionDialog.reject()
} }
} }
@ -118,8 +126,14 @@ UM.ManagementPage
spacing: UM.Theme.getSize("default_margin").height spacing: UM.Theme.getSize("default_margin").height
Label { text: catalog.i18nc("@label", "Type") } Label
Label { text: base.currentItem ? base.currentItem.metadata.definition_name : "" } {
text: catalog.i18nc("@label", "Type")
visible: base.currentItem && base.currentItem.metadata
}
Label {
text: (base.currentItem && base.currentItem.metadata) ? base.currentItem.metadata.definition_name : ""
}
} }
UM.I18nCatalog { id: catalog; name: "uranium"; } UM.I18nCatalog { id: catalog; name: "uranium"; }

View file

@ -255,7 +255,7 @@ UM.ManagementPage
else if(result.status == "success") else if(result.status == "success")
{ {
messageDialog.icon = StandardIcon.Information messageDialog.icon = StandardIcon.Information
messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to <filename>%1</filename>").arg(fileUrl) messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to <filename>%1</filename>").arg(result.path)
messageDialog.open() messageDialog.open()
} }
CuraApplication.setDefaultPath("dialog_material_path", folder) CuraApplication.setDefaultPath("dialog_material_path", folder)

View file

@ -86,18 +86,18 @@ SettingItem
} }
} }
onActivated: { forceActiveFocus(); provider.setPropertyValue("value", definition.options[index].key) } onActivated: { forceActiveFocus(); propertyProvider.setPropertyValue("value", definition.options[index].key) }
onModelChanged: updateCurrentIndex(); onModelChanged: updateCurrentIndex();
Connections Connections
{ {
target: provider target: propertyProvider
onPropertiesChanged: control.updateCurrentIndex() onPropertiesChanged: control.updateCurrentIndex()
} }
function updateCurrentIndex() { function updateCurrentIndex() {
for(var i = 0; i < definition.options.length; ++i) { for(var i = 0; i < definition.options.length; ++i) {
if(definition.options[i].key == provider.properties.value) { if(definition.options[i].key == propertyProvider.properties.value) {
currentIndex = i; currentIndex = i;
return; return;
} }

View file

@ -105,13 +105,13 @@ SettingItem
onActivated: onActivated:
{ {
forceActiveFocus(); forceActiveFocus();
provider.setPropertyValue("value", extruders_model.getItem(index).index) propertyProvider.setPropertyValue("value", extruders_model.getItem(index).index)
} }
onModelChanged: updateCurrentIndex(); onModelChanged: updateCurrentIndex();
Connections Connections
{ {
target: provider target: propertyProvider
onPropertiesChanged: control.updateCurrentIndex(); onPropertiesChanged: control.updateCurrentIndex();
} }
@ -119,7 +119,7 @@ SettingItem
{ {
for(var i = 0; i < extruders_model.rowCount(); ++i) for(var i = 0; i < extruders_model.rowCount(); ++i)
{ {
if(extruders_model.getItem(i).index == provider.properties.value) if(extruders_model.getItem(i).index == propertyProvider.properties.value)
{ {
currentIndex = i; currentIndex = i;
return; return;

View file

@ -138,7 +138,7 @@ Item {
{ {
id: linkedSettingIcon; id: linkedSettingIcon;
visible: base.settablePerExtruder != "True" && base.showLinkedSettingIcon visible: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId && base.settablePerExtruder != "True" && base.showLinkedSettingIcon
height: parent.height; height: parent.height;
width: height; width: height;

View file

@ -116,6 +116,8 @@ Rectangle
return UM.Theme.getIcon("tab_monitor_paused") return UM.Theme.getIcon("tab_monitor_paused")
else if (Cura.MachineManager.printerOutputDevices[0].jobState == "error") else if (Cura.MachineManager.printerOutputDevices[0].jobState == "error")
return UM.Theme.getIcon("tab_monitor_stopped") return UM.Theme.getIcon("tab_monitor_stopped")
else if (Cura.MachineManager.printerOutputDevices[0].jobState == "offline")
return UM.Theme.getIcon("tab_monitor_offline")
else else
return UM.Theme.getIcon("tab_monitor") return UM.Theme.getIcon("tab_monitor")
} }