Merge branch 'master' into feature_support_eraser_ux

This commit is contained in:
fieldOfView 2018-03-15 11:10:49 +01:00
commit 11be8f158f
109 changed files with 2615 additions and 660 deletions

View File

@ -136,6 +136,7 @@ class BuildVolume(SceneNode):
if active_extruder_changed is not None: if active_extruder_changed is not None:
node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild) node.callDecoration("getActiveExtruderChangedSignal").disconnect(self._updateDisallowedAreasAndRebuild)
node.decoratorsChanged.disconnect(self._updateNodeListeners) node.decoratorsChanged.disconnect(self._updateNodeListeners)
self._updateDisallowedAreasAndRebuild() # make sure we didn't miss anything before we updated the node listeners
self._scene_objects = new_scene_objects self._scene_objects = new_scene_objects
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered. self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered.
@ -150,7 +151,6 @@ class BuildVolume(SceneNode):
active_extruder_changed = node.callDecoration("getActiveExtruderChangedSignal") active_extruder_changed = node.callDecoration("getActiveExtruderChangedSignal")
if active_extruder_changed is not None: if active_extruder_changed is not None:
active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild) active_extruder_changed.connect(self._updateDisallowedAreasAndRebuild)
self._updateDisallowedAreasAndRebuild()
def setWidth(self, width): def setWidth(self, width):
if width is not None: if width is not None:
@ -239,7 +239,7 @@ class BuildVolume(SceneNode):
# 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.setOutsideBuildArea(group_node.isOutsideBuildArea) 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):

View File

@ -109,10 +109,6 @@ class CuraActions(QObject):
nodes_to_change = [] nodes_to_change = []
for node in Selection.getAllSelectedObjects(): for node in Selection.getAllSelectedObjects():
# Do not change any nodes that already have the right extruder set.
if node.callDecoration("getActiveExtruder") == extruder_id:
continue
# If the node is a group, apply the active extruder to all children of the group. # If the node is a group, apply the active extruder to all children of the group.
if node.callDecoration("isGroup"): if node.callDecoration("isGroup"):
for grouped_node in BreadthFirstIterator(node): for grouped_node in BreadthFirstIterator(node):
@ -125,6 +121,10 @@ class CuraActions(QObject):
nodes_to_change.append(grouped_node) nodes_to_change.append(grouped_node)
continue continue
# Do not change any nodes that already have the right extruder set.
if node.callDecoration("getActiveExtruder") == extruder_id:
continue
nodes_to_change.append(node) nodes_to_change.append(node)
if not nodes_to_change: if not nodes_to_change:

View File

@ -4,7 +4,7 @@
#Type hinting. #Type hinting.
from typing import Dict from typing import Dict
from PyQt5.QtCore import QObject from PyQt5.QtCore import QObject, QTimer
from PyQt5.QtNetwork import QLocalServer from PyQt5.QtNetwork import QLocalServer
from PyQt5.QtNetwork import QLocalSocket from PyQt5.QtNetwork import QLocalSocket
@ -60,18 +60,20 @@ from cura.Machines.Models.BuildPlateModel import BuildPlateModel
from cura.Machines.Models.NozzleModel import NozzleModel from cura.Machines.Models.NozzleModel import NozzleModel
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel 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.Models.QualityManagementModel import QualityManagementModel
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
from cura.Machines.Models.MachineManagementModel import MachineManagementModel
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
from cura.Machines.VariantManager import VariantManager from cura.Machines.VariantManager import VariantManager
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
from . import PlatformPhysics from . import PlatformPhysics
from . import BuildVolume from . import BuildVolume
@ -88,8 +90,8 @@ from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.UserChangesModel import UserChangesModel from cura.Settings.UserChangesModel import UserChangesModel
from cura.Settings.ExtrudersModel import ExtrudersModel from cura.Settings.ExtrudersModel import ExtrudersModel
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
from cura.Settings.ContainerManager import ContainerManager from cura.Settings.ContainerManager import ContainerManager
from cura.Settings.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
from cura.ObjectsModel import ObjectsModel from cura.ObjectsModel import ObjectsModel
@ -139,15 +141,10 @@ class CuraApplication(QtApplication):
MachineStack = Resources.UserType + 7 MachineStack = Resources.UserType + 7
ExtruderStack = Resources.UserType + 8 ExtruderStack = Resources.UserType + 8
DefinitionChangesContainer = Resources.UserType + 9 DefinitionChangesContainer = Resources.UserType + 9
SettingVisibilityPreset = Resources.UserType + 10
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"]:
@ -192,6 +189,7 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders") Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances") Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality") ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes") ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes")
@ -224,12 +222,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
@ -286,10 +286,15 @@ class CuraApplication(QtApplication):
self._preferred_mimetype = "" self._preferred_mimetype = ""
self._i18n_catalog = i18nCatalog("cura") self._i18n_catalog = i18nCatalog("cura")
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity) self._update_platform_activity_timer = QTimer()
self._update_platform_activity_timer.setInterval(500)
self._update_platform_activity_timer.setSingleShot(True)
self._update_platform_activity_timer.timeout.connect(self.updatePlatformActivity)
self.getController().getScene().sceneChanged.connect(self.updatePlatformActivityDelayed)
self.getController().toolOperationStopped.connect(self._onToolOperationStopped) self.getController().toolOperationStopped.connect(self._onToolOperationStopped)
self.getController().contextMenuRequested.connect(self._onContextMenuRequested) self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivity) self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivityDelayed)
Resources.addType(self.ResourceTypes.QmlFiles, "qml") Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware") Resources.addType(self.ResourceTypes.Firmware, "firmware")
@ -376,19 +381,9 @@ class CuraApplication(QtApplication):
preferences.setDefault("local_file/last_used_type", "text/x-gcode") preferences.setDefault("local_file/last_used_type", "text/x-gcode")
setting_visibily_preset_names = self.getVisibilitySettingPresetTypes() default_visibility_profile = SettingVisibilityPresetsModel.getInstance().getItem(0)
preferences.setDefault("general/visible_settings_preset", setting_visibily_preset_names)
preset_setting_visibility_choice = Preferences.getInstance().getValue("general/preset_setting_visibility_choice") preferences.setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
default_preset_visibility_group_name = "Basic"
if preset_setting_visibility_choice == "" or preset_setting_visibility_choice is None:
if preset_setting_visibility_choice not in setting_visibily_preset_names:
preset_setting_visibility_choice = default_preset_visibility_group_name
visible_settings = self.getVisibilitySettingPreset(settings_preset_name = preset_setting_visibility_choice)
preferences.setDefault("general/visible_settings", visible_settings)
preferences.setDefault("general/preset_setting_visibility_choice", preset_setting_visibility_choice)
self.applicationShuttingDown.connect(self.saveSettings) self.applicationShuttingDown.connect(self.saveSettings)
self.engineCreatedSignal.connect(self._onEngineCreated) self.engineCreatedSignal.connect(self._onEngineCreated)
@ -405,91 +400,6 @@ class CuraApplication(QtApplication):
CuraApplication.Created = True CuraApplication.Created = True
@pyqtSlot(str, result = str)
def getVisibilitySettingPreset(self, settings_preset_name) -> str:
result = self._loadPresetSettingVisibilityGroup(settings_preset_name)
formatted_preset_settings = self._serializePresetSettingVisibilityData(result)
return formatted_preset_settings
## Serialise the given preset setting visibitlity group dictionary into a string which is concatenated by ";"
#
def _serializePresetSettingVisibilityData(self, settings_data: dict) -> str:
result_string = ""
for key in settings_data:
result_string += key + ";"
for value in settings_data[key]:
result_string += value + ";"
return result_string
## Load the preset setting visibility group with the given name
#
def _loadPresetSettingVisibilityGroup(self, visibility_preset_name) -> Dict[str, str]:
preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups)
result = {}
right_preset_found = False
for item in os.listdir(preset_dir):
file_path = os.path.join(preset_dir, item)
if not os.path.isfile(file_path):
continue
parser = ConfigParser(allow_no_value = True) # accept options without any value,
try:
parser.read([file_path])
if not parser.has_option("general", "name"):
continue
if parser["general"]["name"] == visibility_preset_name:
right_preset_found = True
for section in parser.sections():
if section == 'general':
continue
else:
section_settings = []
for option in parser[section].keys():
section_settings.append(option)
result[section] = section_settings
if right_preset_found:
break
except Exception as e:
Logger.log("e", "Failed to load setting visibility preset %s: %s", file_path, str(e))
return result
## Check visibility setting preset folder and returns available types
#
def getVisibilitySettingPresetTypes(self):
preset_dir = Resources.getPath(Resources.PresetSettingVisibilityGroups)
result = {}
for item in os.listdir(preset_dir):
file_path = os.path.join(preset_dir, item)
if not os.path.isfile(file_path):
continue
parser = ConfigParser(allow_no_value=True) # accept options without any value,
try:
parser.read([file_path])
if not parser.has_option("general", "name") and not parser.has_option("general", "weight"):
continue
result[parser["general"]["weight"]] = parser["general"]["name"]
except Exception as e:
Logger.log("e", "Failed to load setting preset %s: %s", file_path, str(e))
return result
def _onEngineCreated(self): def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@ -743,19 +653,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 +700,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 +769,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)
@ -961,6 +886,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel") qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel") qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel") qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0, qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel) "QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
@ -973,6 +899,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel") qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
qmlRegisterSingletonType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel", SettingVisibilityPresetsModel.createSettingVisibilityPresetsModel)
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
@ -1048,6 +975,10 @@ class CuraApplication(QtApplication):
def getSceneBoundingBoxString(self): def getSceneBoundingBoxString(self):
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()} return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
def updatePlatformActivityDelayed(self, node = None):
if node is not None and node.getMeshData() is not None:
self._update_platform_activity_timer.start()
## Update scene bounding box for current build plate ## Update scene bounding box for current build plate
def updatePlatformActivity(self, node = None): def updatePlatformActivity(self, node = None):
count = 0 count = 0

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

@ -16,10 +16,11 @@ from cura.Machines.MaterialNode import MaterialNode #For type checking.
# so "generic_abs_ultimaker3", "generic_abs_ultimaker3_AA_0.4", etc. # so "generic_abs_ultimaker3", "generic_abs_ultimaker3_AA_0.4", etc.
# #
class MaterialGroup: class MaterialGroup:
__slots__ = ("name", "root_material_node", "derived_material_node_list") __slots__ = ("name", "is_read_only", "root_material_node", "derived_material_node_list")
def __init__(self, name: str, root_material_node: MaterialNode): def __init__(self, name: str, root_material_node: MaterialNode):
self.name = name self.name = name
self.is_read_only = False
self.root_material_node = root_material_node self.root_material_node = root_material_node
self.derived_material_node_list = [] #type: List[MaterialNode] self.derived_material_node_list = [] #type: List[MaterialNode]

View File

@ -86,6 +86,7 @@ class MaterialManager(QObject):
root_material_id = material_metadata.get("base_file") root_material_id = material_metadata.get("base_file")
if root_material_id not in self._material_group_map: if root_material_id not in self._material_group_map:
self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id])) self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)
group = self._material_group_map[root_material_id] group = self._material_group_map[root_material_id]
#Store this material in the group of the appropriate root material. #Store this material in the group of the appropriate root material.
@ -100,13 +101,6 @@ class MaterialManager(QObject):
# GUID -> material group list # GUID -> material group list
self._guid_material_groups_map = defaultdict(list) self._guid_material_groups_map = defaultdict(list)
for root_material_id, material_group in self._material_group_map.items(): for root_material_id, material_group in self._material_group_map.items():
# This can happen when we are updating with incomplete data.
if material_group.root_material_node is None:
Logger.log("e", "Missing root material node for [%s]. Probably caused by update using incomplete data."
" Check all related signals for further debugging.",
material_group.name)
self._update_timer.start()
return
guid = material_group.root_material_node.metadata["GUID"] guid = material_group.root_material_node.metadata["GUID"]
self._guid_material_groups_map[guid].append(material_group) self._guid_material_groups_map[guid].append(material_group)
@ -331,6 +325,35 @@ class MaterialManager(QObject):
return material_node return material_node
#
# Gets MaterialNode for the given extruder and machine with the given material type.
# Returns None if:
# 1. the given machine doesn't have materials;
# 2. cannot find any material InstanceContainers with the given settings.
#
def getMaterialNodeByType(self, global_stack: "GlobalStack", extruder_variant_name: str, material_guid: str) -> Optional["MaterialNode"]:
node = None
machine_definition = global_stack.definition
if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
material_diameter = machine_definition.getProperty("material_diameter", "value")
if isinstance(material_diameter, SettingFunction):
material_diameter = material_diameter(global_stack)
# Look at the guid to material dictionary
root_material_id = None
for material_group in self._guid_material_groups_map[material_guid]:
if material_group.is_read_only:
root_material_id = material_group.root_material_node.metadata["id"]
break
if not root_material_id:
Logger.log("i", "Cannot find materials with guid [%s] ", material_guid)
return None
node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
material_diameter, root_material_id)
return node
# #
# Used by QualityManager. Built-in quality profiles may be based on generic material IDs such as "generic_pla". # Used by QualityManager. Built-in quality profiles may be based on generic material IDs such as "generic_pla".
# For materials such as ultimaker_pla_orange, no quality profiles may be found, so we should fall back to use # For materials such as ultimaker_pla_orange, no quality profiles may be found, so we should fall back to use

View File

@ -53,8 +53,8 @@ class BrandMaterialsModel(ListModel):
self._extruder_manager = CuraApplication.getInstance().getExtruderManager() self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._material_manager = CuraApplication.getInstance().getMaterialManager() self._material_manager = CuraApplication.getInstance().getMaterialManager()
self._machine_manager.globalContainerChanged.connect(self._update) self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
self._material_manager.materialsUpdated.connect(self._update) self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
self._update() self._update()
def setExtruderPosition(self, position: int): def setExtruderPosition(self, position: int):

View File

@ -15,8 +15,8 @@ class GenericMaterialsModel(BaseMaterialsModel):
self._extruder_manager = CuraApplication.getInstance().getExtruderManager() self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
self._material_manager = CuraApplication.getInstance().getMaterialManager() self._material_manager = CuraApplication.getInstance().getMaterialManager()
self._machine_manager.globalContainerChanged.connect(self._update) self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
self._material_manager.materialsUpdated.connect(self._update) self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
self._update() self._update()
def _update(self): def _update(self):

View File

@ -0,0 +1,82 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Qt.ListModel import ListModel
from PyQt5.QtCore import Qt
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
#
# This the QML model for the quality management page.
#
class MachineManagementModel(ListModel):
NameRole = Qt.UserRole + 1
IdRole = Qt.UserRole + 2
MetaDataRole = Qt.UserRole + 3
GroupRole = Qt.UserRole + 4
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.MetaDataRole, "metadata")
self.addRoleName(self.GroupRole, "group")
self._local_container_stacks = []
self._network_container_stacks = []
# Listen to changes
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChanged)
ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerChanged)
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChanged)
self._filter_dict = {}
self._update()
## Handler for container added/removed events from registry
def _onContainerChanged(self, container):
# We only need to update when the added / removed container is a stack.
if isinstance(container, ContainerStack) and container.getMetaDataEntry("type") == "machine":
self._update()
## Private convenience function to reset & repopulate the model.
def _update(self):
items = []
# Get first the network enabled printers
network_filter_printers = {"type": "machine",
"um_network_key": "*",
"hidden": "False"}
self._network_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**network_filter_printers)
self._network_container_stacks.sort(key = lambda i: i.getMetaDataEntry("connect_group_name"))
for container in self._network_container_stacks:
metadata = container.getMetaData().copy()
if container.getBottom():
metadata["definition_name"] = container.getBottom().getName()
items.append({"name": metadata["connect_group_name"],
"id": container.getId(),
"metadata": metadata,
"group": catalog.i18nc("@info:title", "Network enabled printers")})
# Get now the local printers
local_filter_printers = {"type": "machine", "um_network_key": None}
self._local_container_stacks = ContainerRegistry.getInstance().findContainerStacks(**local_filter_printers)
self._local_container_stacks.sort(key = lambda i: i.getName())
for container in self._local_container_stacks:
metadata = container.getMetaData().copy()
if container.getBottom():
metadata["definition_name"] = container.getBottom().getName()
items.append({"name": container.getName(),
"id": container.getId(),
"metadata": metadata,
"group": catalog.i18nc("@info:title", "Local printers")})
self.setItems(items)

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 PyQt5.QtCore import pyqtSignal, pyqtProperty from PyQt5.QtCore import QTimer, pyqtSignal, pyqtProperty
from UM.Application import Application from UM.Application import Application
from UM.Scene.Selection import Selection from UM.Scene.Selection import Selection
@ -21,8 +21,13 @@ class MultiBuildPlateModel(ListModel):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
self._update_timer = QTimer()
self._update_timer.setInterval(100)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._updateSelectedObjectBuildPlateNumbers)
self._application = Application.getInstance() self._application = Application.getInstance()
self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers) self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbersDelayed)
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers) Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
self._max_build_plate = 1 # default self._max_build_plate = 1 # default
@ -45,6 +50,9 @@ class MultiBuildPlateModel(ListModel):
def activeBuildPlate(self): def activeBuildPlate(self):
return self._active_build_plate return self._active_build_plate
def _updateSelectedObjectBuildPlateNumbersDelayed(self, *args):
self._update_timer.start()
def _updateSelectedObjectBuildPlateNumbers(self, *args): def _updateSelectedObjectBuildPlateNumbers(self, *args):
result = set() result = set()
for node in Selection.getAllSelectedObjects(): for node in Selection.getAllSelectedObjects():

View File

@ -87,9 +87,11 @@ class QualitySettingsModel(ListModel):
if self._selected_position == self.GLOBAL_STACK_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(str(self._selected_position))
settings_keys = quality_group.getAllKeys() settings_keys = quality_group.getAllKeys()
quality_containers = [quality_node.getContainer()] quality_containers = []
if quality_node is not None:
quality_containers.append(quality_node.getContainer())
# 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.
@ -97,7 +99,7 @@ class QualitySettingsModel(ListModel):
if self._selected_position == self.GLOBAL_STACK_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(str(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())

View File

@ -16,6 +16,7 @@ from .QualityGroup import QualityGroup
from .QualityNode import QualityNode from .QualityNode import QualityNode
if TYPE_CHECKING: if TYPE_CHECKING:
from UM.Settings.DefinitionContainer import DefinitionContainer
from cura.Settings.GlobalStack import GlobalStack from cura.Settings.GlobalStack import GlobalStack
from .QualityChangesGroup import QualityChangesGroup from .QualityChangesGroup import QualityChangesGroup
@ -178,7 +179,7 @@ class QualityManager(QObject):
# Returns a dict of "custom profile name" -> QualityChangesGroup # Returns a dict of "custom profile name" -> QualityChangesGroup
def getQualityChangesGroups(self, machine: "GlobalStack") -> dict: def getQualityChangesGroups(self, machine: "GlobalStack") -> dict:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine) machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id) machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id)
if not machine_node: if not machine_node:
@ -206,7 +207,7 @@ class QualityManager(QObject):
# For more details, see QualityGroup. # For more details, see QualityGroup.
# #
def getQualityGroups(self, machine: "GlobalStack") -> dict: def getQualityGroups(self, machine: "GlobalStack") -> dict:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine) machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks # This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False)) has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False))
@ -315,7 +316,7 @@ class QualityManager(QObject):
return quality_group_dict return quality_group_dict
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict: def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict:
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine) machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
# To find the quality container for the GlobalStack, check in the following fall-back manner: # To find the quality container for the GlobalStack, check in the following fall-back manner:
# (1) the machine-specific node # (1) the machine-specific node
@ -460,7 +461,7 @@ class QualityManager(QObject):
quality_changes.addMetaDataEntry("position", extruder_stack.getMetaDataEntry("position")) quality_changes.addMetaDataEntry("position", extruder_stack.getMetaDataEntry("position"))
# If the machine specifies qualities should be filtered, ensure we match the current criteria. # If the machine specifies qualities should be filtered, ensure we match the current criteria.
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine) machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
quality_changes.setDefinition(machine_definition_id) quality_changes.setDefinition(machine_definition_id)
quality_changes.addMetaDataEntry("setting_version", self._application.SettingVersion) quality_changes.addMetaDataEntry("setting_version", self._application.SettingVersion)
@ -480,12 +481,13 @@ class QualityManager(QObject):
# Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended # Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended
# shares the same set of qualities profiles as Ultimaker 3. # shares the same set of qualities profiles as Ultimaker 3.
# #
def getMachineDefinitionIDForQualitySearch(machine: "GlobalStack", default_definition_id: str = "fdmprinter") -> str: def getMachineDefinitionIDForQualitySearch(machine_definition: "DefinitionContainer",
default_definition_id: str = "fdmprinter") -> str:
machine_definition_id = default_definition_id machine_definition_id = default_definition_id
if parseBool(machine.getMetaDataEntry("has_machine_quality", False)): if parseBool(machine_definition.getMetaDataEntry("has_machine_quality", False)):
# Only use the machine's own quality definition ID if this machine has machine quality. # Only use the machine's own quality definition ID if this machine has machine quality.
machine_definition_id = machine.getMetaDataEntry("quality_definition") machine_definition_id = machine_definition.getMetaDataEntry("quality_definition")
if machine_definition_id is None: if machine_definition_id is None:
machine_definition_id = machine.definition.getId() machine_definition_id = machine_definition.getId()
return machine_definition_id return machine_definition_id

