Merge branch 'master' of github.com:Ultimaker/Cura

This commit is contained in:
Jaime van Kessel 2018-03-13 16:46:51 +01:00
commit 283d08a0d6
31 changed files with 783 additions and 262 deletions

View File

@ -111,6 +111,9 @@ class BuildVolume(SceneNode):
# but it does not update the disallowed areas after material change # but it does not update the disallowed areas after material change
Application.getInstance().getMachineManager().activeStackChanged.connect(self._onStackChanged) Application.getInstance().getMachineManager().activeStackChanged.connect(self._onStackChanged)
# Enable and disable extruder
Application.getInstance().getMachineManager().extruderChanged.connect(self.updateNodeBoundaryCheck)
# list of settings which were updated # list of settings which were updated
self._changed_settings_since_last_rebuild = [] self._changed_settings_since_last_rebuild = []
@ -217,30 +220,26 @@ class BuildVolume(SceneNode):
group_nodes.append(node) # Keep list of affected group_nodes group_nodes.append(node) # Keep list of affected group_nodes
if node.callDecoration("isSliceable") or node.callDecoration("isGroup"): if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
node._outside_buildarea = False if node.collidesWithBbox(build_volume_bounding_box):
bbox = node.getBoundingBox() node.setOutsideBuildArea(True)
# Mark the node as outside the build volume if the bounding box test fails.
if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
node._outside_buildarea = True
continue continue
convex_hull = node.callDecoration("getConvexHull") if node.collidesWithArea(self.getDisallowedAreas()):
if convex_hull: node.setOutsideBuildArea(True)
if not convex_hull.isValid(): continue
return
# Check for collisions between disallowed areas and the object # Mark the node as outside build volume if the set extruder is disabled
for area in self.getDisallowedAreas(): extruder_position = node.callDecoration("getActiveExtruderPosition")
overlap = convex_hull.intersectsPolygon(area) if not self._global_container_stack.extruders[extruder_position].isEnabled:
if overlap is None: node.setOutsideBuildArea(True)
continue continue
node._outside_buildarea = True
continue node.setOutsideBuildArea(False)
# Group nodes should override the _outside_buildarea property of their children. # Group nodes should override the _outside_buildarea property of their children.
for group_node in group_nodes: for group_node in group_nodes:
for child_node in group_node.getAllChildren(): for child_node in group_node.getAllChildren():
child_node._outside_buildarea = group_node._outside_buildarea child_node.setOutsideBuildArea(group_node.isOutsideBuildArea())
## Update the outsideBuildArea of a single node, given bounds or current build volume ## Update the outsideBuildArea of a single node, given bounds or current build volume
def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None): def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None):
@ -260,24 +259,20 @@ class BuildVolume(SceneNode):
build_volume_bounding_box = bounds build_volume_bounding_box = bounds
if node.callDecoration("isSliceable") or node.callDecoration("isGroup"): if node.callDecoration("isSliceable") or node.callDecoration("isGroup"):
bbox = node.getBoundingBox() if node.collidesWithBbox(build_volume_bounding_box):
node.setOutsideBuildArea(True)
# Mark the node as outside the build volume if the bounding box test fails. return
if build_volume_bounding_box.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
if node.collidesWithArea(self.getDisallowedAreas()):
node.setOutsideBuildArea(True)
return
# Mark the node as outside build volume if the set extruder is disabled
extruder_position = node.callDecoration("getActiveExtruderPosition")
if not self._global_container_stack.extruders[extruder_position].isEnabled:
node.setOutsideBuildArea(True) node.setOutsideBuildArea(True)
return return
convex_hull = self.callDecoration("getConvexHull")
if convex_hull:
if not convex_hull.isValid():
return
# Check for collisions between disallowed areas and the object
for area in self.getDisallowedAreas():
overlap = convex_hull.intersectsPolygon(area)
if overlap is None:
continue
node.setOutsideBuildArea(True)
return
node.setOutsideBuildArea(False) node.setOutsideBuildArea(False)
## Recalculates the build volume & disallowed areas. ## Recalculates the build volume & disallowed areas.

View File

@ -67,6 +67,8 @@ from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
from cura.Machines.MachineErrorChecker import MachineErrorChecker
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
@ -142,12 +144,6 @@ class CuraApplication(QtApplication):
Q_ENUMS(ResourceTypes) Q_ENUMS(ResourceTypes)
# FIXME: This signal belongs to the MachineManager, but the CuraEngineBackend plugin requires on it.
# Because plugins are initialized before the ContainerRegistry, putting this signal in MachineManager
# will make it initialized before ContainerRegistry does, and it won't find the active machine, thus
# Cura will always show the Add Machine Dialog upon start.
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
def __init__(self, **kwargs): def __init__(self, **kwargs):
# this list of dir names will be used by UM to detect an old cura directory # this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]: for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
@ -224,12 +220,14 @@ class CuraApplication(QtApplication):
self._machine_manager = None # This is initialized on demand. self._machine_manager = None # This is initialized on demand.
self._extruder_manager = None self._extruder_manager = None
self._material_manager = None self._material_manager = None
self._quality_manager = None
self._object_manager = None self._object_manager = None
self._build_plate_model = None self._build_plate_model = None
self._multi_build_plate_model = None self._multi_build_plate_model = None
self._setting_inheritance_manager = None self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None self._simple_mode_settings_manager = None
self._cura_scene_controller = None self._cura_scene_controller = None
self._machine_error_checker = None
self._additional_components = {} # Components to add to certain areas in the interface self._additional_components = {} # Components to add to certain areas in the interface
@ -743,19 +741,28 @@ class CuraApplication(QtApplication):
self.preRun() self.preRun()
container_registry = ContainerRegistry.getInstance() container_registry = ContainerRegistry.getInstance()
Logger.log("i", "Initializing variant manager")
self._variant_manager = VariantManager(container_registry) self._variant_manager = VariantManager(container_registry)
self._variant_manager.initialize() self._variant_manager.initialize()
Logger.log("i", "Initializing material manager")
from cura.Machines.MaterialManager import MaterialManager from cura.Machines.MaterialManager import MaterialManager
self._material_manager = MaterialManager(container_registry, parent = self) self._material_manager = MaterialManager(container_registry, parent = self)
self._material_manager.initialize() self._material_manager.initialize()
Logger.log("i", "Initializing quality manager")
from cura.Machines.QualityManager import QualityManager from cura.Machines.QualityManager import QualityManager
self._quality_manager = QualityManager(container_registry, parent = self) self._quality_manager = QualityManager(container_registry, parent = self)
self._quality_manager.initialize() self._quality_manager.initialize()
Logger.log("i", "Initializing machine manager")
self._machine_manager = MachineManager(self) self._machine_manager = MachineManager(self)
Logger.log("i", "Initializing machine error checker")
self._machine_error_checker = MachineErrorChecker(self)
self._machine_error_checker.initialize()
# Check if we should run as single instance or not # Check if we should run as single instance or not
self._setUpSingleInstanceServer() self._setUpSingleInstanceServer()
@ -781,8 +788,11 @@ class CuraApplication(QtApplication):
self._openFile(file_name) self._openFile(file_name)
self.started = True self.started = True
self.initializationFinished.emit()
self.exec_() self.exec_()
initializationFinished = pyqtSignal()
## Run Cura without GUI elements and interaction (server mode). ## Run Cura without GUI elements and interaction (server mode).
def runWithoutGUI(self): def runWithoutGUI(self):
self._use_gui = False self._use_gui = False
@ -847,6 +857,9 @@ class CuraApplication(QtApplication):
def hasGui(self): def hasGui(self):
return self._use_gui return self._use_gui
def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
return self._machine_error_checker
def getMachineManager(self, *args) -> MachineManager: def getMachineManager(self, *args) -> MachineManager:
if self._machine_manager is None: if self._machine_manager is None:
self._machine_manager = MachineManager(self) self._machine_manager = MachineManager(self)
@ -1605,6 +1618,8 @@ class CuraApplication(QtApplication):
fixed_nodes.append(node_) fixed_nodes.append(node_)
arranger = Arrange.create(fixed_nodes = fixed_nodes) arranger = Arrange.create(fixed_nodes = fixed_nodes)
min_offset = 8 min_offset = 8
default_extruder_position = self.getMachineManager().defaultExtruderPosition
default_extruder_id = self._global_container_stack.extruders[default_extruder_position].getId()
for original_node in nodes: for original_node in nodes:
@ -1670,6 +1685,8 @@ class CuraApplication(QtApplication):
op = AddSceneNodeOperation(node, scene.getRoot()) op = AddSceneNodeOperation(node, scene.getRoot())
op.push() op.push()
node.callDecoration("setActiveExtruder", default_extruder_id)
scene.sceneChanged.emit(node) scene.sceneChanged.emit(node)
self.fileCompleted.emit(filename) self.fileCompleted.emit(filename)

View File

