CURA-5048 solved merge conflict

This commit is contained in:
Jack Ha 2018-03-14 09:03:17 +01:00
commit 1d4b2eac4b
48 changed files with 1665 additions and 325 deletions

View file

@ -60,18 +60,20 @@ from cura.Machines.Models.BuildPlateModel import BuildPlateModel
from cura.Machines.Models.NozzleModel import NozzleModel
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
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.SimpleModeSettingsManager import SimpleModeSettingsManager
from cura.Machines.VariantManager import VariantManager
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
from . import PlatformPhysics
from . import BuildVolume
@ -88,7 +90,6 @@ from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.UserChangesModel import UserChangesModel
from cura.Settings.ExtrudersModel import ExtrudersModel
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
from cura.Settings.ContainerManager import ContainerManager
from cura.ObjectsModel import ObjectsModel
@ -142,12 +143,6 @@ class CuraApplication(QtApplication):
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):
# 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"]:
@ -224,12 +219,14 @@ class CuraApplication(QtApplication):
self._machine_manager = None # This is initialized on demand.
self._extruder_manager = None
self._material_manager = None
self._quality_manager = None
self._object_manager = None
self._build_plate_model = None
self._multi_build_plate_model = None
self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None
self._cura_scene_controller = None
self._machine_error_checker = None
self._additional_components = {} # Components to add to certain areas in the interface
@ -743,19 +740,28 @@ class CuraApplication(QtApplication):
self.preRun()
container_registry = ContainerRegistry.getInstance()
Logger.log("i", "Initializing variant manager")
self._variant_manager = VariantManager(container_registry)
self._variant_manager.initialize()
Logger.log("i", "Initializing material manager")
from cura.Machines.MaterialManager import MaterialManager
self._material_manager = MaterialManager(container_registry, parent = self)
self._material_manager.initialize()
Logger.log("i", "Initializing quality manager")
from cura.Machines.QualityManager import QualityManager
self._quality_manager = QualityManager(container_registry, parent = self)
self._quality_manager.initialize()
Logger.log("i", "Initializing machine manager")
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
self._setUpSingleInstanceServer()
@ -781,8 +787,11 @@ class CuraApplication(QtApplication):
self._openFile(file_name)
self.started = True
self.initializationFinished.emit()
self.exec_()
initializationFinished = pyqtSignal()
## Run Cura without GUI elements and interaction (server mode).
def runWithoutGUI(self):
self._use_gui = False
@ -847,6 +856,9 @@ class CuraApplication(QtApplication):
def hasGui(self):
return self._use_gui
def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
return self._machine_error_checker
def getMachineManager(self, *args) -> MachineManager:
if self._machine_manager is None:
self._machine_manager = MachineManager(self)
@ -961,6 +973,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)

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.
#
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):
self.name = name
self.is_read_only = False
self.root_material_node = root_material_node
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")
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].is_read_only = self._container_registry.isReadOnly(root_material_id)
group = self._material_group_map[root_material_id]
#Store this material in the group of the appropriate root material.
@ -331,6 +332,35 @@ class MaterialManager(QObject):
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".
# 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._material_manager = CuraApplication.getInstance().getMaterialManager()
self._machine_manager.globalContainerChanged.connect(self._update)
self._material_manager.materialsUpdated.connect(self._update)
self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
self._update()
def setExtruderPosition(self, position: int):

View file

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

View file

@ -0,0 +1,79 @@
# 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 pyqtSlot, pyqtProperty, Qt, pyqtSignal
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):
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 printes
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

@ -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:
#
# [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"
# -> ...
#
# [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.
# 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._machine_to_variant_dict_map = dict() # <machine_type> -> <variant_dict>
self._machine_to_buildplate_dict_map = dict()
self._exclude_variant_id_list = ["empty_variant"]
@ -53,6 +57,7 @@ class VariantManager:
#
def initialize(self):
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.
variant_metadata_list = self._container_registry.findContainersMetadata(type = "variant")
@ -78,6 +83,22 @@ class VariantManager:
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.
# Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
@ -117,3 +138,8 @@ class VariantManager:
if preferred_variant_name:
node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
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