View File

@ -25,7 +25,7 @@ ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
# #
# VariantManager is THE place to look for a specific variant. It maintains a variant lookup table with the following # VariantManager is THE place to look for a specific variant. It maintains two variant lookup tables with the following
# structure: # structure:
# #
# [machine_definition_id] -> [variant_type] -> [variant_name] -> ContainerNode(metadata / container) # [machine_definition_id] -> [variant_type] -> [variant_name] -> ContainerNode(metadata / container)
@ -35,6 +35,9 @@ ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
# -> "BB 0.8" # -> "BB 0.8"
# -> ... # -> ...
# #
# [machine_definition_id] -> [machine_buildplate_type] -> ContainerNode(metadata / container)
# Example: "ultimaker3" -> "glass" (this is different from the variant name) -> ContainerNode
#
# Note that the "container" field is not loaded in the beginning because it would defeat the purpose of lazy-loading. # Note that the "container" field is not loaded in the beginning because it would defeat the purpose of lazy-loading.
# A container is loaded when getVariant() is called to load a variant InstanceContainer. # A container is loaded when getVariant() is called to load a variant InstanceContainer.
# #
@ -44,6 +47,7 @@ class VariantManager:
self._container_registry = container_registry # type: ContainerRegistry self._container_registry = container_registry # type: ContainerRegistry
self._machine_to_variant_dict_map = dict() # <machine_type> -> <variant_dict> self._machine_to_variant_dict_map = dict() # <machine_type> -> <variant_dict>
self._machine_to_buildplate_dict_map = dict()
self._exclude_variant_id_list = ["empty_variant"] self._exclude_variant_id_list = ["empty_variant"]
@ -53,6 +57,7 @@ class VariantManager:
# #
def initialize(self): def initialize(self):
self._machine_to_variant_dict_map = OrderedDict() self._machine_to_variant_dict_map = OrderedDict()
self._machine_to_buildplate_dict_map = OrderedDict()
# Cache all variants from the container registry to a variant map for better searching and organization. # Cache all variants from the container registry to a variant map for better searching and organization.
variant_metadata_list = self._container_registry.findContainersMetadata(type = "variant") variant_metadata_list = self._container_registry.findContainersMetadata(type = "variant")
@ -78,6 +83,22 @@ class VariantManager:
variant_dict[variant_name] = ContainerNode(metadata = variant_metadata) variant_dict[variant_name] = ContainerNode(metadata = variant_metadata)
# If the variant is a buildplate then fill also the buildplate map
if variant_type == VariantType.BUILD_PLATE:
if variant_definition not in self._machine_to_buildplate_dict_map:
self._machine_to_buildplate_dict_map[variant_definition] = OrderedDict()
variant_container = self._container_registry.findContainers(type = "variant", id = variant_metadata["id"])
if not variant_container:
# ERROR: not variant container. This should never happen
raise RuntimeError("Not variant found [%s], type [%s] for machine [%s]" %
(variant_name, variant_type, variant_definition))
buildplate_type = variant_container[0].getProperty("machine_buildplate_type", "value")
if buildplate_type not in self._machine_to_buildplate_dict_map[variant_definition]:
self._machine_to_variant_dict_map[variant_definition][buildplate_type] = dict()
self._machine_to_buildplate_dict_map[variant_definition][buildplate_type] = variant_dict[variant_name]
# #
# Gets the variant InstanceContainer with the given information. # Gets the variant InstanceContainer with the given information.
# Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present. # Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
@ -117,3 +138,8 @@ class VariantManager:
if preferred_variant_name: if preferred_variant_name:
node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type) node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
return node return node
def getBuildplateVariantNode(self, machine_definition_id: str, buildplate_type: str) -> Optional["ContainerNode"]:
if machine_definition_id in self._machine_to_buildplate_dict_map:
return self._machine_to_buildplate_dict_map[machine_definition_id].get(buildplate_type)
return None

View File

@ -1,3 +1,8 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QTimer
from UM.Application import Application from UM.Application import Application
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
@ -14,8 +19,13 @@ class ObjectsModel(ListModel):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
Application.getInstance().getController().getScene().sceneChanged.connect(self._update) Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed)
Preferences.getInstance().preferenceChanged.connect(self._update) Preferences.getInstance().preferenceChanged.connect(self._updateDelayed)
self._update_timer = QTimer()
self._update_timer.setInterval(100)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._update)
self._build_plate_number = -1 self._build_plate_number = -1
@ -23,6 +33,9 @@ class ObjectsModel(ListModel):
self._build_plate_number = nr self._build_plate_number = nr
self._update() self._update()
def _updateDelayed(self, *args):
self._update_timer.start()
def _update(self, *args): def _update(self, *args):
nodes = [] nodes = []
filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate") filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate")

View File

@ -40,6 +40,8 @@ class PlatformPhysics:
Preferences.getInstance().addPreference("physics/automatic_drop_down", True) Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
def _onSceneChanged(self, source): def _onSceneChanged(self, source):
if not source.getMeshData():
return
self._change_timer.start() self._change_timer.start()
def _onChangeTimerFinished(self): def _onChangeTimerFinished(self):

View File

@ -0,0 +1,81 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
from typing import List
MYPY = False
if MYPY:
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
class ConfigurationModel(QObject):
configurationChanged = pyqtSignal()
def __init__(self):
super().__init__()
self._printer_type = None
self._extruder_configurations = [] # type: List[ExtruderConfigurationModel]
self._buildplate_configuration = None
def setPrinterType(self, printer_type):
self._printer_type = printer_type
@pyqtProperty(str, fset = setPrinterType, notify = configurationChanged)
def printerType(self):
return self._printer_type
def setExtruderConfigurations(self, extruder_configurations):
self._extruder_configurations = extruder_configurations
@pyqtProperty("QVariantList", fset = setExtruderConfigurations, notify = configurationChanged)
def extruderConfigurations(self):
return self._extruder_configurations
def setBuildplateConfiguration(self, buildplate_configuration):
self._buildplate_configuration = buildplate_configuration
@pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
def buildplateConfiguration(self):
return self._buildplate_configuration
## This method is intended to indicate whether the configuration is valid or not.
# The method checks if the mandatory fields are or not set
def isValid(self):
if not self._extruder_configurations:
return False
for configuration in self._extruder_configurations:
if configuration is None:
return False
return self._printer_type is not None
def __str__(self):
message_chunks = []
message_chunks.append("Printer type: " + self._printer_type)
message_chunks.append("Extruders: [")
for configuration in self._extruder_configurations:
message_chunks.append(" " + str(configuration))
message_chunks.append("]")
if self._buildplate_configuration is not None:
message_chunks.append("Buildplate: " + self._buildplate_configuration)
return "\n".join(message_chunks)
def __eq__(self, other):
return hash(self) == hash(other)
## The hash function is used to compare and create unique sets. The configuration is unique if the configuration
# of the extruders is unique (the order of the extruders matters), and the type and buildplate is the same.
def __hash__(self):
extruder_hash = hash(0)
first_extruder = None
for configuration in self._extruder_configurations:
extruder_hash ^= hash(configuration)
if configuration.position == 0:
first_extruder = configuration
# To ensure the correct order of the extruders, we add an "and" operation using the first extruder hash value
if first_extruder:
extruder_hash &= hash(first_extruder)
return hash(self._printer_type) ^ extruder_hash ^ hash(self._buildplate_configuration)

View File

@ -0,0 +1,59 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
class ExtruderConfigurationModel(QObject):
extruderConfigurationChanged = pyqtSignal()
def __init__(self):
super().__init__()
self._position = -1
self._material = None
self._hotend_id = None
def setPosition(self, position):
self._position = position
@pyqtProperty(int, fset = setPosition, notify = extruderConfigurationChanged)
def position(self):
return self._position
def setMaterial(self, material):
self._material = material
@pyqtProperty(QObject, fset = setMaterial, notify = extruderConfigurationChanged)
def material(self):
return self._material
def setHotendID(self, hotend_id):
self._hotend_id = hotend_id
@pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)
def hotendID(self):
return self._hotend_id
## This method is intended to indicate whether the configuration is valid or not.
# The method checks if the mandatory fields are or not set
# At this moment is always valid since we allow to have empty material and variants.
def isValid(self):
return True
def __str__(self):
message_chunks = []
message_chunks.append("Position: " + str(self._position))
message_chunks.append("-")
message_chunks.append("Material: " + self.material.type if self.material else "empty")
message_chunks.append("-")
message_chunks.append("HotendID: " + self.hotendID if self.hotendID else "empty")
return " ".join(message_chunks)
def __eq__(self, other):
return hash(self) == hash(other)
# Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
# unique within a set
def __hash__(self):
return hash(self._position) ^ (hash(self._material.guid) if self._material is not None else hash(0)) ^ hash(self._hotend_id)

View File

@ -1,8 +1,8 @@
# Copyright (c) 2017 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 PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
from UM.Logger import Logger from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from typing import Optional from typing import Optional
@ -17,14 +17,18 @@ class ExtruderOutputModel(QObject):
targetHotendTemperatureChanged = pyqtSignal() targetHotendTemperatureChanged = pyqtSignal()
hotendTemperatureChanged = pyqtSignal() hotendTemperatureChanged = pyqtSignal()
activeMaterialChanged = pyqtSignal() activeMaterialChanged = pyqtSignal()
extruderConfigurationChanged = pyqtSignal()
def __init__(self, printer: "PrinterOutputModel", parent=None): def __init__(self, printer: "PrinterOutputModel", position, parent=None):
super().__init__(parent) super().__init__(parent)
self._printer = printer self._printer = printer
self._position = position
self._target_hotend_temperature = 0 self._target_hotend_temperature = 0
self._hotend_temperature = 0 self._hotend_temperature = 0
self._hotend_id = "" self._hotend_id = ""
self._active_material = None # type: Optional[MaterialOutputModel] self._active_material = None # type: Optional[MaterialOutputModel]
self._extruder_configuration = ExtruderConfigurationModel()
self._extruder_configuration.position = self._position
@pyqtProperty(QObject, notify = activeMaterialChanged) @pyqtProperty(QObject, notify = activeMaterialChanged)
def activeMaterial(self) -> "MaterialOutputModel": def activeMaterial(self) -> "MaterialOutputModel":
@ -33,7 +37,9 @@ class ExtruderOutputModel(QObject):
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]): def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
if self._active_material != material: if self._active_material != material:
self._active_material = material self._active_material = material
self._extruder_configuration.material = self._active_material
self.activeMaterialChanged.emit() self.activeMaterialChanged.emit()
self.extruderConfigurationChanged.emit()
## Update the hotend temperature. This only changes it locally. ## Update the hotend temperature. This only changes it locally.
def updateHotendTemperature(self, temperature: float): def updateHotendTemperature(self, temperature: float):
@ -56,7 +62,7 @@ class ExtruderOutputModel(QObject):
def targetHotendTemperature(self) -> float: def targetHotendTemperature(self) -> float:
return self._target_hotend_temperature return self._target_hotend_temperature
@pyqtProperty(float, notify=hotendTemperatureChanged) @pyqtProperty(float, notify = hotendTemperatureChanged)
def hotendTemperature(self) -> float: def hotendTemperature(self) -> float:
return self._hotend_temperature return self._hotend_temperature
@ -67,4 +73,12 @@ class ExtruderOutputModel(QObject):
def updateHotendID(self, id: str): def updateHotendID(self, id: str):
if self._hotend_id != id: if self._hotend_id != id:
self._hotend_id = id self._hotend_id = id
self._extruder_configuration.hotendID = self._hotend_id
self.hotendIDChanged.emit() self.hotendIDChanged.emit()
self.extruderConfigurationChanged.emit()
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
def extruderConfiguration(self):
if self._extruder_configuration.isValid():
return self._extruder_configuration
return None

View File

@ -55,6 +55,17 @@ 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")
printer_type_identifiers = {
"9066": "ultimaker3",
"9511": "ultimaker3_extended"
}
self._printer_type = "Unknown"
for key, value in printer_type_identifiers.items():
if printer_type.startswith(key):
self._printer_type = value
break
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 +312,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

@ -6,7 +6,7 @@ from UM.Logger import Logger
MYPY = False MYPY = False
if MYPY: if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.ExtruderOuputModel import ExtruderOuputModel from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
@ -18,7 +18,7 @@ class PrinterOutputController:
self.can_control_manually = True self.can_control_manually = True
self._output_device = output_device self._output_device = output_device
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOuputModel", temperature: int): def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: int):
Logger.log("w", "Set target hotend temperature not implemented in controller") Logger.log("w", "Set target hotend temperature not implemented in controller")
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int): def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):

View File

@ -1,11 +1,11 @@
# Copyright (c) 2017 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 PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
from UM.Logger import Logger from typing import Optional
from typing import Optional, List
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from cura.PrinterOutput.ExtruderOuputModel import ExtruderOutputModel from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
MYPY = False MYPY = False
if MYPY: if MYPY:
@ -22,8 +22,10 @@ class PrinterOutputModel(QObject):
nameChanged = pyqtSignal() nameChanged = pyqtSignal()
headPositionChanged = pyqtSignal() headPositionChanged = pyqtSignal()
keyChanged = pyqtSignal() keyChanged = pyqtSignal()
typeChanged = pyqtSignal() printerTypeChanged = pyqtSignal()
buildplateChanged = pyqtSignal()
cameraChanged = pyqtSignal() cameraChanged = pyqtSignal()
configurationChanged = pyqtSignal()
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = ""): def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = ""):
super().__init__(parent) super().__init__(parent)
@ -32,13 +34,18 @@ class PrinterOutputModel(QObject):
self._name = "" self._name = ""
self._key = "" # Unique identifier self._key = "" # Unique identifier
self._controller = output_controller self._controller = output_controller
self._extruders = [ExtruderOutputModel(printer=self) for i in range(number_of_extruders)] self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
self._head_position = Vector(0, 0, 0) self._head_position = Vector(0, 0, 0)
self._active_print_job = None # type: Optional[PrintJobOutputModel] self._active_print_job = None # type: Optional[PrintJobOutputModel]
self._firmware_version = firmware_version self._firmware_version = firmware_version
self._printer_state = "unknown" self._printer_state = "unknown"
self._is_preheating = False self._is_preheating = False
self._type = "" self._printer_type = ""
self._buildplate_name = None
# Update the printer configuration every time any of the extruders changes its configuration
for extruder in self._extruders:
extruder.extruderConfigurationChanged.connect(self._updateExtruderConfiguration)
self._camera = None self._camera = None
@ -64,14 +71,27 @@ class PrinterOutputModel(QObject):
def camera(self): def camera(self):
return self._camera return self._camera
@pyqtProperty(str, notify = typeChanged) @pyqtProperty(str, notify = printerTypeChanged)
def type(self): def type(self):
return self._type return self._printer_type
def updateType(self, type): def updateType(self, printer_type):
if self._type != type: if self._printer_type != printer_type:
self._type = type self._printer_type = printer_type
self.typeChanged.emit() self._printer_configuration.printerType = self._printer_type
self.printerTypeChanged.emit()
self.configurationChanged.emit()
@pyqtProperty(str, notify = buildplateChanged)
def buildplate(self):
return self._buildplate_name
def updateBuildplateName(self, buildplate_name):
if self._buildplate_name != buildplate_name:
self._buildplate_name = buildplate_name
self._printer_configuration.buildplateConfiguration = self._buildplate_name
self.buildplateChanged.emit()
self.configurationChanged.emit()
@pyqtProperty(str, notify=keyChanged) @pyqtProperty(str, notify=keyChanged)
def key(self): def key(self):
@ -238,3 +258,14 @@ class PrinterOutputModel(QObject):
if self._controller: if self._controller:
return self._controller.can_control_manually return self._controller.can_control_manually
return False return False
# Returns the configuration (material, variant and buildplate) of the current printer
@pyqtProperty(QObject, notify = configurationChanged)
def printerConfiguration(self):
if self._printer_configuration.isValid():
return self._printer_configuration
return None
def _updateExtruderConfiguration(self):
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders]
self.configurationChanged.emit()

View File

@ -1,12 +1,11 @@
# Copyright (c) 2017 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 UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.OutputDevice.OutputDevice import OutputDevice from UM.OutputDevice.OutputDevice import OutputDevice
from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal, QVariant
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from UM.Logger import Logger from UM.Logger import Logger
from UM.Signal import signalemitter from UM.Signal import signalemitter
from UM.Application import Application from UM.Application import Application
@ -17,6 +16,7 @@ from typing import List, Optional
MYPY = False MYPY = False
if MYPY: if MYPY:
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
@ -44,10 +44,14 @@ class PrinterOutputDevice(QObject, OutputDevice):
# Signal to indicate that the info text about the connection has changed. # Signal to indicate that the info text about the connection has changed.
connectionTextChanged = pyqtSignal() connectionTextChanged = pyqtSignal()
# Signal to indicate that the configuration of one of the printers has changed.
uniqueConfigurationsChanged = pyqtSignal()
def __init__(self, device_id, parent = None): def __init__(self, device_id, parent = None):
super().__init__(device_id = device_id, parent = parent) super().__init__(device_id = device_id, parent = parent)
self._printers = [] # type: List[PrinterOutputModel] self._printers = [] # type: List[PrinterOutputModel]
self._unique_configurations = [] # type: List[ConfigurationModel]
self._monitor_view_qml_path = "" self._monitor_view_qml_path = ""
self._monitor_component = None self._monitor_component = None
@ -69,6 +73,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
self._address = "" self._address = ""
self._connection_text = "" self._connection_text = ""
self.printersChanged.connect(self._onPrintersChanged)
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations)
@pyqtProperty(str, notify = connectionTextChanged) @pyqtProperty(str, notify = connectionTextChanged)
def address(self): def address(self):
@ -175,6 +181,23 @@ class PrinterOutputDevice(QObject, OutputDevice):
self.acceptsCommandsChanged.emit() self.acceptsCommandsChanged.emit()
# Returns the unique configurations of the printers within this output device
@pyqtProperty("QVariantList", notify = uniqueConfigurationsChanged)
def uniqueConfigurations(self):
return self._unique_configurations
def _updateUniqueConfigurations(self):
self._unique_configurations = list(set([printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None]))
self._unique_configurations.sort(key = lambda k: k.printerType)
self.uniqueConfigurationsChanged.emit()
def _onPrintersChanged(self):
for printer in self._printers:
printer.configurationChanged.connect(self._updateUniqueConfigurations)
# At this point there may be non-updated configurations
self._updateUniqueConfigurations()
## The current processing state of the backend. ## The current processing state of the backend.
class ConnectionState(IntEnum): class ConnectionState(IntEnum):

View File

@ -1,6 +1,8 @@
# Copyright (c) 2016 Ultimaker B.V. # Copyright (c) 2016 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 QTimer
from UM.Application import Application from UM.Application import Application
from UM.Math.Polygon import Polygon from UM.Math.Polygon import Polygon
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
@ -22,6 +24,10 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._global_stack = None self._global_stack = None
# Make sure the timer is created on the main thread
self._recompute_convex_hull_timer = None
Application.getInstance().callLater(self.createRecomputeConvexHullTimer)
self._raft_thickness = 0.0 self._raft_thickness = 0.0
# For raft thickness, DRY # For raft thickness, DRY
self._build_volume = Application.getInstance().getBuildVolume() self._build_volume = Application.getInstance().getBuildVolume()
@ -33,6 +39,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
self._onGlobalStackChanged() self._onGlobalStackChanged()
def createRecomputeConvexHullTimer(self):
self._recompute_convex_hull_timer = QTimer()
self._recompute_convex_hull_timer.setInterval(200)
self._recompute_convex_hull_timer.setSingleShot(True)
self._recompute_convex_hull_timer.timeout.connect(self.recomputeConvexHull)
def setNode(self, node): def setNode(self, node):
previous_node = self._node previous_node = self._node
# Disconnect from previous node signals # Disconnect from previous node signals
@ -99,6 +111,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
return self._compute2DConvexHull() return self._compute2DConvexHull()
return None return None
def recomputeConvexHullDelayed(self):
if self._recompute_convex_hull_timer is not None:
self._recompute_convex_hull_timer.start()
else:
self.recomputeConvexHull()
def recomputeConvexHull(self): def recomputeConvexHull(self):
controller = Application.getInstance().getController() controller = Application.getInstance().getController()
root = controller.getScene().getRoot() root = controller.getScene().getRoot()
@ -279,7 +297,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
def _onChanged(self, *args): def _onChanged(self, *args):
self._raft_thickness = self._build_volume.getRaftThickness() self._raft_thickness = self._build_volume.getRaftThickness()
self.recomputeConvexHull() if not args or args[0] == self._node:
self.recomputeConvexHullDelayed()
def _onGlobalStackChanged(self): def _onGlobalStackChanged(self):
if self._global_stack: if self._global_stack:

View File