@ -0,0 +1,181 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import time
from collections import deque
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
from UM.Application import Application
from UM.Logger import Logger
from UM.Settings.SettingDefinition import SettingDefinition
from UM.Settings.Validator import ValidatorState
#
# This class performs setting error checks for the currently active machine.
#
# The whole error checking process is pretty heavy which can take ~0.5 secs, so it can cause GUI to lag.
# The idea here is to split the whole error check into small tasks, each of which only checks a single setting key
# in a stack. According to my profiling results, the maximal runtime for such a sub-task is <0.03 secs, which should
# be good enough. Moreover, if any changes happened to the machine, we can cancel the check in progress without wait
# for it to finish the complete work.
#
class MachineErrorChecker(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._global_stack = None
self._has_errors = True # Result of the error check, indicating whether there are errors in the stack
self._error_keys = set() # A set of settings keys that have errors
self._error_keys_in_progress = set() # The variable that stores the results of the currently in progress check
self._stacks_and_keys_to_check = None # a FIFO queue of tuples (stack, key) to check for errors
self._need_to_check = False # Whether we need to schedule a new check or not. This flag is set when a new
# error check needs to take place while there is already one running at the moment.
self._check_in_progress = False # Whether there is an error check running in progress at the moment.
self._application = Application.getInstance()
self._machine_manager = self._application.getMachineManager()
self._start_time = 0 # measure checking time
# This timer delays the starting of error check so we can react less frequently if the user is frequently
# changing settings.
self._error_check_timer = QTimer(self)
self._error_check_timer.setInterval(100)
self._error_check_timer.setSingleShot(True)
def initialize(self):
self._error_check_timer.timeout.connect(self._rescheduleCheck)
# Reconnect all signals when the active machine gets changed.
self._machine_manager.globalContainerChanged.connect(self._onMachineChanged)
# Whenever the machine settings get changed, we schedule an error check.
self._machine_manager.globalContainerChanged.connect(self.startErrorCheck)
self._machine_manager.globalValueChanged.connect(self.startErrorCheck)
self._onMachineChanged()
def _onMachineChanged(self):
if self._global_stack:
self._global_stack.propertyChanged.disconnect(self.startErrorCheck)
self._global_stack.containersChanged.disconnect(self.startErrorCheck)
for extruder in self._global_stack.extruders.values():
extruder.propertyChanged.disconnect(self.startErrorCheck)
extruder.containersChanged.disconnect(self.startErrorCheck)
self._global_stack = self._machine_manager.activeMachine
if self._global_stack:
self._global_stack.propertyChanged.connect(self.startErrorCheck)
self._global_stack.containersChanged.connect(self.startErrorCheck)
for extruder in self._global_stack.extruders.values():
extruder.propertyChanged.connect(self.startErrorCheck)
extruder.containersChanged.connect(self.startErrorCheck)
hasErrorUpdated = pyqtSignal()
needToWaitForResultChanged = pyqtSignal()
errorCheckFinished = pyqtSignal()
@pyqtProperty(bool, notify = hasErrorUpdated)
def hasError(self) -> bool:
return self._has_errors
@pyqtProperty(bool, notify = needToWaitForResultChanged)
def needToWaitForResult(self) -> bool:
return self._need_to_check or self._check_in_progress
# Starts the error check timer to schedule a new error check.
def startErrorCheck(self, *args):
if not self._check_in_progress:
self._need_to_check = True
self.needToWaitForResultChanged.emit()
self._error_check_timer.start()
# This function is called by the timer to reschedule a new error check.
# If there is no check in progress, it will start a new one. If there is any, it sets the "_need_to_check" flag
# to notify the current check to stop and start a new one.
def _rescheduleCheck(self):
if self._check_in_progress and not self._need_to_check:
self._need_to_check = True
self.needToWaitForResultChanged.emit()
return
self._error_keys_in_progress = set()
self._need_to_check = False
self.needToWaitForResultChanged.emit()
global_stack = self._machine_manager.activeMachine
if global_stack is None:
Logger.log("i", "No active machine, nothing to check.")
return
# Populate the (stack, key) tuples to check
self._stacks_and_keys_to_check = deque()
for stack in [global_stack] + list(global_stack.extruders.values()):
for key in stack.getAllKeys():
self._stacks_and_keys_to_check.append((stack, key))
self._application.callLater(self._checkStack)
self._start_time = time.time()
Logger.log("d", "New error check scheduled.")
def _checkStack(self):
if self._need_to_check:
Logger.log("d", "Need to check for errors again. Discard the current progress and reschedule a check.")
self._check_in_progress = False
self._application.callLater(self.startErrorCheck)
return
self._check_in_progress = True
# If there is nothing to check any more, it means there is no error.
if not self._stacks_and_keys_to_check:
# Finish
self._setResult(False)
return
# Get the next stack and key to check
stack, key = self._stacks_and_keys_to_check.popleft()
enabled = stack.getProperty(key, "enabled")
if not enabled:
self._application.callLater(self._checkStack)
return
validation_state = stack.getProperty(key, "validationState")
if validation_state is None:
# Setting is not validated. This can happen if there is only a setting definition.
# We do need to validate it, because a setting definitions value can be set by a function, which could
# be an invalid setting.
definition = stack.getSettingDefinition(key)
validator_type = SettingDefinition.getValidatorForType(definition.type)
if validator_type:
validator = validator_type(key)
validation_state = validator(stack)
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
# Finish
self._setResult(True)
return
# Schedule the check for the next key
self._application.callLater(self._checkStack)
def _setResult(self, result: bool):
if result != self._has_errors:
self._has_errors = result
self.hasErrorUpdated.emit()
self._machine_manager.stacksValidationChanged.emit()
self._need_to_check = False
self._check_in_progress = False
self.needToWaitForResultChanged.emit()
self.errorCheckFinished.emit()
Logger.log("i", "Error check finished, result = %s, time = %0.1fs", result, time.time() - self._start_time)

View File

@ -39,6 +39,7 @@ class QualityProfilesDropDownMenuModel(ListModel):
self._application.globalContainerStackChanged.connect(self._update) self._application.globalContainerStackChanged.connect(self._update)
self._machine_manager.activeQualityGroupChanged.connect(self._update) self._machine_manager.activeQualityGroupChanged.connect(self._update)
self._machine_manager.extruderChanged.connect(self._update)
self._quality_manager.qualitiesUpdated.connect(self._update) self._quality_manager.qualitiesUpdated.connect(self._update)
self._layer_height_unit = "" # This is cached self._layer_height_unit = "" # This is cached

View File

@ -21,6 +21,8 @@ class QualitySettingsModel(ListModel):
UserValueRole = Qt.UserRole + 6 UserValueRole = Qt.UserRole + 6
CategoryRole = Qt.UserRole + 7 CategoryRole = Qt.UserRole + 7
GLOBAL_STACK_POSITION = -1
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent = parent) super().__init__(parent = parent)
@ -36,8 +38,7 @@ class QualitySettingsModel(ListModel):
self._application = Application.getInstance() self._application = Application.getInstance()
self._quality_manager = self._application.getQualityManager() self._quality_manager = self._application.getQualityManager()
self._selected_position = "" # empty string means GlobalStack self._selected_position = self.GLOBAL_STACK_POSITION #Must be either GLOBAL_STACK_POSITION or an extruder position (0, 1, etc.)
# strings such as "0", "1", etc. mean extruder positions
self._selected_quality_item = None # The selected quality in the quality management page self._selected_quality_item = None # The selected quality in the quality management page
self._i18n_catalog = None self._i18n_catalog = None
@ -54,7 +55,7 @@ class QualitySettingsModel(ListModel):
self.selectedPositionChanged.emit() self.selectedPositionChanged.emit()
self._update() self._update()
@pyqtProperty(str, fset = setSelectedPosition, notify = selectedPositionChanged) @pyqtProperty(int, fset = setSelectedPosition, notify = selectedPositionChanged)
def selectedPosition(self): def selectedPosition(self):
return self._selected_position return self._selected_position
@ -83,7 +84,7 @@ class QualitySettingsModel(ListModel):
quality_group = self._selected_quality_item["quality_group"] quality_group = self._selected_quality_item["quality_group"]
quality_changes_group = self._selected_quality_item["quality_changes_group"] quality_changes_group = self._selected_quality_item["quality_changes_group"]
if self._selected_position == "": if self._selected_position == self.GLOBAL_STACK_POSITION:
quality_node = quality_group.node_for_global quality_node = quality_group.node_for_global
else: else:
quality_node = quality_group.nodes_for_extruders.get(self._selected_position) quality_node = quality_group.nodes_for_extruders.get(self._selected_position)
@ -93,14 +94,14 @@ class QualitySettingsModel(ListModel):
# Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch # Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch
# the settings in that quality_changes_group. # the settings in that quality_changes_group.
if quality_changes_group is not None: if quality_changes_group is not None:
if self._selected_position == "": if self._selected_position == self.GLOBAL_STACK_POSITION:
quality_changes_node = quality_changes_group.node_for_global quality_changes_node = quality_changes_group.node_for_global
else: else:
quality_changes_node = quality_changes_group.nodes_for_extruders.get(self._selected_position) quality_changes_node = quality_changes_group.nodes_for_extruders.get(self._selected_position)
if quality_changes_node is not None: # it can be None if number of extruders are changed during runtime if quality_changes_node is not None: # it can be None if number of extruders are changed during runtime
try: try:
quality_containers.insert(0, quality_changes_node.getContainer()) quality_containers.insert(0, quality_changes_node.getContainer())
except: except RuntimeError:
# FIXME: This is to prevent incomplete update of QualityManager # FIXME: This is to prevent incomplete update of QualityManager
Logger.logException("d", "Failed to get container for quality changes node %s", quality_changes_node) Logger.logException("d", "Failed to get container for quality changes node %s", quality_changes_node)
return return
@ -127,7 +128,7 @@ class QualitySettingsModel(ListModel):
profile_value = new_value profile_value = new_value
# Global tab should use resolve (if there is one) # Global tab should use resolve (if there is one)
if self._selected_position == "": if self._selected_position == self.GLOBAL_STACK_POSITION:
resolve_value = global_container_stack.getProperty(definition.key, "resolve") resolve_value = global_container_stack.getProperty(definition.key, "resolve")
if resolve_value is not None and definition.key in settings_keys: if resolve_value is not None and definition.key in settings_keys:
profile_value = resolve_value profile_value = resolve_value
@ -135,10 +136,10 @@ class QualitySettingsModel(ListModel):
if profile_value is not None: if profile_value is not None:
break break
if not self._selected_position: if self._selected_position == self.GLOBAL_STACK_POSITION:
user_value = global_container_stack.userChanges.getProperty(definition.key, "value") user_value = global_container_stack.userChanges.getProperty(definition.key, "value")
else: else:
extruder_stack = global_container_stack.extruders[self._selected_position] extruder_stack = global_container_stack.extruders[str(self._selected_position)]
user_value = extruder_stack.userChanges.getProperty(definition.key, "value") user_value = extruder_stack.userChanges.getProperty(definition.key, "value")
if profile_value is None and user_value is None: if profile_value is None and user_value is None:

View File

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, List from typing import Dict, Optional, List
from PyQt5.QtCore import QObject, pyqtSlot from PyQt5.QtCore import QObject, pyqtSlot
@ -25,7 +25,7 @@ class QualityGroup(QObject):
super().__init__(parent) super().__init__(parent)
self.name = name self.name = name
self.node_for_global = None # type: Optional["QualityGroup"] self.node_for_global = None # type: Optional["QualityGroup"]
self.nodes_for_extruders = dict() # position str -> QualityGroup self.nodes_for_extruders = {} # type: Dict[int, "QualityGroup"]
self.quality_type = quality_type self.quality_type = quality_type
self.is_available = False self.is_available = False

View File

@ -159,9 +159,9 @@ class QualityManager(QObject):
# Updates the given quality groups' availabilities according to which extruders are being used/ enabled. # Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list): def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list):
used_extruders = set() used_extruders = set()
# TODO: This will change after the Machine refactoring
for i in range(machine.getProperty("machine_extruder_count", "value")): for i in range(machine.getProperty("machine_extruder_count", "value")):
used_extruders.add(str(i)) if machine.extruders[str(i)].isEnabled:
used_extruders.add(str(i))
# Update the "is_available" flag for each quality group. # Update the "is_available" flag for each quality group.
for quality_group in quality_group_list: for quality_group in quality_group_list:

View File

@ -55,6 +55,14 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._connection_state_before_timeout = None # type: Optional[ConnectionState] self._connection_state_before_timeout = None # type: Optional[ConnectionState]
printer_type = self._properties.get(b"machine", b"").decode("utf-8")
if printer_type.startswith("9511"):
self._printer_type = "ultimaker3_extended"
elif printer_type.startswith("9066"):
self._printer_type = "ultimaker3"
else:
self._printer_type = "unknown"
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs) -> None: def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs) -> None:
raise NotImplementedError("requestWrite needs to be implemented") raise NotImplementedError("requestWrite needs to be implemented")
@ -301,6 +309,10 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
def firmwareVersion(self) -> str: def firmwareVersion(self) -> str:
return self._properties.get(b"firmware_version", b"").decode("utf-8") return self._properties.get(b"firmware_version", b"").decode("utf-8")
@pyqtProperty(str, constant=True)
def printerType(self) -> str:
return self._printer_type
## IPadress of this printer ## IPadress of this printer
@pyqtProperty(str, constant=True) @pyqtProperty(str, constant=True)
def ipAddress(self) -> str: def ipAddress(self) -> str:

View File