@ -40,6 +40,8 @@ class PlatformPhysics:
Preferences.getInstance().addPreference("physics/automatic_drop_down", True)
def _onSceneChanged(self, source):
if not source.getMeshData():
return
self._change_timer.start()
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.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
from UM.Logger import Logger
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from typing import Optional
@ -17,14 +17,18 @@ class ExtruderOutputModel(QObject):
targetHotendTemperatureChanged = pyqtSignal()
hotendTemperatureChanged = pyqtSignal()
activeMaterialChanged = pyqtSignal()
extruderConfigurationChanged = pyqtSignal()
def __init__(self, printer: "PrinterOutputModel", parent=None):
def __init__(self, printer: "PrinterOutputModel", position, parent=None):
super().__init__(parent)
self._printer = printer
self._position = position
self._target_hotend_temperature = 0
self._hotend_temperature = 0
self._hotend_id = ""
self._active_material = None # type: Optional[MaterialOutputModel]
self._extruder_configuration = ExtruderConfigurationModel()
self._extruder_configuration.position = self._position
@pyqtProperty(QObject, notify = activeMaterialChanged)
def activeMaterial(self) -> "MaterialOutputModel":
@ -33,7 +37,9 @@ class ExtruderOutputModel(QObject):
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
if self._active_material != material:
self._active_material = material
self._extruder_configuration.material = self._active_material
self.activeMaterialChanged.emit()
self.extruderConfigurationChanged.emit()
## Update the hotend temperature. This only changes it locally.
def updateHotendTemperature(self, temperature: float):
@ -67,4 +73,12 @@ class ExtruderOutputModel(QObject):
def updateHotendID(self, id: str):
if self._hotend_id != id:
self._hotend_id = id
self._extruder_configuration.hotendID = self._hotend_id
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,14 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
printer_type = self._properties.get(b"machine", b"").decode("utf-8")
if printer_type.startswith("9511"):
self._printer_type = "ultimaker3_extended"
elif printer_type.startswith("9066"):
self._printer_type = "ultimaker3"
else:
self._printer_type = "unknown"
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs) -> None:
raise NotImplementedError("requestWrite needs to be implemented")
@ -301,6 +309,10 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
def firmwareVersion(self) -> str:
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
@pyqtProperty(str, constant=True)
def ipAddress(self) -> str:

View file