@ -204,7 +204,7 @@ class CuraContainerRegistry(ContainerRegistry):
global_profile = profile_or_list[0] global_profile = profile_or_list[0]
else: else:
for profile in profile_or_list: for profile in profile_or_list:
if not profile.getMetaDataEntry("extruder"): if not profile.getMetaDataEntry("position"):
global_profile = profile global_profile = profile
break break
if not global_profile: if not global_profile:
@ -212,16 +212,34 @@ class CuraContainerRegistry(ContainerRegistry):
return { "status": "error", return { "status": "error",
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)} "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)}
profile_definition = global_profile.getMetaDataEntry("definition") profile_definition = global_profile.getMetaDataEntry("definition")
expected_machine_definition = "fdmprinter"
if parseBool(global_container_stack.getMetaDataEntry("has_machine_quality", "False")): # Make sure we have a profile_definition in the file:
expected_machine_definition = global_container_stack.getMetaDataEntry("quality_definition") if profile_definition is None:
if not expected_machine_definition: break
expected_machine_definition = global_container_stack.definition.getId() machine_definition = self.findDefinitionContainers(id = profile_definition)
if expected_machine_definition is not None and profile_definition is not None and profile_definition != expected_machine_definition: if not machine_definition:
Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition)
return {"status": "error",
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)
}
machine_definition = machine_definition[0]
# Get the expected machine definition.
# i.e.: We expect gcode for a UM2 Extended to be defined as normal UM2 gcode...
profile_definition = getMachineDefinitionIDForQualitySearch(machine_definition)
expected_machine_definition = getMachineDefinitionIDForQualitySearch(global_container_stack.definition)
# And check if the profile_definition matches either one (showing error if not):
if profile_definition != expected_machine_definition:
Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition) Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition)
return { "status": "error", return { "status": "error",
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)} "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)}
# Fix the global quality profile's definition field in case it's not correct
global_profile.setMetaDataEntry("definition", expected_machine_definition)
quality_name = global_profile.getName()
quality_type = global_profile.getMetaDataEntry("quality_type")
name_seed = os.path.splitext(os.path.basename(file_name))[0] name_seed = os.path.splitext(os.path.basename(file_name))[0]
new_name = self.uniqueName(name_seed) new_name = self.uniqueName(name_seed)
@ -236,11 +254,11 @@ class CuraContainerRegistry(ContainerRegistry):
for idx, extruder in enumerate(global_container_stack.extruders.values()): for idx, extruder in enumerate(global_container_stack.extruders.values()):
profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1)) profile_id = ContainerRegistry.getInstance().uniqueName(global_container_stack.getId() + "_extruder_" + str(idx + 1))
profile = InstanceContainer(profile_id) profile = InstanceContainer(profile_id)
profile.setName(global_profile.getName()) profile.setName(quality_name)
profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) profile.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
profile.addMetaDataEntry("type", "quality_changes") profile.addMetaDataEntry("type", "quality_changes")
profile.addMetaDataEntry("definition", global_profile.getMetaDataEntry("definition")) profile.addMetaDataEntry("definition", expected_machine_definition)
profile.addMetaDataEntry("quality_type", global_profile.getMetaDataEntry("quality_type")) profile.addMetaDataEntry("quality_type", quality_type)
profile.addMetaDataEntry("position", "0") profile.addMetaDataEntry("position", "0")
profile.setDirty(True) profile.setDirty(True)
if idx == 0: if idx == 0:
@ -273,17 +291,17 @@ 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.
continue #Delete the additional profiles. continue #Delete the additional profiles.
result = self._configureProfile(profile, profile_id, new_name) result = self._configureProfile(profile, profile_id, new_name, expected_machine_definition)
if result is not None: if result is not None:
return {"status": "error", "message": catalog.i18nc( return {"status": "error", "message": catalog.i18nc(
"@info:status Don't translate the XML tags <filename> or <message>!", "@info:status Don't translate the XML tags <filename> or <message>!",
@ -311,7 +329,7 @@ class CuraContainerRegistry(ContainerRegistry):
# \param new_name The new name for the profile. # \param new_name The new name for the profile.
# #
# \return None if configuring was successful or an error message if an error occurred. # \return None if configuring was successful or an error message if an error occurred.
def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]: def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str, machine_definition_id: str) -> Optional[str]:
profile.setDirty(True) # Ensure the profiles are correctly saved profile.setDirty(True) # Ensure the profiles are correctly saved
new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile")) new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
@ -321,6 +339,7 @@ class CuraContainerRegistry(ContainerRegistry):
# Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile # Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile
# It also solves an issue with importing profiles from G-Codes # It also solves an issue with importing profiles from G-Codes
profile.setMetaDataEntry("id", new_id) profile.setMetaDataEntry("id", new_id)
profile.setMetaDataEntry("definition", machine_definition_id)
if "type" in profile.getMetaData(): if "type" in profile.getMetaData():
profile.setMetaDataEntry("type", "quality_changes") profile.setMetaDataEntry("type", "quality_changes")
@ -331,9 +350,8 @@ class CuraContainerRegistry(ContainerRegistry):
if not quality_type: if not quality_type:
return catalog.i18nc("@info:status", "Profile is missing a quality type.") return catalog.i18nc("@info:status", "Profile is missing a quality type.")
quality_type_criteria = {"quality_type": quality_type}
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = Application.getInstance().getGlobalContainerStack()
definition_id = getMachineDefinitionIDForQualitySearch(global_stack) definition_id = getMachineDefinitionIDForQualitySearch(global_stack.definition)
profile.setDefinition(definition_id) profile.setDefinition(definition_id)
# Check to make sure the imported profile actually makes sense in context of the current configuration. # Check to make sure the imported profile actually makes sense in context of the current configuration.

View File

@ -668,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

@ -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,13 +20,15 @@ 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
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.Machines.VariantManager import VariantType
from cura.PrinterOutputDevice import PrinterOutputDevice from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from .CuraStackBuilder import CuraStackBuilder from .CuraStackBuilder import CuraStackBuilder
@ -48,7 +50,6 @@ class MachineManager(QObject):
self._global_container_stack = None # type: GlobalStack self._global_container_stack = None # type: GlobalStack
self._current_root_material_id = {} self._current_root_material_id = {}
self._current_root_material_name = {}
self._current_quality_group = None self._current_quality_group = None
self._current_quality_changes_group = None self._current_quality_changes_group = None
@ -56,11 +57,6 @@ class MachineManager(QObject):
self.machine_extruder_material_update_dict = collections.defaultdict(list) self.machine_extruder_material_update_dict = collections.defaultdict(list)
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
self._instance_container_timer = QTimer() self._instance_container_timer = QTimer()
self._instance_container_timer.setInterval(250) self._instance_container_timer.setInterval(250)
self._instance_container_timer.setSingleShot(True) self._instance_container_timer.setSingleShot(True)
@ -109,6 +105,12 @@ class MachineManager(QObject):
# There might already be some output devices by the time the signal is connected # There might already be some output devices by the time the signal is connected
self._onOutputDevicesChanged() self._onOutputDevicesChanged()
self._current_printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
self.activeMaterialChanged.connect(self._onCurrentConfigurationChanged)
self.activeVariantChanged.connect(self._onCurrentConfigurationChanged)
# Force to compute the current configuration
self._onCurrentConfigurationChanged()
self._application.callLater(self.setInitialActiveMachine) self._application.callLater(self.setInitialActiveMachine)
self._material_incompatible_message = Message(catalog.i18nc("@info:status", self._material_incompatible_message = Message(catalog.i18nc("@info:status",
@ -119,12 +121,17 @@ class MachineManager(QObject):
if containers: if containers:
containers[0].nameChanged.connect(self._onMaterialNameChanged) containers[0].nameChanged.connect(self._onMaterialNameChanged)
self._material_manager = self._application._material_manager self._material_manager = self._application.getMaterialManager()
self._variant_manager = self._application.getVariantManager()
self._quality_manager = self._application.getQualityManager() self._quality_manager = self._application.getQualityManager()
# 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)
# When the materials get updated, it can be that an activated material's diameter gets changed. In that case,
# a material update should be triggered to make sure that the machine still has compatible materials activated.
self._material_manager.materialsUpdated.connect(self._updateUponMaterialMetadataChange)
self.rootMaterialChanged.connect(self._onRootMaterialChanged)
activeQualityGroupChanged = pyqtSignal() activeQualityGroupChanged = pyqtSignal()
activeQualityChangesGroupChanged = pyqtSignal() activeQualityChangesGroupChanged = pyqtSignal()
@ -144,6 +151,7 @@ class MachineManager(QObject):
blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
outputDevicesChanged = pyqtSignal() outputDevicesChanged = pyqtSignal()
currentConfigurationChanged = pyqtSignal() # Emitted every time the current configurations of the machine changes
rootMaterialChanged = pyqtSignal() rootMaterialChanged = pyqtSignal()
@ -163,6 +171,39 @@ class MachineManager(QObject):
self.outputDevicesChanged.emit() self.outputDevicesChanged.emit()
@pyqtProperty(QObject, notify = currentConfigurationChanged)
def currentConfiguration(self):
return self._current_printer_configuration
def _onCurrentConfigurationChanged(self) -> None:
if not self._global_container_stack:
return
# Create the configuration model with the current data in Cura
self._current_printer_configuration.printerType = self._global_container_stack.definition.getName()
self._current_printer_configuration.extruderConfigurations = []
for extruder in self._global_container_stack.extruders.values():
extruder_configuration = ExtruderConfigurationModel()
# For compare just the GUID is needed at this moment
mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != self._empty_material_container else None
mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != self._empty_material_container else None
mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != self._empty_material_container else None
mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != self._empty_material_container else None
mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != self._empty_material_container else None
material_model = MaterialOutputModel(mat_guid, mat_type, mat_color, mat_brand, mat_name)
extruder_configuration.position = int(extruder.getMetaDataEntry("position"))
extruder_configuration.material = material_model
extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != self._empty_variant_container else None
self._current_printer_configuration.extruderConfigurations.append(extruder_configuration)
self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != self._empty_variant_container else None
self.currentConfigurationChanged.emit()
@pyqtSlot(QObject, result = bool)
def matchesConfiguration(self, configuration: ConfigurationModel) -> bool:
return self._current_printer_configuration == configuration
@pyqtProperty("QVariantList", notify = outputDevicesChanged) @pyqtProperty("QVariantList", notify = outputDevicesChanged)
def printerOutputDevices(self): def printerOutputDevices(self):
return self._printer_output_devices return self._printer_output_devices
@ -227,15 +268,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.
@ -255,8 +287,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()
@ -265,9 +295,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 = {}
@ -311,9 +338,13 @@ class MachineManager(QObject):
self.__emitChangedSignals() self.__emitChangedSignals()
## Given a definition id, return the machine with this id.
# Optional: add a list of keys and values to filter the list of machines with the given definition id
# \param definition_id \type{str} definition id that needs to look for
# \param metadata_filter \type{dict} list of metadata keys and values used for filtering
@staticmethod @staticmethod
def getMachine(definition_id: str) -> Optional["GlobalStack"]: def getMachine(definition_id: str, metadata_filter: Dict[str, str] = None) -> Optional["GlobalStack"]:
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine") machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
for machine in machines: for machine in machines:
if machine.definition.getId() == definition_id: if machine.definition.getId() == definition_id:
return machine return machine
@ -334,7 +365,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
@ -418,6 +449,12 @@ class MachineManager(QObject):
def stacksHaveErrors(self) -> bool: def stacksHaveErrors(self) -> bool:
return bool(self._stacks_have_errors) return bool(self._stacks_have_errors)
@pyqtProperty(str, notify = globalContainerChanged)
def activeMachineDefinitionName(self) -> str:
if self._global_container_stack:
return self._global_container_stack.definition.getName()
return ""
@pyqtProperty(str, notify = globalContainerChanged) @pyqtProperty(str, notify = globalContainerChanged)
def activeMachineName(self) -> str: def activeMachineName(self) -> str:
if self._global_container_stack: if self._global_container_stack:
@ -430,6 +467,18 @@ class MachineManager(QObject):
return self._global_container_stack.getId() return self._global_container_stack.getId()
return "" return ""
@pyqtProperty(str, notify = outputDevicesChanged)
def activeMachineNetworkKey(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("um_network_key")
return ""
@pyqtProperty(str, notify = outputDevicesChanged)
def activeMachineNetworkGroupName(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("connect_group_name")
return ""
@pyqtProperty(QObject, notify = globalContainerChanged) @pyqtProperty(QObject, notify = globalContainerChanged)
def activeMachine(self) -> Optional["GlobalStack"]: def activeMachine(self) -> Optional["GlobalStack"]:
return self._global_container_stack return self._global_container_stack
@ -579,7 +628,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = globalContainerChanged) @pyqtProperty(str, notify = globalContainerChanged)
def activeQualityDefinitionId(self) -> str: def activeQualityDefinitionId(self) -> str:
if self._global_container_stack: if self._global_container_stack:
return getMachineDefinitionIDForQualitySearch(self._global_container_stack) return getMachineDefinitionIDForQualitySearch(self._global_container_stack.definition)
return "" return ""
## Gets how the active definition calls variants ## Gets how the active definition calls variants
@ -830,10 +879,13 @@ class MachineManager(QObject):
return self._default_extruder_position return self._default_extruder_position
## This will fire the propertiesChanged for all settings so they will be updated in the front-end ## This will fire the propertiesChanged for all settings so they will be updated in the front-end
@pyqtSlot()
def forceUpdateAllSettings(self): def forceUpdateAllSettings(self):
property_names = ["value", "resolve"] with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
for setting_key in self._global_container_stack.getAllKeys(): property_names = ["value", "resolve", "validationState"]
self._global_container_stack.propertiesChanged.emit(setting_key, property_names) for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
for setting_key in container.getAllKeys():
container.propertiesChanged.emit(setting_key, property_names)
@pyqtSlot(int, bool) @pyqtSlot(int, bool)
def setExtruderEnabled(self, position: int, enabled) -> None: def setExtruderEnabled(self, position: int, enabled) -> None:
@ -874,27 +926,22 @@ class MachineManager(QObject):
@pyqtProperty("QVariantList", notify = globalContainerChanged) @pyqtProperty("QVariantList", notify = globalContainerChanged)
def currentExtruderPositions(self): def currentExtruderPositions(self):
if self._global_container_stack is None:
return []
return sorted(list(self._global_container_stack.extruders.keys())) return sorted(list(self._global_container_stack.extruders.keys()))
## Update _current_root_material_id when the current root material was changed.
def _onRootMaterialChanged(self):
self._current_root_material_id = {}
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")
@pyqtProperty("QVariant", notify = rootMaterialChanged) @pyqtProperty("QVariant", notify = rootMaterialChanged)
def currentRootMaterialId(self): def currentRootMaterialId(self):
# initial filling the 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 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:
self._current_root_material_name = {}
for position in self._global_container_stack.extruders:
if position not in self._current_root_material_name:
material = self._global_container_stack.extruders[position].material
self._current_root_material_name[position] = material.getName()
return self._current_root_material_name
## Return the variant names in the extruder stack(s). ## Return the variant names in the extruder stack(s).
## For the variant in the global stack, use activeVariantBuildplateName ## For the variant in the global stack, use activeVariantBuildplateName
@pyqtProperty("QVariant", notify = activeVariantChanged) @pyqtProperty("QVariant", notify = activeVariantChanged)
@ -991,15 +1038,12 @@ class MachineManager(QObject):
if container_node: if container_node:
self._global_container_stack.extruders[position].material = container_node.getContainer() self._global_container_stack.extruders[position].material = container_node.getContainer()
root_material_id = container_node.metadata["base_file"] root_material_id = container_node.metadata["base_file"]
root_material_name = container_node.getContainer().getName()
else: else:
self._global_container_stack.extruders[position].material = self._empty_material_container self._global_container_stack.extruders[position].material = self._empty_material_container
root_material_id = None root_material_id = None
root_material_name = None
# The _current_root_material_id is used in the MaterialMenu to see which material is selected # The _current_root_material_id is used in the MaterialMenu to see which material is selected
if root_material_id != self._current_root_material_id[position]: if root_material_id != self._current_root_material_id[position]:
self._current_root_material_id[position] = root_material_id self._current_root_material_id[position] = root_material_id
self._current_root_material_name[position] = root_material_name
self.rootMaterialChanged.emit() self.rootMaterialChanged.emit()
def activeMaterialsCompatible(self): def activeMaterialsCompatible(self):
@ -1013,7 +1057,7 @@ class MachineManager(QObject):
return True return True
## Update current quality type and machine after setting material ## Update current quality type and machine after setting material
def _updateQualityWithMaterial(self): def _updateQualityWithMaterial(self, *args):
Logger.log("i", "Updating quality/quality_changes due to material change") Logger.log("i", "Updating quality/quality_changes due to material change")
current_quality_type = None current_quality_type = None
if self._current_quality_group: if self._current_quality_group:
@ -1058,9 +1102,15 @@ class MachineManager(QObject):
extruder = self._global_container_stack.extruders[position] extruder = self._global_container_stack.extruders[position]
current_material_base_name = extruder.material.getMetaDataEntry("base_file") current_material_base_name = extruder.material.getMetaDataEntry("base_file")
current_variant_name = None
if extruder.variant.getId() != self._empty_variant_container.getId():
current_variant_name = extruder.variant.getMetaDataEntry("name") current_variant_name = extruder.variant.getMetaDataEntry("name")
material_diameter = self._global_container_stack.getProperty("material_diameter", "value") from UM.Settings.Interfaces import PropertyEvaluationContext
from cura.Settings.CuraContainerStack import _ContainerIndexes
context = PropertyEvaluationContext(extruder)
context.context["evaluate_from_container_index"] = _ContainerIndexes.DefinitionChanges
material_diameter = self._global_container_stack.getProperty("material_diameter", "value", context)
candidate_materials = self._material_manager.getAvailableMaterials( candidate_materials = self._material_manager.getAvailableMaterials(
self._global_container_stack.definition.getId(), self._global_container_stack.definition.getId(),
current_variant_name, current_variant_name,
@ -1075,6 +1125,74 @@ class MachineManager(QObject):
self._setMaterial(position, new_material) self._setMaterial(position, new_material)
continue continue
# The current material is not available, find the preferred one
material_node = self._material_manager.getDefaultMaterial(self._global_container_stack, current_variant_name)
if material_node is not None:
self._setMaterial(position, material_node)
## Given a printer definition name, select the right machine instance. In case it doesn't exist, create a new
# instance with the same network key.
@pyqtSlot(str)
def switchPrinterType(self, machine_name):
# Don't switch if the user tries to change to the same type of printer
if self.activeMachineDefinitionName == machine_name:
return
# Get the definition id corresponding to this machine name
machine_definition_id = ContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
# Try to find a machine with the same network key
new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey})
# If there is no machine, then create a new one and set it to the non-hidden instance
if not new_machine:
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
new_machine.addMetaDataEntry("um_network_key", self.activeMachineNetworkKey)
new_machine.addMetaDataEntry("connect_group_name", self.activeMachineNetworkGroupName)
new_machine.addMetaDataEntry("hidden", False)
else:
Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey)
new_machine.setMetaDataEntry("hidden", False)
# Set the current printer instance to hidden (the metadata entry must exist)
self._global_container_stack.setMetaDataEntry("hidden", True)
self.setActiveMachine(new_machine.getId())
@pyqtSlot(QObject)
def applyRemoteConfiguration(self, configuration: ConfigurationModel):
self.blurSettings.emit()
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self.switchPrinterType(configuration.printerType)
for extruder_configuration in configuration.extruderConfigurations:
position = str(extruder_configuration.position)
variant_container_node = self._variant_manager.getVariantNode(self._global_container_stack.definition.getId(), extruder_configuration.hotendID)
material_container_node = self._material_manager.getMaterialNodeByType(self._global_container_stack, extruder_configuration.hotendID,extruder_configuration.material.guid)
if variant_container_node:
self._setVariantNode(position, variant_container_node)
else:
self._global_container_stack.extruders[position].variant = self._empty_variant_container
if material_container_node:
self._setMaterial(position, material_container_node)
else:
self._global_container_stack.extruders[position].material = self._empty_material_container
self._updateMaterialWithVariant(position)
if configuration.buildplateConfiguration is not None:
global_variant_container_node = self._variant_manager.getBuildplateVariantNode(self._global_container_stack.definition.getId(), configuration.buildplateConfiguration)
if global_variant_container_node:
self._setGlobalVariant(global_variant_container_node)
else:
self._global_container_stack.variant = self._empty_variant_container
else:
self._global_container_stack.variant = self._empty_variant_container
self._updateQualityWithMaterial()
## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value'
def replaceContainersMetadata(self, key: str, value: str, new_value: str):
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
for machine in machines:
if machine.getMetaDataEntry(key) == value:
machine.setMetaDataEntry(key, new_value)
@pyqtSlot("QVariant") @pyqtSlot("QVariant")
def setGlobalVariant(self, container_node): def setGlobalVariant(self, container_node):
self.blurSettings.emit() self.blurSettings.emit()
@ -1136,3 +1254,8 @@ class MachineManager(QObject):
elif self._current_quality_group: elif self._current_quality_group:
name = self._current_quality_group.name name = self._current_quality_group.name
return name return name
def _updateUponMaterialMetadataChange(self):
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self._updateMaterialWithVariant(None)
self._updateQualityWithMaterial()

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 QObject, pyqtProperty, pyqtSignal from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
from UM.Application import Application from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
@ -30,6 +30,11 @@ class SettingInheritanceManager(QObject):
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
self._onActiveExtruderChanged() self._onActiveExtruderChanged()
self._update_timer = QTimer()
self._update_timer.setInterval(500)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._update)
settingsWithIntheritanceChanged = pyqtSignal() settingsWithIntheritanceChanged = pyqtSignal()
## Get the keys of all children settings with an override. ## Get the keys of all children settings with an override.
@ -226,9 +231,7 @@ class SettingInheritanceManager(QObject):
self._onActiveExtruderChanged() self._onActiveExtruderChanged()
def _onContainersChanged(self, container): def _onContainersChanged(self, container):
# TODO: Multiple container changes in sequence now cause quite a few recalculations. self._update_timer.start()
# This isn't that big of an issue, but it could be in the future.
self._update()
@staticmethod @staticmethod
def createSettingInheritanceManager(engine=None, script_engine=None): def createSettingInheritanceManager(engine=None, script_engine=None):

View File

@ -0,0 +1,136 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
import urllib
from configparser import ConfigParser
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot, QUrl
from UM.Logger import Logger
from UM.Qt.ListModel import ListModel
from UM.Preferences import Preferences
from UM.Resources import Resources
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
import cura.CuraApplication
class SettingVisibilityPresetsModel(ListModel):
IdRole = Qt.UserRole + 1
NameRole = Qt.UserRole + 2
SettingsRole = Qt.UserRole + 4
def __init__(self, parent = None):
super().__init__(parent)
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.SettingsRole, "settings")
self._populate()
self._preferences = Preferences.getInstance()
self._preferences.addPreference("cura/active_setting_visibility_preset", "custom") # Preference to store which preset is currently selected
self._preferences.addPreference("cura/custom_visible_settings", "") # Preference that stores the "custom" set so it can always be restored (even after a restart)
self._preferences.preferenceChanged.connect(self._onPreferencesChanged)
self._active_preset = self._preferences.getValue("cura/active_setting_visibility_preset")
if self.find("id", self._active_preset) < 0:
self._active_preset = "custom"
self.activePresetChanged.emit()
def _populate(self):
items = []
for item in Resources.getAllResourcesOfType(cura.CuraApplication.CuraApplication.ResourceTypes.SettingVisibilityPreset):
try:
mime_type = MimeTypeDatabase.getMimeTypeForFile(item)
except MimeTypeNotFoundError:
Logger.log("e", "Could not determine mime type of file %s", item)
continue
id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(item)))
if not os.path.isfile(item):
continue
parser = ConfigParser(allow_no_value=True) # accept options without any value,
try:
parser.read([item])
if not parser.has_option("general", "name") and not parser.has_option("general", "weight"):
continue
settings = []
for section in parser.sections():
if section == 'general':
continue
settings.append(section)
for option in parser[section].keys():
settings.append(option)
items.append({
"id": id,
"name": parser["general"]["name"],
"weight": parser["general"]["weight"],
"settings": settings
})
except Exception as e:
Logger.log("e", "Failed to load setting preset %s: %s", file_path, str(e))
items.sort(key = lambda k: (k["weight"], k["id"]))
self.setItems(items)
@pyqtSlot(str)
def setActivePreset(self, preset_id):
if preset_id != "custom" and self.find("id", preset_id) == -1:
Logger.log("w", "Tried to set active preset to unknown id %s", preset_id)
return
if preset_id == "custom" and self._active_preset == "custom":
# Copy current visibility set to custom visibility set preference so it can be restored later
visibility_string = self._preferences.getValue("general/visible_settings")
self._preferences.setValue("cura/custom_visible_settings", visibility_string)
self._preferences.setValue("cura/active_setting_visibility_preset", preset_id)
self._active_preset = preset_id
self.activePresetChanged.emit()
activePresetChanged = pyqtSignal()
@pyqtProperty(str, notify = activePresetChanged)
def activePreset(self):
return self._active_preset
def _onPreferencesChanged(self, name):
if name != "general/visible_settings":
return
if self._active_preset != "custom":
return
# Copy current visibility set to custom visibility set preference so it can be restored later
visibility_string = self._preferences.getValue("general/visible_settings")
self._preferences.setValue("cura/custom_visible_settings", visibility_string)
# Factory function, used by QML
@staticmethod
def createSettingVisibilityPresetsModel(engine, js_engine):
return SettingVisibilityPresetsModel.getInstance()
## Get the singleton instance for this class.
@classmethod
def getInstance(cls) -> "SettingVisibilityPresetsModel":
# Note: Explicit use of class name to prevent issues with inheritance.
if not SettingVisibilityPresetsModel.__instance:
SettingVisibilityPresetsModel.__instance = cls()
return SettingVisibilityPresetsModel.__instance
__instance = None # type: "SettingVisibilityPresetsModel"

View File

@ -66,7 +66,7 @@ class Snapshot:
size = max(bbox.width, bbox.height, bbox.depth * 0.5) size = max(bbox.width, bbox.height, bbox.depth * 0.5)
# Looking from this direction (x, y, z) in OGL coordinates # Looking from this direction (x, y, z) in OGL coordinates
looking_from_offset = Vector(1, 1, 2) looking_from_offset = Vector(-1, 1, 2)
if size > 0: if size > 0:
# determine the watch distance depending on the size # determine the watch distance depending on the size
looking_from_offset = looking_from_offset * size * 1.3 looking_from_offset = looking_from_offset * size * 1.3

View File

@ -122,7 +122,7 @@ class ThreeMFReader(MeshReader):
um_node.callDecoration("setActiveExtruder", default_stack.getId()) um_node.callDecoration("setActiveExtruder", default_stack.getId())
# Get the definition & set it # Get the definition & set it
definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack) definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack.definition)
um_node.callDecoration("getStack").getTop().setDefinition(definition_id) um_node.callDecoration("getStack").getTop().setDefinition(definition_id)
setting_container = um_node.callDecoration("getStack").getTop() setting_container = um_node.callDecoration("getStack").getTop()