@ -1,9 +1,13 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from copy import deepcopy
from typing import List from typing import List
from UM.Application import Application from UM.Application import Application
from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from copy import deepcopy
from cura.Settings.ExtrudersModel import ExtrudersModel from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
## Scene nodes that are models are only seen when selecting the corresponding build plate ## Scene nodes that are models are only seen when selecting the corresponding build plate
@ -11,6 +15,8 @@ from cura.Settings.ExtrudersModel import ExtrudersModel
class CuraSceneNode(SceneNode): class CuraSceneNode(SceneNode):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if "no_setting_override" not in kwargs:
self.addDecorator(SettingOverrideDecorator()) # now we always have a getActiveExtruderPosition, unless explicitly disabled
self._outside_buildarea = False self._outside_buildarea = False
def setOutsideBuildArea(self, new_value): def setOutsideBuildArea(self, new_value):
@ -72,9 +78,34 @@ class CuraSceneNode(SceneNode):
1.0 1.0
] ]
## Return if the provided bbox collides with the bbox of this scene node
def collidesWithBbox(self, check_bbox):
bbox = self.getBoundingBox()
# Mark the node as outside the build volume if the bounding box test fails.
if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
return True
return False
## Return if any area collides with the convex hull of this scene node
def collidesWithArea(self, areas):
convex_hull = self.callDecoration("getConvexHull")
if convex_hull:
if not convex_hull.isValid():
return False
# Check for collisions between disallowed areas and the object
for area in areas:
overlap = convex_hull.intersectsPolygon(area)
if overlap is None:
continue
return True
return False
## Taken from SceneNode, but replaced SceneNode with CuraSceneNode ## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
copy = CuraSceneNode() copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later
copy.setTransformation(self.getLocalTransformation()) copy.setTransformation(self.getLocalTransformation())
copy.setMeshData(self._mesh_data) copy.setMeshData(self._mesh_data)
copy.setVisible(deepcopy(self._visible, memo)) copy.setVisible(deepcopy(self._visible, memo))

View File

@ -334,10 +334,13 @@ class ContainerManager(QObject):
# Go through global and extruder stacks and clear their topmost container (the user settings). # Go through global and extruder stacks and clear their topmost container (the user settings).
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
container = stack.getTop() container = stack.userChanges
container.clear() container.clear()
send_emits_containers.append(container) send_emits_containers.append(container)
# user changes are possibly added to make the current setup match the current enabled extruders
Application.getInstance().getMachineManager().correctExtruderSettings()
for container in send_emits_containers: for container in send_emits_containers:
container.sendPostponedEmits() container.sendPostponedEmits()

View File

@ -273,11 +273,11 @@ class CuraContainerRegistry(ContainerRegistry):
elif profile_index < len(machine_extruders) + 1: elif profile_index < len(machine_extruders) + 1:
# This is assumed to be an extruder profile # This is assumed to be an extruder profile
extruder_id = machine_extruders[profile_index - 1].definition.getId() extruder_id = machine_extruders[profile_index - 1].definition.getId()
extuder_position = str(profile_index - 1) extruder_position = str(profile_index - 1)
if not profile.getMetaDataEntry("position"): if not profile.getMetaDataEntry("position"):
profile.addMetaDataEntry("position", extuder_position) profile.addMetaDataEntry("position", extruder_position)
else: else:
profile.setMetaDataEntry("position", extuder_position) profile.setMetaDataEntry("position", extruder_position)
profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_") profile_id = (extruder_id + "_" + name_seed).lower().replace(" ", "_")
else: #More extruders in the imported file than in the machine. else: #More extruders in the imported file than in the machine.

View File

@ -241,6 +241,13 @@ class ExtruderManager(QObject):
result.append(extruder_stack.getProperty(setting_key, prop)) result.append(extruder_stack.getProperty(setting_key, prop))
return result return result
def extruderValueWithDefault(self, value):
machine_manager = self._application.getMachineManager()
if value == "-1":
return machine_manager.defaultExtruderPosition
else:
return value
## Gets the extruder stacks that are actually being used at the moment. ## Gets the extruder stacks that are actually being used at the moment.
# #
# An extruder stack is being used if it is the extruder to print any mesh # An extruder stack is being used if it is the extruder to print any mesh
@ -252,7 +259,7 @@ class ExtruderManager(QObject):
# #
# \return A list of extruder stacks. # \return A list of extruder stacks.
def getUsedExtruderStacks(self) -> List["ContainerStack"]: def getUsedExtruderStacks(self) -> List["ContainerStack"]:
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = self._application.getGlobalContainerStack()
container_registry = ContainerRegistry.getInstance() container_registry = ContainerRegistry.getInstance()
used_extruder_stack_ids = set() used_extruder_stack_ids = set()
@ -302,16 +309,19 @@ class ExtruderManager(QObject):
# Check support extruders # Check support extruders
if support_enabled: if support_enabled:
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_infill_extruder_nr", "value"))]) used_extruder_stack_ids.add(self.extruderIds[self.extruderValueWithDefault(str(global_stack.getProperty("support_infill_extruder_nr", "value")))])
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_extruder_nr_layer_0", "value"))]) used_extruder_stack_ids.add(self.extruderIds[self.extruderValueWithDefault(str(global_stack.getProperty("support_extruder_nr_layer_0", "value")))])
if support_bottom_enabled: if support_bottom_enabled:
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_bottom_extruder_nr", "value"))]) used_extruder_stack_ids.add(self.extruderIds[self.extruderValueWithDefault(str(global_stack.getProperty("support_bottom_extruder_nr", "value")))])
if support_roof_enabled: if support_roof_enabled:
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("support_roof_extruder_nr", "value"))]) used_extruder_stack_ids.add(self.extruderIds[self.extruderValueWithDefault(str(global_stack.getProperty("support_roof_extruder_nr", "value")))])
# The platform adhesion extruder. Not used if using none. # The platform adhesion extruder. Not used if using none.
if global_stack.getProperty("adhesion_type", "value") != "none": if global_stack.getProperty("adhesion_type", "value") != "none":
used_extruder_stack_ids.add(self.extruderIds[str(global_stack.getProperty("adhesion_extruder_nr", "value"))]) extruder_nr = str(global_stack.getProperty("adhesion_extruder_nr", "value"))
if extruder_nr == "-1":
extruder_nr = Application.getInstance().getMachineManager().defaultExtruderPosition
used_extruder_stack_ids.add(self.extruderIds[extruder_nr])
try: try:
return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids] return [container_registry.findContainerStacks(id = stack_id)[0] for stack_id in used_extruder_stack_ids]
@ -485,6 +495,8 @@ class ExtruderManager(QObject):
result = [] result = []
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()): for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
if not extruder.isEnabled:
continue
# only include values from extruders that are "active" for the current machine instance # only include values from extruders that are "active" for the current machine instance
if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"): if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"):
continue continue
@ -656,6 +668,8 @@ class ExtruderManager(QObject):
# global stack if not found. # global stack if not found.
@staticmethod @staticmethod
def getExtruderValue(extruder_index, key): def getExtruderValue(extruder_index, key):
if extruder_index == -1:
extruder_index = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index) extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
if extruder: if extruder:

View File

@ -3,13 +3,14 @@
from typing import Any, TYPE_CHECKING, Optional from typing import Any, TYPE_CHECKING, Optional
from PyQt5.QtCore import pyqtProperty from PyQt5.QtCore import pyqtProperty, pyqtSignal
from UM.Decorators import override from UM.Decorators import override
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
from UM.Util import parseBool
from . import Exceptions from . import Exceptions
from .CuraContainerStack import CuraContainerStack, _ContainerIndexes from .CuraContainerStack import CuraContainerStack, _ContainerIndexes
@ -30,6 +31,8 @@ class ExtruderStack(CuraContainerStack):
self.propertiesChanged.connect(self._onPropertiesChanged) self.propertiesChanged.connect(self._onPropertiesChanged)
enabledChanged = pyqtSignal()
## Overridden from ContainerStack ## Overridden from ContainerStack
# #
# This will set the next stack and ensure that we register this stack as an extruder. # This will set the next stack and ensure that we register this stack as an extruder.
@ -46,6 +49,16 @@ class ExtruderStack(CuraContainerStack):
def getNextStack(self) -> Optional["GlobalStack"]: def getNextStack(self) -> Optional["GlobalStack"]:
return super().getNextStack() return super().getNextStack()
def setEnabled(self, enabled):
if "enabled" not in self._metadata:
self.addMetaDataEntry("enabled", "True")
self.setMetaDataEntry("enabled", str(enabled))
self.enabledChanged.emit()
@pyqtProperty(bool, notify = enabledChanged)
def isEnabled(self):
return parseBool(self.getMetaDataEntry("enabled", "True"))
@classmethod @classmethod
def getLoadingPriority(cls) -> int: def getLoadingPriority(cls) -> int:
return 3 return 3

View File

@ -1,7 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, QTimer from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, pyqtProperty, QTimer
from typing import Iterable from typing import Iterable
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
@ -24,6 +24,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
## Human-readable name of the extruder. ## Human-readable name of the extruder.
NameRole = Qt.UserRole + 2 NameRole = Qt.UserRole + 2
## Is the extruder enabled?
EnabledRole = Qt.UserRole + 9
## Colour of the material loaded in the extruder. ## Colour of the material loaded in the extruder.
ColorRole = Qt.UserRole + 3 ColorRole = Qt.UserRole + 3
@ -43,6 +45,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
# The variant of the extruder. # The variant of the extruder.
VariantRole = Qt.UserRole + 7 VariantRole = Qt.UserRole + 7
StackRole = Qt.UserRole + 8
## List of colours to display if there is no material or the material has no known ## List of colours to display if there is no material or the material has no known
# colour. # colour.
@ -57,11 +60,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
self.addRoleName(self.IdRole, "id") self.addRoleName(self.IdRole, "id")
self.addRoleName(self.NameRole, "name") self.addRoleName(self.NameRole, "name")
self.addRoleName(self.EnabledRole, "enabled")
self.addRoleName(self.ColorRole, "color") self.addRoleName(self.ColorRole, "color")
self.addRoleName(self.IndexRole, "index") self.addRoleName(self.IndexRole, "index")
self.addRoleName(self.DefinitionRole, "definition") self.addRoleName(self.DefinitionRole, "definition")
self.addRoleName(self.MaterialRole, "material") self.addRoleName(self.MaterialRole, "material")
self.addRoleName(self.VariantRole, "variant") self.addRoleName(self.VariantRole, "variant")
self.addRoleName(self.StackRole, "stack")
self._update_extruder_timer = QTimer() self._update_extruder_timer = QTimer()
self._update_extruder_timer.setInterval(100) self._update_extruder_timer.setInterval(100)
@ -183,11 +188,13 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
item = { item = {
"id": extruder.getId(), "id": extruder.getId(),
"name": extruder.getName(), "name": extruder.getName(),
"enabled": extruder.isEnabled,
"color": color, "color": color,
"index": position, "index": position,
"definition": extruder.getBottom().getId(), "definition": extruder.getBottom().getId(),
"material": extruder.material.getName() if extruder.material else "", "material": extruder.material.getName() if extruder.material else "",
"variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core "variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core
"stack": extruder,
} }
items.append(item) items.append(item)

View File