@ -6,7 +6,7 @@ from UM.Logger import Logger
MYPY = False
if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.ExtruderOuputModel import ExtruderOuputModel
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
@ -18,7 +18,7 @@ class PrinterOutputController:
self.can_control_manually = True
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")
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.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
from UM.Logger import Logger
from typing import Optional, List
from typing import Optional
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
if MYPY:
@ -22,8 +22,10 @@ class PrinterOutputModel(QObject):
nameChanged = pyqtSignal()
headPositionChanged = pyqtSignal()
keyChanged = pyqtSignal()
typeChanged = pyqtSignal()
printerTypeChanged = pyqtSignal()
buildplateChanged = pyqtSignal()
cameraChanged = pyqtSignal()
configurationChanged = pyqtSignal()
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = ""):
super().__init__(parent)
@ -32,13 +34,18 @@ class PrinterOutputModel(QObject):
self._name = ""
self._key = "" # Unique identifier
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._active_print_job = None # type: Optional[PrintJobOutputModel]
self._firmware_version = firmware_version
self._printer_state = "unknown"
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
@ -64,14 +71,27 @@ class PrinterOutputModel(QObject):
def camera(self):
return self._camera
@pyqtProperty(str, notify = typeChanged)
@pyqtProperty(str, notify = printerTypeChanged)
def type(self):
return self._type
return self._printer_type
def updateType(self, type):
if self._type != type:
self._type = type
self.typeChanged.emit()
def updateType(self, printer_type):
if self._printer_type != printer_type:
self._printer_type = printer_type
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)
def key(self):
@ -238,3 +258,14 @@ class PrinterOutputModel(QObject):
if self._controller:
return self._controller.can_control_manually
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.
from UM.i18n import i18nCatalog
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 UM.Logger import Logger
from UM.Signal import signalemitter
from UM.Application import Application
@ -17,6 +16,7 @@ from typing import List, Optional
MYPY = False
if MYPY:
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
i18n_catalog = i18nCatalog("cura")
@ -44,10 +44,14 @@ class PrinterOutputDevice(QObject, OutputDevice):
# Signal to indicate that the info text about the connection has changed.
connectionTextChanged = pyqtSignal()
# Signal to indicate that the configuration of one of the printers has changed.
uniqueConfigurationsChanged = pyqtSignal()
def __init__(self, device_id, parent = None):
super().__init__(device_id = device_id, parent = parent)
self._printers = [] # type: List[PrinterOutputModel]
self._unique_configurations = [] # type: List[ConfigurationModel]
self._monitor_view_qml_path = ""
self._monitor_component = None
@ -69,6 +73,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
self._address = ""
self._connection_text = ""
self.printersChanged.connect(self._onPrintersChanged)
Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations)
@pyqtProperty(str, notify = connectionTextChanged)
def address(self):
@ -175,6 +181,23 @@ class PrinterOutputDevice(QObject, OutputDevice):
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.
class ConnectionState(IntEnum):

View file