View File

@ -265,13 +265,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
for material_container_file in material_container_files: for material_container_file in material_container_files:
container_id = self._stripFileToId(material_container_file) container_id = self._stripFileToId(material_container_file)
from hashlib import sha1
hex_container_id = sha1(container_id.encode('utf-8')).hexdigest()
serialized = archive.open(material_container_file).read().decode("utf-8") serialized = archive.open(material_container_file).read().decode("utf-8")
metadata_list = xml_material_profile.deserializeMetadata(serialized, hex_container_id) metadata_list = xml_material_profile.deserializeMetadata(serialized, container_id)
reverse_map = {metadata["id"].replace(hex_container_id, container_id): container_id.replace(hex_container_id, container_id) reverse_map = {metadata["id"]: container_id for metadata in metadata_list}
for metadata in metadata_list}
reverse_material_id_dict.update(reverse_map) reverse_material_id_dict.update(reverse_map)
material_labels.append(self._getMaterialLabelFromSerialized(serialized)) material_labels.append(self._getMaterialLabelFromSerialized(serialized))
@ -598,7 +594,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged") Logger.log("w", "Workspace did not contain visible settings. Leaving visibility unchanged")
else: else:
global_preferences.setValue("general/visible_settings", visible_settings) global_preferences.setValue("general/visible_settings", visible_settings)
global_preferences.setValue("general/preset_setting_visibility_choice", "Custom") global_preferences.setValue("cura/active_setting_visibility_preset", "custom")
categories_expanded = temp_preferences.getValue("cura/categories_expanded") categories_expanded = temp_preferences.getValue("cura/categories_expanded")
if categories_expanded is None: if categories_expanded is None:
@ -723,7 +719,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# Get the correct extruder definition IDs for quality changes # Get the correct extruder definition IDs for quality changes
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(global_stack) machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(global_stack.definition)
machine_definition_for_quality = self._container_registry.findDefinitionContainers(id = machine_definition_id_for_quality)[0] machine_definition_for_quality = self._container_registry.findDefinitionContainers(id = machine_definition_id_for_quality)[0]
quality_changes_info = self._machine_info.quality_changes_info quality_changes_info = self._machine_info.quality_changes_info
@ -754,15 +750,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
quality_changes_containers = self._container_registry.findInstanceContainers(name = quality_changes_name, quality_changes_containers = self._container_registry.findInstanceContainers(name = quality_changes_name,
type = "quality_changes") type = "quality_changes")
for container in quality_changes_containers: for container in quality_changes_containers:
extruder_definition_id = container.getMetaDataEntry("extruder") extruder_position = container.getMetaDataEntry("position")
if not extruder_definition_id: if extruder_position is None:
quality_changes_info.global_info.container = container quality_changes_info.global_info.container = container
else: else:
extruder_definition_metadata = self._container_registry.findDefinitionContainersMetadata(id = extruder_definition_id)[0] if extruder_position not in quality_changes_info.extruder_info_dict:
position = extruder_definition_metadata["position"] quality_changes_info.extruder_info_dict[extruder_position] = ContainerInfo(None, None, None)
if position not in quality_changes_info.extruder_info_dict: container_info = quality_changes_info.extruder_info_dict[extruder_position]
quality_changes_info.extruder_info_dict[position] = ContainerInfo(None, None, None)
container_info = quality_changes_info.extruder_info_dict[position]
container_info.container = container container_info.container = container
# If there is no quality changes for any extruder, create one. # If there is no quality changes for any extruder, create one.

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,20 +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)
# extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash
ExtruderManager.getInstance().extrudersChanged.connect(self._extruderChanged)
# 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
@ -125,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)
@ -146,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.
@ -531,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()
@ -561,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()
@ -632,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()
@ -786,7 +801,7 @@ class CuraEngineBackend(QObject, Backend):
self._change_timer.start() self._change_timer.start()
def _extruderChanged(self): def _extruderChanged(self):
for build_plate_number in range(Application.getInstance().getMultiBuildPlateModel().maxBuildPlate + 1): 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: if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number) self._build_plates_to_be_sliced.append(build_plate_number)
self._invokeSlice() self._invokeSlice()

View File

@ -281,7 +281,7 @@ class StartSliceJob(Job):
default_extruder_position = int(Application.getInstance().getMachineManager().defaultExtruderPosition) default_extruder_position = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
result = {} result = {}
for key in stack.getAllKeys(): for key in stack.getAllKeys():
setting_type = stack.getProperty(key, "type") setting_type = stack.definition.getProperty(key, "type")
value = stack.getProperty(key, "value") value = stack.getProperty(key, "value")
if setting_type == "extruder" and value == -1: if setting_type == "extruder" and value == -1:
# replace with the default value # replace with the default value

View File

@ -382,6 +382,11 @@ Cura.MachineAction
property string settingKey: "machine_nozzle_size" property string settingKey: "machine_nozzle_size"
property string label: catalog.i18nc("@label", "Nozzle size") property string label: catalog.i18nc("@label", "Nozzle size")
property string unit: catalog.i18nc("@label", "mm") property string unit: catalog.i18nc("@label", "mm")
function afterOnEditingFinished()
{
// Somehow the machine_nozzle_size dependent settings are not updated otherwise
Cura.MachineManager.forceUpdateAllSettings()
}
property bool isExtruderSetting: true property bool isExtruderSetting: true
} }

View File

@ -22,14 +22,7 @@ class MonitorStage(CuraStage):
def _setActivePrintJob(self, print_job): def _setActivePrintJob(self, print_job):
if self._active_print_job != print_job: if self._active_print_job != print_job:
if self._active_print_job:
self._active_print_job.stateChanged.disconnect(self._updateIconSource)
self._active_print_job = print_job self._active_print_job = print_job
if self._active_print_job:
self._active_print_job.stateChanged.connect(self._updateIconSource)
# Ensure that the right icon source is returned.
self._updateIconSource()
def _setActivePrinter(self, printer): def _setActivePrinter(self, printer):
if self._active_printer != printer: if self._active_printer != printer:
@ -43,9 +36,6 @@ class MonitorStage(CuraStage):
else: else:
self._setActivePrintJob(None) self._setActivePrintJob(None)
# Ensure that the right icon source is returned.
self._updateIconSource()
def _onActivePrintJobChanged(self): def _onActivePrintJobChanged(self):
self._setActivePrintJob(self._active_printer.activePrintJob) self._setActivePrintJob(self._active_printer.activePrintJob)
@ -58,22 +48,13 @@ class MonitorStage(CuraStage):
new_output_device = Application.getInstance().getMachineManager().printerOutputDevices[0] new_output_device = Application.getInstance().getMachineManager().printerOutputDevices[0]
if new_output_device != self._printer_output_device: if new_output_device != self._printer_output_device:
if self._printer_output_device: if self._printer_output_device:
self._printer_output_device.acceptsCommandsChanged.disconnect(self._updateIconSource)
self._printer_output_device.connectionStateChanged.disconnect(self._updateIconSource)
self._printer_output_device.printersChanged.disconnect(self._onActivePrinterChanged) self._printer_output_device.printersChanged.disconnect(self._onActivePrinterChanged)
self._printer_output_device = new_output_device self._printer_output_device = new_output_device
self._printer_output_device.acceptsCommandsChanged.connect(self._updateIconSource)
self._printer_output_device.printersChanged.connect(self._onActivePrinterChanged) self._printer_output_device.printersChanged.connect(self._onActivePrinterChanged)
self._printer_output_device.connectionStateChanged.connect(self._updateIconSource)
self._setActivePrinter(self._printer_output_device.activePrinter) self._setActivePrinter(self._printer_output_device.activePrinter)
# Force an update of the icon source
self._updateIconSource()
except IndexError: except IndexError:
#If index error occurs, then the icon on monitor button also should be updated
self._updateIconSource()
pass pass
def _onEngineCreated(self): def _onEngineCreated(self):
@ -82,7 +63,6 @@ class MonitorStage(CuraStage):
self._onOutputDevicesChanged() self._onOutputDevicesChanged()
self._updateMainOverlay() self._updateMainOverlay()
self._updateSidebar() self._updateSidebar()
self._updateIconSource()
def _updateMainOverlay(self): def _updateMainOverlay(self):
main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("MonitorStage"), "MonitorMainView.qml") main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("MonitorStage"), "MonitorMainView.qml")
@ -92,46 +72,3 @@ class MonitorStage(CuraStage):
# TODO: currently the sidebar component for prepare and monitor stages is the same, this will change with the printer output device refactor! # TODO: currently the sidebar component for prepare and monitor stages is the same, this will change with the printer output device refactor!
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles), "Sidebar.qml") sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles), "Sidebar.qml")
self.addDisplayComponent("sidebar", sidebar_component_path) self.addDisplayComponent("sidebar", sidebar_component_path)
def _updateIconSource(self):
if Application.getInstance().getTheme() is not None:
icon_name = self._getActiveOutputDeviceStatusIcon()
self.setIconSource(Application.getInstance().getTheme().getIcon(icon_name))
## Find the correct status icon depending on the active output device state
def _getActiveOutputDeviceStatusIcon(self):
# We assume that you are monitoring the device with the highest priority.
try:
output_device = Application.getInstance().getMachineManager().printerOutputDevices[0]
except IndexError:
return "tab_status_unknown"
if not output_device.acceptsCommands:
return "tab_status_unknown"
if output_device.activePrinter is None:
return "tab_status_connected"
# TODO: refactor to use enum instead of hardcoded strings?
if output_device.activePrinter.state == "maintenance":
return "tab_status_busy"
if output_device.activePrinter.activePrintJob is None:
return "tab_status_connected"
if output_device.activePrinter.activePrintJob.state in ["printing", "pre_print", "pausing", "resuming"]:
return "tab_status_busy"
if output_device.activePrinter.activePrintJob.state == "wait_cleanup":
return "tab_status_finished"
if output_device.activePrinter.activePrintJob.state in ["ready", ""]:
return "tab_status_connected"
if output_device.activePrinter.activePrintJob.state == "paused":
return "tab_status_paused"
if output_device.activePrinter.activePrintJob.state == "error":
return "tab_status_stopped"
return "tab_status_unknown"

View File

@ -158,6 +158,7 @@ class SimulationView(View):
return self._nozzle_node return self._nozzle_node
def _onSceneChanged(self, node): def _onSceneChanged(self, node):
if node.getMeshData() is not None:
self.setActivity(False) self.setActivity(False)
self.calculateMaxLayers() self.calculateMaxLayers()
self.calculateMaxPathsOnLayer(self._current_layer_num) self.calculateMaxPathsOnLayer(self._current_layer_num)

View File