@ -4,7 +4,7 @@
import collections import collections
import time import time
#Type hinting. #Type hinting.
from typing import Union, List, Dict, TYPE_CHECKING, Optional from typing import List, Dict, TYPE_CHECKING, Optional
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Signal import Signal from UM.Signal import Signal
@ -20,7 +20,6 @@ from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique from UM.Signal import postponeSignals, CompressTechnique
@ -52,12 +51,9 @@ class MachineManager(QObject):
self._current_quality_group = None self._current_quality_group = None
self._current_quality_changes_group = None self._current_quality_changes_group = None
self.machine_extruder_material_update_dict = collections.defaultdict(list) self._default_extruder_position = "0" # to be updated when extruders are switched on and off
self._error_check_timer = QTimer() self.machine_extruder_material_update_dict = collections.defaultdict(list)
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
self._instance_container_timer = QTimer() self._instance_container_timer = QTimer()
self._instance_container_timer.setInterval(250) self._instance_container_timer.setInterval(250)
@ -123,6 +119,7 @@ class MachineManager(QObject):
# When the materials lookup table gets updated, it can mean that a material has its name changed, which should # When the materials lookup table gets updated, it can mean that a material has its name changed, which should
# be reflected on the GUI. This signal emission makes sure that it happens. # be reflected on the GUI. This signal emission makes sure that it happens.
self._material_manager.materialsUpdated.connect(self.rootMaterialChanged) self._material_manager.materialsUpdated.connect(self.rootMaterialChanged)
self.rootMaterialChanged.connect(self._onRootMaterialChanged)
activeQualityGroupChanged = pyqtSignal() activeQualityGroupChanged = pyqtSignal()
activeQualityChangesGroupChanged = pyqtSignal() activeQualityChangesGroupChanged = pyqtSignal()
@ -132,6 +129,7 @@ class MachineManager(QObject):
activeVariantChanged = pyqtSignal() activeVariantChanged = pyqtSignal()
activeQualityChanged = pyqtSignal() activeQualityChanged = pyqtSignal()
activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value) activeStackChanged = pyqtSignal() # Emitted whenever the active stack is changed (ie: when changing between extruders, changing a profile, but not when changing a value)
extruderChanged = pyqtSignal()
globalValueChanged = pyqtSignal() # Emitted whenever a value inside global container is changed. globalValueChanged = pyqtSignal() # Emitted whenever a value inside global container is changed.
activeStackValueChanged = pyqtSignal() # Emitted whenever a value inside the active stack is changed. activeStackValueChanged = pyqtSignal() # Emitted whenever a value inside the active stack is changed.
@ -189,7 +187,9 @@ class MachineManager(QObject):
# Update the local global container stack reference # Update the local global container stack reference
self._global_container_stack = Application.getInstance().getGlobalContainerStack() self._global_container_stack = Application.getInstance().getGlobalContainerStack()
if self._global_container_stack:
self.updateDefaultExtruder()
self.updateNumberExtrudersEnabled()
self.globalContainerChanged.emit() self.globalContainerChanged.emit()
# after switching the global stack we reconnect all the signals and set the variant and material references # after switching the global stack we reconnect all the signals and set the variant and material references
@ -222,15 +222,6 @@ class MachineManager(QObject):
del self.machine_extruder_material_update_dict[self._global_container_stack.getId()] del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
self.activeQualityGroupChanged.emit() self.activeQualityGroupChanged.emit()
self._error_check_timer.start()
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
def _updateStacksHaveErrors(self) -> None:
old_stacks_have_errors = self._stacks_have_errors
self._stacks_have_errors = self._checkStacksHaveErrors()
if old_stacks_have_errors != self._stacks_have_errors:
self.stacksValidationChanged.emit()
Application.getInstance().stacksValidationFinished.emit()
def _onActiveExtruderStackChanged(self) -> None: def _onActiveExtruderStackChanged(self) -> None:
self.blurSettings.emit() # Ensure no-one has focus. self.blurSettings.emit() # Ensure no-one has focus.
@ -250,8 +241,6 @@ class MachineManager(QObject):
self.rootMaterialChanged.emit() self.rootMaterialChanged.emit()
self._error_check_timer.start()
def _onInstanceContainersChanged(self, container) -> None: def _onInstanceContainersChanged(self, container) -> None:
self._instance_container_timer.start() self._instance_container_timer.start()
@ -260,9 +249,6 @@ class MachineManager(QObject):
# Notify UI items, such as the "changed" star in profile pull down menu. # Notify UI items, such as the "changed" star in profile pull down menu.
self.activeStackValueChanged.emit() self.activeStackValueChanged.emit()
elif property_name == "validationState":
self._error_check_timer.start()
## Given a global_stack, make sure that it's all valid by searching for this quality group and applying it again ## Given a global_stack, make sure that it's all valid by searching for this quality group and applying it again
def _initMachineState(self, global_stack): def _initMachineState(self, global_stack):
material_dict = {} material_dict = {}
@ -329,7 +315,7 @@ class MachineManager(QObject):
return False return False
if self._global_container_stack.hasErrors(): if self._global_container_stack.hasErrors():
Logger.log("d", "Checking global stack for errors took %0.2f s and we found and error" % (time.time() - time_start)) Logger.log("d", "Checking global stack for errors took %0.2f s and we found an error" % (time.time() - time_start))
return True return True
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are # Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
@ -641,6 +627,8 @@ class MachineManager(QObject):
buildplate_compatible = True # It is compatible by default buildplate_compatible = True # It is compatible by default
extruder_stacks = self._global_container_stack.extruders.values() extruder_stacks = self._global_container_stack.extruders.values()
for stack in extruder_stacks: for stack in extruder_stacks:
if not stack.isEnabled:
continue
material_container = stack.material material_container = stack.material
if material_container == self._empty_material_container: if material_container == self._empty_material_container:
continue continue
@ -698,6 +686,43 @@ class MachineManager(QObject):
if containers: if containers:
return containers[0].definition.getId() return containers[0].definition.getId()
def getIncompatibleSettingsOnEnabledExtruders(self, container):
extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
result = []
for setting_instance in container.findInstances():
setting_key = setting_instance.definition.key
setting_enabled = self._global_container_stack.getProperty(setting_key, "enabled")
if not setting_enabled:
# A setting is not visible anymore
result.append(setting_key)
Logger.log("d", "Reset setting [%s] from [%s] because the setting is no longer enabled", setting_key, container)
continue
if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
continue
old_value = container.getProperty(setting_key, "value")
if int(old_value) >= extruder_count or not self._global_container_stack.extruders[str(old_value)].isEnabled:
result.append(setting_key)
Logger.log("d", "Reset setting [%s] in [%s] because its old value [%s] is no longer valid", setting_key, container, old_value)
return result
## Update extruder number to a valid value when the number of extruders are changed, or when an extruder is changed
def correctExtruderSettings(self):
for setting_key in self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.userChanges):
self._global_container_stack.userChanges.removeInstance(setting_key)
add_user_changes = self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.qualityChanges)
for setting_key in add_user_changes:
# Apply quality changes that are incompatible to user changes, so we do not change the quality changes itself.
self._global_container_stack.userChanges.setProperty(setting_key, "value", self._default_extruder_position)
if add_user_changes:
caution_message = Message(catalog.i18nc(
"@info:generic",
"Settings have been changed to match the current availability of extruders: [%s]" % ", ".join(add_user_changes)),
lifetime=0,
title = catalog.i18nc("@info:title", "Settings updated"))
caution_message.show()
## Set the amount of extruders on the active machine (global stack) ## Set the amount of extruders on the active machine (global stack)
# \param extruder_count int the number of extruders to set # \param extruder_count int the number of extruders to set
def setActiveMachineExtruderCount(self, extruder_count): def setActiveMachineExtruderCount(self, extruder_count):
@ -711,16 +736,11 @@ class MachineManager(QObject):
if extruder_count == previous_extruder_count: if extruder_count == previous_extruder_count:
return return
# reset all extruder number settings whose value is no longer valid definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
for setting_instance in self._global_container_stack.userChanges.findInstances():
setting_key = setting_instance.definition.key
if not self._global_container_stack.getProperty(setting_key, "type") in ("extruder", "optional_extruder"):
continue
old_value = int(self._global_container_stack.userChanges.getProperty(setting_key, "value")) self.updateDefaultExtruder()
if old_value >= extruder_count: self.updateNumberExtrudersEnabled()
self._global_container_stack.userChanges.removeInstance(setting_key) self.correctExtruderSettings()
Logger.log("d", "Reset [%s] because its old value [%s] is no longer valid ", setting_key, old_value)
# Check to see if any objects are set to print with an extruder that will no longer exist # Check to see if any objects are set to print with an extruder that will no longer exist
root_node = Application.getInstance().getController().getScene().getRoot() root_node = Application.getInstance().getController().getScene().getRoot()
@ -731,21 +751,19 @@ class MachineManager(QObject):
if extruder_nr is not None and int(extruder_nr) > extruder_count - 1: if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId()) node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId())
definition_changes_container.setProperty("machine_extruder_count", "value", extruder_count)
# Make sure one of the extruder stacks is active # Make sure one of the extruder stacks is active
extruder_manager.setActiveExtruderIndex(0) extruder_manager.setActiveExtruderIndex(0)
# Move settable_per_extruder values out of the global container # Move settable_per_extruder values out of the global container
# After CURA-4482 this should not be the case anymore, but we still want to support older project files. # After CURA-4482 this should not be the case anymore, but we still want to support older project files.
global_user_container = self._global_container_stack.getTop() global_user_container = self._global_container_stack.userChanges
# Make sure extruder_stacks exists # Make sure extruder_stacks exists
extruder_stacks = [] extruder_stacks = []
if previous_extruder_count == 1: if previous_extruder_count == 1:
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
global_user_container = self._global_container_stack.getTop() global_user_container = self._global_container_stack.userChanges
for setting_instance in global_user_container.findInstances(): for setting_instance in global_user_container.findInstances():
setting_key = setting_instance.definition.key setting_key = setting_instance.definition.key
@ -754,11 +772,12 @@ class MachineManager(QObject):
if settable_per_extruder: if settable_per_extruder:
limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder")) limit_to_extruder = int(self._global_container_stack.getProperty(setting_key, "limit_to_extruder"))
extruder_stack = extruder_stacks[max(0, limit_to_extruder)] extruder_stack = extruder_stacks[max(0, limit_to_extruder)]
extruder_stack.getTop().setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value")) extruder_stack.userChanges.setProperty(setting_key, "value", global_user_container.getProperty(setting_key, "value"))
global_user_container.removeInstance(setting_key) global_user_container.removeInstance(setting_key)
# Signal that the global stack has changed # Signal that the global stack has changed
Application.getInstance().globalContainerStackChanged.emit() Application.getInstance().globalContainerStackChanged.emit()
self.forceUpdateAllSettings()
@pyqtSlot(int, result = QObject) @pyqtSlot(int, result = QObject)
def getExtruder(self, position: int): def getExtruder(self, position: int):
@ -767,6 +786,54 @@ class MachineManager(QObject):
extruder = self._global_container_stack.extruders.get(str(position)) extruder = self._global_container_stack.extruders.get(str(position))
return extruder return extruder
def updateDefaultExtruder(self):
extruder_items = sorted(self._global_container_stack.extruders.items())
old_position = self._default_extruder_position
new_default_position = "0"
for position, extruder in extruder_items:
if extruder.isEnabled:
new_default_position = position
break
if new_default_position != old_position:
self._default_extruder_position = new_default_position
self.extruderChanged.emit()
def updateNumberExtrudersEnabled(self):
definition_changes_container = self._global_container_stack.definitionChanges
extruder_count = 0
for position, extruder in self._global_container_stack.extruders.items():
if extruder.isEnabled:
extruder_count += 1
definition_changes_container.setProperty("extruders_enabled_count", "value", extruder_count)
@pyqtProperty(str, notify = extruderChanged)
def defaultExtruderPosition(self):
return self._default_extruder_position
## This will fire the propertiesChanged for all settings so they will be updated in the front-end
def forceUpdateAllSettings(self):
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
property_names = ["value", "resolve"]
for setting_key in self._global_container_stack.getAllKeys():
self._global_container_stack.propertiesChanged.emit(setting_key, property_names)
@pyqtSlot(int, bool)
def setExtruderEnabled(self, position: int, enabled) -> None:
extruder = self.getExtruder(position)
extruder.setEnabled(enabled)
self.updateDefaultExtruder()
self.updateNumberExtrudersEnabled()
self.correctExtruderSettings()
# ensure that the quality profile is compatible with current combination, or choose a compatible one if available
self._updateQualityWithMaterial()
self.extruderChanged.emit()
# update material compatibility color
self.activeQualityGroupChanged.emit()
# update items in SettingExtruder
ExtruderManager.getInstance().extrudersChanged.emit(self._global_container_stack.getId())
# Make sure the front end reflects changes
self.forceUpdateAllSettings()
def _onMachineNameChanged(self): def _onMachineNameChanged(self):
self.globalContainerChanged.emit() self.globalContainerChanged.emit()
@ -787,27 +854,32 @@ class MachineManager(QObject):
container = extruder.userChanges container = extruder.userChanges
container.setProperty(setting_name, property_name, property_value) container.setProperty(setting_name, property_name, property_value)
@pyqtProperty("QVariantList", notify = rootMaterialChanged) @pyqtProperty("QVariantList", notify = globalContainerChanged)
def currentExtruderPositions(self): def currentExtruderPositions(self):
return sorted(list(self._current_root_material_id.keys())) if self._global_container_stack is None:
return []
return sorted(list(self._global_container_stack.extruders.keys()))
@pyqtProperty("QVariant", notify = rootMaterialChanged) ## Update _current_root_material_id and _current_root_material_name when
def currentRootMaterialId(self): # the current root material was changed.
# initial filling the current_root_material_id def _onRootMaterialChanged(self):
self._current_root_material_id = {} self._current_root_material_id = {}
for position in self._global_container_stack.extruders:
self._current_root_material_id[position] = self._global_container_stack.extruders[position].material.getMetaDataEntry("base_file")
return self._current_root_material_id
@pyqtProperty("QVariant", notify = rootMaterialChanged)
def currentRootMaterialName(self):
# initial filling the current_root_material_name
if self._global_container_stack: if self._global_container_stack:
for position in self._global_container_stack.extruders:
self._current_root_material_id[position] = self._global_container_stack.extruders[position].material.getMetaDataEntry("base_file")
self._current_root_material_name = {} self._current_root_material_name = {}
for position in self._global_container_stack.extruders: for position in self._global_container_stack.extruders:
if position not in self._current_root_material_name: if position not in self._current_root_material_name:
material = self._global_container_stack.extruders[position].material material = self._global_container_stack.extruders[position].material
self._current_root_material_name[position] = material.getName() self._current_root_material_name[position] = material.getName()
@pyqtProperty("QVariant", notify = rootMaterialChanged)
def currentRootMaterialId(self):
return self._current_root_material_id
@pyqtProperty("QVariant", notify = rootMaterialChanged)
def currentRootMaterialName(self):
return self._current_root_material_name return self._current_root_material_name
## Return the variant names in the extruder stack(s). ## Return the variant names in the extruder stack(s).
@ -854,9 +926,9 @@ class MachineManager(QObject):
# Set quality and quality_changes for each ExtruderStack # Set quality and quality_changes for each ExtruderStack
for position, node in quality_group.nodes_for_extruders.items(): for position, node in quality_group.nodes_for_extruders.items():
self._global_container_stack.extruders[position].quality = node.getContainer() self._global_container_stack.extruders[str(position)].quality = node.getContainer()
if empty_quality_changes: if empty_quality_changes:
self._global_container_stack.extruders[position].qualityChanges = self._empty_quality_changes_container self._global_container_stack.extruders[str(position)].qualityChanges = self._empty_quality_changes_container
self.activeQualityGroupChanged.emit() self.activeQualityGroupChanged.emit()
self.activeQualityChangesGroupChanged.emit() self.activeQualityChangesGroupChanged.emit()
@ -921,6 +993,8 @@ class MachineManager(QObject):
# check material - variant compatibility # check material - variant compatibility
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)): if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
for position, extruder in self._global_container_stack.extruders.items(): for position, extruder in self._global_container_stack.extruders.items():
if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"):
return False
if not extruder.material.getMetaDataEntry("compatible"): if not extruder.material.getMetaDataEntry("compatible"):
return False return False
return True return True