@ -4,7 +4,7 @@
import collections
import time
#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.Signal import Signal
@ -20,13 +20,15 @@ from UM.Logger import Logger
from UM.Message import Message
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.Machines.VariantManager import VariantType
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 .CuraStackBuilder import CuraStackBuilder
@ -48,7 +50,6 @@ class MachineManager(QObject):
self._global_container_stack = None # type: GlobalStack
self._current_root_material_id = {}
self._current_root_material_name = {}
self._current_quality_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._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
self._instance_container_timer = QTimer()
self._instance_container_timer.setInterval(250)
self._instance_container_timer.setSingleShot(True)
@ -109,6 +105,12 @@ class MachineManager(QObject):
# There might already be some output devices by the time the signal is connected
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._material_incompatible_message = Message(catalog.i18nc("@info:status",
@ -119,12 +121,16 @@ class MachineManager(QObject):
if containers:
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()
# 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.
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()
@ -145,6 +151,7 @@ class MachineManager(QObject):
blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly
outputDevicesChanged = pyqtSignal()
currentConfigurationChanged = pyqtSignal() # Emitted every time the current configurations of the machine changes
rootMaterialChanged = pyqtSignal()
@ -164,6 +171,39 @@ class MachineManager(QObject):
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)
def printerOutputDevices(self):
return self._printer_output_devices
@ -228,15 +268,6 @@ class MachineManager(QObject):
del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
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:
self.blurSettings.emit() # Ensure no-one has focus.
@ -256,8 +287,6 @@ class MachineManager(QObject):
self.rootMaterialChanged.emit()
self._error_check_timer.start()
def _onInstanceContainersChanged(self, container) -> None:
self._instance_container_timer.start()
@ -266,9 +295,6 @@ class MachineManager(QObject):
# Notify UI items, such as the "changed" star in profile pull down menu.
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
def _initMachineState(self, global_stack):
material_dict = {}
@ -312,9 +338,13 @@ class MachineManager(QObject):
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
def getMachine(definition_id: str) -> Optional["GlobalStack"]:
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
def getMachine(definition_id: str, metadata_filter: Dict[str, str] = None) -> Optional["GlobalStack"]:
machines = ContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
for machine in machines:
if machine.definition.getId() == definition_id:
return machine
@ -419,6 +449,12 @@ class MachineManager(QObject):
def stacksHaveErrors(self) -> bool:
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)
def activeMachineName(self) -> str:
if self._global_container_stack:
@ -431,6 +467,18 @@ class MachineManager(QObject):
return self._global_container_stack.getId()
return ""
@pyqtProperty(str, notify = globalContainerChanged)
def activeMachineNetworkKey(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("um_network_key")
return ""
@pyqtProperty(str, notify = globalContainerChanged)
def activeMachineNetworkGroupName(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("connect_group_name")
return ""
@pyqtProperty(QObject, notify = globalContainerChanged)
def activeMachine(self) -> Optional["GlobalStack"]:
return self._global_container_stack
@ -833,6 +881,7 @@ class MachineManager(QObject):
## This will fire the propertiesChanged for all settings so they will be updated in the front-end
@pyqtSlot()
def forceUpdateAllSettings(self):
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
property_names = ["value", "resolve"]
for container in [self._global_container_stack] + list(self._global_container_stack.extruders.values()):
for setting_key in container.getAllKeys():
@ -881,28 +930,18 @@ class MachineManager(QObject):
return []
return sorted(list(self._global_container_stack.extruders.keys()))
## Update _current_root_material_id and _current_root_material_name when
# the current root material was changed.
## 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")
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()
@pyqtProperty("QVariant", notify = rootMaterialChanged)
def currentRootMaterialId(self):
return self._current_root_material_id
@pyqtProperty("QVariant", notify = rootMaterialChanged)
def currentRootMaterialName(self):
return self._current_root_material_name
## Return the variant names in the extruder stack(s).
## For the variant in the global stack, use activeVariantBuildplateName
@pyqtProperty("QVariant", notify = activeVariantChanged)
@ -999,15 +1038,12 @@ class MachineManager(QObject):
if container_node:
self._global_container_stack.extruders[position].material = container_node.getContainer()
root_material_id = container_node.metadata["base_file"]
root_material_name = container_node.getContainer().getName()
else:
self._global_container_stack.extruders[position].material = self._empty_material_container
root_material_id = None
root_material_name = None
# 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]:
self._current_root_material_id[position] = root_material_id
self._current_root_material_name[position] = root_material_name
self.rootMaterialChanged.emit()
def activeMaterialsCompatible(self):
@ -1021,7 +1057,7 @@ class MachineManager(QObject):
return True
## 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")
current_quality_type = None
if self._current_quality_group:
@ -1066,9 +1102,15 @@ class MachineManager(QObject):
extruder = self._global_container_stack.extruders[position]
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")
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(
self._global_container_stack.definition.getId(),
current_variant_name,
@ -1083,6 +1125,74 @@ class MachineManager(QObject):
self._setMaterial(position, new_material)
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")
def setGlobalVariant(self, container_node):
self.blurSettings.emit()
@ -1144,3 +1254,8 @@ class MachineManager(QObject):
elif self._current_quality_group:
name = self._current_quality_group.name
return name
def _updateUponMaterialMetadataChange(self):
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self._updateMaterialWithVariant(None)
self._updateQualityWithMaterial()

View file

@ -66,7 +66,7 @@ class Snapshot:
size = max(bbox.width, bbox.height, bbox.depth * 0.5)
# 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:
# determine the watch distance depending on the size
looking_from_offset = looking_from_offset * size * 1.3

View file

@ -10,7 +10,6 @@ from UM.Logger import Logger
from UM.Message import Message
from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources
from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then.
from UM.Platform import Platform
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Qt.Duration import DurationFormat
@ -32,6 +31,7 @@ import Arcus
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
class CuraEngineBackend(QObject, Backend):
backendError = Signal()
@ -62,23 +62,26 @@ class CuraEngineBackend(QObject, Backend):
default_engine_location = execpath
break
self._application = Application.getInstance()
self._multi_build_plate_model = None
self._machine_error_checker = None
if not default_engine_location:
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)
Preferences.getInstance().addPreference("backend/location", default_engine_location)
# Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
Application.getInstance().getMultiBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged()
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._scene = Application.getInstance().getController().getScene()
self._scene = self._application.getController().getScene()
self._scene.sceneChanged.connect(self._onSceneChanged)
# 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
# - 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
# If there is an error check, it will set the "_is_error_check_scheduled" flag, stop the auto-slicing timer,
# and only wait for the error check to be finished to start the auto-slicing timer again.
# If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
# to start the auto-slicing timer again.
#
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.
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._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
Preferences.getInstance().addPreference("general/auto_slice", True)
@ -146,6 +132,30 @@ class CuraEngineBackend(QObject, Backend):
self.determineAutoSlicing()
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.
#
# This function should terminate the engine process.
@ -531,11 +541,9 @@ class CuraEngineBackend(QObject, Backend):
elif property == "validationState":
if self._use_timer:
self._is_error_check_scheduled = True
self._change_timer.stop()
def _onStackErrorCheckFinished(self):
self._is_error_check_scheduled = False
if not self._slicing and self._build_plates_to_be_sliced:
self.needsSlicing()
self._onChanged()
@ -561,12 +569,15 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(message.amount)
self.backendStateChange.emit(BackendState.Processing)
# testing
def _invokeSlice(self):
if self._use_timer:
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
# 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()
else:
self._change_timer.start()
@ -632,7 +643,11 @@ class CuraEngineBackend(QObject, Backend):
if self._use_timer:
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
# 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()
else:
self._change_timer.start()
@ -786,7 +801,7 @@ class CuraEngineBackend(QObject, Backend):
self._change_timer.start()
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:
self._build_plates_to_be_sliced.append(build_plate_number)
self._invokeSlice()

View file

@ -22,14 +22,7 @@ class MonitorStage(CuraStage):
def _setActivePrintJob(self, 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
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):
if self._active_printer != printer:
@ -43,9 +36,6 @@ class MonitorStage(CuraStage):
else:
self._setActivePrintJob(None)
# Ensure that the right icon source is returned.
self._updateIconSource()
def _onActivePrintJobChanged(self):
self._setActivePrintJob(self._active_printer.activePrintJob)
@ -58,22 +48,13 @@ class MonitorStage(CuraStage):
new_output_device = Application.getInstance().getMachineManager().printerOutputDevices[0]
if new_output_device != 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 = new_output_device
self._printer_output_device.acceptsCommandsChanged.connect(self._updateIconSource)
self._printer_output_device.printersChanged.connect(self._onActivePrinterChanged)
self._printer_output_device.connectionStateChanged.connect(self._updateIconSource)
self._setActivePrinter(self._printer_output_device.activePrinter)
# Force an update of the icon source
self._updateIconSource()
except IndexError:
#If index error occurs, then the icon on monitor button also should be updated
self._updateIconSource()
pass
def _onEngineCreated(self):
@ -82,7 +63,6 @@ class MonitorStage(CuraStage):
self._onOutputDevicesChanged()
self._updateMainOverlay()
self._updateSidebar()
self._updateIconSource()
def _updateMainOverlay(self):
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!
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles), "Sidebar.qml")
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