@ -22,7 +22,7 @@ from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
from time import time from time import time
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional, Dict, List
import json import json
import os import os
@ -79,7 +79,6 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._latest_reply_handler = None self._latest_reply_handler = None
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs): def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
self.writeStarted.emit(self) self.writeStarted.emit(self)
@ -116,7 +115,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
@pyqtSlot() @pyqtSlot()
@pyqtSlot(str) @pyqtSlot(str)
def sendPrintJob(self, target_printer = ""): def sendPrintJob(self, target_printer: str = ""):
Logger.log("i", "Sending print job to printer.") Logger.log("i", "Sending print job to printer.")
if self._sending_gcode: if self._sending_gcode:
self._error_message = Message( self._error_message = Message(
@ -157,11 +156,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return True return True
@pyqtProperty(QObject, notify=activePrinterChanged) @pyqtProperty(QObject, notify=activePrinterChanged)
def activePrinter(self) -> Optional["PrinterOutputModel"]: def activePrinter(self) -> Optional[PrinterOutputModel]:
return self._active_printer return self._active_printer
@pyqtSlot(QObject) @pyqtSlot(QObject)
def setActivePrinter(self, printer): def setActivePrinter(self, printer: Optional[PrinterOutputModel]):
if self._active_printer != printer: if self._active_printer != printer:
if self._active_printer and self._active_printer.camera: if self._active_printer and self._active_printer.camera:
self._active_printer.camera.stop() self._active_printer.camera.stop()
@ -173,7 +172,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._compressing_gcode = False self._compressing_gcode = False
self._sending_gcode = False self._sending_gcode = False
def _onUploadPrintJobProgress(self, bytes_sent, bytes_total): def _onUploadPrintJobProgress(self, bytes_sent:int, bytes_total:int):
if bytes_total > 0: if bytes_total > 0:
new_progress = bytes_sent / bytes_total * 100 new_progress = bytes_sent / bytes_total * 100
# Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get # Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
@ -186,7 +185,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._progress_message.setProgress(0) self._progress_message.setProgress(0)
self._progress_message.hide() self._progress_message.hide()
def _progressMessageActionTriggered(self, message_id=None, action_id=None): def _progressMessageActionTriggered(self, message_id: Optional[str]=None, action_id: Optional[str]=None) -> None:
if action_id == "Abort": if action_id == "Abort":
Logger.log("d", "User aborted sending print to remote.") Logger.log("d", "User aborted sending print to remote.")
self._progress_message.hide() self._progress_message.hide()
@ -202,29 +201,29 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
@pyqtSlot() @pyqtSlot()
def openPrintJobControlPanel(self): def openPrintJobControlPanel(self) -> None:
Logger.log("d", "Opening print job control panel...") Logger.log("d", "Opening print job control panel...")
QDesktopServices.openUrl(QUrl("http://" + self._address + "/print_jobs")) QDesktopServices.openUrl(QUrl("http://" + self._address + "/print_jobs"))
@pyqtSlot() @pyqtSlot()
def openPrinterControlPanel(self): def openPrinterControlPanel(self) -> None:
Logger.log("d", "Opening printer control panel...") Logger.log("d", "Opening printer control panel...")
QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers")) QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers"))
@pyqtProperty("QVariantList", notify=printJobsChanged) @pyqtProperty("QVariantList", notify=printJobsChanged)
def printJobs(self): def printJobs(self)-> List[PrintJobOutputModel] :
return self._print_jobs return self._print_jobs
@pyqtProperty("QVariantList", notify=printJobsChanged) @pyqtProperty("QVariantList", notify=printJobsChanged)
def queuedPrintJobs(self): def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is None or print_job.state == "queued"] return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is None or print_job.state == "queued"]
@pyqtProperty("QVariantList", notify=printJobsChanged) @pyqtProperty("QVariantList", notify=printJobsChanged)
def activePrintJobs(self): def activePrintJobs(self) -> List[PrintJobOutputModel]:
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"] return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"]
@pyqtProperty("QVariantList", notify=clusterPrintersChanged) @pyqtProperty("QVariantList", notify=clusterPrintersChanged)
def connectedPrintersTypeCount(self): def connectedPrintersTypeCount(self) -> List[PrinterOutputModel]:
printer_count = {} printer_count = {}
for printer in self._printers: for printer in self._printers:
if printer.type in printer_count: if printer.type in printer_count:
@ -237,22 +236,22 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return result return result
@pyqtSlot(int, result=str) @pyqtSlot(int, result=str)
def formatDuration(self, seconds): def formatDuration(self, seconds: int) -> str:
return Duration(seconds).getDisplayString(DurationFormat.Format.Short) return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
@pyqtSlot(int, result=str) @pyqtSlot(int, result=str)
def getTimeCompleted(self, time_remaining): def getTimeCompleted(self, time_remaining: int) -> str:
current_time = time() current_time = time()
datetime_completed = datetime.fromtimestamp(current_time + time_remaining) datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute) return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute)
@pyqtSlot(int, result=str) @pyqtSlot(int, result=str)
def getDateCompleted(self, time_remaining): def getDateCompleted(self, time_remaining: int) -> str:
current_time = time() current_time = time()
datetime_completed = datetime.fromtimestamp(current_time + time_remaining) datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper() return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()
def _printJobStateChanged(self): def _printJobStateChanged(self) -> None:
username = self._getUserName() username = self._getUserName()
if username is None: if username is None:
@ -275,13 +274,13 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# Keep a list of all completed jobs so we know if something changed next time. # Keep a list of all completed jobs so we know if something changed next time.
self._finished_jobs = finished_jobs self._finished_jobs = finished_jobs
def _update(self): def _update(self) -> None:
if not super()._update(): if not super()._update():
return return
self.get("printers/", onFinished=self._onGetPrintersDataFinished) self.get("printers/", onFinished=self._onGetPrintersDataFinished)
self.get("print_jobs/", onFinished=self._onGetPrintJobsFinished) self.get("print_jobs/", onFinished=self._onGetPrintJobsFinished)
def _onGetPrintJobsFinished(self, reply: QNetworkReply): def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
if not checkValidGetReply(reply): if not checkValidGetReply(reply):
return return
@ -323,7 +322,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if job_list_changed: if job_list_changed:
self.printJobsChanged.emit() # Do a single emit for all print job changes. self.printJobsChanged.emit() # Do a single emit for all print job changes.
def _onGetPrintersDataFinished(self, reply: QNetworkReply): def _onGetPrintersDataFinished(self, reply: QNetworkReply) -> None:
if not checkValidGetReply(reply): if not checkValidGetReply(reply):
return return
@ -352,34 +351,45 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if removed_printers or printer_list_changed: if removed_printers or printer_list_changed:
self.printersChanged.emit() self.printersChanged.emit()
def _createPrinterModel(self, data): def _createPrinterModel(self, data: Dict) -> PrinterOutputModel:
printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self), printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
number_of_extruders=self._number_of_extruders) number_of_extruders=self._number_of_extruders)
printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream")) printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream"))
self._printers.append(printer) self._printers.append(printer)
return printer return printer
def _createPrintJobModel(self, data): def _createPrintJobModel(self, data: Dict) -> PrintJobOutputModel:
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self), print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
key=data["uuid"], name= data["name"]) key=data["uuid"], name= data["name"])
print_job.stateChanged.connect(self._printJobStateChanged) print_job.stateChanged.connect(self._printJobStateChanged)
self._print_jobs.append(print_job) self._print_jobs.append(print_job)
return print_job return print_job
def _updatePrintJob(self, print_job, data): def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict) -> None:
print_job.updateTimeTotal(data["time_total"]) print_job.updateTimeTotal(data["time_total"])
print_job.updateTimeElapsed(data["time_elapsed"]) print_job.updateTimeElapsed(data["time_elapsed"])
print_job.updateState(data["status"]) print_job.updateState(data["status"])
print_job.updateOwner(data["owner"]) print_job.updateOwner(data["owner"])
def _updatePrinter(self, printer, data): def _updatePrinter(self, printer: PrinterOutputModel, data: Dict) -> None:
# For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer. # For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
# Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping. # Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"] self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"]
definitions = ContainerRegistry.getInstance().findDefinitionContainers(name = data["machine_variant"])
if not definitions:
Logger.log("w", "Unable to find definition for machine variant %s", data["machine_variant"])
return
machine_definition = definitions[0]
printer.updateName(data["friendly_name"]) printer.updateName(data["friendly_name"])
printer.updateKey(data["uuid"]) printer.updateKey(data["uuid"])
printer.updateType(data["machine_variant"]) printer.updateType(data["machine_variant"])
# Do not store the buildplate information that comes from connect if the current printer has not buildplate information
if "build_plate" in data and machine_definition.getMetaDataEntry("has_variant_buildplates", False):
printer.updateBuildplateName(data["build_plate"]["type"])
if not data["enabled"]: if not data["enabled"]:
printer.updateState("disabled") printer.updateState("disabled")
else: else:
@ -416,7 +426,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
brand=brand, color=color, name=name) brand=brand, color=color, name=name)
extruder.updateActiveMaterial(material) extruder.updateActiveMaterial(material)
def _removeJob(self, job): def _removeJob(self, job: PrintJobOutputModel):
if job not in self._print_jobs: if job not in self._print_jobs:
return False return False
@ -427,7 +437,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
return True return True
def _removePrinter(self, printer): def _removePrinter(self, printer: PrinterOutputModel):
self._printers.remove(printer) self._printers.remove(printer)
if self._active_printer == printer: if self._active_printer == printer:
self._active_printer = None self._active_printer = None

View File

@ -97,6 +97,25 @@ class DiscoverUM3Action(MachineAction):
else: else:
return [] return []
@pyqtSlot(str)
def setGroupName(self, group_name):
Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name)
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
meta_data = global_container_stack.getMetaData()
if "connect_group_name" in meta_data:
previous_connect_group_name = meta_data["connect_group_name"]
global_container_stack.setMetaDataEntry("connect_group_name", group_name)
# Find all the places where there is the same group name and change it accordingly
Application.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name)
else:
global_container_stack.addMetaDataEntry("connect_group_name", group_name)
global_container_stack.addMetaDataEntry("hidden", False)
if self._network_plugin:
# Ensure that the connection states are refreshed.
self._network_plugin.reCheckConnections()
@pyqtSlot(str) @pyqtSlot(str)
def setKey(self, key): def setKey(self, key):
Logger.log("d", "Attempting to set the network key of the active machine to %s", key) Logger.log("d", "Attempting to set the network key of the active machine to %s", key)
@ -104,11 +123,13 @@ class DiscoverUM3Action(MachineAction):
if global_container_stack: if global_container_stack:
meta_data = global_container_stack.getMetaData() meta_data = global_container_stack.getMetaData()
if "um_network_key" in meta_data: if "um_network_key" in meta_data:
previous_network_key= meta_data["um_network_key"]
global_container_stack.setMetaDataEntry("um_network_key", key) global_container_stack.setMetaDataEntry("um_network_key", key)
# Delete old authentication data. # Delete old authentication data.
Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key) Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key)
global_container_stack.removeMetaDataEntry("network_authentication_id") global_container_stack.removeMetaDataEntry("network_authentication_id")
global_container_stack.removeMetaDataEntry("network_authentication_key") global_container_stack.removeMetaDataEntry("network_authentication_key")
Application.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key)
else: else:
global_container_stack.addMetaDataEntry("um_network_key", key) global_container_stack.addMetaDataEntry("um_network_key", key)

View File

@ -32,10 +32,12 @@ Cura.MachineAction
if(base.selectedDevice && base.completeProperties) if(base.selectedDevice && base.completeProperties)
{ {
var printerKey = base.selectedDevice.key var printerKey = base.selectedDevice.key
var printerName = base.selectedDevice.name // TODO To change when the groups have a name
if(manager.getStoredKey() != printerKey) if(manager.getStoredKey() != printerKey)
{ {
manager.setKey(printerKey); manager.setKey(printerKey)
completed(); manager.setGroupName(printerName) // TODO To change when the groups have a name
completed()
} }
} }
} }
@ -303,7 +305,7 @@ Cura.MachineAction
Button Button
{ {
text: catalog.i18nc("@action:button", "Connect") text: catalog.i18nc("@action:button", "Connect")
enabled: (base.selectedDevice && base.completeProperties) ? true : false enabled: (base.selectedDevice && base.completeProperties && base.selectedDevice.clusterSize > 0) ? true : false
onClicked: connectToPrinter() onClicked: connectToPrinter()
} }
} }

View File

@ -22,6 +22,7 @@ class AutoDetectBaudJob(Job):
def run(self): def run(self):
Logger.log("d", "Auto detect baud rate started.") Logger.log("d", "Auto detect baud rate started.")
timeout = 3 timeout = 3
tries = 2
programmer = Stk500v2() programmer = Stk500v2()
serial = None serial = None
@ -31,6 +32,7 @@ class AutoDetectBaudJob(Job):
except: except:
programmer.close() programmer.close()
for retry in range(tries):
for baud_rate in self._all_baud_rates: for baud_rate in self._all_baud_rates:
Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate)) Logger.log("d", "Checking {serial} if baud rate {baud_rate} works".format(serial= self._serial_port, baud_rate = baud_rate))
@ -63,4 +65,5 @@ class AutoDetectBaudJob(Job):
return return
serial.write(b"M105\n") serial.write(b"M105\n")
sleep(15) # Give the printer some time to init and try again.
self.setResult(None) # Unable to detect the correct baudrate. self.setResult(None) # Unable to detect the correct baudrate.

View File

@ -116,7 +116,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
@pyqtSlot(str) @pyqtSlot(str)
def updateFirmware(self, file): def updateFirmware(self, file):
self._firmware_location = file # the file path is qurl encoded.
self._firmware_location = file.replace("file://", "")
self.showFirmwareInterface() self.showFirmwareInterface()
self.setFirmwareUpdateState(FirmwareUpdateState.updating) self.setFirmwareUpdateState(FirmwareUpdateState.updating)
self._update_firmware_thread.start() self._update_firmware_thread.start()
@ -126,9 +127,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if self._connection_state != ConnectionState.closed: if self._connection_state != ConnectionState.closed:
self.close() self.close()
try:
hex_file = intelHex.readHex(self._firmware_location) hex_file = intelHex.readHex(self._firmware_location)
if len(hex_file) == 0: assert len(hex_file) > 0
Logger.log("e", "Unable to read provided hex file. Could not update firmware") except (FileNotFoundError, AssertionError):
Logger.log("e", "Unable to read provided hex file. Could not update firmware.")
self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error) self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error)
return return
@ -198,7 +201,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
# Reset line number. If this is not done, first line is sometimes ignored # Reset line number. If this is not done, first line is sometimes ignored
self._gcode.insert(0, "M110") self._gcode.insert(0, "M110")
self._gcode_position = 0 self._gcode_position = 0
self._is_printing = True
self._print_start_time = time() self._print_start_time = time()
self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds)) self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))
@ -206,6 +208,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
for i in range(0, 4): # Push first 4 entries before accepting other inputs for i in range(0, 4): # Push first 4 entries before accepting other inputs
self._sendNextGcodeLine() self._sendNextGcodeLine()
self._is_printing = True
self.writeFinished.emit(self) self.writeFinished.emit(self)
def _autoDetectFinished(self, job: AutoDetectBaudJob): def _autoDetectFinished(self, job: AutoDetectBaudJob):
@ -267,7 +270,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if not command.endswith(b"\n"): if not command.endswith(b"\n"):
command += b"\n" command += b"\n"
try: try:
self._serial.write(b"\n")
self._serial.write(command) self._serial.write(command)
except SerialTimeoutException: except SerialTimeoutException:
Logger.log("w", "Timeout when sending command to printer via USB.") Logger.log("w", "Timeout when sending command to printer via USB.")
@ -284,7 +286,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self.sendCommand("M105") self.sendCommand("M105")
self._last_temperature_request = time() self._last_temperature_request = time()
if b"ok T:" in line or line.startswith(b"T:"): # Temperature message if b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line) extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
# Update all temperature values # Update all temperature values
for match, extruder in zip(extruder_temperature_matches, self._printers[0].extruders): for match, extruder in zip(extruder_temperature_matches, self._printers[0].extruders):
@ -302,6 +304,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._printers[0].updateTargetBedTemperature(float(match[1])) self._printers[0].updateTargetBedTemperature(float(match[1]))
if self._is_printing: if self._is_printing:
if line.startswith(b'!!'):
Logger.log('e', "Printer signals fatal error. Cancelling print. {}".format(line))
self.cancelPrint()
if b"ok" in line: if b"ok" in line:
if not self._command_queue.empty(): if not self._command_queue.empty():
self._sendCommand(self._command_queue.get()) self._sendCommand(self._command_queue.get())

View File

@ -56,6 +56,8 @@ _EXTRUDER_TO_POSITION = {
## Upgrades configurations from the state they were in at version 3.2 to the ## Upgrades configurations from the state they were in at version 3.2 to the
# state they should be in at version 3.3. # state they should be in at version 3.3.
class VersionUpgrade32to33(VersionUpgrade): class VersionUpgrade32to33(VersionUpgrade):
temporary_group_name_counter = 1
## Gets the version number from a CFG file in Uranium's 3.2 format. ## Gets the version number from a CFG file in Uranium's 3.2 format.
# #
# Since the format may change, this is implemented for the 3.2 format only # Since the format may change, this is implemented for the 3.2 format only
@ -74,6 +76,28 @@ class VersionUpgrade32to33(VersionUpgrade):
setting_version = int(parser.get("metadata", "setting_version", fallback = 0)) setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
return format_version * 1000000 + setting_version return format_version * 1000000 + setting_version
## Upgrades a container stack from version 3.2 to 3.3.
#
# \param serialised The serialised form of a container stack.
# \param filename The name of the file to upgrade.
def upgradeStack(self, serialized, filename):
parser = configparser.ConfigParser(interpolation = None)
parser.read_string(serialized)
if "metadata" in parser and "um_network_key" in parser["metadata"]:
if "hidden" not in parser["metadata"]:
parser["metadata"]["hidden"] = "False"
if "connect_group_name" not in parser["metadata"]:
parser["metadata"]["connect_group_name"] = "Temporary group name #" + str(self.temporary_group_name_counter)
self.temporary_group_name_counter += 1
#Update version number.
parser["general"]["version"] = "4"
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]
## Upgrades non-quality-changes instance containers to have the new version ## Upgrades non-quality-changes instance containers to have the new version
# number. # number.
def upgradeInstanceContainer(self, serialized, filename): def upgradeInstanceContainer(self, serialized, filename):

View File

@ -9,11 +9,22 @@ def getMetaData():
return { return {
"version_upgrade": { "version_upgrade": {
# From To Upgrade function # From To Upgrade function
("machine_stack", 3000004): ("machine_stack", 4000004, upgrade.upgradeStack),
("extruder_train", 3000004): ("extruder_train", 4000004, upgrade.upgradeStack),
("definition_changes", 2000004): ("definition_changes", 3000004, upgrade.upgradeInstanceContainer), ("definition_changes", 2000004): ("definition_changes", 3000004, upgrade.upgradeInstanceContainer),
("quality_changes", 2000004): ("quality_changes", 3000004, upgrade.upgradeQualityChanges), ("quality_changes", 2000004): ("quality_changes", 3000004, upgrade.upgradeQualityChanges),
("user", 2000004): ("user", 3000004, upgrade.upgradeInstanceContainer) ("user", 2000004): ("user", 3000004, upgrade.upgradeInstanceContainer)
}, },
"sources": { "sources": {
"machine_stack": {
"get_version": upgrade.getCfgVersion,
"location": {"./machine_instances"}
},
"extruder_train": {
"get_version": upgrade.getCfgVersion,
"location": {"./extruders"}
},
"definition_changes": { "definition_changes": {
"get_version": upgrade.getCfgVersion, "get_version": upgrade.getCfgVersion,
"location": {"./definition_changes"} "location": {"./definition_changes"}

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:
@ -838,14 +838,10 @@ class XmlMaterialProfile(InstanceContainer):
if machine_compatibility: if machine_compatibility:
new_material_id = container_id + "_" + machine_id new_material_id = container_id + "_" + machine_id
# The child or derived material container may already exist. This can happen when a material in a # Do not look for existing container/container metadata with the same ID although they may exist.
# project file and the a material in Cura have the same ID. # In project loading and perhaps some other places, we only want to get information (metadata)
# In the case if a derived material already exists, override that material container because if # from a file without changing the current state of the system. If we overwrite the existing
# the data in the parent material has been changed, the derived ones should be updated too. # metadata here, deserializeMetadata() will not be safe for retrieving information.
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_material_id)
if found_materials:
new_material_metadata = found_materials[0]
else:
new_material_metadata = {} new_material_metadata = {}
new_material_metadata.update(base_metadata) new_material_metadata.update(base_metadata)
@ -854,7 +850,6 @@ class XmlMaterialProfile(InstanceContainer):
new_material_metadata["machine_manufacturer"] = machine_manufacturer new_material_metadata["machine_manufacturer"] = machine_manufacturer
new_material_metadata["definition"] = machine_id new_material_metadata["definition"] = machine_id
if len(found_materials) == 0: #This is a new material.
result_metadata.append(new_material_metadata) result_metadata.append(new_material_metadata)
buildplates = machine.iterfind("./um:buildplate", cls.__namespaces) buildplates = machine.iterfind("./um:buildplate", cls.__namespaces)
@ -866,12 +861,12 @@ class XmlMaterialProfile(InstanceContainer):
if buildplate_id is None: if buildplate_id is None:
continue continue
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = buildplate_id) variant_metadata = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = buildplate_id)
if not variant_containers: if not variant_metadata:
# It is not really properly defined what "ID" is so also search for variants by name. # It is not really properly defined what "ID" is so also search for variants by name.
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = buildplate_id) variant_metadata = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = buildplate_id)
if not variant_containers: if not variant_metadata:
continue continue
settings = buildplate.iterfind("./um:setting", cls.__namespaces) settings = buildplate.iterfind("./um:setting", cls.__namespaces)
@ -900,11 +895,7 @@ class XmlMaterialProfile(InstanceContainer):
new_hotend_specific_material_id = container_id + "_" + machine_id + "_" + hotend_name.replace(" ", "_") new_hotend_specific_material_id = container_id + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
# Same as machine compatibility, keep the derived material containers consistent with the parent material # Same as above, do not overwrite existing metadata.
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_specific_material_id)
if found_materials:
new_hotend_material_metadata = found_materials[0]
else:
new_hotend_material_metadata = {} new_hotend_material_metadata = {}
new_hotend_material_metadata.update(base_metadata) new_hotend_material_metadata.update(base_metadata)
@ -917,7 +908,6 @@ class XmlMaterialProfile(InstanceContainer):
new_hotend_material_metadata["buildplate_compatible"] = buildplate_map["buildplate_compatible"] new_hotend_material_metadata["buildplate_compatible"] = buildplate_map["buildplate_compatible"]
new_hotend_material_metadata["buildplate_recommended"] = buildplate_map["buildplate_recommended"] new_hotend_material_metadata["buildplate_recommended"] = buildplate_map["buildplate_recommended"]
if len(found_materials) == 0:
result_metadata.append(new_hotend_material_metadata) result_metadata.append(new_hotend_material_metadata)
# there is only one ID for a machine. Once we have reached here, it means we have already found # there is only one ID for a machine. Once we have reached here, it means we have already found

View File