View File

@ -62,7 +62,7 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh" # use value from the stack because there can be a delay in signal triggering and "_is_non_printing_mesh"
# has not been updated yet. # has not been updated yet.
deep_copy._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings) deep_copy._is_non_printing_mesh = self.evaluateIsNonPrintingMesh()
return deep_copy return deep_copy
@ -90,10 +90,18 @@ class SettingOverrideDecorator(SceneNodeDecorator):
def isNonPrintingMesh(self): def isNonPrintingMesh(self):
return self._is_non_printing_mesh return self._is_non_printing_mesh
def evaluateIsNonPrintingMesh(self):
return any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
def _onSettingChanged(self, instance, property_name): # Reminder: 'property' is a built-in function def _onSettingChanged(self, instance, property_name): # Reminder: 'property' is a built-in function
# Trigger slice/need slicing if the value has changed. object_has_instance_setting = False
if property_name == "value": for container in self._stack.getContainers():
self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings) if container.hasProperty(instance, "value"):
object_has_instance_setting = True
break
if property_name == "value" and object_has_instance_setting:
# Trigger slice/need slicing if the value has changed.
self._is_non_printing_mesh = self.evaluateIsNonPrintingMesh()
Application.getInstance().getBackend().needsSlicing() Application.getInstance().getBackend().needsSlicing()
Application.getInstance().getBackend().tickle() Application.getInstance().getBackend().tickle()

View File

@ -10,7 +10,6 @@ from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources from UM.Resources import Resources
from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then.
from UM.Platform import Platform from UM.Platform import Platform
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Qt.Duration import DurationFormat from UM.Qt.Duration import DurationFormat
@ -32,6 +31,7 @@ import Arcus
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
class CuraEngineBackend(QObject, Backend): class CuraEngineBackend(QObject, Backend):
backendError = Signal() backendError = Signal()
@ -62,23 +62,26 @@ class CuraEngineBackend(QObject, Backend):
default_engine_location = execpath default_engine_location = execpath
break break
self._application = Application.getInstance()
self._multi_build_plate_model = None
self._machine_error_checker = None
if not default_engine_location: if not default_engine_location:
raise EnvironmentError("Could not find CuraEngine") raise EnvironmentError("Could not find CuraEngine")
Logger.log("i", "Found CuraEngine at: %s" %(default_engine_location)) Logger.log("i", "Found CuraEngine at: %s", default_engine_location)
default_engine_location = os.path.abspath(default_engine_location) default_engine_location = os.path.abspath(default_engine_location)
Preferences.getInstance().addPreference("backend/location", default_engine_location) Preferences.getInstance().addPreference("backend/location", default_engine_location)
# Workaround to disable layer view processing if layer view is not active. # Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False self._layer_view_active = False
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
Application.getInstance().getMultiBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged() self._onActiveViewChanged()
self._stored_layer_data = [] self._stored_layer_data = []
self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._scene = Application.getInstance().getController().getScene() self._scene = self._application.getController().getScene()
self._scene.sceneChanged.connect(self._onSceneChanged) self._scene.sceneChanged.connect(self._onSceneChanged)
# Triggers for auto-slicing. Auto-slicing is triggered as follows: # Triggers for auto-slicing. Auto-slicing is triggered as follows:
@ -86,18 +89,10 @@ class CuraEngineBackend(QObject, Backend):
# - whenever there is a value change, we start the timer # - whenever there is a value change, we start the timer
# - sometimes an error check can get scheduled for a value change, in that case, we ONLY want to start the # - sometimes an error check can get scheduled for a value change, in that case, we ONLY want to start the
# auto-slicing timer when that error check is finished # auto-slicing timer when that error check is finished
# If there is an error check, it will set the "_is_error_check_scheduled" flag, stop the auto-slicing timer, # If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
# and only wait for the error check to be finished to start the auto-slicing timer again. # to start the auto-slicing timer again.
# #
self._global_container_stack = None self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)
# A flag indicating if an error check was scheduled
# If so, we will stop the auto-slice timer and start upon the error check
self._is_error_check_scheduled = False
# 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
@ -123,13 +118,6 @@ class CuraEngineBackend(QObject, Backend):
self._last_num_objects = defaultdict(int) # Count number of objects to see if there is something changed self._last_num_objects = defaultdict(int) # Count number of objects to see if there is something changed
self._postponed_scene_change_sources = [] # scene change is postponed (by a tool) self._postponed_scene_change_sources = [] # scene change is postponed (by a tool)
self.backendQuit.connect(self._onBackendQuit)
self.backendConnected.connect(self._onBackendConnected)
# 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().toolOperationStopped.connect(self._onToolOperationStopped)
self._slice_start_time = None self._slice_start_time = None
Preferences.getInstance().addPreference("general/auto_slice", True) Preferences.getInstance().addPreference("general/auto_slice", True)
@ -144,6 +132,30 @@ class CuraEngineBackend(QObject, Backend):
self.determineAutoSlicing() self.determineAutoSlicing()
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
self._application.initializationFinished.connect(self.initialize)
def initialize(self):
self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
self._application.getController().activeViewChanged.connect(self._onActiveViewChanged)
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
# extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash
ExtruderManager.getInstance().extrudersChanged.connect(self._extruderChanged)
self.backendQuit.connect(self._onBackendQuit)
self.backendConnected.connect(self._onBackendConnected)
# When a tool operation is in progress, don't slice. So we need to listen for tool operations.
self._application.getController().toolOperationStarted.connect(self._onToolOperationStarted)
self._application.getController().toolOperationStopped.connect(self._onToolOperationStopped)
self._machine_error_checker = self._application.getMachineErrorChecker()
self._machine_error_checker.errorCheckFinished.connect(self._onStackErrorCheckFinished)
## Terminate the engine process. ## Terminate the engine process.
# #
# This function should terminate the engine process. # This function should terminate the engine process.
@ -529,11 +541,9 @@ class CuraEngineBackend(QObject, Backend):
elif property == "validationState": elif property == "validationState":
if self._use_timer: if self._use_timer:
self._is_error_check_scheduled = True
self._change_timer.stop() self._change_timer.stop()
def _onStackErrorCheckFinished(self): def _onStackErrorCheckFinished(self):
self._is_error_check_scheduled = False
if not self._slicing and self._build_plates_to_be_sliced: if not self._slicing and self._build_plates_to_be_sliced:
self.needsSlicing() self.needsSlicing()
self._onChanged() self._onChanged()
@ -559,12 +569,15 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(message.amount) self.processingProgress.emit(message.amount)
self.backendStateChange.emit(BackendState.Processing) self.backendStateChange.emit(BackendState.Processing)
# testing
def _invokeSlice(self): def _invokeSlice(self):
if self._use_timer: if self._use_timer:
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice, # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
# otherwise business as usual # otherwise business as usual
if self._is_error_check_scheduled: if self._machine_error_checker is None:
self._change_timer.stop()
return
if self._machine_error_checker.needToWaitForResult:
self._change_timer.stop() self._change_timer.stop()
else: else:
self._change_timer.start() self._change_timer.start()
@ -630,7 +643,11 @@ class CuraEngineBackend(QObject, Backend):
if self._use_timer: if self._use_timer:
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice, # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
# otherwise business as usual # otherwise business as usual
if self._is_error_check_scheduled: if self._machine_error_checker is None:
self._change_timer.stop()
return
if self._machine_error_checker.needToWaitForResult:
self._change_timer.stop() self._change_timer.stop()
else: else:
self._change_timer.start() self._change_timer.start()
@ -782,3 +799,9 @@ class CuraEngineBackend(QObject, Backend):
def tickle(self): def tickle(self):
if self._use_timer: if self._use_timer:
self._change_timer.start() self._change_timer.start()
def _extruderChanged(self):
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number)
self._invokeSlice()