@ -376,10 +376,15 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# 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.
self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"]
machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(name = data["machine_variant"])[0]
printer.updateName(data["friendly_name"])
printer.updateKey(data["uuid"])
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"]:
printer.updateState("disabled")
else:

View file

@ -97,6 +97,25 @@ class DiscoverUM3Action(MachineAction):
else:
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)
def setKey(self, 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:
meta_data = global_container_stack.getMetaData()
if "um_network_key" in meta_data:
previous_network_key= meta_data["um_network_key"]
global_container_stack.setMetaDataEntry("um_network_key", key)
# Delete old authentication data.
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_key")
Application.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key)
else:
global_container_stack.addMetaDataEntry("um_network_key", key)

View file

@ -32,10 +32,12 @@ Cura.MachineAction
if(base.selectedDevice && base.completeProperties)
{
var printerKey = base.selectedDevice.key
var printerName = base.selectedDevice.name // TODO To change when the groups have a name
if(manager.getStoredKey() != printerKey)
{
manager.setKey(printerKey);
completed();
manager.setKey(printerKey)
manager.setGroupName(printerName) // TODO To change when the groups have a name
completed()
}
}
}
@ -303,7 +305,7 @@ Cura.MachineAction
Button
{
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()
}
}

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).
material_container = variant_dict["material_container"]
compatible = container.getMetaDataEntry("compatible")
compatible = material_container.getMetaDataEntry("compatible")
if compatible is not None:
builder.start("setting", {"key": "hardware compatible"})
if compatible:

View file

@ -215,7 +215,7 @@
{
"label": "Number of Extruders that are enabled",
"description": "Number of extruder trains that are enabled; automatically set in software",
"default_value": "machine_extruder_count",
"value": "machine_extruder_count",
"minimum_value": "1",
"maximum_value": "16",
"type": "int",

View file

@ -10,35 +10,30 @@ import UM 1.2 as UM
import Cura 1.0 as Cura
import "Menus"
ToolButton
{
text: Cura.MachineManager.activeMachineName
ToolButton {
id: base
property var isNetworkPrinter: Cura.MachineManager.activeMachineNetworkKey != ""
property var printerStatus: Cura.MachineManager.printerOutputDevices.length != 0 ? "connected" : "disconnected"
text: isNetworkPrinter ? Cura.MachineManager.activeMachineNetworkGroupName : Cura.MachineManager.activeMachineName
tooltip: Cura.MachineManager.activeMachineName
style: ButtonStyle
{
background: Rectangle
{
color:
{
if(control.pressed)
{
style: ButtonStyle {
background: Rectangle {
color: {
if (control.pressed) {
return UM.Theme.getColor("sidebar_header_active");
}
else if(control.hovered)
{
else if (control.hovered) {
return UM.Theme.getColor("sidebar_header_hover");
}
else
{
else {
return UM.Theme.getColor("sidebar_header_bar");
}
}
Behavior on color { ColorAnimation { duration: 50; } }
UM.RecolorImage
{
UM.RecolorImage {
id: downArrow
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
@ -50,22 +45,43 @@ ToolButton
color: UM.Theme.getColor("text_emphasis")
source: UM.Theme.getIcon("arrow_bottom")
}
Label
{
PrinterStatusIcon {
id: printerStatusIcon
visible: isNetworkPrinter
status: printerStatus
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
leftMargin: UM.Theme.getSize("sidebar_margin").width
}
}
Label {
id: sidebarComboBoxLabel
color: UM.Theme.getColor("sidebar_header_text_active")
text: control.text;
elide: Text.ElideRight;
anchors.left: parent.left;
anchors.leftMargin: UM.Theme.getSize("default_margin").width * 2
anchors.left: isNetworkPrinter ? printerStatusIcon.right : parent.left;
anchors.leftMargin: isNetworkPrinter ? UM.Theme.getSize("sidebar_lining").width : UM.Theme.getSize("sidebar_margin").width
anchors.right: downArrow.left;
anchors.rightMargin: control.rightMargin;
anchors.verticalCenter: parent.verticalCenter;
font: UM.Theme.getFont("large")
font: UM.Theme.getFont("medium_bold")
}
}
label: Label {}
}
menu: PrinterMenu { }
// Make the toolbutton react when the outputdevice changes
Connections
{
target: Cura.MachineManager
onOutputDevicesChanged:
{
base.isNetworkPrinter = Cura.MachineManager.activeMachineNetworkKey != ""
}
}
}

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,85 @@
// 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: (ouputDevice != null) ? outputDevice.uniqueConfigurations : []
delegate: ConfigurationItem
{
width: parent.width - UM.Theme.getSize("default_margin").width
configuration: modelData
onActivateConfiguration:
{
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,66 @@
// 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
property var panelVisible: false
SyncButton {
onClicked: configurationSelector.state == "open" ? configurationSelector.state = "closed" : configurationSelector.state = "open"
outputDevice: connectedDevice
}
Popup {
id: popup
clip: true
y: configurationSelector.height - UM.Theme.getSize("default_lining").height
x: configurationSelector.width - width
width: panelWidth
visible: panelVisible && connectedDevice != null
padding: UM.Theme.getSize("default_lining").width
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")
}
}
states: [
// This adds a second state to the container where the rectangle is farther to the right
State {
name: "open"
PropertyChanges {
target: popup
height: configList.computedHeight
}
},
State {
name: "closed"
PropertyChanges {
target: popup
height: 0
}
}
]
transitions: [
// This adds a transition that defaults to applying to all state changes
Transition {
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { properties: "height"; duration: 200; easing.type: Easing.InOutQuad; }
}
]
}

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,103 @@
// 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 {}
}
onClicked:
{
panelVisible = !panelVisible
}
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.
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 Cura 1.0 as Cura
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
{
text: model.name;
checkable: true;
checked: Cura.MachineManager.activeMachineId == model.id
exclusiveGroup: group;
onTriggered: Cura.MachineManager.setActiveMachine(model.id);
text: catalog.i18nc("@label:category menu label", "Network enabled printers")
enabled: false
visible: networkPrinterMenu.count > 0
}
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; }
MenuSeparator { }
MenuSeparator
{
visible: localPrinterMenu.count > 0
}
MenuItem { action: Cura.Actions.addMachine; }
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

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

View file

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

View file

@ -8,6 +8,7 @@ import QtQuick.Layouts 1.3
import UM 1.2 as UM
import Cura 1.0 as Cura
import "Menus"
import "Menus/ConfigurationMenu"
Rectangle
{
@ -87,10 +88,30 @@ Rectangle
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: printerConnected && !sidebar.monitoringPrint && !sidebar.hideSettings
width: visible ? Math.round(base.width * 0.15) : 0
height: UM.Theme.getSize("sidebar_header").height
anchors.top: base.top
anchors.right: parent.right
panelWidth: base.width
}
SidebarHeader {

View file

@ -16,6 +16,8 @@ Column
property int currentExtruderIndex: Cura.ExtruderManager.activeExtruderIndex;
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)
@ -24,16 +26,66 @@ Column
Item
{
id: initialSeparator
anchors
{
left: parent.left
right: parent.right
}
visible: extruderSelectionRow.visible
visible: printerTypeSelectionRow.visible || buildplateRow.visible || extruderSelectionRow.visible
height: UM.Theme.getSize("default_lining").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
Item
{
@ -261,7 +313,7 @@ Column
id: variantRowSpacer
height: Math.round(UM.Theme.getSize("sidebar_margin").height / 4)
width: height
visible: !extruderSelectionRow.visible
visible: !extruderSelectionRow.visible && !initialSeparator.visible
}
// Material Row
@ -284,6 +336,8 @@ Column
id: materialLabel
text: catalog.i18nc("@label", "Material");
width: Math.round(parent.width * 0.45 - UM.Theme.getSize("default_margin").width)
height: parent.height
verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text");
}
@ -292,15 +346,8 @@ Column
{
id: materialSelection
property var currentRootMaterialName:
{
var materials = Cura.MachineManager.currentRootMaterialName;
var materialName = "";
if (base.currentExtruderIndex in materials) {
materialName = materials[base.currentExtruderIndex];
}
return materialName;
}
property var activeExtruder: Cura.MachineManager.activeStack
property var currentRootMaterialName: activeExtruder.material.name
text: currentRootMaterialName
tooltip: currentRootMaterialName
@ -319,7 +366,7 @@ Column
property var valueWarning: ! Cura.MachineManager.isActiveQualitySupported
function isMaterialSupported () {
return Cura.ContainerManager.getContainerMetaDataEntry(Cura.MachineManager.activeMaterialId, "compatible") == "True"
return Cura.ContainerManager.getContainerMetaDataEntry(activeExtruder.material.id, "compatible") == "True"
}
}
}
@ -344,6 +391,8 @@ Column
id: variantLabel
text: Cura.MachineManager.activeDefinitionVariantsName;
width: Math.round(parent.width * 0.45 - UM.Theme.getSize("default_margin").width)
height: parent.height
verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text");
}
@ -364,17 +413,14 @@ Column
}
}
//Buildplate row separator
Rectangle {
id: separator
id: buildplateSeparator
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width
anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 2 * UM.Theme.getSize("sidebar_margin").width
visible: buildplateRow.visible
width: parent.width - UM.Theme.getSize("sidebar_margin").width * 2
height: visible ? Math.floor(UM.Theme.getSize("sidebar_lining_thin").height / 2) : 0
color: UM.Theme.getColor("sidebar_lining_thin")
height: visible ? UM.Theme.getSize("sidebar_lining_thin").height : 0
color: UM.Theme.getColor("sidebar_lining")
}
//Buildplate row
@ -397,6 +443,8 @@ Column
id: bulidplateLabel
text: catalog.i18nc("@label", "Build plate");
width: Math.floor(parent.width * 0.45 - UM.Theme.getSize("default_margin").width)
height: parent.height
verticalAlignment: Text.AlignVCenter
font: UM.Theme.getFont("default");
color: UM.Theme.getColor("text");
}
@ -420,74 +468,6 @@ Column
}
}
// Material info row
Item
{
id: materialInfoRow
height: Math.round(UM.Theme.getSize("sidebar_setup").height / 2)
visible: (Cura.MachineManager.hasVariants || Cura.MachineManager.hasMaterials) && !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
}
Item {
height: UM.Theme.getSize("sidebar_setup").height
anchors.right: parent.right
width: Math.round(parent.width * 0.7 + UM.Theme.getSize("sidebar_margin").width)
UM.RecolorImage {
id: warningImage
anchors.right: materialInfoLabel.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
anchors.verticalCenter: parent.Bottom
source: UM.Theme.getIcon("warning")
width: UM.Theme.getSize("section_icon").width
height: UM.Theme.getSize("section_icon").height
color: UM.Theme.getColor("material_compatibility_warning")
visible: !Cura.MachineManager.isCurrentSetupSupported
}
Label {
id: materialInfoLabel
wrapMode: Text.WordWrap
text: "<a href='%1'>" + catalog.i18nc("@label", "Check compatibility") + "</a>"
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link")
verticalAlignment: Text.AlignTop
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: parent.bottom
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
// open the material URL with web browser
var version = UM.Application.version;
var machineName = Cura.MachineManager.activeMachine.definition.id;
var url = "https://ultimaker.com/materialcompatibility/" + version + "/" + machineName + "?utm_source=cura&utm_medium=software&utm_campaign=resources";
Qt.openUrlExternally(url);
}
onEntered: {
var content = catalog.i18nc("@tooltip", "Click to check the material compatibility on Ultimaker.com.");
base.showTooltip(
materialInfoRow,
Qt.point(-UM.Theme.getSize("sidebar_margin").width, 0),
catalog.i18nc("@tooltip", content)
);
}
onExited: base.hideTooltip();
}
}
}
}
UM.SettingPropertyProvider
{
id: machineExtruderCount

View file

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

View file

@ -173,7 +173,7 @@ UM.Dialog
}
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
}
}

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 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,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,
"family": "Noto Sans"
},
"medium": {
"size": 1.16,
"weight": 50,
"family": "Noto Sans"
},
"medium_bold": {
"size": 1.16,
"weight": 63,
"family": "Noto Sans"
},
"default": {
"size": 1.0,
"weight": 50,
@ -289,7 +299,21 @@
"layerview_move_combing": [0, 0, 255, 255],
"layerview_move_retraction": [128, 128, 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": {
@ -342,6 +366,9 @@
"small_button": [2, 2],
"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_button": [8, 4],
"topbar_button_icon": [1.2, 1.2],