@ -215,7 +215,7 @@
{ {
"label": "Number of Extruders that are enabled", "label": "Number of Extruders that are enabled",
"description": "Number of extruder trains that are enabled; automatically set in software", "description": "Number of extruder trains that are enabled; automatically set in software",
"default_value": "machine_extruder_count", "value": "machine_extruder_count",
"minimum_value": "1", "minimum_value": "1",
"maximum_value": "16", "maximum_value": "16",
"type": "int", "type": "int",
@ -632,6 +632,73 @@
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false
}, },
"machine_steps_per_mm_x":
{
"label": "Steps per Millimeter (X)",
"description": "How many steps of the stepper motor will result in one millimeter of movement in the X direction.",
"type": "int",
"default_value": 50,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_steps_per_mm_y":
{
"label": "Steps per Millimeter (Y)",
"description": "How many steps of the stepper motor will result in one millimeter of movement in the Y direction.",
"type": "int",
"default_value": 50,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_steps_per_mm_z":
{
"label": "Steps per Millimeter (Z)",
"description": "How many steps of the stepper motor will result in one millimeter of movement in the Z direction.",
"type": "int",
"default_value": 50,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_steps_per_mm_e":
{
"label": "Steps per Millimeter (E)",
"description": "How many steps of the stepper motors will result in one millimeter of extrusion.",
"type": "int",
"default_value": 1600,
"minimum_value": "0.0000001",
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_endstop_positive_direction_x":
{
"label": "X Endstop in Positive Direction",
"description": "Whether the endstop of the X axis is in the positive direction (high X coordinate) or negative (low X coordinate).",
"type": "bool",
"default_value": false,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_endstop_positive_direction_y":
{
"label": "Y Endstop in Positive Direction",
"description": "Whether the endstop of the Y axis is in the positive direction (high Y coordinate) or negative (low Y coordinate).",
"type": "bool",
"default_value": false,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_endstop_positive_direction_z":
{
"label": "Z Endstop in Positive Direction",
"description": "Whether the endstop of the Z axis is in the positive direction (high Z coordinate) or negative (low Z coordinate).",
"type": "bool",
"default_value": true,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"machine_minimum_feedrate": "machine_minimum_feedrate":
{ {
"label": "Minimum Feedrate", "label": "Minimum Feedrate",
@ -642,6 +709,16 @@
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false
},
"machine_feeder_wheel_diameter":
{
"label": "Feeder Wheel Diameter",
"description": "The diameter of the wheel that drives the material in the feeder.",
"unit": "mm",
"type": "float",
"default_value": 10.0,
"settable_per_mesh": false,
"settable_per_extruder": true
} }
} }
}, },

View File

@ -25,8 +25,7 @@
"default_value": true "default_value": true
}, },
"machine_nozzle_size": { "machine_nozzle_size": {
"default_value": 0.4, "default_value": 0.4
"minimum_value": "0.001"
}, },
"machine_head_with_fans_polygon": { "machine_head_with_fans_polygon": {
"default_value": [ "default_value": [
@ -36,6 +35,21 @@
[ 18, 35 ] [ 18, 35 ]
] ]
}, },
"machine_max_feedrate_z": {
"default_value": 400
},
"machine_steps_per_mm_x": {
"default_value": 93
},
"machine_steps_per_mm_y": {
"default_value": 93
},
"machine_steps_per_mm_z": {
"default_value": 1600
},
"machine_steps_per_mm_e": {
"default_value": 92
},
"gantry_height": { "gantry_height": {
"default_value": 55 "default_value": 55
}, },

View File

@ -0,0 +1,38 @@
{
"version": 2,
"name": "Printrbot Simple Maker's Kit (1405)",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Timur Tabi",
"manufacturer": "Printrbot",
"file_formats": "text/x-gcode"
},
"overrides": {
"machine_name": { "default_value": "Printrbot Simple Maker's Kit (1405)" },
"machine_heated_bed": { "default_value": false },
"machine_width": { "default_value": 100 },
"machine_depth": { "default_value": 100 },
"machine_height": { "default_value": 115 },
"material_diameter": { "default_value": 1.75 },
"machine_nozzle_size": { "default_value": 0.4 },
"machine_head_with_fans_polygon": {
"default_value": [
[-40, 1000],
[-40, -10],
[60, 1000],
[60, -10]
]
},
"gantry_height": { "default_value": 1000 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
"machine_start_gcode": {
"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;home X/Y\nG28 Z0 ;home Z\nG92 E0 ;zero the extruded length\nG29 ;initiate auto bed leveling sequence"
},
"machine_end_gcode": {
"default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nM106 S0 ;fan off\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit\nG1 Z+1 E-5 F9000 ;move Z up a bit and retract even more\nG28 X0 Y0 ;home X/Y, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning"
}
}
}

View File

@ -126,7 +126,7 @@
"retraction_count_max": { "value": "10" }, "retraction_count_max": { "value": "10" },
"retraction_extrusion_window": { "value": "1" }, "retraction_extrusion_window": { "value": "1" },
"retraction_hop": { "value": "2" }, "retraction_hop": { "value": "2" },
"retraction_hop_enabled": { "value": "True" }, "retraction_hop_enabled": { "value": "extruders_enabled_count > 1" },
"retraction_hop_only_when_collides": { "value": "True" }, "retraction_hop_only_when_collides": { "value": "True" },
"retraction_min_travel": { "value": "5" }, "retraction_min_travel": { "value": "5" },
"retraction_prime_speed": { "value": "15" }, "retraction_prime_speed": { "value": "15" },
@ -150,7 +150,7 @@
"switch_extruder_prime_speed": { "value": "15" }, "switch_extruder_prime_speed": { "value": "15" },
"switch_extruder_retraction_amount": { "value": "8" }, "switch_extruder_retraction_amount": { "value": "8" },
"top_bottom_thickness": { "value": "1" }, "top_bottom_thickness": { "value": "1" },
"travel_avoid_distance": { "value": "3" }, "travel_avoid_distance": { "value": "3 if extruders_enabled_count > 1 else machine_nozzle_tip_outer_diameter / 2 * 1.5" },
"wall_0_inset": { "value": "0" }, "wall_0_inset": { "value": "0" },
"wall_line_width_x": { "value": "round(wall_line_width * 0.3 / 0.35, 2)" }, "wall_line_width_x": { "value": "round(wall_line_width * 0.3 / 0.35, 2)" },
"wall_thickness": { "value": "1" }, "wall_thickness": { "value": "1" },

View File

@ -653,9 +653,12 @@ UM.MainWindow
{ {
preferences.visible = true; preferences.visible = true;
preferences.setPage(1); preferences.setPage(1);
if(source && source.key)
{
preferences.getCurrentItem().scrollToSection(source.key); preferences.getCurrentItem().scrollToSection(source.key);
} }
} }
}
UM.ExtensionModel { UM.ExtensionModel {
id: curaExtensions id: curaExtensions

View File

@ -12,7 +12,11 @@ import "Menus"
ToolButton ToolButton
{ {
text: Cura.MachineManager.activeMachineName id: base
property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property var printerStatus: Cura.MachineManager.printerOutputDevices.length != 0 ? "connected" : "disconnected"
text: isNetworkPrinter ? Cura.MachineManager.activeMachineNetworkGroupName : Cura.MachineManager.activeMachineName
tooltip: Cura.MachineManager.activeMachineName tooltip: Cura.MachineManager.activeMachineName
@ -22,16 +26,13 @@ ToolButton
{ {
color: color:
{ {
if(control.pressed) if (control.pressed) {
{
return UM.Theme.getColor("sidebar_header_active"); return UM.Theme.getColor("sidebar_header_active");
} }
else if(control.hovered) else if (control.hovered) {
{
return UM.Theme.getColor("sidebar_header_hover"); return UM.Theme.getColor("sidebar_header_hover");
} }
else else {
{
return UM.Theme.getColor("sidebar_header_bar"); return UM.Theme.getColor("sidebar_header_bar");
} }
} }
@ -50,18 +51,32 @@ ToolButton
color: UM.Theme.getColor("text_emphasis") color: UM.Theme.getColor("text_emphasis")
source: UM.Theme.getIcon("arrow_bottom") source: UM.Theme.getIcon("arrow_bottom")
} }
PrinterStatusIcon
{
id: printerStatusIcon
visible: printerConnected || isNetworkPrinter
status: printerStatus
anchors
{
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: UM.Theme.getSize("sidebar_margin").width
}
}
Label Label
{ {
id: sidebarComboBoxLabel id: sidebarComboBoxLabel
color: UM.Theme.getColor("sidebar_header_text_active") color: UM.Theme.getColor("sidebar_header_text_active")
text: control.text; text: control.text;
elide: Text.ElideRight; elide: Text.ElideRight;
anchors.left: parent.left; anchors.left: isNetworkPrinter ? printerStatusIcon.right : parent.left;
anchors.leftMargin: UM.Theme.getSize("default_margin").width * 2 anchors.leftMargin: isNetworkPrinter ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width
anchors.right: downArrow.left; anchors.right: downArrow.left;
anchors.rightMargin: control.rightMargin; anchors.rightMargin: control.rightMargin;
anchors.verticalCenter: parent.verticalCenter; anchors.verticalCenter: parent.verticalCenter;
font: UM.Theme.getFont("large") font: UM.Theme.getFont("medium_bold")
} }
} }
label: Label {} label: Label {}

View File

@ -0,0 +1,124 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.0
import UM 1.2 as UM
import Cura 1.0 as Cura
Rectangle
{
id: configurationItem
property var configuration: null
property var selected: false
signal activateConfiguration()
height: childrenRect.height
border.width: UM.Theme.getSize("default_lining").width
border.color: updateBorderColor()
color: selected ? UM.Theme.getColor("configuration_item_active") : UM.Theme.getColor("configuration_item")
property var textColor: selected ? UM.Theme.getColor("configuration_item_text_active") : UM.Theme.getColor("configuration_item_text")
function updateBorderColor()
{
border.color = selected ? UM.Theme.getColor("configuration_item_border_active") : UM.Theme.getColor("configuration_item_border")
}
Column
{
id: contentColumn
width: parent.width
padding: UM.Theme.getSize("default_margin").width
spacing: Math.round(UM.Theme.getSize("default_margin").height / 2)
Row
{
id: extruderRow
width: parent.width - 2 * parent.padding
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width
Repeater
{
id: repeater
height: childrenRect.height
model: configuration.extruderConfigurations
delegate: PrintCoreConfiguration
{
width: Math.round(parent.width / 2)
printCoreConfiguration: modelData
mainColor: textColor
}
}
}
//Buildplate row separator
Rectangle
{
id: separator
visible: buildplateInformation.visible
width: parent.width - 2 * parent.padding
height: visible ? Math.round(UM.Theme.getSize("sidebar_lining_thin").height / 2) : 0
color: textColor
}
Item
{
id: buildplateInformation
width: parent.width - 2 * parent.padding
height: childrenRect.height
visible: configuration.buildplateConfiguration != ""
UM.RecolorImage {
id: buildplateIcon
anchors.left: parent.left
width: UM.Theme.getSize("topbar_button_icon").width
height: UM.Theme.getSize("topbar_button_icon").height
sourceSize.width: width
sourceSize.height: height
source: UM.Theme.getIcon("buildplate")
color: textColor
}
Label
{
id: buildplateLabel
anchors.left: buildplateIcon.right
anchors.verticalCenter: buildplateIcon.verticalCenter
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").height / 2)
text: configuration.buildplateConfiguration
color: textColor
}
}
}
MouseArea
{
id: mouse
anchors.fill: parent
onClicked: activateConfiguration()
hoverEnabled: true
onEntered: parent.border.color = UM.Theme.getColor("configuration_item_border_hover")
onExited: updateBorderColor()
}
Connections
{
target: Cura.MachineManager
onCurrentConfigurationChanged: {
configurationItem.selected = Cura.MachineManager.matchesConfiguration(configuration)
updateBorderColor()
}
}
Component.onCompleted:
{
configurationItem.selected = Cura.MachineManager.matchesConfiguration(configuration)
updateBorderColor()
}
}

View File

@ -0,0 +1,86 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
import Cura 1.0 as Cura
Column
{
id: base
property var outputDevice: null
property var computedHeight: container.height + configurationListHeading.height + 3 * padding
height: childrenRect.height + 2 * padding
padding: UM.Theme.getSize("default_margin").width
spacing: Math.round(UM.Theme.getSize("default_margin").height / 2)
Label
{
id: configurationListHeading
text: catalog.i18nc("@label:header configurations", "Available configurations")
font: UM.Theme.getFont("large")
width: parent.width - 2 * parent.padding
}
Component
{
id: sectionHeading
Rectangle
{
height: childrenRect.height + UM.Theme.getSize("default_margin").height
Label
{
text: section
font: UM.Theme.getFont("default_bold")
}
}
}
ScrollView
{
id: container
width: parent.width - parent.padding
height: Math.min(configurationList.contentHeight, 350 * screenScaleFactor)
style: UM.Theme.styles.scrollview
__wheelAreaScrollSpeed: 75 // Scroll three lines in one scroll event
ListView
{
id: configurationList
spacing: Math.round(UM.Theme.getSize("default_margin").height / 2)
width: container.width
contentHeight: childrenRect.height
section.property: "modelData.printerType"
section.criteria: ViewSection.FullString
section.delegate: sectionHeading
model: (outputDevice != null) ? outputDevice.uniqueConfigurations : []
delegate: ConfigurationItem
{
width: parent.width - UM.Theme.getSize("default_margin").width
configuration: modelData
onActivateConfiguration:
{
switchPopupState()
Cura.MachineManager.applyRemoteConfiguration(configuration)
}
}
}
}
Connections
{
target: outputDevice
onUniqueConfigurationsChanged:
{
// FIXME For now the model should be removed and then created again, otherwise changes in the printer don't automatically update the UI
configurationList.model = []
configurationList.model = outputDevice.uniqueConfigurations
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
import Cura 1.0 as Cura
Item
{
id: configurationSelector
property var connectedDevice: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
property var panelWidth: control.width
function switchPopupState()
{
popup.visible ? popup.close() : popup.open()
}
SyncButton
{
id: syncButton
onClicked: switchPopupState()
outputDevice: connectedDevice
}
Popup
{
// TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
id: popup
clip: true
closePolicy: Popup.CloseOnPressOutsideParent
y: configurationSelector.height - UM.Theme.getSize("default_lining").height
x: configurationSelector.width - width
width: panelWidth
visible: false
padding: UM.Theme.getSize("default_lining").width
transformOrigin: Popup.Top
contentItem: ConfigurationListView
{
id: configList
width: panelWidth - 2 * popup.padding
outputDevice: connectedDevice
}
background: Rectangle
{
color: UM.Theme.getColor("setting_control")
border.color: UM.Theme.getColor("setting_control_border")
}
exit: Transition
{
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { property: "visible"; duration: 75; }
}
enter: Transition
{
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { property: "visible"; duration: 75; }
}
onClosed: visible = false
onOpened: visible = true
}
}

View File

@ -0,0 +1,87 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 2.0
import UM 1.2 as UM
Column
{
id: extruderInfo
property var printCoreConfiguration
property var mainColor: "black"
spacing: Math.round(UM.Theme.getSize("default_margin").height / 2)
height: childrenRect.height
Item
{
id: extruder
width: parent.width
height: childrenRect.height
Label
{
id: extruderLabel
text: catalog.i18nc("@label:extruder label", "Extruder")
elide: Text.ElideRight
anchors.left: parent.left
font: UM.Theme.getFont("default")
color: mainColor
}
// Rounded item to show the extruder number
Item
{
id: extruderIconItem
anchors.verticalCenter: extruderLabel.verticalCenter
anchors.left: extruderLabel.right
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
width: UM.Theme.getSize("section_icon").width
height: UM.Theme.getSize("section_icon").height
UM.RecolorImage {
id: mainCircle
anchors.fill: parent
anchors.centerIn: parent
sourceSize.width: parent.width
sourceSize.height: parent.height
source: UM.Theme.getIcon("extruder_button")
color: mainColor
}
Label
{
id: extruderNumberText
anchors.centerIn: parent
text: printCoreConfiguration.position + 1
font: UM.Theme.getFont("default")
color: mainColor
}
}
}
Label
{
id: materialLabel
text: printCoreConfiguration.material.name
elide: Text.ElideRight
width: parent.width
font: UM.Theme.getFont("default_bold")
color: mainColor
}
Label
{
id: printCoreTypeLabel
text: printCoreConfiguration.hotendID
elide: Text.ElideRight
width: parent.width
font: UM.Theme.getFont("default")
color: mainColor
}
}

View File

@ -0,0 +1,102 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
import Cura 1.0 as Cura
Button
{
id: base
property var outputDevice: null
property var matched: updateOnSync()
text: matched == true ? "Yes" : "No"
width: parent.width
height: parent.height
function updateOnSync()
{
if (outputDevice != undefined)
{
for (var index in outputDevice.uniqueConfigurations)
{
var configuration = outputDevice.uniqueConfigurations[index]
if (Cura.MachineManager.matchesConfiguration(configuration))
{
base.matched = true;
return;
}
}
}
base.matched = false;
}
style: ButtonStyle
{
background: Rectangle
{
color:
{
if(control.pressed)
{
return UM.Theme.getColor("sidebar_header_active");
}
else if(control.hovered)
{
return UM.Theme.getColor("sidebar_header_hover");
}
else
{
return UM.Theme.getColor("sidebar_header_bar");
}
}
Behavior on color { ColorAnimation { duration: 50; } }
UM.RecolorImage
{
id: downArrow
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width
sourceSize.height: height
color: UM.Theme.getColor("text_emphasis")
source: UM.Theme.getIcon("arrow_bottom")
}
UM.RecolorImage {
id: sidebarComboBoxLabel
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.verticalCenter;
width: UM.Theme.getSize("printer_sync_icon").width
height: UM.Theme.getSize("printer_sync_icon").height
color: control.matched ? UM.Theme.getColor("printer_config_matched") : UM.Theme.getColor("printer_config_mismatch")
source: UM.Theme.getIcon("tab_status_connected")
sourceSize.width: width
sourceSize.height: height
}
}
label: Label {}
}
Connections {
target: outputDevice
onUniqueConfigurationsChanged: {
updateOnSync()
}
}
Connections {
target: Cura.MachineManager
onCurrentConfigurationChanged: {
updateOnSync()
}
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import UM 1.2 as UM
import Cura 1.0 as Cura
Instantiator {
model: UM.ContainerStacksModel {
filter: {"type": "machine", "um_network_key": null}
}
MenuItem {
text: model.name;
checkable: true;
checked: Cura.MachineManager.activeMachineId == model.id
exclusiveGroup: group;
onTriggered: Cura.MachineManager.setActiveMachine(model.id);
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}

View File

@ -0,0 +1,25 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import UM 1.2 as UM
import Cura 1.0 as Cura
Instantiator {
model: UM.ContainerStacksModel {
filter: {"type": "machine", "um_network_key": "*", "hidden": "False"}
}
MenuItem {
// TODO: Use printer_group icon when it's a cluster. Not use it for now since it doesn't look as expected
// iconSource: UM.Theme.getIcon("printer_single")
text: model.metadata["connect_group_name"]
checkable: true;
checked: Cura.MachineManager.activeMachineNetworkGroupName == model.metadata["connect_group_name"]
exclusiveGroup: group;
onTriggered: Cura.MachineManager.setActiveMachine(model.id);
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}

View File

@ -1,37 +1,60 @@
// Copyright (c) 2016 Ultimaker B.V. // Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2 import QtQuick 2.2
import QtQuick.Controls 1.1 import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
Menu Menu
{ {
id: menu; id: menu
// TODO Enable custom style to the menu
// style: MenuStyle
// {
// frame: Rectangle
// {
// color: "white"
// }
// }
Instantiator
{
model: UM.ContainerStacksModel
{
filter: {"type": "machine"}
}
MenuItem MenuItem
{ {
text: model.name; text: catalog.i18nc("@label:category menu label", "Network enabled printers")
checkable: true; enabled: false
checked: Cura.MachineManager.activeMachineId == model.id visible: networkPrinterMenu.count > 0
exclusiveGroup: group;
onTriggered: Cura.MachineManager.setActiveMachine(model.id);
} }
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object) NetworkPrinterMenu
{
id: networkPrinterMenu
}
MenuSeparator
{
visible: networkPrinterMenu.count > 0
}
MenuItem
{
text: catalog.i18nc("@label:category menu label", "Local printers")
enabled: false
visible: localPrinterMenu.count > 0
}
LocalPrinterMenu
{
id: localPrinterMenu
} }
ExclusiveGroup { id: group; } ExclusiveGroup { id: group; }
MenuSeparator { } MenuSeparator
{
visible: localPrinterMenu.count > 0
}
MenuItem { action: Cura.Actions.addMachine; } MenuItem { action: Cura.Actions.addMachine; }
MenuItem { action: Cura.Actions.configureMachines; } MenuItem { action: Cura.Actions.configureMachines; }

View File

@ -0,0 +1,27 @@
// Copyright (c) 2017 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import UM 1.2 as UM
import Cura 1.0 as Cura
Item
{
property var status: "disconnected"
width: childrenRect.width
height: childrenRect.height
UM.RecolorImage
{
id: statusIcon
width: UM.Theme.getSize("printer_status_icon").width
height: UM.Theme.getSize("printer_status_icon").height
sourceSize.width: width
sourceSize.height: width
color: UM.Theme.getColor("tab_status_" + parent.status)
source: UM.Theme.getIcon(parent.status)
}
}

View File

@ -0,0 +1,37 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.4
import UM 1.3 as UM
import Cura 1.0 as Cura
Menu
{
id: menu
title: "Printer type"
property var outputDevice: Cura.MachineManager.printerOutputDevices[0]
Instantiator
{
id: printerTypeInstantiator
model: outputDevice != null ? outputDevice.connectedPrintersTypeCount : []
MenuItem
{
text: modelData.machine_type
checkable: true
checked: Cura.MachineManager.activeMachineDefinitionName == modelData.machine_type
exclusiveGroup: group
onTriggered:
{
Cura.MachineManager.switchPrinterType(modelData.machine_type)
}
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
ExclusiveGroup { id: group }
}

View File

@ -0,0 +1,82 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import UM 1.2 as UM
import Cura 1.0 as Cura
Menu
{
id: menu
title: catalog.i18nc("@action:inmenu", "Visible Settings")
property bool showingSearchResults
property bool showingAllSettings
signal showAllSettings()
signal showSettingVisibilityProfile()
MenuItem
{
text: catalog.i18nc("@action:inmenu", "Custom selection")
checkable: true
checked: !showingSearchResults && !showingAllSettings && Cura.SettingVisibilityPresetsModel.activePreset == "custom"
exclusiveGroup: group
onTriggered:
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
// Restore custom set from preference
UM.Preferences.setValue("general/visible_settings", UM.Preferences.getValue("cura/custom_visible_settings"));
showSettingVisibilityProfile();
}
}
MenuSeparator { }
Instantiator
{
model: Cura.SettingVisibilityPresetsModel
MenuItem
{
text: model.name
checkable: true
checked: model.id == Cura.SettingVisibilityPresetsModel.activePreset
exclusiveGroup: group
onTriggered:
{
Cura.SettingVisibilityPresetsModel.setActivePreset(model.id);
UM.Preferences.setValue("general/visible_settings", model.settings.join(";"));
showSettingVisibilityProfile();
}
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
MenuSeparator {}
MenuItem
{
text: catalog.i18nc("@action:inmenu", "All Settings")
checkable: true
checked: showingAllSettings
exclusiveGroup: group
onTriggered:
{
showAllSettings();
}
}
MenuSeparator {}
MenuItem
{
text: catalog.i18nc("@action:inmenu", "Manage Setting Visibility...")
iconName: "configure"
onTriggered: Cura.Actions.configureSettingVisibility.trigger()
}
ExclusiveGroup { id: group }
}

View File

@ -14,10 +14,7 @@ UM.ManagementPage
id: base; id: base;
title: catalog.i18nc("@title:tab", "Printers"); title: catalog.i18nc("@title:tab", "Printers");
model: UM.ContainerStacksModel model: Cura.MachineManagementModel { }
{
filter: {"type": "machine"}
}
activeId: Cura.MachineManager.activeMachineId activeId: Cura.MachineManager.activeMachineId
activeIndex: activeMachineIndex() activeIndex: activeMachineIndex()
@ -57,7 +54,7 @@ UM.ManagementPage
{ {
text: catalog.i18nc("@action:button", "Rename"); text: catalog.i18nc("@action:button", "Rename");
iconName: "edit-rename"; iconName: "edit-rename";
enabled: base.currentItem != null enabled: base.currentItem != null && base.currentItem.metadata.connect_group_name == null
onClicked: renameDialog.open(); onClicked: renameDialog.open();
} }
] ]

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

@ -26,8 +26,8 @@ UM.PreferencesPage
UM.Preferences.resetPreference("general/visible_settings") UM.Preferences.resetPreference("general/visible_settings")
// After calling this function update Setting visibility preset combobox. // After calling this function update Setting visibility preset combobox.
// Reset should set "Basic" setting preset // Reset should set default setting preset ("Basic")
visibilityPreset.setBasicPreset() visibilityPreset.setDefaultPreset()
} }
resetEnabled: true; resetEnabled: true;
@ -37,6 +37,8 @@ UM.PreferencesPage
id: base; id: base;
anchors.fill: parent; anchors.fill: parent;
property bool inhibitSwitchToCustom: false
CheckBox CheckBox
{ {
id: toggleVisibleSettings id: toggleVisibleSettings
@ -84,7 +86,7 @@ UM.PreferencesPage
if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1) if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1)
{ {
visibilityPreset.currentIndex = visibilityPreset.model.count - 1 visibilityPreset.currentIndex = visibilityPreset.model.count - 1
UM.Preferences.setValue("general/preset_setting_visibility_choice", visibilityPreset.model.get(visibilityPreset.currentIndex).text) UM.Preferences.setValue("cura/active_setting_visibility_preset", visibilityPreset.model.getItem(visibilityPreset.currentIndex).id)
} }
} }
} }
@ -110,25 +112,13 @@ UM.PreferencesPage
ComboBox ComboBox
{ {
property int customOptionValue: 100 function setDefaultPreset()
function setBasicPreset()
{ {
var index = 0 visibilityPreset.currentIndex = 0
for(var i = 0; i < presetNamesList.count; ++i)
{
if(model.get(i).text == "Basic")
{
index = i;
break;
}
}
visibilityPreset.currentIndex = index
} }
id: visibilityPreset id: visibilityPreset
width: 150 width: 150 * screenScaleFactor
anchors anchors
{ {
top: parent.top top: parent.top
@ -137,56 +127,49 @@ UM.PreferencesPage
model: ListModel model: ListModel
{ {
id: presetNamesList id: visibilityPresetsModel
Component.onCompleted: Component.onCompleted:
{ {
// returned value is Dictionary (Ex: {1:"Basic"}, The number 1 is the weight and sort by weight) visibilityPresetsModel.append({text: catalog.i18nc("@action:inmenu", "Custom selection"), id: "custom"});
var itemsDict = UM.Preferences.getValue("general/visible_settings_preset")
var sorted = [];
for(var key in itemsDict) {
sorted[sorted.length] = key;
}
sorted.sort(); var presets = Cura.SettingVisibilityPresetsModel;
for(var i = 0; i < sorted.length; i++) { for(var i = 0; i < presets.rowCount(); i++)
presetNamesList.append({text: itemsDict[sorted[i]], value: i}); {
visibilityPresetsModel.append({text: presets.getItem(i)["name"], id: presets.getItem(i)["id"]});
} }
// By agreement lets "Custom" option will have value 100
presetNamesList.append({text: "Custom", value: visibilityPreset.customOptionValue});
} }
} }
currentIndex: currentIndex:
{ {
// Load previously selected preset. // Load previously selected preset.
var text = UM.Preferences.getValue("general/preset_setting_visibility_choice"); var index = Cura.SettingVisibilityPresetsModel.find("id", Cura.SettingVisibilityPresetsModel.activePreset);
if(index == -1)
var index = 0;
for(var i = 0; i < presetNamesList.count; ++i)
{ {
if(model.get(i).text == text) return 0;
{
index = i;
break;
} }
}
return index; return index + 1; // "Custom selection" entry is added in front, so index is off by 1
} }
onActivated: onActivated:
{ {
// TODO What to do if user is selected "Custom from Combobox" ? base.inhibitSwitchToCustom = true;
if (model.get(index).text == "Custom"){ var preset_id = visibilityPresetsModel.get(index).id;
UM.Preferences.setValue("general/preset_setting_visibility_choice", model.get(index).text) Cura.SettingVisibilityPresetsModel.setActivePreset(preset_id);
return
}
var newVisibleSettings = CuraApplication.getVisibilitySettingPreset(model.get(index).text) UM.Preferences.setValue("cura/active_setting_visibility_preset", preset_id);
UM.Preferences.setValue("general/visible_settings", newVisibleSettings) if (preset_id != "custom")
UM.Preferences.setValue("general/preset_setting_visibility_choice", model.get(index).text) {
UM.Preferences.setValue("general/visible_settings", Cura.SettingVisibilityPresetsModel.getItem(index - 1).settings.join(";"));
// "Custom selection" entry is added in front, so index is off by 1
}
else
{
// Restore custom set from preference
UM.Preferences.setValue("general/visible_settings", UM.Preferences.getValue("cura/custom_visible_settings"));
}
base.inhibitSwitchToCustom = false;
} }
} }
@ -216,7 +199,16 @@ UM.PreferencesPage
exclude: ["machine_settings", "command_line_settings"] exclude: ["machine_settings", "command_line_settings"]
showAncestors: true showAncestors: true
expanded: ["*"] expanded: ["*"]
visibilityHandler: UM.SettingPreferenceVisibilityHandler { } visibilityHandler: UM.SettingPreferenceVisibilityHandler
{
onVisibilityChanged:
{
if(Cura.SettingVisibilityPresetsModel.activePreset != "" && !base.inhibitSwitchToCustom)
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
}
}
}
} }
delegate: Loader delegate: Loader
@ -259,19 +251,7 @@ UM.PreferencesPage
{ {
id: settingVisibilityItem; id: settingVisibilityItem;
UM.SettingVisibilityItem { UM.SettingVisibilityItem { }
// after changing any visibility of settings, set the preset to the "Custom" option
visibilityChangeCallback : function()
{
// If already "Custom" then don't do nothing
if (visibilityPreset.currentIndex != visibilityPreset.model.count - 1)
{
visibilityPreset.currentIndex = visibilityPreset.model.count - 1
UM.Preferences.setValue("general/preset_setting_visibility_choice", visibilityPreset.model.get(visibilityPreset.currentIndex).text)
}
}
}
} }
} }
} }