View File

@ -81,7 +81,8 @@ class ProcessSlicedLayersJob(Job):
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
new_node = CuraSceneNode() # The no_setting_override is here because adding the SettingOverrideDecorator will trigger a reslice
new_node = CuraSceneNode(no_setting_override = True)
new_node.addDecorator(BuildPlateDecorator(self._build_plate_number)) new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))
# Force garbage collection. # Force garbage collection.

View File

@ -129,8 +129,10 @@ class StartSliceJob(Job):
self.setResult(StartJobResult.MaterialIncompatible) self.setResult(StartJobResult.MaterialIncompatible)
return return
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): for position, extruder_stack in stack.extruders.items():
material = extruder_stack.findContainer({"type": "material"}) material = extruder_stack.findContainer({"type": "material"})
if not extruder_stack.isEnabled:
continue
if material: if material:
if material.getMetaDataEntry("compatible") == False: if material.getMetaDataEntry("compatible") == False:
self.setResult(StartJobResult.MaterialIncompatible) self.setResult(StartJobResult.MaterialIncompatible)
@ -189,11 +191,18 @@ class StartSliceJob(Job):
if per_object_stack: if per_object_stack:
is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS) is_non_printing_mesh = any(per_object_stack.getProperty(key, "value") for key in NON_PRINTING_MESH_SETTINGS)
if node.callDecoration("getBuildPlateNumber") == self._build_plate_number: # Find a reason not to add the node
if not getattr(node, "_outside_buildarea", False) or is_non_printing_mesh: if node.callDecoration("getBuildPlateNumber") != self._build_plate_number:
temp_list.append(node) continue
if not is_non_printing_mesh: if getattr(node, "_outside_buildarea", False) and not is_non_printing_mesh:
has_printing_mesh = True continue
node_position = node.callDecoration("getActiveExtruderPosition")
if not stack.extruders[str(node_position)].isEnabled:
continue
temp_list.append(node)
if not is_non_printing_mesh:
has_printing_mesh = True
Job.yieldThread() Job.yieldThread()
@ -269,9 +278,15 @@ class StartSliceJob(Job):
# \return A dictionary of replacement tokens to the values they should be # \return A dictionary of replacement tokens to the values they should be
# replaced with. # replaced with.
def _buildReplacementTokens(self, stack) -> dict: def _buildReplacementTokens(self, stack) -> dict:
default_extruder_position = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
result = {} result = {}
for key in stack.getAllKeys(): for key in stack.getAllKeys():
result[key] = stack.getProperty(key, "value") setting_type = stack.getProperty(key, "type")
value = stack.getProperty(key, "value")
if setting_type == "extruder" and value == -1:
# replace with the default value
value = default_extruder_position
result[key] = value
Job.yieldThread() Job.yieldThread()
result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings. result["print_bed_temperature"] = result["material_bed_temperature"] # Renamed settings.
@ -377,11 +392,11 @@ class StartSliceJob(Job):
# limit_to_extruder property. # limit_to_extruder property.
def _buildGlobalInheritsStackMessage(self, stack): def _buildGlobalInheritsStackMessage(self, stack):
for key in stack.getAllKeys(): for key in stack.getAllKeys():
extruder = int(round(float(stack.getProperty(key, "limit_to_extruder")))) extruder_position = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
if extruder >= 0: #Set to a specific extruder. if extruder_position >= 0: # Set to a specific extruder.
setting_extruder = self._slice_message.addRepeatedMessage("limit_to_extruder") setting_extruder = self._slice_message.addRepeatedMessage("limit_to_extruder")
setting_extruder.name = key setting_extruder.name = key
setting_extruder.extruder = extruder setting_extruder.extruder = extruder_position
Job.yieldThread() Job.yieldThread()
## Check if a node has per object settings and ensure that they are set correctly in the message ## Check if a node has per object settings and ensure that they are set correctly in the message

View File

@ -78,17 +78,13 @@ class SolidView(View):
for node in DepthFirstIterator(scene.getRoot()): for node in DepthFirstIterator(scene.getRoot()):
if not node.render(renderer): if not node.render(renderer):
if node.getMeshData() and node.isVisible(): if node.getMeshData() and node.isVisible() and not node.callDecoration("getLayerData"):
uniforms = {} uniforms = {}
shade_factor = 1.0 shade_factor = 1.0
per_mesh_stack = node.callDecoration("getStack") per_mesh_stack = node.callDecoration("getStack")
# Get color to render this mesh in from ExtrudersModel extruder_index = int(node.callDecoration("getActiveExtruderPosition"))
extruder_index = 0
extruder_id = node.callDecoration("getActiveExtruder")
if extruder_id:
extruder_index = max(0, self._extruders_model.find("id", extruder_id))
# Use the support extruder instead of the active extruder if this is a support_mesh # Use the support extruder instead of the active extruder if this is a support_mesh
if per_mesh_stack: if per_mesh_stack:

View File

@ -277,7 +277,7 @@ class XmlMaterialProfile(InstanceContainer):
# Compatible is a special case, as it's added as a meta data entry (instead of an instance). # Compatible is a special case, as it's added as a meta data entry (instead of an instance).
material_container = variant_dict["material_container"] material_container = variant_dict["material_container"]
compatible = container.getMetaDataEntry("compatible") compatible = material_container.getMetaDataEntry("compatible")
if compatible is not None: if compatible is not None:
builder.start("setting", {"key": "hardware compatible"}) builder.start("setting", {"key": "hardware compatible"})
if compatible: if compatible:

View File

@ -211,6 +211,18 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false
}, },
"extruders_enabled_count":
{
"label": "Number of Extruders that are enabled",
"description": "Number of extruder trains that are enabled; automatically set in software",
"default_value": "machine_extruder_count",
"minimum_value": "1",
"maximum_value": "16",
"type": "int",
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
},
"machine_nozzle_tip_outer_diameter": "machine_nozzle_tip_outer_diameter":
{ {
"label": "Outer nozzle diameter", "label": "Outer nozzle diameter",
@ -887,7 +899,7 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": true, "settable_per_meshgroup": true,
"settable_globally": true, "settable_globally": true,
"enabled": "machine_extruder_count > 1", "enabled": "extruders_enabled_count > 1",
"children": { "children": {
"wall_0_extruder_nr": "wall_0_extruder_nr":
{ {
@ -900,7 +912,7 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": true, "settable_per_meshgroup": true,
"settable_globally": true, "settable_globally": true,
"enabled": "machine_extruder_count > 1" "enabled": "extruders_enabled_count > 1"
}, },
"wall_x_extruder_nr": "wall_x_extruder_nr":
{ {
@ -913,7 +925,7 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": true, "settable_per_meshgroup": true,
"settable_globally": true, "settable_globally": true,
"enabled": "machine_extruder_count > 1" "enabled": "extruders_enabled_count > 1"
} }
} }
}, },
@ -970,7 +982,7 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": true, "settable_per_meshgroup": true,
"settable_globally": true, "settable_globally": true,
"enabled": "machine_extruder_count > 1 and max(extruderValues('roofing_layer_count')) > 0 and max(extruderValues('top_layers')) > 0" "enabled": "extruders_enabled_count > 1 and max(extruderValues('roofing_layer_count')) > 0 and max(extruderValues('top_layers')) > 0"
}, },
"roofing_layer_count": "roofing_layer_count":
{ {
@ -995,7 +1007,7 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": true, "settable_per_meshgroup": true,
"settable_globally": true, "settable_globally": true,
"enabled": "machine_extruder_count > 1" "enabled": "extruders_enabled_count > 1"
}, },
"top_bottom_thickness": "top_bottom_thickness":
{ {
@ -1465,7 +1477,7 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": true, "settable_per_meshgroup": true,
"settable_globally": true, "settable_globally": true,
"enabled": "machine_extruder_count > 1" "enabled": "extruders_enabled_count > 1"
}, },
"infill_sparse_density": "infill_sparse_density":
{ {
@ -1916,7 +1928,7 @@
"minimum_value": "0", "minimum_value": "0",
"maximum_value_warning": "10.0", "maximum_value_warning": "10.0",
"maximum_value": "machine_nozzle_heat_up_speed", "maximum_value": "machine_nozzle_heat_up_speed",
"enabled": "material_flow_dependent_temperature or (machine_extruder_count > 1 and material_final_print_temperature != material_print_temperature)", "enabled": "material_flow_dependent_temperature or (extruders_enabled_count > 1 and material_final_print_temperature != material_print_temperature)",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
}, },
@ -2179,7 +2191,7 @@
"minimum_value": "-273.15", "minimum_value": "-273.15",
"minimum_value_warning": "0", "minimum_value_warning": "0",
"maximum_value_warning": "260", "maximum_value_warning": "260",
"enabled": "machine_extruder_count > 1 and machine_nozzle_temp_enabled", "enabled": "extruders_enabled_count > 1 and machine_nozzle_temp_enabled",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
}, },
@ -3281,7 +3293,7 @@
"description": "After the machine switched from one extruder to the other, the build plate is lowered to create clearance between the nozzle and the print. This prevents the nozzle from leaving oozed material on the outside of a print.", "description": "After the machine switched from one extruder to the other, the build plate is lowered to create clearance between the nozzle and the print. This prevents the nozzle from leaving oozed material on the outside of a print.",
"type": "bool", "type": "bool",
"default_value": true, "default_value": true,
"enabled": "retraction_hop_enabled and machine_extruder_count > 1", "enabled": "retraction_hop_enabled and extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
} }
@ -3459,7 +3471,8 @@
"description": "The extruder train to use for printing the support. This is used in multi-extrusion.", "description": "The extruder train to use for printing the support. This is used in multi-extrusion.",
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1", "value": "-1",
"enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"children": { "children": {
@ -3470,7 +3483,7 @@
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "support_extruder_nr", "value": "support_extruder_nr",
"enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },
@ -3481,7 +3494,7 @@
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "support_extruder_nr", "value": "support_extruder_nr",
"enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },
@ -3492,7 +3505,7 @@
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "support_extruder_nr", "value": "support_extruder_nr",
"enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"children": "children":
@ -3504,7 +3517,7 @@
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "support_interface_extruder_nr", "value": "support_interface_extruder_nr",
"enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },
@ -3515,7 +3528,7 @@
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"value": "support_interface_extruder_nr", "value": "support_interface_extruder_nr",
"enabled": "(support_enable or support_tree_enable) and machine_extruder_count > 1", "enabled": "(support_enable or support_tree_enable) and extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
} }
@ -4185,7 +4198,8 @@
"description": "The extruder train to use for printing the skirt/brim/raft. This is used in multi-extrusion.", "description": "The extruder train to use for printing the skirt/brim/raft. This is used in multi-extrusion.",
"type": "extruder", "type": "extruder",
"default_value": "0", "default_value": "0",
"enabled": "machine_extruder_count > 1 and resolveOrValue('adhesion_type') != 'none'", "value": "-1",
"enabled": "extruders_enabled_count > 1 and resolveOrValue('adhesion_type') != 'none'",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },
@ -4756,7 +4770,7 @@
"label": "Enable Prime Tower", "label": "Enable Prime Tower",
"description": "Print a tower next to the print which serves to prime the material after each nozzle switch.", "description": "Print a tower next to the print which serves to prime the material after each nozzle switch.",
"type": "bool", "type": "bool",
"enabled": "machine_extruder_count > 1", "enabled": "extruders_enabled_count > 1",
"default_value": false, "default_value": false,
"resolve": "any(extruderValues('prime_tower_enable'))", "resolve": "any(extruderValues('prime_tower_enable'))",
"settable_per_mesh": false, "settable_per_mesh": false,
@ -4904,7 +4918,7 @@
"description": "Enable exterior ooze shield. This will create a shell around the model which is likely to wipe a second nozzle if it's at the same height as the first nozzle.", "description": "Enable exterior ooze shield. This will create a shell around the model which is likely to wipe a second nozzle if it's at the same height as the first nozzle.",
"type": "bool", "type": "bool",
"resolve": "any(extruderValues('ooze_shield_enabled'))", "resolve": "any(extruderValues('ooze_shield_enabled'))",
"enabled": "machine_extruder_count > 1", "enabled": "extruders_enabled_count > 1",
"default_value": false, "default_value": false,
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
@ -4997,7 +5011,7 @@
"description": "Remove areas where multiple meshes are overlapping with each other. This may be used if merged dual material objects overlap with each other.", "description": "Remove areas where multiple meshes are overlapping with each other. This may be used if merged dual material objects overlap with each other.",
"type": "bool", "type": "bool",
"default_value": true, "default_value": true,
"value": "machine_extruder_count > 1", "value": "extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": true "settable_per_meshgroup": true
@ -5044,7 +5058,7 @@
"one_at_a_time": "One at a Time" "one_at_a_time": "One at a Time"
}, },
"default_value": "all_at_once", "default_value": "all_at_once",
"enabled": "machine_extruder_count == 1", "enabled": "extruders_enabled_count == 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false

View File

@ -43,10 +43,10 @@
{ {
"default_value": "default_value":
[ [
[ -29, 6.1 ], [ -41.9, -45.8 ],
[ -29, -33.9 ], [ -41.9, 33.9 ],
[ 71, 6.1 ], [ 59.9, 33.9 ],
[ 71, -33.9 ] [ 59.9, -45.8 ]
] ]
}, },
"machine_gcode_flavor": { "default_value": "Griffin" }, "machine_gcode_flavor": { "default_value": "Griffin" },

View File

@ -194,14 +194,31 @@ UM.MainWindow
NozzleMenu { title: Cura.MachineManager.activeDefinitionVariantsName; visible: Cura.MachineManager.hasVariants; extruderIndex: index } NozzleMenu { title: Cura.MachineManager.activeDefinitionVariantsName; visible: Cura.MachineManager.hasVariants; extruderIndex: index }
MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: Cura.MachineManager.hasMaterials; extruderIndex: index } MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: Cura.MachineManager.hasMaterials; extruderIndex: index }
MenuSeparator { MenuSeparator
{
visible: Cura.MachineManager.hasVariants || Cura.MachineManager.hasMaterials visible: Cura.MachineManager.hasVariants || Cura.MachineManager.hasMaterials
} }
MenuItem { MenuItem
{
text: catalog.i18nc("@action:inmenu", "Set as Active Extruder") text: catalog.i18nc("@action:inmenu", "Set as Active Extruder")
onTriggered: Cura.ExtruderManager.setActiveExtruderIndex(model.index) onTriggered: Cura.MachineManager.setExtruderIndex(model.index)
} }
MenuItem
{
text: catalog.i18nc("@action:inmenu", "Enable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, true)
visible: !Cura.MachineManager.getExtruder(model.index).isEnabled
}
MenuItem
{
text: catalog.i18nc("@action:inmenu", "Disable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false)
visible: Cura.MachineManager.getExtruder(model.index).isEnabled
}
} }
onObjectAdded: settingsMenu.insertItem(index, object) onObjectAdded: settingsMenu.insertItem(index, object)
onObjectRemoved: settingsMenu.removeItem(object) onObjectRemoved: settingsMenu.removeItem(object)

View File

@ -19,7 +19,7 @@ Button
iconSource: UM.Theme.getIcon("extruder_button") iconSource: UM.Theme.getIcon("extruder_button")
checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1 checked: Cura.ExtruderManager.selectedObjectExtruders.indexOf(extruder.id) != -1
enabled: UM.Selection.hasSelection enabled: UM.Selection.hasSelection && extruder.stack.isEnabled
property color customColor: base.hovered ? UM.Theme.getColor("button_hover") : UM.Theme.getColor("button"); property color customColor: base.hovered ? UM.Theme.getColor("button_hover") : UM.Theme.getColor("button");

View File