View File

@ -13,12 +13,18 @@ SettingItem
property string textBeforeEdit property string textBeforeEdit
property bool textHasChanged property bool textHasChanged
property bool focusGainedByClick: false
onFocusReceived: onFocusReceived:
{ {
textHasChanged = false; textHasChanged = false;
textBeforeEdit = focusItem.text; textBeforeEdit = focusItem.text;
if(!focusGainedByClick)
{
// select all text when tabbing through fields (but not when selecting a field with the mouse)
focusItem.selectAll(); focusItem.selectAll();
} }
}
contents: Rectangle contents: Rectangle
{ {
@ -93,14 +99,6 @@ SettingItem
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
} }
MouseArea
{
id: mouseArea
anchors.fill: parent;
//hoverEnabled: true;
cursorShape: Qt.IBeamCursor
}
TextInput TextInput
{ {
id: input id: input
@ -142,6 +140,7 @@ SettingItem
{ {
base.focusReceived(); base.focusReceived();
} }
base.focusGainedByClick = false;
} }
color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text") color: !enabled ? UM.Theme.getColor("setting_control_disabled_text") : UM.Theme.getColor("setting_control_text")
@ -178,6 +177,22 @@ SettingItem
} }
when: !input.activeFocus when: !input.activeFocus
} }
MouseArea
{
id: mouseArea
anchors.fill: parent;
cursorShape: Qt.IBeamCursor
onPressed: {
if(!input.activeFocus) {
base.focusGainedByClick = true;
input.forceActiveFocus();
}
mouse.accepted = false;
}
}
} }
} }
} }

View File

@ -15,10 +15,11 @@ Item
{ {
id: base; id: base;
property Action configureSettings; property Action configureSettings
property bool findingSettings; property bool findingSettings
signal showTooltip(Item item, point location, string text); property bool showingAllSettings
signal hideTooltip(); signal showTooltip(Item item, point location, string text)
signal hideTooltip()
Item Item
{ {
@ -107,6 +108,57 @@ Item
} }
} }
ToolButton
{
id: settingVisibilityMenu
width: height
height: UM.Theme.getSize("setting_control").height
anchors
{
top: globalProfileRow.bottom
topMargin: UM.Theme.getSize("sidebar_margin").height
right: parent.right
rightMargin: UM.Theme.getSize("sidebar_margin").width
}
style: ButtonStyle
{
background: Item {
UM.RecolorImage {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
width: UM.Theme.getSize("standard_arrow").width
height: UM.Theme.getSize("standard_arrow").height
sourceSize.width: width
sourceSize.height: width
color: control.enabled ? UM.Theme.getColor("setting_category_text") : UM.Theme.getColor("setting_category_disabled_text")
source: UM.Theme.getIcon("menu")
}
}
label: Label{}
}
menu: SettingVisibilityPresetsMenu
{
showingSearchResults: findingSettings
showingAllSettings: showingAllSettings
onShowAllSettings:
{
base.showingAllSettings = true;
base.findingSettings = false;
filter.text = "";
filter.updateDefinitionModel();
}
onShowSettingVisibilityProfile:
{
base.showingAllSettings = false;
base.findingSettings = false;
filter.text = "";
filter.updateDefinitionModel();
}
}
}
Rectangle Rectangle
{ {
id: filterContainer id: filterContainer
@ -132,9 +184,9 @@ Item
top: globalProfileRow.bottom top: globalProfileRow.bottom
topMargin: UM.Theme.getSize("sidebar_margin").height topMargin: UM.Theme.getSize("sidebar_margin").height
left: parent.left left: parent.left
leftMargin: Math.round(UM.Theme.getSize("sidebar_margin").width) leftMargin: UM.Theme.getSize("sidebar_margin").width
right: parent.right right: settingVisibilityMenu.left
rightMargin: Math.round(UM.Theme.getSize("sidebar_margin").width) rightMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
} }
height: visible ? UM.Theme.getSize("setting_control").height : 0 height: visible ? UM.Theme.getSize("setting_control").height : 0
Behavior on height { NumberAnimation { duration: 100 } } Behavior on height { NumberAnimation { duration: 100 } }
@ -168,17 +220,9 @@ Item
{ {
if(findingSettings) if(findingSettings)
{ {
expandedCategories = definitionsModel.expanded.slice(); showingAllSettings = false;
definitionsModel.expanded = ["*"];
definitionsModel.showAncestors = true;
definitionsModel.showAll = true;
}
else
{
definitionsModel.expanded = expandedCategories;
definitionsModel.showAncestors = false;
definitionsModel.showAll = false;
} }
updateDefinitionModel();
lastFindingSettings = findingSettings; lastFindingSettings = findingSettings;
} }
} }
@ -187,6 +231,27 @@ Item
{ {
filter.text = ""; filter.text = "";
} }
function updateDefinitionModel()
{
if(findingSettings || showingAllSettings)
{
expandedCategories = definitionsModel.expanded.slice();
definitionsModel.expanded = [""]; // keep categories closed while to prevent render while making settings visible one by one
definitionsModel.showAncestors = true;
definitionsModel.showAll = true;
definitionsModel.expanded = ["*"];
}
else
{
if(expandedCategories)
{
definitionsModel.expanded = expandedCategories;
}
definitionsModel.showAncestors = false;
definitionsModel.showAll = false;
}
}
} }
MouseArea MouseArea
@ -209,7 +274,7 @@ Item
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Math.round(UM.Theme.getSize("sidebar_margin").width) anchors.rightMargin: UM.Theme.getSize("default_margin").width
color: UM.Theme.getColor("setting_control_button") color: UM.Theme.getColor("setting_control_button")
hoverColor: UM.Theme.getColor("setting_control_button_hover") hoverColor: UM.Theme.getColor("setting_control_button_hover")
@ -374,8 +439,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
@ -493,9 +556,17 @@ Item
MenuItem MenuItem
{ {
//: Settings context menu action //: Settings context menu action
visible: !findingSettings; visible: !(findingSettings || showingAllSettings);
text: catalog.i18nc("@action:menu", "Hide this setting"); text: catalog.i18nc("@action:menu", "Hide this setting");
onTriggered: definitionsModel.hide(contextMenu.key); onTriggered:
{
definitionsModel.hide(contextMenu.key);
// visible settings have changed, so we're no longer showing a preset
if (Cura.SettingVisibilityPresetsModel.activePreset != "" && !showingAllSettings)
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
}
}
} }
MenuItem MenuItem
{ {
@ -511,7 +582,7 @@ Item
return catalog.i18nc("@action:menu", "Keep this setting visible"); return catalog.i18nc("@action:menu", "Keep this setting visible");
} }
} }
visible: findingSettings; visible: (findingSettings || showingAllSettings);
onTriggered: onTriggered:
{ {
if (contextMenu.settingVisible) if (contextMenu.settingVisible)
@ -522,6 +593,11 @@ Item
{ {
definitionsModel.show(contextMenu.key); definitionsModel.show(contextMenu.key);
} }
// visible settings have changed, so we're no longer showing a preset
if (Cura.SettingVisibilityPresetsModel.activePreset != "" && !showingAllSettings)
{
Cura.SettingVisibilityPresetsModel.setActivePreset("custom");
}
} }
} }
MenuItem MenuItem

View File

@ -8,6 +8,7 @@ import QtQuick.Layouts 1.3
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.0 as Cura
import "Menus" import "Menus"
import "Menus/ConfigurationMenu"
Rectangle Rectangle
{ {
@ -18,6 +19,7 @@ Rectangle
property bool hideView: Cura.MachineManager.activeMachineName == "" property bool hideView: Cura.MachineManager.activeMachineName == ""
// Is there an output device for this printer? // Is there an output device for this printer?
property bool isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null property var connectedPrinter: Cura.MachineManager.printerOutputDevices.length >= 1 ? Cura.MachineManager.printerOutputDevices[0] : null
@ -85,12 +87,34 @@ Rectangle
} }
} }
MachineSelection { MachineSelection
{
id: machineSelection id: machineSelection
width: base.width width: base.width - configSelection.width - separator.width
height: UM.Theme.getSize("sidebar_header").height
anchors.top: base.top
anchors.left: parent.left
}
Rectangle
{
id: separator
visible: configSelection.visible
width: visible ? Math.round(UM.Theme.getSize("sidebar_lining_thin").height / 2) : 0
height: UM.Theme.getSize("sidebar_header").height
color: UM.Theme.getColor("sidebar_lining_thin")
anchors.left: machineSelection.right
}
ConfigurationSelection
{
id: configSelection
visible: isNetworkPrinter
width: visible ? Math.round(base.width * 0.15) : 0
height: UM.Theme.getSize("sidebar_header").height height: UM.Theme.getSize("sidebar_header").height
anchors.top: base.top anchors.top: base.top
anchors.right: parent.right anchors.right: parent.right
panelWidth: base.width
} }
SidebarHeader { SidebarHeader {

View File

@ -16,6 +16,8 @@ Column
property int currentExtruderIndex: Cura.ExtruderManager.activeExtruderIndex; property int currentExtruderIndex: Cura.ExtruderManager.activeExtruderIndex;
property bool currentExtruderVisible: extrudersList.visible; property bool currentExtruderVisible: extrudersList.visible;
property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0
property bool hasManyPrinterTypes: printerConnected ? Cura.MachineManager.printerOutputDevices[0].connectedPrintersTypeCount.length > 1 : false
spacing: Math.round(UM.Theme.getSize("sidebar_margin").width * 0.9) spacing: Math.round(UM.Theme.getSize("sidebar_margin").width * 0.9)
@ -24,16 +26,66 @@ Column
Item Item
{ {
id: initialSeparator
anchors anchors
{ {
left: parent.left left: parent.left
right: parent.right right: parent.right
} }
visible: extruderSelectionRow.visible visible: printerTypeSelectionRow.visible || buildplateRow.visible || extruderSelectionRow.visible
height: UM.Theme.getSize("default_lining").height height: UM.Theme.getSize("default_lining").height
width: height width: height
} }
// Printer Type Row
Item
{
id: printerTypeSelectionRow
height: UM.Theme.getSize("sidebar_setup").height
visible: printerConnected && hasManyPrinterTypes && !sidebar.monitoringPrint && !sidebar.hideSettings
anchors
{
left: parent.left
leftMargin: UM.Theme.getSize("sidebar_margin").width
right: parent.right
rightMargin: UM.Theme.getSize("sidebar_margin").width
}
Label
{
id: configurationLabel
text: catalog.i18nc("@label", "Printer type");
width: Math.round(parent.width * 0.4 - UM.Theme.getSize("default_margin").width)
height: parent.height
verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text");
}
ToolButton
{
id: printerTypeSelection
text: Cura.MachineManager.activeMachineDefinitionName
tooltip: Cura.MachineManager.activeMachineDefinitionName
height: UM.Theme.getSize("setting_control").height
width: Math.round(parent.width * 0.7) + UM.Theme.getSize("sidebar_margin").width
anchors.right: parent.right
style: UM.Theme.styles.sidebar_header_button
activeFocusOnPress: true;
menu: PrinterTypeMenu { }
}
}
Rectangle {
id: headerSeparator
width: parent.width
visible: printerTypeSelectionRow.visible
height: visible ? UM.Theme.getSize("sidebar_lining").height : 0
color: UM.Theme.getColor("sidebar_lining")
}
// Extruder Row // Extruder Row
Item Item
{ {
@ -91,6 +143,8 @@ Column
exclusiveGroup: extruderMenuGroup exclusiveGroup: extruderMenuGroup
checked: base.currentExtruderIndex == index checked: base.currentExtruderIndex == index
property bool extruder_enabled: true
MouseArea MouseArea
{ {
anchors.fill: parent anchors.fill: parent
@ -102,6 +156,7 @@ Column
Cura.ExtruderManager.setActiveExtruderIndex(index); Cura.ExtruderManager.setActiveExtruderIndex(index);
break; break;
case Qt.RightButton: case Qt.RightButton:
extruder_enabled = Cura.MachineManager.getExtruder(model.index).isEnabled
extruderMenu.popup(); extruderMenu.popup();
break; break;
} }
@ -116,13 +171,13 @@ Column
MenuItem { MenuItem {
text: catalog.i18nc("@action:inmenu", "Enable Extruder") text: catalog.i18nc("@action:inmenu", "Enable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, true) onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, true)
visible: !Cura.MachineManager.getExtruder(model.index).isEnabled visible: !extruder_enabled // using an intermediate variable prevents an empty popup that occured now and then
} }
MenuItem { MenuItem {
text: catalog.i18nc("@action:inmenu", "Disable Extruder") text: catalog.i18nc("@action:inmenu", "Disable Extruder")
onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false) onTriggered: Cura.MachineManager.setExtruderEnabled(model.index, false)
visible: Cura.MachineManager.getExtruder(model.index).isEnabled visible: extruder_enabled
} }
} }
@ -258,7 +313,7 @@ Column
id: variantRowSpacer id: variantRowSpacer
height: Math.round(UM.Theme.getSize("sidebar_margin").height / 4) height: Math.round(UM.Theme.getSize("sidebar_margin").height / 4)
width: height width: height
visible: !extruderSelectionRow.visible visible: !extruderSelectionRow.visible && !initialSeparator.visible
} }
// Material Row // Material Row
@ -281,6 +336,8 @@ Column
id: materialLabel id: materialLabel
text: catalog.i18nc("@label", "Material"); text: catalog.i18nc("@label", "Material");
width: Math.round(parent.width * 0.45 - UM.Theme.getSize("default_margin").width) width: Math.round(parent.width * 0.45 - UM.Theme.getSize("default_margin").width)
height: parent.height
verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default"); font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text"); color: UM.Theme.getColor("text");
} }
@ -289,15 +346,9 @@ Column
{ {
id: materialSelection id: materialSelection
property var currentRootMaterialName: property var activeExtruder: Cura.MachineManager.activeStack
{ property var hasActiveExtruder: activeExtruder != null
var materials = Cura.MachineManager.currentRootMaterialName; property var currentRootMaterialName: hasActiveExtruder ? activeExtruder.material.name : ""
var materialName = "";
if (base.currentExtruderIndex in materials) {
materialName = materials[base.currentExtruderIndex];
}
return materialName;
}
text: currentRootMaterialName text: currentRootMaterialName
tooltip: currentRootMaterialName tooltip: currentRootMaterialName
@ -316,7 +367,11 @@ Column
property var valueWarning: ! Cura.MachineManager.isActiveQualitySupported property var valueWarning: ! Cura.MachineManager.isActiveQualitySupported
function isMaterialSupported () { function isMaterialSupported () {
return Cura.ContainerManager.getContainerMetaDataEntry(Cura.MachineManager.activeMaterialId, "compatible") == "True" if (!hasActiveExtruder)
{
return false;
}
return Cura.ContainerManager.getContainerMetaDataEntry(activeExtruder.material.id, "compatible") == "True"
} }
} }
} }
@ -341,6 +396,8 @@ Column
id: variantLabel id: variantLabel
text: Cura.MachineManager.activeDefinitionVariantsName; text: Cura.MachineManager.activeDefinitionVariantsName;
width: Math.round(parent.width * 0.45 - UM.Theme.getSize("default_margin").width) width: Math.round(parent.width * 0.45 - UM.Theme.getSize("default_margin").width)
height: parent.height
verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default"); font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text"); color: UM.Theme.getColor("text");
} }
@ -361,17 +418,14 @@ Column
} }
} }
//Buildplate row separator
Rectangle { Rectangle {
id: separator id: buildplateSeparator
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width width: parent.width - 2 * UM.Theme.getSize("sidebar_margin").width
anchors.horizontalCenter: parent.horizontalCenter
visible: buildplateRow.visible visible: buildplateRow.visible
width: parent.width - UM.Theme.getSize("sidebar_margin").width * 2 height: visible ? UM.Theme.getSize("sidebar_lining_thin").height : 0
height: visible ? Math.floor(UM.Theme.getSize("sidebar_lining_thin").height / 2) : 0 color: UM.Theme.getColor("sidebar_lining")
color: UM.Theme.getColor("sidebar_lining_thin")
} }
//Buildplate row //Buildplate row
@ -394,6 +448,8 @@ Column
id: bulidplateLabel id: bulidplateLabel
text: catalog.i18nc("@label", "Build plate"); text: catalog.i18nc("@label", "Build plate");
width: Math.floor(parent.width * 0.45 - UM.Theme.getSize("default_margin").width) width: Math.floor(parent.width * 0.45 - UM.Theme.getSize("default_margin").width)
height: parent.height
verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default"); font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text"); color: UM.Theme.getColor("text");
} }

View File

@ -243,6 +243,81 @@ Item
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("sidebar_margin").height anchors.topMargin: UM.Theme.getSize("sidebar_margin").height
// This Item is used only for tooltip, for slider area which is unavailable
Item
{
function showTooltip (showTooltip)
{
if (showTooltip) {
var content = catalog.i18nc("@tooltip", "This quality profile is not available for you current material and nozzle configuration. Please change these to enable this quality profile")
base.showTooltip(qualityRow, Qt.point(-UM.Theme.getSize("sidebar_margin").width, customisedSettings.height), content)
}
else {
base.hideTooltip()
}
}
id: unavailableLineToolTip
height: 20 // hovered area height
z: parent.z + 1 // should be higher, otherwise the area can be hovered
x: 0
anchors.verticalCenter: qualitySlider.verticalCenter
Rectangle
{
id: leftArea
width:
{
if (qualityModel.availableTotalTicks == 0) {
return qualityModel.qualitySliderStepWidth * qualityModel.totalTicks
}
return qualityModel.qualitySliderStepWidth * qualityModel.qualitySliderAvailableMin - 10
}
height: parent.height
color: "transparent"
MouseArea
{
anchors.fill: parent
hoverEnabled: true
enabled: Cura.SimpleModeSettingsManager.isProfileUserCreated == false
onEntered: unavailableLineToolTip.showTooltip(true)
onExited: unavailableLineToolTip.showTooltip(false)
}
}
Rectangle
{
id: rightArea
width: {
if(qualityModel.availableTotalTicks == 0)
return 0
return qualityModel.qualitySliderMarginRight - 10
}
height: parent.height
color: "transparent"
x: {
if (qualityModel.availableTotalTicks == 0) {
return 0
}
var leftUnavailableArea = qualityModel.qualitySliderStepWidth * qualityModel.qualitySliderAvailableMin
var totalGap = qualityModel.qualitySliderStepWidth * (qualityModel.availableTotalTicks -1) + leftUnavailableArea + 10
return totalGap
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: Cura.SimpleModeSettingsManager.isProfileUserCreated == false
onEntered: unavailableLineToolTip.showTooltip(true)
onExited: unavailableLineToolTip.showTooltip(false)
}
}
}
// Draw Unavailable line // Draw Unavailable line
Rectangle Rectangle
{ {
@ -516,8 +591,13 @@ 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
// 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) Cura.MachineManager.setSettingForAllExtruders("infill_sparse_density", "value", roundedSliderValue)
} }
}
style: SliderStyle style: SliderStyle
{ {

View File

@ -150,7 +150,7 @@ Rectangle
visible: base.width - allItemsWidth - 1 * this.width > 0 visible: base.width - allItemsWidth - 1 * this.width > 0
} }
// #5 Left view // #5 Right view
Button Button
{ {
iconSource: UM.Theme.getIcon("view_right") iconSource: UM.Theme.getIcon("view_right")

View File

@ -101,7 +101,7 @@ UM.Dialog
} }
Label Label
{ {
text: Cura.MachineManager.activeMachine.definition.name text: (Cura.MachineManager.activeMachine == null) ? "" : Cura.MachineManager.activeMachine.definition.name
width: (parent.width / 3) | 0 width: (parent.width / 3) | 0
} }
} }
@ -173,7 +173,7 @@ UM.Dialog
} }
Label Label
{ {
text: Cura.MachineManager.activeVariantNames[modelData] + ", " + Cura.MachineManager.currentRootMaterialName[modelData] text: Cura.MachineManager.activeVariantNames[modelData] + ", " + Cura.MachineManager.getExtruder(modelData).material.name
width: (parent.width / 3) | 0 width: (parent.width / 3) | 0
} }
} }

View File

@ -34,7 +34,6 @@ raft_airgap = 0.25
raft_interface_thickness = =max(layer_height * 1.5, 0.225) raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80 retraction_count_max = 80
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = 0.8 retraction_min_travel = 0.8
retraction_prime_speed = 15 retraction_prime_speed = 15

View File

@ -38,7 +38,6 @@ retraction_count_max = 45
retraction_extra_prime_amount = 0.2 retraction_extra_prime_amount = 0.2
retraction_extrusion_window = 6.5 retraction_extrusion_window = 6.5
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = 0.8 retraction_min_travel = 0.8
retraction_prime_speed = 13 retraction_prime_speed = 13

View File

@ -44,7 +44,6 @@ raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80 retraction_count_max = 80
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = 0.8 retraction_min_travel = 0.8
retraction_prime_speed = 15 retraction_prime_speed = 15

View File

@ -43,7 +43,6 @@ raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80 retraction_count_max = 80
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = 0.8 retraction_min_travel = 0.8
retraction_prime_speed = 15 retraction_prime_speed = 15

View File

@ -44,7 +44,6 @@ raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80 retraction_count_max = 80
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = 0.8 retraction_min_travel = 0.8
retraction_prime_speed = 15 retraction_prime_speed = 15

View File

@ -41,7 +41,6 @@ raft_interface_thickness = =max(layer_height * 1.5, 0.225)
retraction_count_max = 80 retraction_count_max = 80
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = 0.8 retraction_min_travel = 0.8
retraction_prime_speed = 15 retraction_prime_speed = 15

View File

@ -45,7 +45,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8 retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = 0.8 retraction_min_travel = 0.8
retraction_prime_speed = 18 retraction_prime_speed = 18
@ -61,7 +60,6 @@ support_angle = 50
switch_extruder_prime_speed = 15 switch_extruder_prime_speed = 15
switch_extruder_retraction_amount = 20 switch_extruder_retraction_amount = 20
switch_extruder_retraction_speeds = 35 switch_extruder_retraction_speeds = 35
travel_avoid_distance = 3
wall_0_inset = 0 wall_0_inset = 0
wall_line_width_x = =line_width wall_line_width_x = =line_width
wall_thickness = =line_width * 3 wall_thickness = =line_width * 3

View File

@ -44,7 +44,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8 retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = 0.8 retraction_min_travel = 0.8
retraction_prime_speed = 18 retraction_prime_speed = 18
@ -61,7 +60,6 @@ switch_extruder_prime_speed = 15
switch_extruder_retraction_amount = 20 switch_extruder_retraction_amount = 20
switch_extruder_retraction_speeds = 35 switch_extruder_retraction_speeds = 35
top_bottom_thickness = 1.1 top_bottom_thickness = 1.1
travel_avoid_distance = 3
wall_0_inset = 0 wall_0_inset = 0
wall_line_width_x = =line_width wall_line_width_x = =line_width
wall_thickness = =line_width * 3 wall_thickness = =line_width * 3

View File

@ -43,7 +43,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8 retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = 0.8 retraction_min_travel = 0.8
retraction_prime_speed = 18 retraction_prime_speed = 18
@ -60,7 +59,6 @@ switch_extruder_prime_speed = 15
switch_extruder_retraction_amount = 20 switch_extruder_retraction_amount = 20
switch_extruder_retraction_speeds = 35 switch_extruder_retraction_speeds = 35
top_bottom_thickness = 1 top_bottom_thickness = 1
travel_avoid_distance = 3
wall_0_inset = 0 wall_0_inset = 0
wall_line_width_x = =line_width wall_line_width_x = =line_width
wall_thickness = =line_width * 3 wall_thickness = =line_width * 3

View File

@ -43,7 +43,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8 retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 1.5 retraction_hop = 1.5
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = =line_width * 2 retraction_min_travel = =line_width * 2
retraction_prime_speed = 15 retraction_prime_speed = 15

View File

@ -44,7 +44,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8 retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 1.5 retraction_hop = 1.5
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = =line_width * 2 retraction_min_travel = =line_width * 2
retraction_prime_speed = 15 retraction_prime_speed = 15

View File

@ -41,7 +41,6 @@ retraction_count_max = 12
retraction_extra_prime_amount = 0.8 retraction_extra_prime_amount = 0.8
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 1.5 retraction_hop = 1.5
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
retraction_min_travel = =line_width * 2 retraction_min_travel = =line_width * 2
retraction_prime_speed = 15 retraction_prime_speed = 15

View File

@ -36,4 +36,3 @@ support_bottom_distance = =support_z_distance
support_line_width = =round(line_width * 0.6 / 0.7, 2) support_line_width = =round(line_width * 0.6 / 0.7, 2)
support_z_distance = =layer_height support_z_distance = =layer_height
top_bottom_thickness = 1.2 top_bottom_thickness = 1.2
travel_avoid_distance = 1.5

View File

@ -37,4 +37,3 @@ support_bottom_distance = =support_z_distance
support_line_width = =round(line_width * 0.6 / 0.7, 2) support_line_width = =round(line_width * 0.6 / 0.7, 2)
support_z_distance = =layer_height support_z_distance = =layer_height
top_bottom_thickness = 1.2 top_bottom_thickness = 1.2
travel_avoid_distance = 1.5

View File

@ -37,4 +37,3 @@ support_bottom_distance = =support_z_distance
support_line_width = =round(line_width * 0.6 / 0.7, 2) support_line_width = =round(line_width * 0.6 / 0.7, 2)
support_z_distance = =layer_height support_z_distance = =layer_height
top_bottom_thickness = 1.2 top_bottom_thickness = 1.2
travel_avoid_distance = 1.5

View File

@ -29,4 +29,3 @@ speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50) speed_wall = =math.ceil(speed_print * 40 / 50)
speed_wall_0 = =math.ceil(speed_wall * 30 / 40) speed_wall_0 = =math.ceil(speed_wall * 30 / 40)
support_line_width = =round(line_width * 0.6 / 0.7, 2) support_line_width = =round(line_width * 0.6 / 0.7, 2)
travel_avoid_distance = 3

View File

@ -29,4 +29,3 @@ speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50) speed_wall = =math.ceil(speed_print * 40 / 50)
speed_wall_0 = =math.ceil(speed_wall * 30 / 40) speed_wall_0 = =math.ceil(speed_wall * 30 / 40)
support_line_width = =round(line_width * 0.6 / 0.7, 2) support_line_width = =round(line_width * 0.6 / 0.7, 2)
travel_avoid_distance = 3

View File

@ -30,4 +30,3 @@ speed_topbottom = =math.ceil(speed_print * 25 / 50)
speed_wall = =math.ceil(speed_print * 40 / 50) speed_wall = =math.ceil(speed_print * 40 / 50)
speed_wall_0 = =math.ceil(speed_wall * 30 / 40) speed_wall_0 = =math.ceil(speed_wall * 30 / 40)
support_line_width = =round(line_width * 0.6 / 0.7, 2) support_line_width = =round(line_width * 0.6 / 0.7, 2)
travel_avoid_distance = 3

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
<title>icn_buildplate</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Visual" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="Printer-status-icon" transform="translate(-33.000000, -366.000000)" stroke="#000000">
<g id="icn_buildplate" transform="translate(33.000000, 367.000000)">
<polyline id="Stroke-6823" points="0 5 7 8 14 5"></polyline>
<polyline id="Stroke-6823-Copy" points="0 7 7 10 14 7"></polyline>
<polyline id="Stroke-6823-Copy" points="0 9 7 12 14 9"></polyline>
<polygon id="Stroke-6824" points="0 2.75 7 0 14 2.75 7 5.5"></polygon>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,5 @@
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<polygon id="Polygon" points="15 8 16 9 9 16 8 15"></polygon>
<circle id="Oval-2" cx="6" cy="18" r="4"></circle>
<circle id="Oval-2-Copy" cx="18" cy="6" r="4"></circle>
</svg>

After

Width:  |  Height:  |  Size: 332 B

View File

@ -0,0 +1,6 @@
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<circle id="Oval-2" stroke="#FFFFFF" cx="4" cy="20" r="3.5"></circle>
<circle id="Oval-2-Copy" stroke="#FFFFFF" cx="20" cy="4" r="3.5"></circle>
<polygon id="Polygon" fill="#FFFFFF" points="11 12 12 13 9 16 8 15"></polygon>
<polygon id="Polygon-Copy" fill="#FFFFFF" points="15 8 16 9 13 12 12 11"></polygon>
</svg>

After

Width:  |  Height:  |  Size: 475 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
<path d="m 30,23.75 v 2.5 q 0,0.50781 -0.37109,0.87891 Q 29.25781,27.5 28.75,27.5 H 1.25 Q 0.74219,27.5 0.37109,27.12891 0,26.75781 0,26.25 v -2.5 Q 0,23.24219 0.37109,22.87109 0.74219,22.5 1.25,22.5 h 27.5 q 0.50781,0 0.87891,0.37109 Q 30,23.24219 30,23.75 Z m 0,-10 v 2.5 q 0,0.50781 -0.37109,0.87891 Q 29.25781,17.5 28.75,17.5 H 1.25 Q 0.74219,17.5 0.37109,17.12891 0,16.75781 0,16.25 v -2.5 Q 0,13.24219 0.37109,12.87109 0.74219,12.5 1.25,12.5 h 27.5 q 0.50781,0 0.87891,0.37109 Q 30,13.24219 30,13.75 Z m 0,-10 v 2.5 Q 30,6.75781 29.62891,7.12891 29.25781,7.5 28.75,7.5 H 1.25 Q 0.74219,7.5 0.37109,7.12891 0,6.75781 0,6.25 V 3.75 Q 0,3.24219 0.37109,2.87109 0.74219,2.5 1.25,2.5 h 27.5 q 0.50781,0 0.87891,0.37109 Q 30,3.24219 30,3.75 Z" />
</svg>

After

Width:  |  Height:  |  Size: 817 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="32px" height="16px" viewBox="0 0 32 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
<title>icn_groupPrinters</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Visual" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Printer-status-icon" transform="translate(-24.000000, -176.000000)" fill="#000000">
<path d="M48,188 L54,188 L54,190 L48,190 L48,188 L48,188 Z M48,180 L54,180 L54,182 L48,182 L48,180 L48,180 Z M54,189 L56,189 L56,191 L54,191 L54,189 L54,189 Z M54,177 L56,177 L56,189 L54,189 L54,177 L54,177 Z M48,182 L51,182 L51,183 L48,183 L48,182 L48,182 Z M49,183 L50,183 L50,185 L49,185 L49,183 L49,183 Z M48,177 L54,177 L54,179 L48,179 L48,177 L48,177 Z M34,176 L46,176 L48,176 L48,187 L53,187 L53,188 L48,188 L48,190 L48,192 L46,192 L46,191 L34,191 L34,192 L32,192 L32,190 L26,190 L26,191 L24,191 L24,189 L24,177 L26,177 L32,177 L32,176 L34,176 Z M34,178 L34,179 L46,179 L46,178 L34,178 Z M41,182 L41,184 L39,184 L39,182 L38,182 L38,181 L42,181 L42,182 L41,182 Z M46,189 L46,181 L34,181 L34,189 L46,189 Z M32,179 L26,179 L26,180 L32,180 L32,179 Z M32,183 L31,183 L31,185 L30,185 L30,183 L29,183 L29,182 L26,182 L26,188 L27,188 L27,187 L32,187 L32,183 Z M35,188 L45,188 L45,189 L35,189 L35,188 Z" id="icn_groupPrinters"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 49 (51002) - http://www.bohemiancoding.com/sketch -->
<title>icn_singlePrinter</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Visual" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Printer-status-icon" transform="translate(-217.000000, -176.000000)" fill="#000000">
<g id="icn_singlePrinter" transform="translate(217.000000, 176.000000)">
<path d="M2,13 L14,13 L14,15 L2,15 L2,13 L2,13 Z M2,3 L14,3 L14,5 L2,5 L2,3 L2,3 Z M0,14 L2,14 L2,16 L0,16 L0,14 L0,14 Z M14,14 L16,14 L16,16 L14,16 L14,14 L14,14 Z M0,0 L2,0 L2,14 L0,14 L0,0 L0,0 Z M14,0 L16,0 L16,14 L14,14 L14,0 L14,0 Z M6,5 L10,5 L10,6 L6,6 L6,5 L6,5 Z M7,6 L9,6 L9,8 L7,8 L7,6 L7,6 Z M2,0 L14,0 L14,2 L2,2 L2,0 L2,0 Z M3,12 L13,12 L13,13 L3,13 L3,12 L3,12 Z" id="Rectangle-185-Copy-5-Copy-4"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -14,6 +14,16 @@
"weight": 50, "weight": 50,
"family": "Noto Sans" "family": "Noto Sans"
}, },
"medium": {
"size": 1.16,
"weight": 50,
"family": "Noto Sans"
},
"medium_bold": {
"size": 1.16,
"weight": 63,
"family": "Noto Sans"
},
"default": { "default": {
"size": 1.0, "size": 1.0,
"weight": 50, "weight": 50,
@ -289,7 +299,21 @@
"layerview_move_combing": [0, 0, 255, 255], "layerview_move_combing": [0, 0, 255, 255],
"layerview_move_retraction": [128, 128, 255, 255], "layerview_move_retraction": [128, 128, 255, 255],
"layerview_support_interface": [64, 192, 255, 255], "layerview_support_interface": [64, 192, 255, 255],
"layerview_nozzle": [181, 166, 66, 50] "layerview_nozzle": [181, 166, 66, 50],
"configuration_item": [255, 255, 255, 0],
"configuration_item_active": [12, 169, 227, 32],
"configuration_item_text": [0, 0, 0, 255],
"configuration_item_text_active": [0, 0, 0, 255],
"configuration_item_border": [127, 127, 127, 255],
"configuration_item_border_active": [12, 169, 227, 32],
"configuration_item_border_hover": [12, 169, 227, 255],
"tab_status_connected": [12, 169, 227, 255],
"tab_status_disconnected": [200, 200, 200, 255],
"printer_config_matched": [12, 169, 227, 255],
"printer_config_mismatch": [127, 127, 127, 255]
}, },
"sizes": { "sizes": {
@ -342,6 +366,9 @@
"small_button": [2, 2], "small_button": [2, 2],
"small_button_icon": [1.5, 1.5], "small_button_icon": [1.5, 1.5],
"printer_status_icon": [1.8, 1.8],
"printer_sync_icon": [1.2, 1.2],
"topbar_logo_right_margin": [3, 0], "topbar_logo_right_margin": [3, 0],
"topbar_button": [8, 4], "topbar_button": [8, 4],
"topbar_button_icon": [1.2, 1.2], "topbar_button_icon": [1.2, 1.2],

View File

@ -45,7 +45,6 @@ retraction_amount = 6.5
retraction_count_max = 25 retraction_count_max = 25
retraction_extrusion_window = 1 retraction_extrusion_window = 1
retraction_hop = 2 retraction_hop = 2
retraction_hop_enabled = True
retraction_hop_only_when_collides = True retraction_hop_only_when_collides = True
skin_overlap = 5 skin_overlap = 5
speed_equalize_flow_enabled = True speed_equalize_flow_enabled = True

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