@ -19,14 +19,17 @@ Item
UM.I18nCatalog { id: catalog; name: "cura"; } UM.I18nCatalog { id: catalog; name: "cura"; }
Cura.MaterialManagementModel { Cura.MaterialManagementModel
{
id: materialsModel id: materialsModel
} }
Label { Label
{
id: titleLabel id: titleLabel
anchors { anchors
{
top: parent.top top: parent.top
left: parent.left left: parent.left
right: parent.right right: parent.right
@ -170,22 +173,27 @@ Item
Connections Connections
{ {
target: materialsModel target: materialsModel
onItemsChanged: { onItemsChanged:
{
var currentItemId = base.currentItem == null ? "" : base.currentItem.root_material_id; var currentItemId = base.currentItem == null ? "" : base.currentItem.root_material_id;
var position = Cura.ExtruderManager.activeExtruderIndex; var position = Cura.ExtruderManager.activeExtruderIndex;
// try to pick the currently selected item; it may have been moved // try to pick the currently selected item; it may have been moved
if (base.newRootMaterialIdToSwitchTo == "") { if (base.newRootMaterialIdToSwitchTo == "")
{
base.newRootMaterialIdToSwitchTo = currentItemId; base.newRootMaterialIdToSwitchTo = currentItemId;
} }
for (var idx = 0; idx < materialsModel.rowCount(); ++idx) { for (var idx = 0; idx < materialsModel.rowCount(); ++idx)
{
var item = materialsModel.getItem(idx); var item = materialsModel.getItem(idx);
if (item.root_material_id == base.newRootMaterialIdToSwitchTo) { if (item.root_material_id == base.newRootMaterialIdToSwitchTo)
{
// Switch to the newly created profile if needed // Switch to the newly created profile if needed
materialListView.currentIndex = idx; materialListView.currentIndex = idx;
materialListView.activateDetailsWithIndex(materialListView.currentIndex); materialListView.activateDetailsWithIndex(materialListView.currentIndex);
if (base.toActivateNewMaterial) { if (base.toActivateNewMaterial)
{
Cura.MachineManager.setMaterial(position, item.container_node); Cura.MachineManager.setMaterial(position, item.container_node);
} }
base.newRootMaterialIdToSwitchTo = ""; base.newRootMaterialIdToSwitchTo = "";
@ -196,7 +204,8 @@ Item
materialListView.currentIndex = 0; materialListView.currentIndex = 0;
materialListView.activateDetailsWithIndex(materialListView.currentIndex); materialListView.activateDetailsWithIndex(materialListView.currentIndex);
if (base.toActivateNewMaterial) { if (base.toActivateNewMaterial)
{
Cura.MachineManager.setMaterial(position, materialsModel.getItem(0).container_node); Cura.MachineManager.setMaterial(position, materialsModel.getItem(0).container_node);
} }
base.newRootMaterialIdToSwitchTo = ""; base.newRootMaterialIdToSwitchTo = "";
@ -233,14 +242,17 @@ Item
messageDialog.title = catalog.i18nc("@title:window", "Import Material"); messageDialog.title = catalog.i18nc("@title:window", "Import Material");
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Could not import material <filename>%1</filename>: <message>%2</message>").arg(fileUrl).arg(result.message); messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Could not import material <filename>%1</filename>: <message>%2</message>").arg(fileUrl).arg(result.message);
if (result.status == "success") { if (result.status == "success")
{
messageDialog.icon = StandardIcon.Information; messageDialog.icon = StandardIcon.Information;
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Successfully imported material <filename>%1</filename>").arg(fileUrl); messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Successfully imported material <filename>%1</filename>").arg(fileUrl);
} }
else if (result.status == "duplicate") { else if (result.status == "duplicate")
{
messageDialog.icon = StandardIcon.Warning; messageDialog.icon = StandardIcon.Warning;
} }
else { else
{
messageDialog.icon = StandardIcon.Critical; messageDialog.icon = StandardIcon.Critical;
} }
messageDialog.open(); messageDialog.open();
@ -260,12 +272,14 @@ Item
var result = Cura.ContainerManager.exportContainer(base.currentItem.root_material_id, selectedNameFilter, fileUrl); var result = Cura.ContainerManager.exportContainer(base.currentItem.root_material_id, selectedNameFilter, fileUrl);
messageDialog.title = catalog.i18nc("@title:window", "Export Material"); messageDialog.title = catalog.i18nc("@title:window", "Export Material");
if (result.status == "error") { if (result.status == "error")
{
messageDialog.icon = StandardIcon.Critical; messageDialog.icon = StandardIcon.Critical;
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tags <filename> and <message>!", "Failed to export material to <filename>%1</filename>: <message>%2</message>").arg(fileUrl).arg(result.message); messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tags <filename> and <message>!", "Failed to export material to <filename>%1</filename>: <message>%2</message>").arg(fileUrl).arg(result.message);
messageDialog.open(); messageDialog.open();
} }
else if (result.status == "success") { else if (result.status == "success")
{
messageDialog.icon = StandardIcon.Information; messageDialog.icon = StandardIcon.Information;
messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Successfully exported material to <filename>%1</filename>").arg(result.path); messageDialog.text = catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Successfully exported material to <filename>%1</filename>").arg(result.path);
messageDialog.open(); messageDialog.open();
@ -283,7 +297,8 @@ Item
Item { Item {
id: contentsItem id: contentsItem
anchors { anchors
{
top: titleLabel.bottom top: titleLabel.bottom
left: parent.left left: parent.left
right: parent.right right: parent.right
@ -297,7 +312,8 @@ Item
Item Item
{ {
anchors { anchors
{
top: buttonRow.bottom top: buttonRow.bottom
topMargin: UM.Theme.getSize("default_margin").height topMargin: UM.Theme.getSize("default_margin").height
left: parent.left left: parent.left
@ -310,12 +326,14 @@ Item
Label Label
{ {
id: captionLabel id: captionLabel
anchors { anchors
{
top: parent.top top: parent.top
left: parent.left left: parent.left
} }
visible: text != "" visible: text != ""
text: { text:
{
var caption = catalog.i18nc("@action:label", "Printer") + ": " + Cura.MachineManager.activeMachineName; var caption = catalog.i18nc("@action:label", "Printer") + ": " + Cura.MachineManager.activeMachineName;
if (Cura.MachineManager.hasVariants) if (Cura.MachineManager.hasVariants)
{ {
@ -330,14 +348,16 @@ Item
ScrollView ScrollView
{ {
id: materialScrollView id: materialScrollView
anchors { anchors
{
top: captionLabel.visible ? captionLabel.bottom : parent.top top: captionLabel.visible ? captionLabel.bottom : parent.top
topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0 topMargin: captionLabel.visible ? UM.Theme.getSize("default_margin").height : 0
bottom: parent.bottom bottom: parent.bottom
left: parent.left left: parent.left
} }
Rectangle { Rectangle
{
parent: viewport parent: viewport
anchors.fill: parent anchors.fill: parent
color: palette.light color: palette.light
@ -418,13 +438,15 @@ Item
MouseArea MouseArea
{ {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked:
{
parent.ListView.view.currentIndex = model.index; parent.ListView.view.currentIndex = model.index;
} }
} }
} }
function activateDetailsWithIndex(index) { function activateDetailsWithIndex(index)
{
var model = materialsModel.getItem(index); var model = materialsModel.getItem(index);
base.currentItem = model; base.currentItem = model;
materialDetailsView.containerId = model.container_id; materialDetailsView.containerId = model.container_id;
@ -446,7 +468,8 @@ Item
{ {
id: detailsPanel id: detailsPanel
anchors { anchors
{
left: materialScrollView.right left: materialScrollView.right
leftMargin: UM.Theme.getSize("default_margin").width leftMargin: UM.Theme.getSize("default_margin").width
top: parent.top top: parent.top

View File

@ -11,7 +11,7 @@ Tab
{ {
id: base id: base
property string extruderPosition: "" property int extruderPosition: -1 //Denotes the global stack.
property var qualityItem: null property var qualityItem: null
property bool isQualityItemCurrentlyActivated: property bool isQualityItemCurrentlyActivated:

View File

@ -17,14 +17,39 @@ SettingItem
id: control id: control
anchors.fill: parent anchors.fill: parent
model: Cura.ExtrudersModel { onModelChanged: control.color = getItem(control.currentIndex).color } model: Cura.ExtrudersModel
{
onModelChanged: {
control.color = getItem(control.currentIndex).color;
}
}
textRole: "name" textRole: "name"
// knowing the extruder position, try to find the item index in the model
function getIndexByPosition(position)
{
for (var item_index in model.items)
{
var item = model.getItem(item_index)
if (item.index == position)
{
return item_index
}
}
return -1
}
onActivated: onActivated:
{ {
forceActiveFocus(); if (model.getItem(index).enabled)
propertyProvider.setPropertyValue("value", model.getItem(index).index); {
forceActiveFocus();
propertyProvider.setPropertyValue("value", model.getItem(index).index);
} else
{
currentIndex = propertyProvider.properties.value; // keep the old value
}
} }
onActiveFocusChanged: onActiveFocusChanged:
@ -64,6 +89,23 @@ SettingItem
value: control.currentText != "" ? control.model.getItem(control.currentIndex).color : "" value: control.currentText != "" ? control.model.getItem(control.currentIndex).color : ""
} }
Binding
{
target: control
property: "currentIndex"
value:
{
if(propertyProvider.properties.value == -1)
{
return control.getIndexByPosition(Cura.MachineManager.defaultExtruderPosition);
}
return propertyProvider.properties.value
}
// Sometimes when the value is already changed, the model is still being built.
// The when clause ensures that the current index is not updated when this happens.
when: control.model.items.length > 0
}
indicator: UM.RecolorImage indicator: UM.RecolorImage
{ {
id: downArrow id: downArrow
@ -173,7 +215,13 @@ SettingItem
{ {
text: model.name text: model.name
renderType: Text.NativeRendering renderType: Text.NativeRendering
color: UM.Theme.getColor("setting_control_text") color: {
if (model.enabled) {
UM.Theme.getColor("setting_control_text")
} else {
UM.Theme.getColor("action_button_disabled_text");
}
}
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter

View File

@ -374,8 +374,6 @@ Item
key: model.key ? model.key : "" key: model.key ? model.key : ""
watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ] watchedProperties: [ "value", "enabled", "state", "validationState", "settable_per_extruder", "resolve" ]
storeIndex: 0 storeIndex: 0
// Due to the way setPropertyValue works, removeUnusedValue gives the correct output in case of resolve
removeUnusedValue: model.resolve == undefined
} }
Connections Connections

View File

@ -91,10 +91,42 @@ Column
exclusiveGroup: extruderMenuGroup exclusiveGroup: extruderMenuGroup
checked: base.currentExtruderIndex == index checked: base.currentExtruderIndex == index
onClicked: property bool extruder_enabled: true
MouseArea
{ {
forceActiveFocus() // Changing focus applies the currently-being-typed values so it can change the displayed setting values. anchors.fill: parent
Cura.ExtruderManager.setActiveExtruderIndex(index); acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
switch (mouse.button) {
case Qt.LeftButton:
forceActiveFocus(); // Changing focus applies the currently-being-typed values so it can change the displayed setting values.
Cura.ExtruderManager.setActiveExtruderIndex(index);
break;
case Qt.RightButton:
extruder_enabled = Cura.MachineManager.getExtruder(model.index).isEnabled
extruderMenu.popup();
break;
}
}
}
Menu
{
id: extruderMenu
MenuItem {
text: catalog.i18nc("@action:inmenu", "Enable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, true)
visible: !extruder_enabled // using an intermediate variable prevents an empty popup that occured now and then
}
MenuItem {
text: catalog.i18nc("@action:inmenu", "Disable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false)
visible: extruder_enabled
}
} }
style: ButtonStyle style: ButtonStyle
@ -114,6 +146,18 @@ Column
Behavior on color { ColorAnimation { duration: 50; } } Behavior on color { ColorAnimation { duration: 50; } }
} }
function buttonColor(index) {
var extruder = Cura.MachineManager.getExtruder(index);
if (extruder.isEnabled) {
return (
control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_text") :
control.hovered ? UM.Theme.getColor("action_button_hovered_text") :
UM.Theme.getColor("action_button_text");
} else {
return UM.Theme.getColor("action_button_disabled_text");
}
}
Item Item
{ {
id: extruderButtonFace id: extruderButtonFace
@ -131,9 +175,7 @@ Column
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_text") : color: buttonColor(index)
control.hovered ? UM.Theme.getColor("action_button_hovered_text") :
UM.Theme.getColor("action_button_text")
font: UM.Theme.getFont("large_nonbold") font: UM.Theme.getFont("large_nonbold")
text: catalog.i18nc("@label", "Extruder") text: catalog.i18nc("@label", "Extruder")
@ -176,9 +218,7 @@ Column
id: extruderNumberText id: extruderNumberText
anchors.centerIn: parent anchors.centerIn: parent
text: index + 1; text: index + 1;
color: (control.checked || control.pressed) ? UM.Theme.getColor("action_button_active_text") : color: buttonColor(index)
control.hovered ? UM.Theme.getColor("action_button_hovered_text") :
UM.Theme.getColor("action_button_text")
font: UM.Theme.getFont("default_bold") font: UM.Theme.getFont("default_bold")
} }

View File

@ -19,7 +19,7 @@ Item
property Action configureSettings; property Action configureSettings;
property variant minimumPrintTime: PrintInformation.minimumPrintTime; property variant minimumPrintTime: PrintInformation.minimumPrintTime;
property variant maximumPrintTime: PrintInformation.maximumPrintTime; property variant maximumPrintTime: PrintInformation.maximumPrintTime;
property bool settingsEnabled: Cura.ExtruderManager.activeExtruderStackId || machineExtruderCount.properties.value == 1 property bool settingsEnabled: Cura.ExtruderManager.activeExtruderStackId || extrudersEnabledCount.properties.value == 1
Component.onCompleted: PrintInformation.enabled = true Component.onCompleted: PrintInformation.enabled = true
Component.onDestruction: PrintInformation.enabled = false Component.onDestruction: PrintInformation.enabled = false
@ -67,10 +67,8 @@ Item
Connections Connections
{ {
target: Cura.MachineManager target: Cura.QualityProfilesDropDownMenuModel
onActiveQualityChanged: qualityModel.update() onItemsChanged: qualityModel.update()
onActiveMaterialChanged: qualityModel.update()
onActiveVariantChanged: qualityModel.update()
} }
Connections { Connections {
@ -518,7 +516,12 @@ Item
// Update the slider value to represent the rounded value // Update the slider value to represent the rounded value
infillSlider.value = roundedSliderValue infillSlider.value = roundedSliderValue
Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue) // Update value only if the Recomended mode is Active,
// Otherwise if I change the value in the Custom mode the Recomended view will try to repeat
// same operation
if (UM.Preferences.getValue("cura/active_mode") == 0) {
Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue)
}
} }
style: SliderStyle style: SliderStyle
@ -788,25 +791,10 @@ Item
} }
} }
Label
{
id: supportExtruderLabel
visible: supportExtruderCombobox.visible
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
anchors.right: infillCellLeft.right
anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
anchors.verticalCenter: supportExtruderCombobox.verticalCenter
text: catalog.i18nc("@label", "Support Extruder");
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text");
elide: Text.ElideRight
}
ComboBox ComboBox
{ {
id: supportExtruderCombobox id: supportExtruderCombobox
visible: enableSupportCheckBox.visible && (supportEnabled.properties.value == "True") && (machineExtruderCount.properties.value > 1) visible: enableSupportCheckBox.visible && (supportEnabled.properties.value == "True") && (extrudersEnabledCount.properties.value > 1)
model: extruderModel model: extruderModel
property string color_override: "" // for manually setting values property string color_override: "" // for manually setting values
@ -820,11 +808,12 @@ Item
textRole: "text" // this solves that the combobox isn't populated in the first time Cura is started textRole: "text" // this solves that the combobox isn't populated in the first time Cura is started
anchors.top: enableSupportCheckBox.bottom anchors.top: enableSupportCheckBox.top
anchors.topMargin: ((supportEnabled.properties.value === "True") && (machineExtruderCount.properties.value > 1)) ? UM.Theme.getSize("sidebar_margin").height : 0 //anchors.topMargin: ((supportEnabled.properties.value === "True") && (machineExtruderCount.properties.value > 1)) ? UM.Theme.getSize("sidebar_margin").height : 0
anchors.left: infillCellRight.left anchors.left: enableSupportCheckBox.right
anchors.leftMargin: Math.round(UM.Theme.getSize("sidebar_margin").width / 2)
width: Math.round(UM.Theme.getSize("sidebar").width * .55) width: Math.round(UM.Theme.getSize("sidebar").width * .55) - Math.round(UM.Theme.getSize("sidebar_margin").width / 2) - enableSupportCheckBox.width
height: ((supportEnabled.properties.value == "True") && (machineExtruderCount.properties.value > 1)) ? UM.Theme.getSize("setting_control").height : 0 height: ((supportEnabled.properties.value == "True") && (machineExtruderCount.properties.value > 1)) ? UM.Theme.getSize("setting_control").height : 0
Behavior on height { NumberAnimation { duration: 100 } } Behavior on height { NumberAnimation { duration: 100 } }
@ -891,7 +880,7 @@ Item
id: adhesionCheckBox id: adhesionCheckBox
property alias _hovered: adhesionMouseArea.containsMouse property alias _hovered: adhesionMouseArea.containsMouse
anchors.top: enableSupportCheckBox.visible ? supportExtruderCombobox.bottom : infillCellRight.bottom anchors.top: enableSupportCheckBox.bottom
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height anchors.topMargin: UM.Theme.getSize("sidebar_margin").height
anchors.left: infillCellRight.left anchors.left: infillCellRight.left
@ -1022,9 +1011,9 @@ Item
UM.SettingPropertyProvider UM.SettingPropertyProvider
{ {
id: machineExtruderCount id: extrudersEnabledCount
containerStackId: Cura.MachineManager.activeMachineId containerStackId: Cura.MachineManager.activeMachineId
key: "machine_extruder_count" key: "extruders_enabled_count"
watchedProperties: [ "value" ] watchedProperties: [ "value" ]
storeIndex: 0 storeIndex: 0
} }