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

This commit is contained in:
Jaime van Kessel 2017-12-14 13:03:02 +01:00
commit 24bd32477a
127 changed files with 1078 additions and 824 deletions

1
.gitignore vendored
View file

@ -46,6 +46,7 @@ plugins/cura-siemensnx-plugin
plugins/CuraVariSlicePlugin plugins/CuraVariSlicePlugin
plugins/CuraLiveScriptingPlugin plugins/CuraLiveScriptingPlugin
plugins/CuraPrintProfileCreator plugins/CuraPrintProfileCreator
plugins/OctoPrintPlugin
#Build stuff #Build stuff
CMakeCache.txt CMakeCache.txt

View file

@ -3,7 +3,7 @@ Cura
This is the new, shiny frontend for Cura. [daid/Cura](https://github.com/daid/Cura.git) is the old legacy Cura that everyone knows and loves/hates. This is the new, shiny frontend for Cura. [daid/Cura](https://github.com/daid/Cura.git) is the old legacy Cura that everyone knows and loves/hates.
We re-worked the whole GUI code at Ultimaker, because the old code started to become a unmaintainable. We re-worked the whole GUI code at Ultimaker, because the old code started to become unmaintainable.
Logging Issues Logging Issues

View file

@ -56,11 +56,6 @@ class ConvexHullDecorator(SceneNodeDecorator):
if self._node is None: if self._node is None:
return None return None
if getattr(self._node, "_non_printing_mesh", False):
# infill_mesh, cutting_mesh and anti_overhang_mesh do not need a convex hull
# node._non_printing_mesh is set in SettingOverrideDecorator
return None
hull = self._compute2DConvexHull() hull = self._compute2DConvexHull()
if self._global_stack and self._node: if self._global_stack and self._node:

View file

@ -59,7 +59,7 @@ class CrashHandler:
self.data = dict() self.data = dict()
self.data["time_stamp"] = time.time() self.data["time_stamp"] = time.time()
Logger.log("c", "An uncaught exception has occurred!") Logger.log("c", "An uncaught error has occurred!")
for line in traceback.format_exception(exception_type, value, tb): for line in traceback.format_exception(exception_type, value, tb):
for part in line.rstrip("\n").split("\n"): for part in line.rstrip("\n").split("\n"):
Logger.log("c", part) Logger.log("c", part)
@ -90,7 +90,7 @@ class CrashHandler:
def _messageWidget(self): def _messageWidget(self):
label = QLabel() label = QLabel()
label.setText(catalog.i18nc("@label crash message", """<p><b>A fatal exception has occurred. Please send us this Crash Report to fix the problem</p></b> label.setText(catalog.i18nc("@label crash message", """<p><b>A fatal error has occurred. Please send us this Crash Report to fix the problem</p></b>
<p>Please use the "Send report" button to post a bug report automatically to our servers</p> <p>Please use the "Send report" button to post a bug report automatically to our servers</p>
""")) """))
@ -143,7 +143,7 @@ class CrashHandler:
def _exceptionInfoWidget(self): def _exceptionInfoWidget(self):
group = QGroupBox() group = QGroupBox()
group.setTitle(catalog.i18nc("@title:groupbox", "Exception traceback")) group.setTitle(catalog.i18nc("@title:groupbox", "Error traceback"))
layout = QVBoxLayout() layout = QVBoxLayout()
text_area = QTextEdit() text_area = QTextEdit()

View file

@ -172,13 +172,14 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances") Resources.addStorageType(self.ResourceTypes.MachineStack, "machine_instances")
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality_changes")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer, "user")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer) ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack, "machine")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
## Initialise the version upgrade manager with Cura's storage paths. ## Initialise the version upgrade manager with Cura's storage paths.
# Needs to be here to prevent circular dependencies. # Needs to be here to prevent circular dependencies.
@ -266,17 +267,17 @@ class CuraApplication(QtApplication):
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
empty_variant_container = copy.deepcopy(empty_container) empty_variant_container = copy.deepcopy(empty_container)
empty_variant_container._id = "empty_variant" empty_variant_container.setMetaDataEntry("id", "empty_variant")
empty_variant_container.addMetaDataEntry("type", "variant") empty_variant_container.addMetaDataEntry("type", "variant")
ContainerRegistry.getInstance().addContainer(empty_variant_container) ContainerRegistry.getInstance().addContainer(empty_variant_container)
empty_material_container = copy.deepcopy(empty_container) empty_material_container = copy.deepcopy(empty_container)
empty_material_container._id = "empty_material" empty_material_container.setMetaDataEntry("id", "empty_material")
empty_material_container.addMetaDataEntry("type", "material") empty_material_container.addMetaDataEntry("type", "material")
ContainerRegistry.getInstance().addContainer(empty_material_container) ContainerRegistry.getInstance().addContainer(empty_material_container)
empty_quality_container = copy.deepcopy(empty_container) empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container._id = "empty_quality" empty_quality_container.setMetaDataEntry("id", "empty_quality")
empty_quality_container.setName("Not Supported") empty_quality_container.setName("Not Supported")
empty_quality_container.addMetaDataEntry("quality_type", "not_supported") empty_quality_container.addMetaDataEntry("quality_type", "not_supported")
empty_quality_container.addMetaDataEntry("type", "quality") empty_quality_container.addMetaDataEntry("type", "quality")
@ -284,12 +285,12 @@ class CuraApplication(QtApplication):
ContainerRegistry.getInstance().addContainer(empty_quality_container) ContainerRegistry.getInstance().addContainer(empty_quality_container)
empty_quality_changes_container = copy.deepcopy(empty_container) empty_quality_changes_container = copy.deepcopy(empty_container)
empty_quality_changes_container._id = "empty_quality_changes" empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes") empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container) ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
with ContainerRegistry.getInstance().lockFile(): with ContainerRegistry.getInstance().lockFile():
ContainerRegistry.getInstance().load() ContainerRegistry.getInstance().loadAllMetadata()
# set the setting version for Preferences # set the setting version for Preferences
preferences = Preferences.getInstance() preferences = Preferences.getInstance()
@ -312,6 +313,7 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/material_settings", "{}") preferences.addPreference("cura/material_settings", "{}")
preferences.addPreference("view/invert_zoom", False) preferences.addPreference("view/invert_zoom", False)
preferences.addPreference("cura/sidebar_collapse", False)
self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement") self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement")
@ -469,69 +471,10 @@ class CuraApplication(QtApplication):
if not self._started: # Do not do saving during application start if not self._started: # Do not do saving during application start
return return
# Lock file for "more" atomically loading and saving to/from config dir. ContainerRegistry.getInstance().saveDirtyContainers()
with ContainerRegistry.getInstance().lockFile():
for instance in ContainerRegistry.getInstance().findInstanceContainers():
if not instance.isDirty():
continue
try:
data = instance.serialize()
except NotImplementedError:
continue
except Exception:
Logger.logException("e", "An exception occurred when serializing container %s", instance.getId())
continue
mime_type = ContainerRegistry.getMimeTypeForContainer(type(instance))
file_name = urllib.parse.quote_plus(instance.getId()) + "." + mime_type.preferredSuffix
instance_type = instance.getMetaDataEntry("type")
path = None
if instance_type == "material":
path = Resources.getStoragePath(self.ResourceTypes.MaterialInstanceContainer, file_name)
elif instance_type == "quality" or instance_type == "quality_changes":
path = Resources.getStoragePath(self.ResourceTypes.QualityInstanceContainer, file_name)
elif instance_type == "user":
path = Resources.getStoragePath(self.ResourceTypes.UserInstanceContainer, file_name)
elif instance_type == "variant":
path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name)
elif instance_type == "definition_changes":
path = Resources.getStoragePath(self.ResourceTypes.DefinitionChangesContainer, file_name)
if path:
instance.setPath(path)
with SaveFile(path, "wt") as f:
f.write(data)
for stack in ContainerRegistry.getInstance().findContainerStacks():
self.saveStack(stack)
def saveStack(self, stack): def saveStack(self, stack):
if not stack.isDirty(): ContainerRegistry.getInstance().saveContainer(stack)
return
try:
data = stack.serialize()
except NotImplementedError:
return
except Exception:
Logger.logException("e", "An exception occurred when serializing container %s", stack.getId())
return
mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack))
file_name = urllib.parse.quote_plus(stack.getId()) + "." + mime_type.preferredSuffix
path = None
if isinstance(stack, GlobalStack):
path = Resources.getStoragePath(self.ResourceTypes.MachineStack, file_name)
elif isinstance(stack, ExtruderStack):
path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name)
else:
path = Resources.getStoragePath(Resources.ContainerStacks, file_name)
stack.setPath(path)
with SaveFile(path, "wt") as f:
f.write(data)
@pyqtSlot(str, result = QUrl) @pyqtSlot(str, result = QUrl)
def getDefaultPath(self, key): def getDefaultPath(self, key):
@ -733,7 +676,7 @@ class CuraApplication(QtApplication):
self.exec_() self.exec_()
def getMachineManager(self, *args): def getMachineManager(self, *args) -> MachineManager:
if self._machine_manager is None: if self._machine_manager is None:
self._machine_manager = MachineManager.createMachineManager() self._machine_manager = MachineManager.createMachineManager()
return self._machine_manager return self._machine_manager

View file

@ -238,7 +238,7 @@ class PrintInformation(QObject):
pass pass
active_material_id = Application.getInstance().getMachineManager().activeMaterialId active_material_id = Application.getInstance().getMachineManager().activeMaterialId
active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id=active_material_id) active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id = active_material_id)
if active_material_containers: if active_material_containers:
self._active_material_container = active_material_containers[0] self._active_material_container = active_material_containers[0]

View file

@ -1,13 +1,12 @@
# Copyright (c) 2016 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
# This collects a lot of quality and quality changes related code which was split between ContainerManager # This collects a lot of quality and quality changes related code which was split between ContainerManager
# and the MachineManager and really needs to usable from both. # and the MachineManager and really needs to usable from both.
from typing import List, Optional, Dict, TYPE_CHECKING from typing import Any, Dict, List, Optional, TYPE_CHECKING
from UM.Application import Application from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
@ -33,16 +32,16 @@ class QualityManager:
# \param quality_name # \param quality_name
# \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is # \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is
# specified then the currently selected machine definition is used. # specified then the currently selected machine definition is used.
# \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then # \param material_containers_metadata If nothing is specified then the
# the current set of selected materials is used. # current set of selected materials is used.
# \return the matching quality container \type{InstanceContainer} # \return the matching quality container \type{InstanceContainer}
def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None) -> Optional[InstanceContainer]: def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None) -> Optional[InstanceContainer]:
criteria = {"type": "quality", "name": quality_name} criteria = {"type": "quality", "name": quality_name}
result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria) result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria)
# Fall back to using generic materials and qualities if nothing could be found. # Fall back to using generic materials and qualities if nothing could be found.
if not result and material_containers and len(material_containers) == 1: if not result and material_containers_metadata and len(material_containers_metadata) == 1:
basic_materials = self._getBasicMaterials(material_containers[0]) basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0])
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria) result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
return result[0] if result else None return result[0] if result else None
@ -108,18 +107,18 @@ class QualityManager:
# \param quality_type \type{str} the name of the quality type to search for. # \param quality_type \type{str} the name of the quality type to search for.
# \param machine_definition (Optional) \type{InstanceContainer} If nothing is # \param machine_definition (Optional) \type{InstanceContainer} If nothing is
# specified then the currently selected machine definition is used. # specified then the currently selected machine definition is used.
# \param material_containers (Optional) \type{List[InstanceContainer]} If nothing is specified then # \param material_containers_metadata If nothing is specified then the
# the current set of selected materials is used. # current set of selected materials is used.
# \return the matching quality container \type{InstanceContainer} # \return the matching quality container \type{InstanceContainer}
def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers: List[InstanceContainer] = None, **kwargs) -> InstanceContainer: def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs) -> InstanceContainer:
criteria = kwargs criteria = kwargs
criteria["type"] = "quality" criteria["type"] = "quality"
if quality_type: if quality_type:
criteria["quality_type"] = quality_type criteria["quality_type"] = quality_type
result = self._getFilteredContainersForStack(machine_definition, material_containers, **criteria) result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria)
# Fall back to using generic materials and qualities if nothing could be found. # Fall back to using generic materials and qualities if nothing could be found.
if not result and material_containers and len(material_containers) == 1: if not result and material_containers_metadata and len(material_containers_metadata) == 1:
basic_materials = self._getBasicMaterials(material_containers[0]) basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0])
if basic_materials: if basic_materials:
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria) result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
return result[0] if result else None return result[0] if result else None
@ -131,9 +130,9 @@ class QualityManager:
# \return \type{List[InstanceContainer]} the list of suitable qualities. # \return \type{List[InstanceContainer]} the list of suitable qualities.
def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]: def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]:
criteria = {"type": "quality"} criteria = {"type": "quality"}
result = self._getFilteredContainersForStack(machine_definition, [material_container], **criteria) result = self._getFilteredContainersForStack(machine_definition, [material_container.getMetaData()], **criteria)
if not result: if not result:
basic_materials = self._getBasicMaterials(material_container) basic_materials = self._getBasicMaterialMetadatas(material_container.getMetaData())
if basic_materials: if basic_materials:
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria) result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
@ -202,22 +201,34 @@ class QualityManager:
## Fetch more basic versions of a material. ## Fetch more basic versions of a material.
# #
# This tries to find a generic or basic version of the given material. # This tries to find a generic or basic version of the given material.
# \param material_container \type{InstanceContainer} the material # \param material_container \type{Dict[str, Any]} The metadata of a
# \return \type{List[InstanceContainer]} a list of the basic materials or an empty list if one could not be found. # material to find the basic versions of.
def _getBasicMaterials(self, material_container: InstanceContainer): # \return \type{List[Dict[str, Any]]} A list of the metadata of basic
base_material = material_container.getMetaDataEntry("material") # materials, or an empty list if none could be found.
material_container_definition = material_container.getDefinition() def _getBasicMaterialMetadatas(self, material_container: Dict[str, Any]) -> List[Dict[str, Any]]:
if material_container_definition and material_container_definition.getMetaDataEntry("has_machine_quality"): if "definition" not in material_container:
definition_id = material_container.getDefinition().getMetaDataEntry("quality_definition", material_container.getDefinition().getId())
else:
definition_id = "fdmprinter" definition_id = "fdmprinter"
else:
material_container_definition = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = material_container["definition"])
if not material_container_definition:
definition_id = "fdmprinter"
else:
material_container_definition = material_container_definition[0]
if "has_machine_quality" not in material_container_definition:
definition_id = "fdmprinter"
else:
definition_id = material_container_definition.get("quality_definition", material_container_definition["id"])
base_material = material_container.get("material")
if base_material: if base_material:
# There is a basic material specified # There is a basic material specified
criteria = { "type": "material", "name": base_material, "definition": definition_id } criteria = {
containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria) "type": "material",
containers = [basic_material for basic_material in containers if "name": base_material,
basic_material.getMetaDataEntry("variant") == material_container.getMetaDataEntry( "definition": definition_id,
"variant")] "variant": material_container.get("variant")
}
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria)
return containers return containers
return [] return []
@ -225,29 +236,25 @@ class QualityManager:
def _getFilteredContainers(self, **kwargs): def _getFilteredContainers(self, **kwargs):
return self._getFilteredContainersForStack(None, None, **kwargs) return self._getFilteredContainersForStack(None, None, **kwargs)
def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_containers: List[InstanceContainer] = None, **kwargs): def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs):
# Fill in any default values. # Fill in any default values.
if machine_definition is None: if machine_definition is None:
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
quality_definition_id = machine_definition.getMetaDataEntry("quality_definition") quality_definition_id = machine_definition.getMetaDataEntry("quality_definition")
if quality_definition_id is not None: if quality_definition_id is not None:
machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id=quality_definition_id)[0] machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition_id)[0]
# for convenience if not material_metadata:
if material_containers is None:
material_containers = []
if not material_containers:
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
if active_stacks: if active_stacks:
material_containers = [stack.material for stack in active_stacks] material_metadata = [stack.material.getMetaData() for stack in active_stacks]
criteria = kwargs criteria = kwargs
filter_by_material = False filter_by_material = False
machine_definition = self.getParentMachineDefinition(machine_definition) machine_definition = self.getParentMachineDefinition(machine_definition)
criteria["definition"] = machine_definition.getId() criteria["definition"] = machine_definition.getId()
found_containers_with_machine_definition = ContainerRegistry.getInstance().findInstanceContainers(**criteria) found_containers_with_machine_definition = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria)
whole_machine_definition = self.getWholeMachineDefinition(machine_definition) whole_machine_definition = self.getWholeMachineDefinition(machine_definition)
if whole_machine_definition.getMetaDataEntry("has_machine_quality"): if whole_machine_definition.getMetaDataEntry("has_machine_quality"):
definition_id = machine_definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId()) definition_id = machine_definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId())
@ -261,12 +268,12 @@ class QualityManager:
# Stick the material IDs in a set # Stick the material IDs in a set
material_ids = set() material_ids = set()
for material_instance in material_containers: for material_instance in material_metadata:
if material_instance is not None: if material_instance is not None:
# Add the parent material too. # Add the parent material too.
for basic_material in self._getBasicMaterials(material_instance): for basic_material in self._getBasicMaterialMetadatas(material_instance):
material_ids.add(basic_material.getId()) material_ids.add(basic_material["id"])
material_ids.add(material_instance.getId()) material_ids.add(material_instance["id"])
containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria) containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
result = [] result = []
@ -292,13 +299,13 @@ class QualityManager:
# We have a normal (whole) machine defintion # We have a normal (whole) machine defintion
quality_definition = machine_definition.getMetaDataEntry("quality_definition") quality_definition = machine_definition.getMetaDataEntry("quality_definition")
if quality_definition is not None: if quality_definition is not None:
parent_machine_definition = container_registry.findDefinitionContainers(id=quality_definition)[0] parent_machine_definition = container_registry.findDefinitionContainers(id = quality_definition)[0]
return self.getParentMachineDefinition(parent_machine_definition) return self.getParentMachineDefinition(parent_machine_definition)
else: else:
return machine_definition return machine_definition
else: else:
# This looks like an extruder. Find the rest of the machine. # This looks like an extruder. Find the rest of the machine.
whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0] whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0]
parent_machine = self.getParentMachineDefinition(whole_machine) parent_machine = self.getParentMachineDefinition(whole_machine)
if whole_machine is parent_machine: if whole_machine is parent_machine:
# This extruder already belongs to a 'parent' machine def. # This extruder already belongs to a 'parent' machine def.
@ -307,7 +314,7 @@ class QualityManager:
# Look up the corresponding extruder definition in the parent machine definition. # Look up the corresponding extruder definition in the parent machine definition.
extruder_position = machine_definition.getMetaDataEntry("position") extruder_position = machine_definition.getMetaDataEntry("position")
parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position] parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position]
return container_registry.findDefinitionContainers(id=parent_extruder_id)[0] return container_registry.findDefinitionContainers(id = parent_extruder_id)[0]
## Get the whole/global machine definition from an extruder definition. ## Get the whole/global machine definition from an extruder definition.
# #
@ -321,5 +328,5 @@ class QualityManager:
return machine_definition return machine_definition
else: else:
container_registry = ContainerRegistry.getInstance() container_registry = ContainerRegistry.getInstance()
whole_machine = container_registry.findDefinitionContainers(id=machine_entry)[0] whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0]
return whole_machine return whole_machine

View file

@ -1,10 +1,11 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import copy
import os.path import os.path
import urllib import urllib
import uuid import uuid
from typing import Dict, Union from typing import Any, Dict, List, Union
from PyQt5.QtCore import QObject, QUrl, QVariant from PyQt5.QtCore import QObject, QUrl, QVariant
from UM.FlameProfiler import pyqtSlot from UM.FlameProfiler import pyqtSlot
@ -55,7 +56,8 @@ class ContainerManager(QObject):
# \return The ID of the new container, or an empty string if duplication failed. # \return The ID of the new container, or an empty string if duplication failed.
@pyqtSlot(str, result = str) @pyqtSlot(str, result = str)
def duplicateContainer(self, container_id): def duplicateContainer(self, container_id):
containers = self._container_registry.findContainers(None, id = container_id) #TODO: It should be able to duplicate a container of which only the metadata is known.
containers = self._container_registry.findContainers(id = container_id)
if not containers: if not containers:
Logger.log("w", "Could duplicate container %s because it was not found.", container_id) Logger.log("w", "Could duplicate container %s because it was not found.", container_id)
return "" return ""
@ -98,14 +100,14 @@ class ContainerManager(QObject):
# \return True if successful, False if not. # \return True if successful, False if not.
@pyqtSlot(str, str, str, result = bool) @pyqtSlot(str, str, str, result = bool)
def renameContainer(self, container_id, new_id, new_name): def renameContainer(self, container_id, new_id, new_name):
containers = self._container_registry.findContainers(None, id = container_id) containers = self._container_registry.findContainers(id = container_id)
if not containers: if not containers:
Logger.log("w", "Could rename container %s because it was not found.", container_id) Logger.log("w", "Could rename container %s because it was not found.", container_id)
return False return False
container = containers[0] container = containers[0]
# First, remove the container from the registry. This will clean up any files related to the container. # First, remove the container from the registry. This will clean up any files related to the container.
self._container_registry.removeContainer(container) self._container_registry.removeContainer(container_id)
# Ensure we have a unique name for the container # Ensure we have a unique name for the container
new_name = self._container_registry.uniqueName(new_name) new_name = self._container_registry.uniqueName(new_name)
@ -126,9 +128,9 @@ class ContainerManager(QObject):
# \return True if the container was successfully removed, False if not. # \return True if the container was successfully removed, False if not.
@pyqtSlot(str, result = bool) @pyqtSlot(str, result = bool)
def removeContainer(self, container_id): def removeContainer(self, container_id):
containers = self._container_registry.findContainers(None, id = container_id) containers = self._container_registry.findContainers(id = container_id)
if not containers: if not containers:
Logger.log("w", "Could remove container %s because it was not found.", container_id) Logger.log("w", "Could not remove container %s because it was not found.", container_id)
return False return False
self._container_registry.removeContainer(containers[0].getId()) self._container_registry.removeContainer(containers[0].getId())
@ -146,14 +148,14 @@ class ContainerManager(QObject):
# \return True if successfully merged, False if not. # \return True if successfully merged, False if not.
@pyqtSlot(str, result = bool) @pyqtSlot(str, result = bool)
def mergeContainers(self, merge_into_id, merge_id): def mergeContainers(self, merge_into_id, merge_id):
containers = self._container_registry.findContainers(None, id = merge_into_id) containers = self._container_registry.findContainers(id = merge_into_id)
if not containers: if not containers:
Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id) Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id)
return False return False
merge_into = containers[0] merge_into = containers[0]
containers = self._container_registry.findContainers(None, id = merge_id) containers = self._container_registry.findContainers(id = merge_id)
if not containers: if not containers:
Logger.log("w", "Could not merge container %s because it was not found", merge_id) Logger.log("w", "Could not merge container %s because it was not found", merge_id)
return False return False
@ -175,13 +177,13 @@ class ContainerManager(QObject):
# \return True if successful, False if not. # \return True if successful, False if not.
@pyqtSlot(str, result = bool) @pyqtSlot(str, result = bool)
def clearContainer(self, container_id): def clearContainer(self, container_id):
containers = self._container_registry.findContainers(None, id = container_id) if self._container_registry.isReadOnly(container_id):
if not containers: Logger.log("w", "Cannot clear read-only container %s", container_id)
Logger.log("w", "Could clear container %s because it was not found.", container_id)
return False return False
if containers[0].isReadOnly(): containers = self._container_registry.findContainers(id = container_id)
Logger.log("w", "Cannot clear read-only container %s", container_id) if not containers:
Logger.log("w", "Could clear container %s because it was not found.", container_id)
return False return False
containers[0].clear() containers[0].clear()
@ -190,16 +192,12 @@ class ContainerManager(QObject):
@pyqtSlot(str, str, result=str) @pyqtSlot(str, str, result=str)
def getContainerMetaDataEntry(self, container_id, entry_name): def getContainerMetaDataEntry(self, container_id, entry_name):
containers = self._container_registry.findContainers(None, id=container_id) metadatas = self._container_registry.findContainersMetadata(id = container_id)
if not containers: if not metadatas:
Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id) Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id)
return "" return ""
result = containers[0].getMetaDataEntry(entry_name) return str(metadatas[0].get(entry_name, ""))
if result is not None:
return str(result)
else:
return ""
## Set a metadata entry of the specified container. ## Set a metadata entry of the specified container.
# #
@ -215,17 +213,17 @@ class ContainerManager(QObject):
# \return True if successful, False if not. # \return True if successful, False if not.
@pyqtSlot(str, str, str, result = bool) @pyqtSlot(str, str, str, result = bool)
def setContainerMetaDataEntry(self, container_id, entry_name, entry_value): def setContainerMetaDataEntry(self, container_id, entry_name, entry_value):
containers = self._container_registry.findContainers(None, id = container_id) if self._container_registry.isReadOnly(container_id):
Logger.log("w", "Cannot set metadata of read-only container %s.", container_id)
return False
containers = self._container_registry.findContainers(id = container_id) #We need the complete container, since we need to know whether the container is read-only or not.
if not containers: if not containers:
Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id) Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id)
return False return False
container = containers[0] container = containers[0]
if container.isReadOnly():
Logger.log("w", "Cannot set metadata of read-only container %s.", container_id)
return False
entries = entry_name.split("/") entries = entry_name.split("/")
entry_name = entries.pop() entry_name = entries.pop()
@ -265,17 +263,17 @@ class ContainerManager(QObject):
# \return True if successful, False if not. # \return True if successful, False if not.
@pyqtSlot(str, str, str, str, result = bool) @pyqtSlot(str, str, str, str, result = bool)
def setContainerProperty(self, container_id, setting_key, property_name, property_value): def setContainerProperty(self, container_id, setting_key, property_name, property_value):
containers = self._container_registry.findContainers(None, id = container_id) if self._container_registry.isReadOnly(container_id):
Logger.log("w", "Cannot set properties of read-only container %s.", container_id)
return False
containers = self._container_registry.findContainers(id = container_id)
if not containers: if not containers:
Logger.log("w", "Could not set properties of container %s because it was not found.", container_id) Logger.log("w", "Could not set properties of container %s because it was not found.", container_id)
return False return False
container = containers[0] container = containers[0]
if container.isReadOnly():
Logger.log("w", "Cannot set properties of read-only container %s.", container_id)
return False
container.setProperty(setting_key, property_name, property_value) container.setProperty(setting_key, property_name, property_value)
basefile = container.getMetaDataEntry("base_file", container_id) basefile = container.getMetaDataEntry("base_file", container_id)
@ -311,35 +309,30 @@ class ContainerManager(QObject):
## Set the name of the specified container. ## Set the name of the specified container.
@pyqtSlot(str, str, result = bool) @pyqtSlot(str, str, result = bool)
def setContainerName(self, container_id, new_name): def setContainerName(self, container_id, new_name):
containers = self._container_registry.findContainers(None, id = container_id) if self._container_registry.isReadOnly(container_id):
Logger.log("w", "Cannot set name of read-only container %s.", container_id)
return False
containers = self._container_registry.findContainers(id = container_id) #We need to get the full container, not just metadata, since we need to know whether it's read-only.
if not containers: if not containers:
Logger.log("w", "Could not set name of container %s because it was not found.", container_id) Logger.log("w", "Could not set name of container %s because it was not found.", container_id)
return False return False
container = containers[0] containers[0].setName(new_name)
if container.isReadOnly():
Logger.log("w", "Cannot set name of read-only container %s.", container_id)
return False
container.setName(new_name)
return True return True
## Find instance containers matching certain criteria. ## Find instance containers matching certain criteria.
# #
# This effectively forwards to ContainerRegistry::findInstanceContainers. # This effectively forwards to
# ContainerRegistry::findInstanceContainersMetadata.
# #
# \param criteria A dict of key - value pairs to search for. # \param criteria A dict of key - value pairs to search for.
# #
# \return A list of container IDs that match the given criteria. # \return A list of container IDs that match the given criteria.
@pyqtSlot("QVariantMap", result = "QVariantList") @pyqtSlot("QVariantMap", result = "QVariantList")
def findInstanceContainers(self, criteria): def findInstanceContainers(self, criteria):
result = [] return [entry["id"] for entry in self._container_registry.findInstanceContainersMetadata(**criteria)]
for entry in self._container_registry.findInstanceContainers(**criteria):
result.append(entry.getId())
return result
@pyqtSlot(str, result = bool) @pyqtSlot(str, result = bool)
def isContainerUsed(self, container_id): def isContainerUsed(self, container_id):
@ -347,15 +340,17 @@ class ContainerManager(QObject):
# check if this is a material container. If so, check if any material with the same base is being used by any # check if this is a material container. If so, check if any material with the same base is being used by any
# stacks. # stacks.
container_ids_to_check = [container_id] container_ids_to_check = [container_id]
container_results = self._container_registry.findInstanceContainers(id = container_id, type = "material") container_results = self._container_registry.findInstanceContainersMetadata(id = container_id, type = "material")
if container_results: if container_results:
this_container = container_results[0] this_container = container_results[0]
material_base_file = this_container.getMetaDataEntry("base_file", this_container.getId()) material_base_file = this_container["id"]
if "base_file" in this_container:
material_base_file = this_container["base_file"]
# check all material container IDs with the same base # check all material container IDs with the same base
material_containers = self._container_registry.findInstanceContainers(base_file = material_base_file, material_containers = self._container_registry.findInstanceContainersMetadata(base_file = material_base_file,
type = "material") type = "material")
if material_containers: if material_containers:
container_ids_to_check = [container.getId() for container in material_containers] container_ids_to_check = [container["id"] for container in material_containers]
all_stacks = self._container_registry.findContainerStacks() all_stacks = self._container_registry.findContainerStacks()
for stack in all_stacks: for stack in all_stacks:
@ -423,7 +418,7 @@ class ContainerManager(QObject):
else: else:
mime_type = self._container_name_filters[file_type]["mime"] mime_type = self._container_name_filters[file_type]["mime"]
containers = self._container_registry.findContainers(None, id = container_id) containers = self._container_registry.findContainers(id = container_id)
if not containers: if not containers:
return { "status": "error", "message": "Container not found"} return { "status": "error", "message": "Container not found"}
container = containers[0] container = containers[0]
@ -627,9 +622,9 @@ class ContainerManager(QObject):
elif activate_quality: elif activate_quality:
definition_id = "fdmprinter" if not self._machine_manager.filterQualityByMachine else self._machine_manager.activeDefinitionId definition_id = "fdmprinter" if not self._machine_manager.filterQualityByMachine else self._machine_manager.activeDefinitionId
containers = self._container_registry.findInstanceContainers(type = "quality", definition = definition_id, quality_type = activate_quality_type) containers = self._container_registry.findInstanceContainersMetadata(type = "quality", definition = definition_id, quality_type = activate_quality_type)
if containers: if containers:
self._machine_manager.setActiveQuality(containers[0].getId()) self._machine_manager.setActiveQuality(containers[0]["id"])
self._machine_manager.activeQualityChanged.emit() self._machine_manager.activeQualityChanged.emit()
return containers_found return containers_found
@ -664,11 +659,13 @@ class ContainerManager(QObject):
container_registry = self._container_registry container_registry = self._container_registry
containers_to_rename = self._container_registry.findInstanceContainers(type = "quality_changes", name = quality_name) containers_to_rename = self._container_registry.findInstanceContainersMetadata(type = "quality_changes", name = quality_name)
for container in containers_to_rename: for container in containers_to_rename:
stack_id = container.getMetaDataEntry("extruder", global_stack.getId()) stack_id = global_stack.getId()
container_registry.renameContainer(container.getId(), new_name, self._createUniqueId(stack_id, new_name)) if "extruder" in container:
stack_id = container["extruder"]
container_registry.renameContainer(container["id"], new_name, self._createUniqueId(stack_id, new_name))
if not containers_to_rename: if not containers_to_rename:
Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name) Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name)
@ -693,27 +690,29 @@ class ContainerManager(QObject):
machine_definition = global_stack.getBottom() machine_definition = global_stack.getBottom()
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks() active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
material_containers = [stack.material for stack in active_stacks] if active_stacks is None:
return ""
material_metadatas = [stack.material.getMetaData() for stack in active_stacks]
result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name, result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name,
QualityManager.getInstance().getParentMachineDefinition(machine_definition), QualityManager.getInstance().getParentMachineDefinition(machine_definition),
material_containers) material_metadatas)
return result[0].getName() if result else "" return result[0].getName() if result else ""
## Duplicate a quality or quality changes profile specific to a machine type ## Duplicate a quality or quality changes profile specific to a machine type
# #
# \param quality_name \type{str} the name of the quality or quality changes container to duplicate. # \param quality_name The name of the quality or quality changes container to duplicate.
# \param base_name \type{str} the desired name for the new container. # \param base_name The desired name for the new container.
# \param machine_definition \type{DefinitionContainer} # \param machine_definition The machine with the specific machine type.
# \param material_instances \type{List[InstanceContainer]} # \param material_metadatas Metadata of materials
# \return \type{str} the name of the newly created container. # \return List of duplicated quality profiles.
def _duplicateQualityOrQualityChangesForMachineType(self, quality_name, base_name, machine_definition, material_instances): def _duplicateQualityOrQualityChangesForMachineType(self, quality_name: str, base_name: str, machine_definition: DefinitionContainer, material_metadatas: List[Dict[str, Any]]) -> List[InstanceContainer]:
Logger.log("d", "Attempting to duplicate the quality %s", quality_name) Logger.log("d", "Attempting to duplicate the quality %s", quality_name)
if base_name is None: if base_name is None:
base_name = quality_name base_name = quality_name
# Try to find a Quality with the name. # Try to find a Quality with the name.
container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_instances) container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_metadatas)
if container: if container:
Logger.log("d", "We found a quality to duplicate.") Logger.log("d", "We found a quality to duplicate.")
return self._duplicateQualityForMachineType(container, base_name, machine_definition) return self._duplicateQualityForMachineType(container, base_name, machine_definition)
@ -722,7 +721,7 @@ class ContainerManager(QObject):
return self._duplicateQualityChangesForMachineType(quality_name, base_name, machine_definition) return self._duplicateQualityChangesForMachineType(quality_name, base_name, machine_definition)
# Duplicate a quality profile # Duplicate a quality profile
def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition): def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition) -> List[InstanceContainer]:
if base_name is None: if base_name is None:
base_name = quality_container.getName() base_name = quality_container.getName()
new_name = self._container_registry.uniqueName(base_name) new_name = self._container_registry.uniqueName(base_name)
@ -746,7 +745,7 @@ class ContainerManager(QObject):
return new_change_instances return new_change_instances
# Duplicate a quality changes container # Duplicate a quality changes container
def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition): def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition) -> List[InstanceContainer]:
new_change_instances = [] new_change_instances = []
for container in QualityManager.getInstance().findQualityChangesByName(quality_changes_name, for container in QualityManager.getInstance().findQualityChangesByName(quality_changes_name,
machine_definition): machine_definition):
@ -765,27 +764,57 @@ class ContainerManager(QObject):
# \return \type{str} the id of the newly created container. # \return \type{str} the id of the newly created container.
@pyqtSlot(str, result = str) @pyqtSlot(str, result = str)
def duplicateMaterial(self, material_id: str) -> str: def duplicateMaterial(self, material_id: str) -> str:
containers = self._container_registry.findInstanceContainers(id=material_id) original = self._container_registry.findContainersMetadata(id = material_id)
if not containers: if not original:
Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id) Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id)
return "" return ""
original = original[0]
base_container_id = original.get("base_file")
base_container = self._container_registry.findContainers(id = base_container_id)
if not base_container:
Logger.log("d", "Unable to duplicate the material with id {material_id}, because base_file {base_container_id} doesn't exist.".format(material_id = material_id, base_container_id = base_container_id))
return ""
base_container = base_container[0]
#We'll copy all containers with the same base.
#This way the correct variant and machine still gets assigned when loading the copy of the material.
containers_to_copy = self._container_registry.findInstanceContainers(base_file = base_container_id)
# Ensure all settings are saved. # Ensure all settings are saved.
Application.getInstance().saveSettings() Application.getInstance().saveSettings()
# Create a new ID & container to hold the data. # Create a new ID & container to hold the data.
new_id = self._container_registry.uniqueName(material_id) new_containers = []
container_type = type(containers[0]) # Could be either a XMLMaterialProfile or a InstanceContainer new_base_id = self._container_registry.uniqueName(base_container.getId())
duplicated_container = container_type(new_id) new_base_container = copy.deepcopy(base_container)
new_base_container.getMetaData()["id"] = new_base_id
new_base_container.getMetaData()["base_file"] = new_base_id
new_containers.append(new_base_container)
# Instead of duplicating we load the data from the basefile again. #Clone all of them.
# This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile clone_of_original = None #Keeping track of which one is the clone of the original material, since we need to return that.
# are also correctly created. for container_to_copy in containers_to_copy:
with open(containers[0].getPath(), encoding="utf-8") as f: #Create unique IDs for every clone.
duplicated_container.deserialize(f.read()) current_id = container_to_copy.getId()
duplicated_container.setDirty(True) new_id = new_base_id
self._container_registry.addContainer(duplicated_container) if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
return self._getMaterialContainerIdForActiveMachine(new_id) new_id += "_" + container_to_copy.getMetaDataEntry("definition")
if container_to_copy.getMetaDataEntry("variant"):
variant = self._container_registry.findContainers(id = container_to_copy.getMetaDataEntry("variant"))[0]
new_id += "_" + variant.getName().replace(" ", "_")
if current_id == material_id:
clone_of_original = new_id
new_container = copy.deepcopy(container_to_copy)
new_container.getMetaData()["id"] = new_id
new_container.getMetaData()["base_file"] = new_base_id
new_containers.append(new_container)
for container_to_add in new_containers:
container_to_add.setDirty(True)
ContainerRegistry.getInstance().addContainer(container_to_add)
return self._getMaterialContainerIdForActiveMachine(clone_of_original)
## Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue ## Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue
# #
@ -800,12 +829,12 @@ class ContainerManager(QObject):
return "" return ""
approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value"))) approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
containers = self._container_registry.findInstanceContainers(id = "generic_pla*", approximate_diameter = approximate_diameter) containers = self._container_registry.findInstanceContainersMetadata(id = "generic_pla*", approximate_diameter = approximate_diameter)
if not containers: if not containers:
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.") Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.")
return "" return ""
base_file = containers[0].getMetaDataEntry("base_file") base_file = containers[0].get("base_file")
containers = self._container_registry.findInstanceContainers(id = base_file) containers = self._container_registry.findInstanceContainers(id = base_file)
if not containers: if not containers:
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.") Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.")
@ -846,14 +875,14 @@ class ContainerManager(QObject):
has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False)) has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False))
if has_machine_materials or has_variant_materials: if has_machine_materials or has_variant_materials:
if has_variants: if has_variants:
materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId) materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
else: else:
materials = self._container_registry.findInstanceContainers(type = "material", base_file = base_file, definition = global_stack.getBottom().getId()) materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
if materials: if materials:
return materials[0].getId() return materials[0]["id"]
Logger.log("w", "Unable to find a suitable container based on %s for the current machine .", base_file) Logger.log("w", "Unable to find a suitable container based on %s for the current machine.", base_file)
return "" # do not activate a new material if a container can not be found return "" # do not activate a new material if a container can not be found
return base_file return base_file
@ -864,25 +893,25 @@ class ContainerManager(QObject):
# \return \type{list} a list of names of materials with the same GUID # \return \type{list} a list of names of materials with the same GUID
@pyqtSlot(str, result = "QStringList") @pyqtSlot(str, result = "QStringList")
def getLinkedMaterials(self, material_id: str): def getLinkedMaterials(self, material_id: str):
containers = self._container_registry.findInstanceContainers(id=material_id) containers = self._container_registry.findInstanceContainersMetadata(id = material_id)
if not containers: if not containers:
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id) Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
return [] return []
material_container = containers[0] material_container = containers[0]
material_base_file = material_container.getMetaDataEntry("base_file", "") material_base_file = material_container.get("base_file", "")
material_guid = material_container.getMetaDataEntry("GUID", "") material_guid = material_container.get("GUID", "")
if not material_guid: if not material_guid:
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id) Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id)
return [] return []
containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_guid) containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_guid)
linked_material_names = [] linked_material_names = []
for container in containers: for container in containers:
if container.getId() in [material_id, material_base_file] or container.getMetaDataEntry("base_file") != container.getId(): if container["id"] in [material_id, material_base_file] or container.get("base_file") != container["id"]:
continue continue
linked_material_names.append(container.getName()) linked_material_names.append(container["name"])
return linked_material_names return linked_material_names
## Unlink a material from all other materials by creating a new GUID ## Unlink a material from all other materials by creating a new GUID
@ -968,14 +997,6 @@ class ContainerManager(QObject):
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list) name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
self._container_name_filters[name_filter] = entry self._container_name_filters[name_filter] = entry
## Get containers filtered by machine type and material if required.
#
# \param kwargs Initial search criteria that the containers need to match.
#
# \return A list of containers matching the search criteria.
def _getFilteredContainers(self, **kwargs):
return QualityManager.getInstance()._getFilteredContainers(**kwargs)
## Creates a unique ID for a container by prefixing the name with the stack ID. ## Creates a unique ID for a container by prefixing the name with the stack ID.
# #
# This method creates a unique ID for a container by prefixing it with a specified stack ID. # This method creates a unique ID for a container by prefixing it with a specified stack ID.
@ -1015,9 +1036,9 @@ class ContainerManager(QObject):
# If the machine specifies qualities should be filtered, ensure we match the current criteria. # If the machine specifies qualities should be filtered, ensure we match the current criteria.
if not machine_definition.getMetaDataEntry("has_machine_quality"): if not machine_definition.getMetaDataEntry("has_machine_quality"):
quality_changes.setDefinition(self._container_registry.findContainers(id = "fdmprinter")[0]) quality_changes.setDefinition("fdmprinter")
else: else:
quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition)) quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition).getId())
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)

View file

@ -44,7 +44,6 @@ class CuraContainerRegistry(ContainerRegistry):
# Global stack based on metadata information. # Global stack based on metadata information.
@override(ContainerRegistry) @override(ContainerRegistry)
def addContainer(self, container): def addContainer(self, container):
# Note: Intentional check with type() because we want to ignore subclasses # Note: Intentional check with type() because we want to ignore subclasses
if type(container) == ContainerStack: if type(container) == ContainerStack:
container = self._convertContainerStack(container) container = self._convertContainerStack(container)
@ -89,8 +88,8 @@ class CuraContainerRegistry(ContainerRegistry):
def _containerExists(self, container_type, container_name): def _containerExists(self, container_type, container_name):
container_class = ContainerStack if container_type == "machine" else InstanceContainer container_class = ContainerStack if container_type == "machine" else InstanceContainer
return self.findContainers(container_class, id = container_name, type = container_type, ignore_case = True) or \ return self.findContainersMetadata(id = container_name, type = container_type, ignore_case = True) or \
self.findContainers(container_class, name = container_name, type = container_type) self.findContainersMetadata(container_type = container_class, name = container_name, type = container_type)
## Exports an profile to a file ## Exports an profile to a file
# #
@ -119,7 +118,7 @@ class CuraContainerRegistry(ContainerRegistry):
found_containers = [] found_containers = []
extruder_positions = [] extruder_positions = []
for instance_id in instance_ids: for instance_id in instance_ids:
containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id) containers = ContainerRegistry.getInstance().findInstanceContainers(id = instance_id)
if containers: if containers:
found_containers.append(containers[0]) found_containers.append(containers[0])
@ -129,9 +128,9 @@ class CuraContainerRegistry(ContainerRegistry):
# Global stack # Global stack
extruder_positions.append(-1) extruder_positions.append(-1)
else: else:
extruder_containers = ContainerRegistry.getInstance().findDefinitionContainers(id=extruder_id) extruder_containers = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = extruder_id)
if extruder_containers: if extruder_containers:
extruder_positions.append(int(extruder_containers[0].getMetaDataEntry("position", 0))) extruder_positions.append(int(extruder_containers[0].get("position", 0)))
else: else:
extruder_positions.append(0) extruder_positions.append(0)
# Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...) # Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...)
@ -274,7 +273,6 @@ class CuraContainerRegistry(ContainerRegistry):
# #
# \return None if configuring was successful or an error message if an error occurred. # \return None if configuring was successful or an error message if an error occurred.
def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]: def _configureProfile(self, profile: InstanceContainer, id_seed: str, new_name: str) -> Optional[str]:
profile.setReadOnly(False)
profile.setDirty(True) # Ensure the profiles are correctly saved profile.setDirty(True) # Ensure the profiles are correctly saved
new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile")) new_id = self.createUniqueName("quality_changes", "", id_seed, catalog.i18nc("@label", "Custom profile"))
@ -292,7 +290,7 @@ class CuraContainerRegistry(ContainerRegistry):
quality_type_criteria = {"quality_type": quality_type} quality_type_criteria = {"quality_type": quality_type}
if self._machineHasOwnQualities(): if self._machineHasOwnQualities():
profile.setDefinition(self._activeQualityDefinition()) profile.setDefinition(self._activeQualityDefinition().getId())
if self._machineHasOwnMaterials(): if self._machineHasOwnMaterials():
active_material_id = self._activeMaterialId() active_material_id = self._activeMaterialId()
if active_material_id and active_material_id != "empty": # only update if there is an active material if active_material_id and active_material_id != "empty": # only update if there is an active material
@ -302,7 +300,7 @@ class CuraContainerRegistry(ContainerRegistry):
quality_type_criteria["definition"] = profile.getDefinition().getId() quality_type_criteria["definition"] = profile.getDefinition().getId()
else: else:
profile.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0]) profile.setDefinition(fdmprinter)
quality_type_criteria["definition"] = "fdmprinter" quality_type_criteria["definition"] = "fdmprinter"
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom() machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
@ -349,7 +347,7 @@ class CuraContainerRegistry(ContainerRegistry):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack: if global_container_stack:
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(global_container_stack.getBottom()) definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(global_container_stack.getBottom())
definition = self.findDefinitionContainers(id=definition_id)[0] definition = self.findDefinitionContainers(id = definition_id)[0]
if definition: if definition:
return definition return definition
@ -534,13 +532,13 @@ class CuraContainerRegistry(ContainerRegistry):
# set after upgrading, because the proper global stack was not yet loaded. This method # set after upgrading, because the proper global stack was not yet loaded. This method
# makes sure those extruders also get the right stack set. # makes sure those extruders also get the right stack set.
def _connectUpgradedExtruderStacksToMachines(self): def _connectUpgradedExtruderStacksToMachines(self):
extruder_stacks = self.findContainers(ExtruderStack.ExtruderStack) extruder_stacks = self.findContainers(container_type = ExtruderStack.ExtruderStack)
for extruder_stack in extruder_stacks: for extruder_stack in extruder_stacks:
if extruder_stack.getNextStack(): if extruder_stack.getNextStack():
# Has the right next stack, so ignore it. # Has the right next stack, so ignore it.
continue continue
machines = ContainerRegistry.getInstance().findContainerStacks(id=extruder_stack.getMetaDataEntry("machine", "")) machines = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack.getMetaDataEntry("machine", ""))
if machines: if machines:
extruder_stack.setNextStack(machines[0]) extruder_stack.setNextStack(machines[0])
else: else:

View file

@ -14,7 +14,7 @@ from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackErro
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.Interfaces import ContainerInterface from UM.Settings.Interfaces import ContainerInterface, DefinitionContainerInterface
from . import Exceptions from . import Exceptions
@ -246,7 +246,7 @@ class CuraContainerStack(ContainerStack):
## Set the definition container. ## Set the definition container.
# #
# \param new_quality_changes The new definition container. It is expected to have a "type" metadata entry with the value "quality_changes". # \param new_quality_changes The new definition container. It is expected to have a "type" metadata entry with the value "quality_changes".
def setDefinition(self, new_definition: DefinitionContainer) -> None: def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
self.replaceContainer(_ContainerIndexes.Definition, new_definition) self.replaceContainer(_ContainerIndexes.Definition, new_definition)
## Set the definition container by an ID. ## Set the definition container by an ID.
@ -487,11 +487,17 @@ class CuraContainerStack(ContainerStack):
search_criteria.pop("name", None) search_criteria.pop("name", None)
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria) materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
if materials: if not materials:
return materials[0] Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id)
return None
for material in materials:
# Prefer a read-only material
if ContainerRegistry.getInstance().isReadOnly(material.getId()):
return material
return materials[0]
Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id)
return None
## Find the quality that should be used as "default" quality. ## Find the quality that should be used as "default" quality.
# #
@ -502,7 +508,7 @@ class CuraContainerStack(ContainerStack):
def findDefaultQuality(self) -> Optional[ContainerInterface]: def findDefaultQuality(self) -> Optional[ContainerInterface]:
definition = self._getMachineDefinition() definition = self._getMachineDefinition()
registry = ContainerRegistry.getInstance() registry = ContainerRegistry.getInstance()
material_container = self.material if self.material != self._empty_instance_container else None material_container = self.material if self.material.getId() not in (self._empty_material.getId(), self._empty_instance_container.getId()) else None
search_criteria = {"type": "quality"} search_criteria = {"type": "quality"}
@ -546,7 +552,7 @@ class CuraContainerStack(ContainerStack):
material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"} material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
if definition.getMetaDataEntry("has_machine_quality"): if definition.getMetaDataEntry("has_machine_quality"):
if self.material != self._empty_instance_container: if self.material != self._empty_instance_container:
material_search_criteria["definition"] = material_container.getDefinition().id material_search_criteria["definition"] = material_container.getMetaDataEntry("definition")
if definition.getMetaDataEntry("has_variants"): if definition.getMetaDataEntry("has_variants"):
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant") material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
@ -557,10 +563,10 @@ class CuraContainerStack(ContainerStack):
material_search_criteria["variant"] = self.variant.id material_search_criteria["variant"] = self.variant.id
else: else:
material_search_criteria["definition"] = "fdmprinter" material_search_criteria["definition"] = "fdmprinter"
material_containers = registry.findInstanceContainers(**material_search_criteria) material_containers = registry.findInstanceContainersMetadata(**material_search_criteria)
# Try all materials to see if there is a quality profile available. # Try all materials to see if there is a quality profile available.
for material_container in material_containers: for material_container in material_containers:
search_criteria["material"] = material_container.getId() search_criteria["material"] = material_container["id"]
containers = registry.findInstanceContainers(**search_criteria) containers = registry.findInstanceContainers(**search_criteria)
if containers: if containers:

View file

@ -3,7 +3,7 @@
from UM.Logger import Logger from UM.Logger import Logger
from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
@ -34,7 +34,7 @@ class CuraStackBuilder:
# Make sure the new name does not collide with any definition or (quality) profile # Make sure the new name does not collide with any definition or (quality) profile
# createUniqueName() only looks at other stacks, but not at definitions or quality profiles # createUniqueName() only looks at other stacks, but not at definitions or quality profiles
# Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true # Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true
if registry.findContainers(id = generated_name): if registry.findContainersMetadata(id = generated_name):
generated_name = registry.uniqueName(generated_name) generated_name = registry.uniqueName(generated_name)
new_global_stack = cls.createGlobalStack( new_global_stack = cls.createGlobalStack(
@ -55,12 +55,12 @@ class CuraStackBuilder:
new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id) new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id)
new_extruder = cls.createExtruderStack( new_extruder = cls.createExtruderStack(
new_extruder_id, new_extruder_id,
definition=extruder_definition, definition = extruder_definition,
machine_definition=machine_definition, machine_definition_id = machine_definition.getId(),
quality="default", quality = "default",
material="default", material = "default",
variant="default", variant = "default",
next_stack=new_global_stack next_stack = new_global_stack
) )
new_global_stack.addExtruder(new_extruder) new_global_stack.addExtruder(new_extruder)
else: else:
@ -74,7 +74,7 @@ class CuraStackBuilder:
new_extruder = cls.createExtruderStack( new_extruder = cls.createExtruderStack(
new_extruder_id, new_extruder_id,
definition = extruder_definition, definition = extruder_definition,
machine_definition = machine_definition, machine_definition_id = machine_definition.getId(),
quality = "default", quality = "default",
material = "default", material = "default",
variant = "default", variant = "default",
@ -88,12 +88,13 @@ class CuraStackBuilder:
# #
# \param new_stack_id The ID of the new stack. # \param new_stack_id The ID of the new stack.
# \param definition The definition to base the new stack on. # \param definition The definition to base the new stack on.
# \param machine_definition The machine definition to use for the user container. # \param machine_definition_id The ID of the machine definition to use for
# the user container.
# \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm" # \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
# #
# \return A new Global stack instance with the specified parameters. # \return A new Global stack instance with the specified parameters.
@classmethod @classmethod
def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainer, machine_definition: DefinitionContainer, **kwargs) -> ExtruderStack: def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, machine_definition_id: str, **kwargs) -> ExtruderStack:
stack = ExtruderStack(new_stack_id) stack = ExtruderStack(new_stack_id)
stack.setName(definition.getName()) stack.setName(definition.getName())
stack.setDefinition(definition) stack.setDefinition(definition)
@ -108,7 +109,7 @@ class CuraStackBuilder:
user_container.addMetaDataEntry("extruder", new_stack_id) user_container.addMetaDataEntry("extruder", new_stack_id)
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(machine_definition) user_container.setDefinition(machine_definition_id)
stack.setUserChanges(user_container) stack.setUserChanges(user_container)
@ -148,7 +149,7 @@ class CuraStackBuilder:
# #
# \return A new Global stack instance with the specified parameters. # \return A new Global stack instance with the specified parameters.
@classmethod @classmethod
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainer, **kwargs) -> GlobalStack: def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, **kwargs) -> GlobalStack:
stack = GlobalStack(new_stack_id) stack = GlobalStack(new_stack_id)
stack.setDefinition(definition) stack.setDefinition(definition)
@ -157,7 +158,7 @@ class CuraStackBuilder:
user_container.addMetaDataEntry("machine", new_stack_id) user_container.addMetaDataEntry("machine", new_stack_id)
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
user_container.setDefinition(definition) user_container.setDefinition(definition.getId())
stack.setUserChanges(user_container) stack.setUserChanges(user_container)
@ -193,8 +194,7 @@ class CuraStackBuilder:
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name) unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
definition_changes_container = InstanceContainer(unique_container_name) definition_changes_container = InstanceContainer(unique_container_name)
definition = container_stack.getBottom() definition_changes_container.setDefinition(container_stack.getBottom().getId())
definition_changes_container.setDefinition(definition)
definition_changes_container.addMetaDataEntry("type", "definition_changes") definition_changes_container.addMetaDataEntry("type", "definition_changes")
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)

View file

@ -130,6 +130,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
self._active_machine_extruders = [] self._active_machine_extruders = []
extruder_manager = Application.getInstance().getExtruderManager() extruder_manager = Application.getInstance().getExtruderManager()
for extruder in extruder_manager.getExtruderStacks(): for extruder in extruder_manager.getExtruderStacks():
if extruder is None: #This extruder wasn't loaded yet. This happens asynchronously while this model is constructed from QML.
continue
extruder.containersChanged.connect(self._onExtruderStackContainersChanged) extruder.containersChanged.connect(self._onExtruderStackContainersChanged)
self._active_machine_extruders.append(extruder) self._active_machine_extruders.append(extruder)

View file

@ -43,15 +43,11 @@ class GlobalStack(CuraContainerStack):
def getLoadingPriority(cls) -> int: def getLoadingPriority(cls) -> int:
return 2 return 2
def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]: @classmethod
configuration_type = None def getConfigurationTypeFromSerialized(cls, serialized: str) -> Optional[str]:
try: configuration_type = super().getConfigurationTypeFromSerialized(serialized)
parser = self._readAndValidateSerialized(serialized) if configuration_type == "machine":
configuration_type = parser["metadata"].get("type") return "machine_stack"
if configuration_type == "machine":
configuration_type = "machine_stack"
except Exception as e:
Logger.log("e", "Could not get configuration type: %s", e)
return configuration_type return configuration_type
## Add an extruder to the list of extruders of this stack. ## Add an extruder to the list of extruders of this stack.
@ -67,7 +63,7 @@ class GlobalStack(CuraContainerStack):
return return
if any(item.getId() == extruder.id for item in self._extruders.values()): if any(item.getId() == extruder.id for item in self._extruders.values()):
Logger.log("w", "Extruder [%s] has already been added to this stack [%s]", extruder.id, self._id) Logger.log("w", "Extruder [%s] has already been added to this stack [%s]", extruder.id, self.getId())
return return
self._extruders[position] = extruder self._extruders[position] = extruder

View file

@ -32,6 +32,7 @@ from .CuraStackBuilder import CuraStackBuilder
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
from cura.Settings.ProfilesModel import ProfilesModel
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING: if TYPE_CHECKING:
@ -60,9 +61,11 @@ class MachineManager(QObject):
self._instance_container_timer = QTimer() self._instance_container_timer = QTimer()
self._instance_container_timer.setInterval(250) self._instance_container_timer.setInterval(250)
self._instance_container_timer.setSingleShot(True) self._instance_container_timer.setSingleShot(True)
self._instance_container_timer.timeout.connect(self.__onInstanceContainersChanged) self._instance_container_timer.timeout.connect(self.__emitChangedSignals)
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
Application.getInstance().getContainerRegistry().containerLoadComplete.connect(self._onInstanceContainersChanged)
self._connected_to_profiles_model = False
## When the global container is changed, active material probably needs to be updated. ## When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged) self.globalContainerChanged.connect(self.activeMaterialChanged)
@ -104,7 +107,7 @@ class MachineManager(QObject):
# There might already be some output devices by the time the signal is connected # There might already be some output devices by the time the signal is connected
self._onOutputDevicesChanged() self._onOutputDevicesChanged()
if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacks(id = active_machine_id): if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id):
# An active machine was saved, so restore it. # An active machine was saved, so restore it.
self.setActiveMachine(active_machine_id) self.setActiveMachine(active_machine_id)
# Make sure _active_container_stack is properly initiated # Make sure _active_container_stack is properly initiated
@ -117,6 +120,10 @@ class MachineManager(QObject):
"The selected material is incompatible with the selected machine or configuration."), "The selected material is incompatible with the selected machine or configuration."),
title = catalog.i18nc("@info:title", "Incompatible Material")) title = catalog.i18nc("@info:title", "Incompatible Material"))
containers = ContainerRegistry.getInstance().findInstanceContainers(id = self.activeMaterialId)
if containers:
containers[0].nameChanged.connect(self._onMaterialNameChanged)
globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value) globalContainerChanged = pyqtSignal() # Emitted whenever the global stack is changed (ie: when changing between printers, changing a global profile, but not when changing a value)
activeMaterialChanged = pyqtSignal() activeMaterialChanged = pyqtSignal()
activeVariantChanged = pyqtSignal() activeVariantChanged = pyqtSignal()
@ -166,7 +173,7 @@ class MachineManager(QObject):
if not self._global_container_stack: if not self._global_container_stack:
return return
containers = ContainerRegistry.getInstance().findInstanceContainers(type="variant", definition=self._global_container_stack.getBottom().getId(), name=hotend_id) containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "variant", definition = self._global_container_stack.definition.getId(), name = hotend_id)
if containers: # New material ID is known if containers: # New material ID is known
extruder_manager = ExtruderManager.getInstance() extruder_manager = ExtruderManager.getInstance()
machine_id = self.activeMachineId machine_id = self.activeMachineId
@ -178,10 +185,10 @@ class MachineManager(QObject):
break break
if matching_extruder and matching_extruder.variant.getName() != hotend_id: if matching_extruder and matching_extruder.variant.getName() != hotend_id:
# Save the material that needs to be changed. Multiple changes will be handled by the callback. # Save the material that needs to be changed. Multiple changes will be handled by the callback.
self._auto_hotends_changed[str(index)] = containers[0].getId() self._auto_hotends_changed[str(index)] = containers[0]["id"]
self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback) self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback)
else: else:
Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.getBottom().getId(), hotend_id)) Logger.log("w", "No variant found for printer definition %s with id %s" % (self._global_container_stack.definition.getId(), hotend_id))
def _onMaterialIdChanged(self, index: Union[str, int], material_id: str): def _onMaterialIdChanged(self, index: Union[str, int], material_id: str):
if not self._global_container_stack: if not self._global_container_stack:
@ -191,7 +198,7 @@ class MachineManager(QObject):
if self._global_container_stack.getMetaDataEntry("has_machine_materials", False): if self._global_container_stack.getMetaDataEntry("has_machine_materials", False):
definition_id = self.activeQualityDefinitionId definition_id = self.activeQualityDefinitionId
extruder_manager = ExtruderManager.getInstance() extruder_manager = ExtruderManager.getInstance()
containers = ContainerRegistry.getInstance().findInstanceContainers(type = "material", definition = definition_id, GUID = material_id) containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "material", definition = definition_id, GUID = material_id)
if containers: # New material ID is known if containers: # New material ID is known
extruders = list(extruder_manager.getMachineExtruders(self.activeMachineId)) extruders = list(extruder_manager.getMachineExtruders(self.activeMachineId))
matching_extruder = None matching_extruder = None
@ -202,15 +209,15 @@ class MachineManager(QObject):
if matching_extruder and matching_extruder.material.getMetaDataEntry("GUID") != material_id: if matching_extruder and matching_extruder.material.getMetaDataEntry("GUID") != material_id:
# Save the material that needs to be changed. Multiple changes will be handled by the callback. # Save the material that needs to be changed. Multiple changes will be handled by the callback.
if self._global_container_stack.getBottom().getMetaDataEntry("has_variants") and matching_extruder.variant: if self._global_container_stack.definition.getMetaDataEntry("has_variants") and matching_extruder.variant:
variant_id = self.getQualityVariantId(self._global_container_stack.getBottom(), matching_extruder.variant) variant_id = self.getQualityVariantId(self._global_container_stack.definition, matching_extruder.variant)
for container in containers: for container in containers:
if container.getMetaDataEntry("variant") == variant_id: if container.get("variant") == variant_id:
self._auto_materials_changed[str(index)] = container.getId() self._auto_materials_changed[str(index)] = container["id"]
break break
else: else:
# Just use the first result we found. # Just use the first result we found.
self._auto_materials_changed[str(index)] = containers[0].getId() self._auto_materials_changed[str(index)] = containers[0]["id"]
self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback) self._printer_output_devices[0].materialHotendChangedMessage(self._materialHotendChangedCallback)
else: else:
Logger.log("w", "No material definition found for printer definition %s and GUID %s" % (definition_id, material_id)) Logger.log("w", "No material definition found for printer definition %s and GUID %s" % (definition_id, material_id))
@ -328,14 +335,24 @@ class MachineManager(QObject):
# on _active_container_stack. If it changes, then the properties change. # on _active_container_stack. If it changes, then the properties change.
self.activeQualityChanged.emit() self.activeQualityChanged.emit()
def __onInstanceContainersChanged(self): def __emitChangedSignals(self):
self.activeQualityChanged.emit() self.activeQualityChanged.emit()
self.activeVariantChanged.emit() self.activeVariantChanged.emit()
self.activeMaterialChanged.emit() self.activeMaterialChanged.emit()
self._updateStacksHaveErrors() # Prevents unwanted re-slices after changing machine self._updateStacksHaveErrors() # Prevents unwanted re-slices after changing machine
self._error_check_timer.start() self._error_check_timer.start()
def _onProfilesModelChanged(self, *args):
self.__emitChangedSignals()
def _onInstanceContainersChanged(self, container): def _onInstanceContainersChanged(self, container):
# This should not trigger the ProfilesModel to be created, or there will be an infinite recursion
if not self._connected_to_profiles_model and ProfilesModel.hasInstance():
# This triggers updating the qualityModel in SidebarSimple whenever ProfilesModel is updated
Logger.log("d", "Connecting profiles model...")
ProfilesModel.getInstance().itemsChanged.connect(self._onProfilesModelChanged)
self._connected_to_profiles_model = True
self._instance_container_timer.start() self._instance_container_timer.start()
def _onPropertyChanged(self, key: str, property_name: str): def _onPropertyChanged(self, key: str, property_name: str):
@ -355,7 +372,7 @@ class MachineManager(QObject):
if containers: if containers:
Application.getInstance().setGlobalContainerStack(containers[0]) Application.getInstance().setGlobalContainerStack(containers[0])
self.__onInstanceContainersChanged() self.__emitChangedSignals()
@pyqtSlot(str, str) @pyqtSlot(str, str)
def addMachine(self, name: str, definition_id: str) -> None: def addMachine(self, name: str, definition_id: str) -> None:
@ -703,10 +720,7 @@ class MachineManager(QObject):
## Check if a container is read_only ## Check if a container is read_only
@pyqtSlot(str, result = bool) @pyqtSlot(str, result = bool)
def isReadOnly(self, container_id: str) -> bool: def isReadOnly(self, container_id: str) -> bool:
containers = ContainerRegistry.getInstance().findInstanceContainers(id = container_id) return ContainerRegistry.getInstance().isReadOnly(container_id)
if not containers or not self._active_container_stack:
return True
return containers[0].isReadOnly()
## Copy the value of the setting of the current extruder to all other extruders as well as the global container. ## Copy the value of the setting of the current extruder to all other extruders as well as the global container.
@pyqtSlot(str) @pyqtSlot(str)
@ -774,7 +788,7 @@ class MachineManager(QObject):
if quality_type: if quality_type:
candidate_quality = quality_manager.findQualityByQualityType(quality_type, candidate_quality = quality_manager.findQualityByQualityType(quality_type,
quality_manager.getWholeMachineDefinition(global_stack.definition), quality_manager.getWholeMachineDefinition(global_stack.definition),
[material_container]) [material_container.getMetaData()])
if not candidate_quality or candidate_quality.getId() == self._empty_quality_changes_container: if not candidate_quality or candidate_quality.getId() == self._empty_quality_changes_container:
Logger.log("d", "Attempting to find fallback quality") Logger.log("d", "Attempting to find fallback quality")
@ -822,7 +836,7 @@ class MachineManager(QObject):
preferred_material_name = None preferred_material_name = None
if old_material: if old_material:
preferred_material_name = old_material.getName() preferred_material_name = old_material.getName()
preferred_material_id = self._updateMaterialContainer(self._global_container_stack.getBottom(), self._global_container_stack, containers[0], preferred_material_name).id preferred_material_id = self._updateMaterialContainer(self._global_container_stack.definition, self._global_container_stack, containers[0], preferred_material_name).id
self.setActiveMaterial(preferred_material_id) self.setActiveMaterial(preferred_material_id)
else: else:
Logger.log("w", "While trying to set the active variant, no variant was found to replace.") Logger.log("w", "While trying to set the active variant, no variant was found to replace.")
@ -834,15 +848,15 @@ class MachineManager(QObject):
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
self.blurSettings.emit() self.blurSettings.emit()
containers = ContainerRegistry.getInstance().findInstanceContainers(id = quality_id) containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = quality_id)
if not containers or not self._global_container_stack: if not containers or not self._global_container_stack:
return return
# Quality profile come in two flavours: type=quality and type=quality_changes # Quality profile come in two flavours: type=quality and type=quality_changes
# If we found a quality_changes profile then look up its parent quality profile. # If we found a quality_changes profile then look up its parent quality profile.
container_type = containers[0].getMetaDataEntry("type") container_type = containers[0].get("type")
quality_name = containers[0].getName() quality_name = containers[0]["name"]
quality_type = containers[0].getMetaDataEntry("quality_type") quality_type = containers[0].get("quality_type")
# Get quality container and optionally the quality_changes container. # Get quality container and optionally the quality_changes container.
if container_type == "quality": if container_type == "quality":
@ -850,7 +864,7 @@ class MachineManager(QObject):
elif container_type == "quality_changes": elif container_type == "quality_changes":
new_quality_settings_list = self._determineQualityAndQualityChangesForQualityChanges(quality_name) new_quality_settings_list = self._determineQualityAndQualityChangesForQualityChanges(quality_name)
else: else:
Logger.log("e", "Tried to set quality to a container that is not of the right type") Logger.log("e", "Tried to set quality to a container that is not of the right type: {container_id}".format(container_id = containers[0]["id"]))
return return
# Check if it was at all possible to find new settings # Check if it was at all possible to find new settings
@ -939,18 +953,18 @@ class MachineManager(QObject):
if not global_container_stack: if not global_container_stack:
return [] return []
global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.getBottom()) global_machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks() extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
# find qualities for extruders # find qualities for extruders
for extruder_stack in extruder_stacks: for extruder_stack in extruder_stacks:
material = extruder_stack.material material_metadata = extruder_stack.material.getMetaData()
# TODO: fix this # TODO: fix this
if self._new_material_container and extruder_stack.getId() == self._active_container_stack.getId(): if self._new_material_container and extruder_stack.getId() == self._active_container_stack.getId():
material = self._new_material_container material_metadata = self._new_material_container.getMetaData()
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material_metadata])
if not quality: if not quality:
# No quality profile is found for this quality type. # No quality profile is found for this quality type.
@ -1000,12 +1014,6 @@ class MachineManager(QObject):
Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name) Logger.log("e", "Could not find the global quality changes container with name %s", quality_changes_name)
return None return None
material = global_container_stack.material
# find a quality type that matches both machine and materials
if self._new_material_container and self._active_container_stack.getId() == global_container_stack.getId():
material = self._new_material_container
# For the global stack, find a quality which matches the quality_type in # For the global stack, find a quality which matches the quality_type in
# the quality changes profile and also satisfies any material constraints. # the quality changes profile and also satisfies any material constraints.
quality_type = global_quality_changes.getMetaDataEntry("quality_type") quality_type = global_quality_changes.getMetaDataEntry("quality_type")
@ -1025,12 +1033,12 @@ class MachineManager(QObject):
if not quality_changes: if not quality_changes:
quality_changes = self._empty_quality_changes_container quality_changes = self._empty_quality_changes_container
material = extruder_stack.material material_metadata = extruder_stack.material.getMetaData()
if self._new_material_container and self._active_container_stack.getId() == extruder_stack.getId(): if self._new_material_container and self._active_container_stack.getId() == extruder_stack.getId():
material = self._new_material_container material_metadata = self._new_material_container.getMetaData()
quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material]) quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material_metadata])
if not quality: if not quality:
# No quality profile found for this quality type. # No quality profile found for this quality type.
@ -1043,7 +1051,7 @@ class MachineManager(QObject):
}) })
# append the global quality changes # append the global quality changes
global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, [material], global_quality = "True") global_quality = quality_manager.findQualityByQualityType(quality_type, global_machine_definition, global_quality = "True")
# if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482 # if there is not global quality but we're using a single extrusion machine, copy the quality of the first extruder - CURA-4482
if not global_quality and len(extruder_stacks) == 1: if not global_quality and len(extruder_stacks) == 1:
@ -1097,18 +1105,14 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = globalContainerChanged) @pyqtProperty(str, notify = globalContainerChanged)
def activeDefinitionId(self) -> str: def activeDefinitionId(self) -> str:
if self._global_container_stack: if self._global_container_stack:
definition = self._global_container_stack.getBottom() return self._global_container_stack.definition.id
if definition:
return definition.id
return "" return ""
@pyqtProperty(str, notify=globalContainerChanged) @pyqtProperty(str, notify=globalContainerChanged)
def activeDefinitionName(self) -> str: def activeDefinitionName(self) -> str:
if self._global_container_stack: if self._global_container_stack:
definition = self._global_container_stack.getBottom() return self._global_container_stack.definition.getName()
if definition:
return definition.getName()
return "" return ""
@ -1118,7 +1122,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = globalContainerChanged) @pyqtProperty(str, notify = globalContainerChanged)
def activeQualityDefinitionId(self) -> str: def activeQualityDefinitionId(self) -> str:
if self._global_container_stack: if self._global_container_stack:
return self.getQualityDefinitionId(self._global_container_stack.getBottom()) return self.getQualityDefinitionId(self._global_container_stack.definition)
return "" return ""
## Get the Definition ID to use to select quality profiles for machines of the specified definition ## Get the Definition ID to use to select quality profiles for machines of the specified definition
@ -1136,7 +1140,7 @@ class MachineManager(QObject):
if self._active_container_stack: if self._active_container_stack:
variant = self._active_container_stack.variant variant = self._active_container_stack.variant
if variant: if variant:
return self.getQualityVariantId(self._global_container_stack.getBottom(), variant) return self.getQualityVariantId(self._global_container_stack.definition, variant)
return "" return ""
## Get the Variant ID to use to select quality profiles for variants of the specified definitions ## Get the Variant ID to use to select quality profiles for variants of the specified definitions
@ -1160,7 +1164,7 @@ class MachineManager(QObject):
def activeDefinitionVariantsName(self) -> str: def activeDefinitionVariantsName(self) -> str:
fallback_title = catalog.i18nc("@label", "Nozzle") fallback_title = catalog.i18nc("@label", "Nozzle")
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.getBottom().getMetaDataEntry("variants_name", fallback_title) return self._global_container_stack.definition.getMetaDataEntry("variants_name", fallback_title)
return fallback_title return fallback_title
@ -1169,7 +1173,7 @@ class MachineManager(QObject):
container_registry = ContainerRegistry.getInstance() container_registry = ContainerRegistry.getInstance()
machine_stack = container_registry.findContainerStacks(id = machine_id) machine_stack = container_registry.findContainerStacks(id = machine_id)
if machine_stack: if machine_stack:
new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].getBottom().getName()) new_name = container_registry.createUniqueName("machine", machine_stack[0].getName(), new_name, machine_stack[0].definition.getName())
machine_stack[0].setName(new_name) machine_stack[0].setName(new_name)
self.globalContainerChanged.emit() self.globalContainerChanged.emit()
@ -1180,15 +1184,15 @@ class MachineManager(QObject):
# activate a new machine before removing a machine because this is safer # activate a new machine before removing a machine because this is safer
if activate_new_machine: if activate_new_machine:
machine_stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine") machine_stacks = ContainerRegistry.getInstance().findContainerStacksMetadata(type = "machine")
other_machine_stacks = [s for s in machine_stacks if s.getId() != machine_id] other_machine_stacks = [s for s in machine_stacks if s["id"] != machine_id]
if other_machine_stacks: if other_machine_stacks:
self.setActiveMachine(other_machine_stacks[0].getId()) self.setActiveMachine(other_machine_stacks[0]["id"])
ExtruderManager.getInstance().removeMachineExtruders(machine_id) ExtruderManager.getInstance().removeMachineExtruders(machine_id)
containers = ContainerRegistry.getInstance().findInstanceContainers(type = "user", machine = machine_id) containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers: for container in containers:
ContainerRegistry.getInstance().removeContainer(container.getId()) ContainerRegistry.getInstance().removeContainer(container["id"])
ContainerRegistry.getInstance().removeContainer(machine_id) ContainerRegistry.getInstance().removeContainer(machine_id)
@pyqtProperty(bool, notify = globalContainerChanged) @pyqtProperty(bool, notify = globalContainerChanged)
@ -1225,9 +1229,9 @@ class MachineManager(QObject):
# \returns DefinitionID (string) if found, None otherwise # \returns DefinitionID (string) if found, None otherwise
@pyqtSlot(str, result = str) @pyqtSlot(str, result = str)
def getDefinitionByMachineId(self, machine_id: str) -> str: def getDefinitionByMachineId(self, machine_id: str) -> str:
containers = ContainerRegistry.getInstance().findContainerStacks(id=machine_id) containers = ContainerRegistry.getInstance().findContainerStacks(id = machine_id)
if containers: if containers:
return containers[0].getBottom().getId() return containers[0].definition.getId()
@staticmethod @staticmethod
def createMachineManager(): def createMachineManager():

View file

@ -1,6 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Any, List
from UM.Settings.ContainerRegistry import ContainerRegistry #To listen for changes to the materials. from UM.Settings.ContainerRegistry import ContainerRegistry #To listen for changes to the materials.
from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel #We're extending this class. from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel #We're extending this class.
@ -18,8 +19,19 @@ class MaterialsModel(InstanceContainersModel):
# \param container The container whose metadata was changed. # \param container The container whose metadata was changed.
def _onContainerMetaDataChanged(self, container): def _onContainerMetaDataChanged(self, container):
if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed. if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed.
self._update() self._container_change_timer.start()
def _onContainerChanged(self, container): def _onContainerChanged(self, container):
if container.getMetaDataEntry("type", "") == "material": if container.getMetaDataEntry("type", "") == "material":
super()._onContainerChanged(container) super()._onContainerChanged(container)
## Group brand together
def _sortKey(self, item) -> List[Any]:
result = []
result.append(item["metadata"]["brand"])
result.append(item["metadata"]["material"])
result.append(item["metadata"]["name"])
result.append(item["metadata"]["color_name"])
result.append(item["metadata"]["id"])
result.extend(super()._sortKey(item))
return result

View file

@ -49,6 +49,10 @@ class ProfilesModel(InstanceContainersModel):
ProfilesModel.__instance = cls() ProfilesModel.__instance = cls()
return ProfilesModel.__instance return ProfilesModel.__instance
@classmethod
def hasInstance(cls) -> bool:
return ProfilesModel.__instance is not None
__instance = None # type: "ProfilesModel" __instance = None # type: "ProfilesModel"
## Fetch the list of containers to display. ## Fetch the list of containers to display.
@ -57,8 +61,7 @@ class ProfilesModel(InstanceContainersModel):
def _fetchInstanceContainers(self): def _fetchInstanceContainers(self):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None: if global_container_stack is None:
return [] return {}, {}
global_stack_definition = global_container_stack.definition global_stack_definition = global_container_stack.definition
# Get the list of extruders and place the selected extruder at the front of the list. # Get the list of extruders and place the selected extruder at the front of the list.
@ -88,11 +91,10 @@ class ProfilesModel(InstanceContainersModel):
not_supported_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] not_supported_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
result.append(not_supported_container) result.append(not_supported_container)
return result return {item.getId():item for item in result}, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
## Re-computes the items in this model, and adds the layer height role. ## Re-computes the items in this model, and adds the layer height role.
def _recomputeItems(self): def _recomputeItems(self):
# Some globals that we can re-use. # Some globals that we can re-use.
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack is None: if global_container_stack is None:
@ -112,8 +114,12 @@ class ProfilesModel(InstanceContainersModel):
# active machine and material, and later yield the right ones. # active machine and material, and later yield the right ones.
tmp_all_quality_items = OrderedDict() tmp_all_quality_items = OrderedDict()
for item in super()._recomputeItems(): for item in super()._recomputeItems():
profile = container_registry.findContainers(id=item["id"])
quality_type = profile[0].getMetaDataEntry("quality_type") if profile else "" profiles = container_registry.findContainersMetadata(id = item["id"])
if not profiles or "quality_type" not in profiles[0]:
quality_type = ""
else:
quality_type = profiles[0]["quality_type"]
if quality_type not in tmp_all_quality_items: if quality_type not in tmp_all_quality_items:
tmp_all_quality_items[quality_type] = {"suitable_container": None, "all_containers": []} tmp_all_quality_items[quality_type] = {"suitable_container": None, "all_containers": []}

View file

@ -18,7 +18,7 @@ class QualityAndUserProfilesModel(ProfilesModel):
def _fetchInstanceContainers(self): def _fetchInstanceContainers(self):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack: if not global_container_stack:
return [] return {}, {}
# Fetch the list of quality changes. # Fetch the list of quality changes.
quality_manager = QualityManager.getInstance() quality_manager = QualityManager.getInstance()
@ -35,10 +35,12 @@ class QualityAndUserProfilesModel(ProfilesModel):
# Filter the quality_change by the list of available quality_types # Filter the quality_change by the list of available quality_types
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
filtered_quality_changes = [qc for qc in quality_changes_list if filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if
qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("quality_type") in quality_type_set and
qc.getMetaDataEntry("extruder") is not None and qc.getMetaDataEntry("extruder") is not None and
(qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())] qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
return quality_list + filtered_quality_changes result = filtered_quality_changes
result.update({q.getId():q for q in quality_list})
return result, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.

View file

@ -92,7 +92,6 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel):
items = [] items = []
settings = collections.OrderedDict()
definition_container = Application.getInstance().getGlobalContainerStack().getBottom() definition_container = Application.getInstance().getGlobalContainerStack().getBottom()
containers = self._container_registry.findInstanceContainers(id = self._quality_id) containers = self._container_registry.findInstanceContainers(id = self._quality_id)

View file

@ -1,7 +1,7 @@
# Copyright (c) 2017 Ultimaker B.V. # Copyright (c) 2017 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from UM.Application import Application
from UM.Application import Application
from cura.QualityManager import QualityManager from cura.QualityManager import QualityManager
from cura.Settings.ProfilesModel import ProfilesModel from cura.Settings.ProfilesModel import ProfilesModel
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
@ -12,13 +12,23 @@ class UserProfilesModel(ProfilesModel):
def __init__(self, parent = None): def __init__(self, parent = None):
super().__init__(parent) super().__init__(parent)
#Need to connect to the metaDataChanged signal of the active materials.
self.__current_extruders = []
self.__current_materials = []
Application.getInstance().getExtruderManager().extrudersChanged.connect(self.__onExtrudersChanged)
self.__onExtrudersChanged()
self.__current_materials = [extruder.material for extruder in self.__current_extruders]
for material in self.__current_materials:
material.metaDataChanged.connect(self._onContainerChanged)
## Fetch the list of containers to display. ## Fetch the list of containers to display.
# #
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers(). # See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
def _fetchInstanceContainers(self): def _fetchInstanceContainers(self):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if not global_container_stack: if not global_container_stack:
return [] return {}, {}
# Fetch the list of quality changes. # Fetch the list of quality changes.
quality_manager = QualityManager.getInstance() quality_manager = QualityManager.getInstance()
@ -35,10 +45,36 @@ class UserProfilesModel(ProfilesModel):
# Filter the quality_change by the list of available quality_types # Filter the quality_change by the list of available quality_types
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
filtered_quality_changes = [qc for qc in quality_changes_list if
filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if
qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("quality_type") in quality_type_set and
qc.getMetaDataEntry("extruder") is not None and qc.getMetaDataEntry("extruder") is not None and
(qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())] qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
return filtered_quality_changes return filtered_quality_changes, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
## Called when a container changed on an extruder stack.
#
# If it's the material we need to connect to the metaDataChanged signal of
# that.
def __onContainerChanged(self, new_container):
#Careful not to update when a quality or quality changes profile changed!
#If you then update you're going to have an infinite recursion because the update may change the container.
if new_container.getMetaDataEntry("type") == "material":
for material in self.__current_materials:
material.metaDataChanged.disconnect(self._onContainerChanged)
self.__current_materials = [extruder.material for extruder in self.__current_extruders]
for material in self.__current_materials:
material.metaDataChanged.connect(self._onContainerChanged)
## Called when the current set of extruders change.
#
# This makes sure that we are listening to the signal for when the
# materials change.
def __onExtrudersChanged(self):
for extruder in self.__current_extruders:
extruder.containersChanged.disconnect(self.__onContainerChanged)
self.__current_extruders = Application.getInstance().getExtruderManager().getExtruderStacks()
for extruder in self.__current_extruders:
extruder.containersChanged.connect(self.__onContainerChanged)

View file

@ -12,7 +12,8 @@ from UM.Platform import Platform
#WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612 #WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612
if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX if Platform.isLinux(): # Needed for platform.linux_distribution, which is not available on Windows and OSX
# For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826 # For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
if platform.linux_distribution()[0] in ("debian", "Ubuntu", "LinuxMint"): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix. linux_distro_name = platform.linux_distribution()[0].lower()
if linux_distro_name in ("debian", "ubuntu", "linuxmint", "fedora"): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
import ctypes import ctypes
from ctypes.util import find_library from ctypes.util import find_library
libGL = find_library("GL") libGL = find_library("GL")

View file

@ -117,7 +117,7 @@ class ThreeMFReader(MeshReader):
# Get the definition & set it # Get the definition & set it
definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom()) definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom())
um_node.callDecoration("getStack").getTop().setDefinition(definition) um_node.callDecoration("getStack").getTop().setDefinition(definition.getId())
setting_container = um_node.callDecoration("getStack").getTop() setting_container = um_node.callDecoration("getStack").getTop()

View file

@ -122,7 +122,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace") Logger.log("w", "Could not find reader that was able to read the scene data for 3MF workspace")
return WorkspaceReader.PreReadResult.failed return WorkspaceReader.PreReadResult.failed
machine_name = ""
machine_type = "" machine_type = ""
variant_type_name = i18n_catalog.i18nc("@label", "Nozzle") variant_type_name = i18n_catalog.i18nc("@label", "Nozzle")
@ -133,9 +132,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# A few lists of containers in this project files. # A few lists of containers in this project files.
# When loading the global stack file, it may be associated with those containers, which may or may not be # When loading the global stack file, it may be associated with those containers, which may or may not be
# in Cura already, so we need to provide them as alternative search lists. # in Cura already, so we need to provide them as alternative search lists.
definition_container_list = []
instance_container_list = [] instance_container_list = []
material_container_list = []
resolve_strategy_keys = ["machine", "material", "quality_changes"] resolve_strategy_keys = ["machine", "material", "quality_changes"]
self._resolve_strategies = {k: None for k in resolve_strategy_keys} self._resolve_strategies = {k: None for k in resolve_strategy_keys}
@ -149,21 +146,20 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
for each_definition_container_file in definition_container_files: for each_definition_container_file in definition_container_files:
container_id = self._stripFileToId(each_definition_container_file) container_id = self._stripFileToId(each_definition_container_file)
definitions = self._container_registry.findDefinitionContainers(id=container_id) definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id)
if not definitions: if not definitions:
definition_container = DefinitionContainer(container_id) definition_container = DefinitionContainer(container_id)
definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"), definition_container.deserialize(archive.open(each_definition_container_file).read().decode("utf-8"), file_name = each_definition_container_file)
file_name = each_definition_container_file) definition_container = definition_container.getMetaData()
else: else:
definition_container = definitions[0] definition_container = definitions[0]
definition_container_list.append(definition_container)
definition_container_type = definition_container.getMetaDataEntry("type") definition_container_type = definition_container.get("type")
if definition_container_type == "machine": if definition_container_type == "machine":
machine_type = definition_container.getName() machine_type = definition_container["name"]
variant_type_name = definition_container.getMetaDataEntry("variants_name", variant_type_name) variant_type_name = definition_container.get("variants_name", variant_type_name)
machine_definition_container_count += 1 machine_definition_container_count += 1
elif definition_container_type == "extruder": elif definition_container_type == "extruder":
@ -187,11 +183,10 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)] material_container_files = [name for name in cura_file_names if name.endswith(self._material_container_suffix)]
for material_container_file in material_container_files: for material_container_file in material_container_files:
container_id = self._stripFileToId(material_container_file) container_id = self._stripFileToId(material_container_file)
materials = self._container_registry.findInstanceContainers(id=container_id)
material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8"))) material_labels.append(self._getMaterialLabelFromSerialized(archive.open(material_container_file).read().decode("utf-8")))
if materials: if self._container_registry.findContainersMetadata(id = container_id): #This material already exists.
containers_found_dict["material"] = True containers_found_dict["material"] = True
if not materials[0].isReadOnly(): # Only non readonly materials can be in conflict if not self._container_registry.isReadOnly(container_id): # Only non readonly materials can be in conflict
material_conflict = True material_conflict = True
Job.yieldThread() Job.yieldThread()
@ -462,7 +457,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
extruder_stack_id_map = {} # new and old ExtruderStack IDs map extruder_stack_id_map = {} # new and old ExtruderStack IDs map
if self._resolve_strategies["machine"] == "new": if self._resolve_strategies["machine"] == "new":
# We need a new id if the id already exists # We need a new id if the id already exists
if self._container_registry.findContainerStacks(id = global_stack_id_original): if self._container_registry.findContainerStacksMetadata(id = global_stack_id_original):
global_stack_id_new = self.getNewId(global_stack_id_original) global_stack_id_new = self.getNewId(global_stack_id_original)
global_stack_need_rename = True global_stack_need_rename = True
@ -471,7 +466,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
for each_extruder_stack_file in extruder_stack_files: for each_extruder_stack_file in extruder_stack_files:
old_container_id = self._stripFileToId(each_extruder_stack_file) old_container_id = self._stripFileToId(each_extruder_stack_file)
new_container_id = old_container_id new_container_id = old_container_id
if self._container_registry.findContainerStacks(id = old_container_id): if self._container_registry.findContainerStacksMetadata(id = old_container_id):
# get a new name for this extruder # get a new name for this extruder
new_container_id = self.getNewId(old_container_id) new_container_id = self.getNewId(old_container_id)
@ -485,7 +480,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)] definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
for definition_container_file in definition_container_files: for definition_container_file in definition_container_files:
container_id = self._stripFileToId(definition_container_file) container_id = self._stripFileToId(definition_container_file)
definitions = self._container_registry.findDefinitionContainers(id = container_id) definitions = self._container_registry.findDefinitionContainersMetadata(id = container_id)
if not definitions: if not definitions:
definition_container = DefinitionContainer(container_id) definition_container = DefinitionContainer(container_id)
definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"), definition_container.deserialize(archive.open(definition_container_file).read().decode("utf-8"),
@ -512,7 +507,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
containers_to_add.append(material_container) containers_to_add.append(material_container)
else: else:
material_container = materials[0] material_container = materials[0]
if not material_container.isReadOnly(): # Only create new materials if they are not read only. if not self._container_registry.isReadOnly(container_id): # Only create new materials if they are not read only.
if self._resolve_strategies["material"] == "override": if self._resolve_strategies["material"] == "override":
material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"), material_container.deserialize(archive.open(material_container_file).read().decode("utf-8"),
file_name = material_container_file) file_name = material_container_file)
@ -579,7 +574,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if old_extruder_id: if old_extruder_id:
new_extruder_id = extruder_stack_id_map[old_extruder_id] new_extruder_id = extruder_stack_id_map[old_extruder_id]
new_id = new_extruder_id + "_current_settings" new_id = new_extruder_id + "_current_settings"
instance_container._id = new_id instance_container.setMetaDataEntry("id", new_id)
instance_container.setName(new_id) instance_container.setName(new_id)
instance_container.setMetaDataEntry("extruder", new_extruder_id) instance_container.setMetaDataEntry("extruder", new_extruder_id)
containers_to_add.append(instance_container) containers_to_add.append(instance_container)
@ -588,7 +583,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
if machine_id: if machine_id:
new_machine_id = self.getNewId(machine_id) new_machine_id = self.getNewId(machine_id)
new_id = new_machine_id + "_current_settings" new_id = new_machine_id + "_current_settings"
instance_container._id = new_id instance_container.setMetadataEntry("id", new_id)
instance_container.setName(new_id) instance_container.setName(new_id)
instance_container.setMetaDataEntry("machine", new_machine_id) instance_container.setMetaDataEntry("machine", new_machine_id)
containers_to_add.append(instance_container) containers_to_add.append(instance_container)
@ -644,7 +639,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
machine_extruder_count = definition_changes_extruder_count machine_extruder_count = definition_changes_extruder_count
else: else:
existing_container = self._container_registry.findInstanceContainers(id = container_id) existing_container = self._container_registry.findInstanceContainersMetadata(id = container_id)
if not existing_container: if not existing_container:
containers_to_add.append(instance_container) containers_to_add.append(instance_container)
if global_stack_need_rename: if global_stack_need_rename:
@ -670,14 +665,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
# HACK # HACK
# There is a machine, check if it has authentication data. If so, keep that data. # There is a machine, check if it has authentication data. If so, keep that data.
network_authentication_id = container_stacks[0].getMetaDataEntry("network_authentication_id") network_authentication_id = stack.getMetaDataEntry("network_authentication_id")
network_authentication_key = container_stacks[0].getMetaDataEntry("network_authentication_key") network_authentication_key = stack.getMetaDataEntry("network_authentication_key")
container_stacks[0].deserialize(archive.open(global_stack_file).read().decode("utf-8"), stack.deserialize(archive.open(global_stack_file).read().decode("utf-8"), file_name = global_stack_file)
file_name = global_stack_file)
if network_authentication_id: if network_authentication_id:
container_stacks[0].addMetaDataEntry("network_authentication_id", network_authentication_id) stack.addMetaDataEntry("network_authentication_id", network_authentication_id)
if network_authentication_key: if network_authentication_key:
container_stacks[0].addMetaDataEntry("network_authentication_key", network_authentication_key) stack.addMetaDataEntry("network_authentication_key", network_authentication_key)
elif self._resolve_strategies["machine"] == "new": elif self._resolve_strategies["machine"] == "new":
# create a new global stack # create a new global stack

View file

@ -97,7 +97,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter):
file_in_archive.compress_type = zipfile.ZIP_DEFLATED file_in_archive.compress_type = zipfile.ZIP_DEFLATED
# Do not include the network authentication keys # Do not include the network authentication keys
ignore_keys = ["network_authentication_id", "network_authentication_key"] ignore_keys = {"network_authentication_id", "network_authentication_key"}
serialized_data = container.serialize(ignored_metadata_keys = ignore_keys) serialized_data = container.serialize(ignored_metadata_keys = ignore_keys)
archive.writestr(file_in_archive, serialized_data) archive.writestr(file_in_archive, serialized_data)

View file

@ -86,6 +86,7 @@ class CuraEngineBackend(QObject, Backend):
# #
self._global_container_stack = None self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
Application.getInstance().getExtruderManager().activeExtruderChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged() self._onGlobalStackChanged()
Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished) Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)

View file

@ -131,12 +131,21 @@ class StartSliceJob(Job):
Logger.log("w", "No objects suitable for one at a time found, or no correct order found") Logger.log("w", "No objects suitable for one at a time found, or no correct order found")
else: else:
temp_list = [] temp_list = []
has_printing_mesh = False
for node in DepthFirstIterator(self._scene.getRoot()): for node in DepthFirstIterator(self._scene.getRoot()):
if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None: if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
if not getattr(node, "_outside_buildarea", False) or getattr(node, "_non_printing_mesh", False): _non_printing_mesh = getattr(node, "_non_printing_mesh", False)
if not getattr(node, "_outside_buildarea", False) or _non_printing_mesh:
temp_list.append(node) temp_list.append(node)
if not _non_printing_mesh:
has_printing_mesh = True
Job.yieldThread() Job.yieldThread()
#If the list doesn't have any model with suitable settings then clean the list
# otherwise CuraEngine will crash
if not has_printing_mesh:
temp_list.clear()
if temp_list: if temp_list:
object_groups.append(temp_list) object_groups.append(temp_list)

View file

@ -74,12 +74,13 @@ class GCodeWriter(MeshWriter):
## Create a new container with container 2 as base and container 1 written over it. ## Create a new container with container 2 as base and container 1 written over it.
def _createFlattenedContainerInstance(self, instance_container1, instance_container2): def _createFlattenedContainerInstance(self, instance_container1, instance_container2):
flat_container = InstanceContainer(instance_container2.getName()) flat_container = InstanceContainer(instance_container2.getName())
if instance_container1.getDefinition():
flat_container.setDefinition(instance_container1.getDefinition()) # The metadata includes id, name and definition
else:
flat_container.setDefinition(instance_container2.getDefinition())
flat_container.setMetaData(copy.deepcopy(instance_container2.getMetaData())) flat_container.setMetaData(copy.deepcopy(instance_container2.getMetaData()))
if instance_container1.getDefinition():
flat_container.setDefinition(instance_container1.getDefinition().getId())
for key in instance_container2.getAllKeys(): for key in instance_container2.getAllKeys():
flat_container.setProperty(key, "value", instance_container2.getProperty(key, "value")) flat_container.setProperty(key, "value", instance_container2.getProperty(key, "value"))

View file

@ -121,7 +121,7 @@ class LegacyProfileReader(ProfileReader):
Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?") Logger.log("e", "Dictionary of Doom has no translation. Is it the correct JSON file?")
return None return None
current_printer_definition = global_container_stack.getBottom() current_printer_definition = global_container_stack.getBottom()
profile.setDefinition(current_printer_definition) profile.setDefinition(current_printer_definition.getId())
for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations. for new_setting in dict_of_doom["translation"]: # Evaluate all new settings that would get a value from the translations.
old_setting_expression = dict_of_doom["translation"][new_setting] old_setting_expression = dict_of_doom["translation"][new_setting]
compiled = compile(old_setting_expression, new_setting, "eval") compiled = compile(old_setting_expression, new_setting, "eval")

View file

@ -22,7 +22,7 @@ Cura.MachineAction
onModelChanged: onModelChanged:
{ {
var extruderCount = base.extrudersModel.rowCount(); var extruderCount = base.extrudersModel.rowCount();
base.extruderTabsCount = extruderCount > 1 ? extruderCount : 0; base.extruderTabsCount = extruderCount;
} }
} }
@ -241,7 +241,6 @@ Cura.MachineAction
UM.TooltipArea UM.TooltipArea
{ {
visible: manager.definedExtruderCount > 1
height: childrenRect.height height: childrenRect.height
width: childrenRect.width width: childrenRect.width
text: machineExtruderCountProvider.properties.description text: machineExtruderCountProvider.properties.description
@ -291,15 +290,6 @@ Cura.MachineAction
property var afterOnEditingFinished: manager.updateMaterialForDiameter property var afterOnEditingFinished: manager.updateMaterialForDiameter
property string label: catalog.i18nc("@label", "Material diameter") property string label: catalog.i18nc("@label", "Material diameter")
} }
Loader
{
id: nozzleSizeField
visible: !Cura.MachineManager.hasVariants && machineExtruderCountProvider.properties.value == 1
sourceComponent: numericTextFieldWithUnit
property string settingKey: "machine_nozzle_size"
property string label: catalog.i18nc("@label", "Nozzle size")
property string unit: catalog.i18nc("@label", "mm")
}
} }
} }

View file

@ -8,6 +8,9 @@ import Cura 1.0 as Cura
Item Item
{ {
width: parent.width
height: parent.height
// We show a nice overlay on the 3D viewer when the current output device has no monitor view // We show a nice overlay on the 3D viewer when the current output device has no monitor view
Rectangle Rectangle
{ {
@ -29,6 +32,9 @@ Item
{ {
id: monitorViewComponent id: monitorViewComponent
width: parent.width
height: parent.height
property real maximumWidth: parent.width property real maximumWidth: parent.width
property real maximumHeight: parent.height property real maximumHeight: parent.height

View file

@ -179,7 +179,7 @@ class VersionUpgrade30to31(VersionUpgrade):
if not os.path.isfile(file_path): if not os.path.isfile(file_path):
continue continue
parser = configparser.ConfigParser() parser = configparser.ConfigParser(interpolation = None)
try: try:
parser.read([file_path]) parser.read([file_path])
except: except:
@ -213,7 +213,7 @@ class VersionUpgrade30to31(VersionUpgrade):
new_filename = machine_name + "_" + "fdmextruder" + suffix new_filename = machine_name + "_" + "fdmextruder" + suffix
extruder_quality_changes_parser = configparser.ConfigParser() extruder_quality_changes_parser = configparser.ConfigParser(interpolation = None)
extruder_quality_changes_parser.add_section("general") extruder_quality_changes_parser.add_section("general")
extruder_quality_changes_parser["general"]["version"] = str(2) extruder_quality_changes_parser["general"]["version"] = str(2)
extruder_quality_changes_parser["general"]["name"] = global_quality_changes["general"]["name"] extruder_quality_changes_parser["general"]["name"] = global_quality_changes["general"]["name"]

View file

@ -3,7 +3,10 @@
import copy import copy
import io import io
from typing import List, Optional import json #To parse the product-to-id mapping file.
import os.path #To find the product-to-id mapping.
import sys
from typing import Any, Dict, List, Optional
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from UM.Resources import Resources from UM.Resources import Resources
@ -14,7 +17,6 @@ import UM.Dictionary
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
## Handles serializing and deserializing material containers from an XML file ## Handles serializing and deserializing material containers from an XML file
class XmlMaterialProfile(InstanceContainer): class XmlMaterialProfile(InstanceContainer):
CurrentFdmMaterialVersion = "1.3" CurrentFdmMaterialVersion = "1.3"
@ -42,25 +44,18 @@ class XmlMaterialProfile(InstanceContainer):
def getInheritedFiles(self): def getInheritedFiles(self):
return self._inherited_files return self._inherited_files
## Overridden from InstanceContainer
def setReadOnly(self, read_only):
super().setReadOnly(read_only)
basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is self.id, this is a basefile.
for container in ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile):
container._read_only = read_only # prevent loop instead of calling setReadOnly
## Overridden from InstanceContainer ## Overridden from InstanceContainer
# set the meta data for all machine / variant combinations # set the meta data for all machine / variant combinations
def setMetaDataEntry(self, key, value): def setMetaDataEntry(self, key, value):
if self.isReadOnly(): registry = ContainerRegistry.getInstance()
if registry.isReadOnly(self.getId()):
return return
super().setMetaDataEntry(key, value) super().setMetaDataEntry(key, value)
basefile = self.getMetaDataEntry("base_file", self._id) #if basefile is self.id, this is a basefile. basefile = self.getMetaDataEntry("base_file", self.getId()) #if basefile is self.getId, this is a basefile.
# Update all containers that share basefile # Update all containers that share basefile
for container in ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): for container in registry.findInstanceContainers(base_file = basefile):
if container.getMetaDataEntry(key, None) != value: # Prevent recursion if container.getMetaDataEntry(key, None) != value: # Prevent recursion
container.setMetaDataEntry(key, value) container.setMetaDataEntry(key, value)
@ -68,7 +63,8 @@ class XmlMaterialProfile(InstanceContainer):
# without this function the setName would only set the name of the specific nozzle / material / machine combination container # without this function the setName would only set the name of the specific nozzle / material / machine combination container
# The function is a bit tricky. It will not set the name of all containers if it has the correct name itself. # The function is a bit tricky. It will not set the name of all containers if it has the correct name itself.
def setName(self, new_name): def setName(self, new_name):
if self.isReadOnly(): registry = ContainerRegistry.getInstance()
if registry.isReadOnly(self.getId()):
return return
# Not only is this faster, it also prevents a major loop that causes a stack overflow. # Not only is this faster, it also prevents a major loop that causes a stack overflow.
@ -77,10 +73,10 @@ class XmlMaterialProfile(InstanceContainer):
super().setName(new_name) super().setName(new_name)
basefile = self.getMetaDataEntry("base_file", self._id) # if basefile is self.id, this is a basefile. basefile = self.getMetaDataEntry("base_file", self.getId()) # if basefile is self.getId, this is a basefile.
# Update the basefile as well, this is actually what we're trying to do # Update the basefile as well, this is actually what we're trying to do
# Update all containers that share GUID and basefile # Update all containers that share GUID and basefile
containers = ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile) containers = registry.findInstanceContainers(base_file = basefile)
for container in containers: for container in containers:
container.setName(new_name) container.setName(new_name)
@ -88,33 +84,20 @@ class XmlMaterialProfile(InstanceContainer):
def setDirty(self, dirty): def setDirty(self, dirty):
super().setDirty(dirty) super().setDirty(dirty)
base_file = self.getMetaDataEntry("base_file", None) base_file = self.getMetaDataEntry("base_file", None)
if base_file is not None and base_file != self._id: registry = ContainerRegistry.getInstance()
containers = ContainerRegistry.getInstance().findContainers(id=base_file) if base_file is not None and base_file != self.getId() and not registry.isReadOnly(base_file):
containers = registry.findContainers(id = base_file)
if containers: if containers:
base_container = containers[0] containers[0].setDirty(dirty)
if not base_container.isReadOnly():
base_container.setDirty(dirty)
## Overridden from InstanceContainer
# def setProperty(self, key, property_name, property_value, container = None):
# if self.isReadOnly():
# return
#
# super().setProperty(key, property_name, property_value)
#
# basefile = self.getMetaDataEntry("base_file", self._id) #if basefile is self.id, this is a basefile.
# for container in UM.Settings.ContainerRegistry.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile):
# if not container.isReadOnly():
# container.setDirty(True)
## Overridden from InstanceContainer ## Overridden from InstanceContainer
# base file: common settings + supported machines # base file: common settings + supported machines
# machine / variant combination: only changes for itself. # machine / variant combination: only changes for itself.
def serialize(self, ignored_metadata_keys: Optional[List] = None): def serialize(self, ignored_metadata_keys: Optional[set] = None):
registry = ContainerRegistry.getInstance() registry = ContainerRegistry.getInstance()
base_file = self.getMetaDataEntry("base_file", "") base_file = self.getMetaDataEntry("base_file", "")
if base_file and self.id != base_file: if base_file and self.getId() != base_file:
# Since we create an instance of XmlMaterialProfile for each machine and nozzle in the profile, # Since we create an instance of XmlMaterialProfile for each machine and nozzle in the profile,
# we should only serialize the "base" material definition, since that can then take care of # we should only serialize the "base" material definition, since that can then take care of
# serializing the machine/nozzle specific profiles. # serializing the machine/nozzle specific profiles.
@ -132,8 +115,8 @@ class XmlMaterialProfile(InstanceContainer):
metadata = copy.deepcopy(self.getMetaData()) metadata = copy.deepcopy(self.getMetaData())
# setting_version is derived from the "version" tag in the schema, so don't serialize it into a file # setting_version is derived from the "version" tag in the schema, so don't serialize it into a file
if ignored_metadata_keys is None: if ignored_metadata_keys is None:
ignored_metadata_keys = [] ignored_metadata_keys = set()
ignored_metadata_keys = ignored_metadata_keys + ["setting_version"] ignored_metadata_keys |= {"setting_version"}
# remove the keys that we want to ignore in the metadata # remove the keys that we want to ignore in the metadata
for key in ignored_metadata_keys: for key in ignored_metadata_keys:
if key in metadata: if key in metadata:
@ -146,6 +129,9 @@ class XmlMaterialProfile(InstanceContainer):
metadata.pop("type", "") metadata.pop("type", "")
metadata.pop("base_file", "") metadata.pop("base_file", "")
metadata.pop("approximate_diameter", "") metadata.pop("approximate_diameter", "")
metadata.pop("id", "")
metadata.pop("container_type", "")
metadata.pop("name", "")
## Begin Name Block ## Begin Name Block
builder.start("name") builder.start("name")
@ -163,7 +149,7 @@ class XmlMaterialProfile(InstanceContainer):
builder.end("color") builder.end("color")
builder.start("label") builder.start("label")
builder.data(self._name) builder.data(self.getName())
builder.end("label") builder.end("label")
builder.end("name") builder.end("name")
@ -195,16 +181,16 @@ class XmlMaterialProfile(InstanceContainer):
## Begin Settings Block ## Begin Settings Block
builder.start("settings") builder.start("settings")
if self.getDefinition().id == "fdmprinter": if self.getDefinition().getId() == "fdmprinter":
for instance in self.findInstances(): for instance in self.findInstances():
self._addSettingElement(builder, instance) self._addSettingElement(builder, instance)
machine_container_map = {} machine_container_map = {}
machine_nozzle_map = {} machine_nozzle_map = {}
all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self._id) all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self.getId())
for container in all_containers: for container in all_containers:
definition_id = container.getDefinition().id definition_id = container.getDefinition().getId()
if definition_id == "fdmprinter": if definition_id == "fdmprinter":
continue continue
@ -222,17 +208,16 @@ class XmlMaterialProfile(InstanceContainer):
machine_container_map[definition_id] = container machine_container_map[definition_id] = container
# Map machine human-readable names to IDs # Map machine human-readable names to IDs
product_id_map = {} product_id_map = self.getProductIdMap()
for container in registry.findDefinitionContainers(type = "machine"):
product_id_map[container.getName()] = container.getId()
for definition_id, container in machine_container_map.items(): for definition_id, container in machine_container_map.items():
definition = container.getDefinition() definition = container.getDefinition()
try:
product = UM.Dictionary.findKey(product_id_map, definition_id) product = definition_id
except ValueError: for product_name, product_id_list in product_id_map.items():
# An unknown product id; export it anyway if definition_id in product_id_list:
product = definition_id product = product_name
break
builder.start("machine") builder.start("machine")
builder.start("machine_identifier", { builder.start("machine_identifier", {
@ -242,7 +227,7 @@ class XmlMaterialProfile(InstanceContainer):
builder.end("machine_identifier") builder.end("machine_identifier")
for instance in container.findInstances(): for instance in container.findInstances():
if self.getDefinition().id == "fdmprinter" and self.getInstance(instance.definition.key) and self.getProperty(instance.definition.key, "value") == instance.value: if self.getDefinition().getId() == "fdmprinter" and self.getInstance(instance.definition.key) and self.getProperty(instance.definition.key, "value") == instance.value:
# If the settings match that of the base profile, just skip since we inherit the base profile. # If the settings match that of the base profile, just skip since we inherit the base profile.
continue continue
@ -250,11 +235,12 @@ class XmlMaterialProfile(InstanceContainer):
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile. # Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
for hotend_id, hotend in machine_nozzle_map[definition_id].items(): for hotend_id, hotend in machine_nozzle_map[definition_id].items():
variant_containers = registry.findInstanceContainers(id = hotend.getMetaDataEntry("variant")) variant_containers = registry.findInstanceContainersMetadata(id = hotend.getMetaDataEntry("variant"))
if not variant_containers: if not variant_containers:
continue continue
builder.start("hotend", {"id": variant_containers[0].getName()}) # The hotend identifier is not the containers name, but its "name".
builder.start("hotend", {"id": variant_containers[0]["name"]})
# Compatible is a special case, as it's added as a meta data entry (instead of an instance). # Compatible is a special case, as it's added as a meta data entry (instead of an instance).
compatible = hotend.getMetaDataEntry("compatible") compatible = hotend.getMetaDataEntry("compatible")
@ -397,15 +383,18 @@ class XmlMaterialProfile(InstanceContainer):
first.append(element) first.append(element)
def clearData(self): def clearData(self):
self._metadata = {} self._metadata = {
self._name = "" "id": self.getId(),
"name": ""
}
self._definition = None self._definition = None
self._instances = {} self._instances = {}
self._read_only = False self._read_only = False
self._dirty = False self._dirty = False
self._path = "" self._path = ""
def getConfigurationTypeFromSerialized(self, serialized: str) -> Optional[str]: @classmethod
def getConfigurationTypeFromSerialized(cls, serialized: str) -> Optional[str]:
return "materials" return "materials"
@classmethod @classmethod
@ -415,9 +404,9 @@ class XmlMaterialProfile(InstanceContainer):
version = XmlMaterialProfile.Version version = XmlMaterialProfile.Version
# get setting version # get setting version
if "version" in data.attrib: if "version" in data.attrib:
setting_version = XmlMaterialProfile.xmlVersionToSettingVersion(data.attrib["version"]) setting_version = cls.xmlVersionToSettingVersion(data.attrib["version"])
else: else:
setting_version = XmlMaterialProfile.xmlVersionToSettingVersion("1.2") setting_version = cls.xmlVersionToSettingVersion("1.2")
return version * 1000000 + setting_version return version * 1000000 + setting_version
@ -431,15 +420,18 @@ class XmlMaterialProfile(InstanceContainer):
try: try:
data = ET.fromstring(serialized) data = ET.fromstring(serialized)
except: except:
Logger.logException("e", "An exception occured while parsing the material profile") Logger.logException("e", "An exception occurred while parsing the material profile")
return return
# Reset previous metadata # Reset previous metadata
old_id = self.getId()
self.clearData() # Ensure any previous data is gone. self.clearData() # Ensure any previous data is gone.
meta_data = {} meta_data = {}
meta_data["type"] = "material" meta_data["type"] = "material"
meta_data["base_file"] = self.id meta_data["base_file"] = self.getId()
meta_data["status"] = "unknown" # TODO: Add material verification meta_data["status"] = "unknown" # TODO: Add material verification
meta_data["id"] = old_id
meta_data["container_type"] = XmlMaterialProfile
common_setting_values = {} common_setting_values = {}
@ -454,8 +446,8 @@ class XmlMaterialProfile(InstanceContainer):
else: else:
meta_data["setting_version"] = self.xmlVersionToSettingVersion("1.2") #1.2 and lower didn't have that version number there yet. meta_data["setting_version"] = self.xmlVersionToSettingVersion("1.2") #1.2 and lower didn't have that version number there yet.
metadata = data.iterfind("./um:metadata/*", self.__namespaces) meta_data["name"] = "Unknown Material" #In case the name tag is missing.
for entry in metadata: for entry in data.iterfind("./um:metadata/*", self.__namespaces):
tag_name = _tag_without_namespace(entry) tag_name = _tag_without_namespace(entry)
if tag_name == "name": if tag_name == "name":
@ -465,9 +457,9 @@ class XmlMaterialProfile(InstanceContainer):
label = entry.find("./um:label", self.__namespaces) label = entry.find("./um:label", self.__namespaces)
if label is not None: if label is not None:
self._name = label.text meta_data["name"] = label.text
else: else:
self._name = self._profile_name(material.text, color.text) meta_data["name"] = self._profile_name(material.text, color.text)
meta_data["brand"] = brand.text meta_data["brand"] = brand.text
meta_data["material"] = material.text meta_data["material"] = material.text
meta_data["color_name"] = color.text meta_data["color_name"] = color.text
@ -499,8 +491,7 @@ class XmlMaterialProfile(InstanceContainer):
meta_data["approximate_diameter"] = str(round(float(property_values.get("diameter", 2.85)))) # In mm meta_data["approximate_diameter"] = str(round(float(property_values.get("diameter", 2.85)))) # In mm
meta_data["properties"] = property_values meta_data["properties"] = property_values
meta_data["definition"] = "fdmprinter"
self.setDefinition(ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")[0])
common_compatibility = True common_compatibility = True
settings = data.iterfind("./um:settings/um:setting", self.__namespaces) settings = data.iterfind("./um:settings/um:setting", self.__namespaces)
@ -518,9 +509,7 @@ class XmlMaterialProfile(InstanceContainer):
self._dirty = False self._dirty = False
# Map machine human-readable names to IDs # Map machine human-readable names to IDs
product_id_map = {} product_id_map = self.getProductIdMap()
for container in ContainerRegistry.getInstance().findDefinitionContainers(type = "machine"):
product_id_map[container.getName()] = container.getId()
machines = data.iterfind("./um:settings/um:machine", self.__namespaces) machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
for machine in machines: for machine in machines:
@ -542,112 +531,285 @@ class XmlMaterialProfile(InstanceContainer):
identifiers = machine.iterfind("./um:machine_identifier", self.__namespaces) identifiers = machine.iterfind("./um:machine_identifier", self.__namespaces)
for identifier in identifiers: for identifier in identifiers:
machine_id = product_id_map.get(identifier.get("product"), None) machine_id_list = product_id_map.get(identifier.get("product"), [])
if machine_id is None: if not machine_id_list:
# Lets try again with some naive heuristics. machine_id_list = self.getPossibleDefinitionIDsFromName(identifier.get("product"))
machine_id = identifier.get("product").replace(" ", "").lower()
definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = machine_id) for machine_id in machine_id_list:
if not definitions: definitions = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
# Logger.log("w", "No definition found for machine ID %s", machine_id) if not definitions:
continue Logger.log("w", "No definition found for machine ID %s", machine_id)
definition = definitions[0]
machine_manufacturer = identifier.get("manufacturer", definition.getMetaDataEntry("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
if machine_compatibility:
new_material_id = self.id + "_" + machine_id
# The child or derived material container may already exist. This can happen when a material in a
# project file and the a material in Cura have the same ID.
# In the case if a derived material already exists, override that material container because if
# the data in the parent material has been changed, the derived ones should be updated too.
found_materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id)
is_new_material = False
if found_materials:
new_material = found_materials[0]
else:
new_material = XmlMaterialProfile(new_material_id)
is_new_material = True
# Update the private directly, as we want to prevent the lookup that is done when using setName
new_material._name = self.getName()
new_material.setMetaData(copy.deepcopy(self.getMetaData()))
new_material.setDefinition(definition)
# Don't use setMetadata, as that overrides it for all materials with same base file
new_material.getMetaData()["compatible"] = machine_compatibility
new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
new_material.setCachedValues(cached_machine_setting_properties)
new_material._dirty = False
if is_new_material:
containers_to_add.append(new_material)
hotends = machine.iterfind("./um:hotend", self.__namespaces)
for hotend in hotends:
hotend_id = hotend.get("id")
if hotend_id is None:
continue continue
variant_containers = ContainerRegistry.getInstance().findInstanceContainers(id = hotend_id) Logger.log("d", "Found definition for machine ID %s", machine_id)
if not variant_containers: definition = definitions[0]
# It is not really properly defined what "ID" is so also search for variants by name.
variant_containers = ContainerRegistry.getInstance().findInstanceContainers(definition = definition.id, name = hotend_id)
if not variant_containers: machine_manufacturer = identifier.get("manufacturer", definition.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
#Logger.log("d", "No variants found with ID or name %s for machine %s", hotend_id, definition.id)
continue
hotend_compatibility = machine_compatibility if machine_compatibility:
hotend_setting_values = {} new_material_id = self.getId() + "_" + machine_id
settings = hotend.iterfind("./um:setting", self.__namespaces)
for entry in settings: # The child or derived material container may already exist. This can happen when a material in a
key = entry.get("key") # project file and the a material in Cura have the same ID.
if key in self.__material_settings_setting_map: # In the case if a derived material already exists, override that material container because if
hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text # the data in the parent material has been changed, the derived ones should be updated too.
elif key in self.__unmapped_settings: if ContainerRegistry.getInstance().isLoaded(new_material_id):
if key == "hardware compatible": new_material = ContainerRegistry.getInstance().findContainers(id = new_material_id)[0]
hotend_compatibility = self._parseCompatibleValue(entry.text) is_new_material = False
else: else:
Logger.log("d", "Unsupported material setting %s", key) new_material = XmlMaterialProfile(new_material_id)
is_new_material = True
new_hotend_id = self.id + "_" + machine_id + "_" + hotend_id.replace(" ", "_") new_material.setMetaData(copy.deepcopy(self.getMetaData()))
new_material.getMetaData()["id"] = new_material_id
new_material.getMetaData()["name"] = self.getName()
new_material.setDefinition(machine_id)
# Don't use setMetadata, as that overrides it for all materials with same base file
new_material.getMetaData()["compatible"] = machine_compatibility
new_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
new_material.getMetaData()["definition"] = machine_id
# Same as machine compatibility, keep the derived material containers consistent with the parent new_material.setCachedValues(cached_machine_setting_properties)
# material
found_materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_hotend_id)
is_new_material = False
if found_materials:
new_hotend_material = found_materials[0]
else:
new_hotend_material = XmlMaterialProfile(new_hotend_id)
is_new_material = True
# Update the private directly, as we want to prevent the lookup that is done when using setName new_material._dirty = False
new_hotend_material._name = self.getName()
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
new_hotend_material.setDefinition(definition)
new_hotend_material.addMetaDataEntry("variant", variant_containers[0].id)
# Don't use setMetadata, as that overrides it for all materials with same base file
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
cached_hotend_setting_properties = cached_machine_setting_properties.copy() if is_new_material:
cached_hotend_setting_properties.update(hotend_setting_values) containers_to_add.append(new_material)
new_hotend_material.setCachedValues(cached_hotend_setting_properties) hotends = machine.iterfind("./um:hotend", self.__namespaces)
for hotend in hotends:
hotend_id = hotend.get("id")
if hotend_id is None:
continue
new_hotend_material._dirty = False variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id)
if not variant_containers:
# It is not really properly defined what "ID" is so also search for variants by name.
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
if is_new_material: if not variant_containers:
containers_to_add.append(new_hotend_material) continue
hotend_compatibility = machine_compatibility
hotend_setting_values = {}
settings = hotend.iterfind("./um:setting", self.__namespaces)
for entry in settings:
key = entry.get("key")
if key in self.__material_settings_setting_map:
hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text
elif key in self.__unmapped_settings:
if key == "hardware compatible":
hotend_compatibility = self._parseCompatibleValue(entry.text)
else:
Logger.log("d", "Unsupported material setting %s", key)
new_hotend_id = self.getId() + "_" + machine_id + "_" + hotend_id.replace(" ", "_")
# Same as machine compatibility, keep the derived material containers consistent with the parent
# material
if ContainerRegistry.getInstance().isLoaded(new_hotend_id):
new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_id)[0]
is_new_material = False
else:
new_hotend_material = XmlMaterialProfile(new_hotend_id)
is_new_material = True
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
new_hotend_material.getMetaData()["id"] = new_hotend_id
new_hotend_material.getMetaData()["name"] = self.getName()
new_hotend_material.getMetaData()["variant"] = variant_containers[0]["id"]
new_hotend_material.setDefinition(machine_id)
# Don't use setMetadata, as that overrides it for all materials with same base file
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
new_hotend_material.getMetaData()["definition"] = machine_id
cached_hotend_setting_properties = cached_machine_setting_properties.copy()
cached_hotend_setting_properties.update(hotend_setting_values)
new_hotend_material.setCachedValues(cached_hotend_setting_properties)
new_hotend_material._dirty = False
if is_new_material:
containers_to_add.append(new_hotend_material)
# there is only one ID for a machine. Once we have reached here, it means we have already found
# a workable ID for that machine, so there is no need to continue
break
for container_to_add in containers_to_add: for container_to_add in containers_to_add:
ContainerRegistry.getInstance().addContainer(container_to_add) ContainerRegistry.getInstance().addContainer(container_to_add)
@classmethod
def deserializeMetadata(cls, serialized: str, container_id: str) -> List[Dict[str, Any]]:
result_metadata = [] #All the metadata that we found except the base (because the base is returned).
#Update the serialized data to the latest version.
serialized = cls._updateSerialized(serialized)
base_metadata = {
"type": "material",
"status": "unknown", #TODO: Add material verification.
"container_type": XmlMaterialProfile,
"id": container_id,
"base_file": container_id
}
try:
data = ET.fromstring(serialized)
except:
Logger.logException("e", "An exception occurred while parsing the material profile")
return []
#TODO: Implement the <inherits> tag. It's unused at the moment though.
if "version" in data.attrib:
base_metadata["setting_version"] = cls.xmlVersionToSettingVersion(data.attrib["version"])
else:
base_metadata["setting_version"] = cls.xmlVersionToSettingVersion("1.2") #1.2 and lower didn't have that version number there yet.
for entry in data.iterfind("./um:metadata/*", cls.__namespaces):
tag_name = _tag_without_namespace(entry)
if tag_name == "name":
brand = entry.find("./um:brand", cls.__namespaces)
material = entry.find("./um:material", cls.__namespaces)
color = entry.find("./um:color", cls.__namespaces)
label = entry.find("./um:label", cls.__namespaces)
if label is not None:
base_metadata["name"] = label.text
else:
base_metadata["name"] = cls._profile_name(material.text, color.text)
base_metadata["brand"] = brand.text
base_metadata["material"] = material.text
base_metadata["color_name"] = color.text
continue
#Setting_version is derived from the "version" tag in the schema earlier, so don't set it here.
if tag_name == "setting_version":
continue
base_metadata[tag_name] = entry.text
if "description" not in base_metadata:
base_metadata["description"] = ""
if "adhesion_info" not in base_metadata:
base_metadata["adhesion_info"] = ""
property_values = {}
properties = data.iterfind("./um:properties/*", cls.__namespaces)
for entry in properties:
tag_name = _tag_without_namespace(entry)
property_values[tag_name] = entry.text
base_metadata["approximate_diameter"] = str(round(float(property_values.get("diameter", 2.85)))) # In mm
base_metadata["properties"] = property_values
base_metadata["definition"] = "fdmprinter"
compatible_entries = data.iterfind("./um:settings/um:setting[@key='hardware compatible']", cls.__namespaces)
try:
common_compatibility = cls._parseCompatibleValue(next(compatible_entries).text)
except StopIteration: #No 'hardware compatible' setting.
common_compatibility = True
base_metadata["compatible"] = common_compatibility
result_metadata.append(base_metadata)
# Map machine human-readable names to IDs
product_id_map = cls.getProductIdMap()
for machine in data.iterfind("./um:settings/um:machine", cls.__namespaces):
machine_compatibility = common_compatibility
for entry in machine.iterfind("./um:setting", cls.__namespaces):
key = entry.get("key")
if key == "hardware compatible":
machine_compatibility = cls._parseCompatibleValue(entry.text)
for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces):
machine_id_list = product_id_map.get(identifier.get("product"), [])
if not machine_id_list:
machine_id_list = cls.getPossibleDefinitionIDsFromName(identifier.get("product"))
for machine_id in machine_id_list:
definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
if not definition_metadata:
Logger.log("w", "No definition found for machine ID %s", machine_id)
continue
Logger.log("d", "Found def for machine [%s]", machine_id)
definition_metadata = definition_metadata[0]
machine_manufacturer = identifier.get("manufacturer", definition_metadata.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
if machine_compatibility:
new_material_id = container_id + "_" + machine_id
# The child or derived material container may already exist. This can happen when a material in a
# project file and the a material in Cura have the same ID.
# In the case if a derived material already exists, override that material container because if
# the data in the parent material has been changed, the derived ones should be updated too.
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_material_id)
if found_materials:
new_material_metadata = found_materials[0]
else:
new_material_metadata = {}
new_material_metadata.update(base_metadata)
new_material_metadata["id"] = new_material_id
new_material_metadata["compatible"] = machine_compatibility
new_material_metadata["machine_manufacturer"] = machine_manufacturer
new_material_metadata["definition"] = machine_id
if len(found_materials) == 0: #This is a new material.
result_metadata.append(new_material_metadata)
for hotend in machine.iterfind("./um:hotend", cls.__namespaces):
hotend_id = hotend.get("id")
if hotend_id is None:
continue
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id)
if not variant_containers:
# It is not really properly defined what "ID" is so also search for variants by name.
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
hotend_compatibility = machine_compatibility
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
key = entry.get("key")
if key == "hardware compatible":
hotend_compatibility = cls._parseCompatibleValue(entry.text)
new_hotend_id = container_id + "_" + machine_id + "_" + hotend_id.replace(" ", "_")
# Same as machine compatibility, keep the derived material containers consistent with the parent
# material
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_id)
if found_materials:
new_hotend_material_metadata = found_materials[0]
else:
new_hotend_material_metadata = {}
new_hotend_material_metadata.update(base_metadata)
if variant_containers:
new_hotend_material_metadata["variant"] = variant_containers[0]["id"]
else:
new_hotend_material_metadata["variant"] = hotend_id
_with_missing_variants.append(new_hotend_material_metadata)
new_hotend_material_metadata["compatible"] = hotend_compatibility
new_hotend_material_metadata["machine_manufacturer"] = machine_manufacturer
new_hotend_material_metadata["id"] = new_hotend_id
new_hotend_material_metadata["definition"] = machine_id
if len(found_materials) == 0:
result_metadata.append(new_hotend_material_metadata)
# there is only one ID for a machine. Once we have reached here, it means we have already found
# a workable ID for that machine, so there is no need to continue
break
return result_metadata
def _addSettingElement(self, builder, instance): def _addSettingElement(self, builder, instance):
try: try:
key = UM.Dictionary.findKey(self.__material_settings_setting_map, instance.definition.key) key = UM.Dictionary.findKey(self.__material_settings_setting_map, instance.definition.key)
@ -658,16 +820,58 @@ class XmlMaterialProfile(InstanceContainer):
builder.data(str(instance.value)) builder.data(str(instance.value))
builder.end("setting") builder.end("setting")
def _profile_name(self, material_name, color_name): @classmethod
def _profile_name(cls, material_name, color_name):
if color_name != "Generic": if color_name != "Generic":
return "%s %s" % (color_name, material_name) return "%s %s" % (color_name, material_name)
else: else:
return material_name return material_name
@classmethod
def getPossibleDefinitionIDsFromName(cls, name):
name_parts = name.lower().split(" ")
merged_name_parts = []
for part in name_parts:
if len(part) == 0:
continue
if len(merged_name_parts) == 0:
merged_name_parts.append(part)
continue
if part.isdigit():
# for names with digit(s) such as Ultimaker 3 Extended, we generate an ID like
# "ultimaker3_extended", ignoring the space between "Ultimaker" and "3".
merged_name_parts[-1] = merged_name_parts[-1] + part
else:
merged_name_parts.append(part)
id_list = [name.lower().replace(" ", ""), # simply removing all spaces
name.lower().replace(" ", "_"), # simply replacing all spaces with underscores
"_".join(merged_name_parts),
]
return id_list
## Gets a mapping from product names in the XML files to their definition
# IDs.
#
# This loads the mapping from a file.
@classmethod
def getProductIdMap(cls) -> Dict[str, List[str]]:
product_to_id_file = os.path.join(os.path.dirname(sys.modules[cls.__module__].__file__), "product_to_id.json")
with open(product_to_id_file) as f:
product_to_id_map = json.load(f)
product_to_id_map = {key: [value] for key, value in product_to_id_map.items()}
return product_to_id_map
## Parse the value of the "material compatible" property. ## Parse the value of the "material compatible" property.
def _parseCompatibleValue(self, value: str): @classmethod
def _parseCompatibleValue(cls, value: str):
return value in {"yes", "unknown"} return value in {"yes", "unknown"}
## Small string representation for debugging.
def __str__(self):
return "<XmlMaterialProfile '{my_id}' ('{name}') from base file '{base_file}'>".format(my_id = self.getId(), name = self.getName(), base_file = self.getMetaDataEntry("base_file"))
# Map XML file setting names to internal names # Map XML file setting names to internal names
__material_settings_setting_map = { __material_settings_setting_map = {
"print temperature": "default_material_print_temperature", "print temperature": "default_material_print_temperature",
@ -716,4 +920,22 @@ def _indent(elem, level = 0):
# We are only interested in the actual tag name, so discard everything # We are only interested in the actual tag name, so discard everything
# before the last } # before the last }
def _tag_without_namespace(element): def _tag_without_namespace(element):
return element.tag[element.tag.rfind("}") + 1:] return element.tag[element.tag.rfind("}") + 1:]
#While loading XML profiles, some of these profiles don't know what variant
#they belong to. We'd like to search by the machine ID and the variant's
#name, but we don't know the variant's ID. Not all variants have been loaded
#yet so we can't run a filter on the name and machine. The ID is unknown
#so we can't lazily load the variant either. So we have to wait until all
#the rest is loaded properly and then assign the correct variant to the
#material files that were missing it.
_with_missing_variants = []
def _fillMissingVariants():
registry = ContainerRegistry.getInstance()
for variant_metadata in _with_missing_variants:
variants = registry.findContainersMetadata(definition = variant_metadata["definition"], name = variant_metadata["variant"])
if not variants:
Logger.log("w", "Could not find variant for variant-specific material {material_id}.".format(material_id = variant_metadata["id"]))
continue
variant_metadata["variant"] = variants[0]["id"]
ContainerRegistry.allMetadataLoaded.connect(_fillMissingVariants)

View file

@ -0,0 +1,12 @@
{
"Ultimaker 2": "ultimaker2",
"Ultimaker 2 Extended": "ultimaker2_extended",
"Ultimaker 2 Extended+": "ultimaker2_extended_plus",
"Ultimaker 2 Go": "ultimaker2_go",
"Ultimaker 2+": "ultimaker2_plus",
"Ultimaker 3": "ultimaker3",
"Ultimaker 3 Extended": "ultimaker3_extended",
"Ultimaker Original": "ultimaker_original",
"Ultimaker Original+": "ultimaker_original_plus",
"IMADE3D JellyBOX": "imade3d_jellybox"
}

View file

@ -1,5 +1,4 @@
{ {
"id": "101Hero",
"version": 2, "version": 2,
"name": "101Hero", "name": "101Hero",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "3Dator",
"version": 2, "version": 2,
"name": "3Dator", "name": "3Dator",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "PRi3",
"name": "ABAX PRi3", "name": "ABAX PRi3",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "PRi5",
"name": "ABAX PRi5", "name": "ABAX PRi5",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "Titan",
"name": "ABAX Titan", "name": "ABAX Titan",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "alya3dp",
"name": "ALYA", "name": "ALYA",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "bfb",
"name": "BFB", "name": "BFB",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "bq_hephestos",
"name": "BQ Prusa i3 Hephestos", "name": "BQ Prusa i3 Hephestos",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "bq_hephestos_2",
"version": 2, "version": 2,
"name": "BQ Hephestos 2", "name": "BQ Hephestos 2",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "bq_hephestos_xl",
"version": 2, "version": 2,
"name": "BQ Prusa i3 Hephestos XL", "name": "BQ Prusa i3 Hephestos XL",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "bq_witbox",
"version": 2, "version": 2,
"name": "BQ Witbox", "name": "BQ Witbox",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "bq_witbox_2",
"version": 2, "version": 2,
"name": "BQ Witbox 2", "name": "BQ Witbox 2",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "cartesio",
"name": "Cartesio", "name": "Cartesio",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "creality_cr10",
"name": "Creality CR-10", "name": "Creality CR-10",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "creality_cr10s4",
"name": "Creality CR-10 S4", "name": "Creality CR-10 S4",
"version": 2, "version": 2,
"inherits": "creality_cr10", "inherits": "creality_cr10",

View file

@ -1,5 +1,4 @@
{ {
"id": "creality_cr10s5",
"name": "Creality CR-10 S5", "name": "Creality CR-10 S5",
"version": 2, "version": 2,
"inherits": "creality_cr10", "inherits": "creality_cr10",

View file

@ -1,5 +1,4 @@
{ {
"id": "custom",
"version": 2, "version": 2,
"name": "Custom FDM printer", "name": "Custom FDM printer",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "Dagoma_discoeasy200",
"name": "Dagoma DiscoEasy200", "name": "Dagoma DiscoEasy200",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "Delta_Go",
"name": "Delta Go", "name": "Delta Go",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "deltabot",
"name": "DeltaBot", "name": "DeltaBot",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "easyarts_ares",
"name": "EasyArts Ares", "name": "EasyArts Ares",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "fabtotum",
"version": 2, "version": 2,
"name": "FABtotum Personal Fabricator", "name": "FABtotum Personal Fabricator",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "fdmextruder",
"name": "Extruder", "name": "Extruder",
"version": 2, "version": 2,
"metadata": "metadata":

View file

@ -1,5 +1,4 @@
{ {
"id": "fdmprinter",
"name": "FDM Printer Base Description", "name": "FDM Printer Base Description",
"version": 2, "version": 2,
"metadata": "metadata":

View file

@ -1,5 +1,4 @@
{ {
"id": "grr_neo",
"version": 2, "version": 2,
"name": "German RepRap Neo", "name": "German RepRap Neo",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "BEEVERYCREATIVE-helloBEEprusa",
"version": 2, "version": 2,
"name": "Hello BEE Prusa", "name": "Hello BEE Prusa",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "imade3d_jellybox",
"version": 2, "version": 2,
"name": "IMADE3D JellyBOX", "name": "IMADE3D JellyBOX",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "innovo-inventor",
"version": 2, "version": 2,
"name": "Innovo INVENTOR", "name": "Innovo INVENTOR",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "julia",
"name": "Julia", "name": "Julia",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "kemiq_q2_beta",
"version": 2, "version": 2,
"name": "Kemiq Q2 Beta", "name": "Kemiq Q2 Beta",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "kemiq_q2_gama",
"version": 2, "version": 2,
"name": "Kemiq Q2 Gama", "name": "Kemiq Q2 Gama",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "kossel_mini",
"version": 2, "version": 2,
"name": "Kossel Mini", "name": "Kossel Mini",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "kossel_pro",
"version": 2, "version": 2,
"name": "Kossel Pro", "name": "Kossel Pro",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "kupido",
"name": "Kupido", "name": "Kupido",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "m180",
"version": 2, "version": 2,
"name": "Malyan M180", "name": "Malyan M180",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "makeR_pegasus",
"version": 2, "version": 2,
"name": "makeR Pegasus", "name": "makeR Pegasus",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "makeR_prusa_tairona_i3",
"version": 2, "version": 2,
"name": "makeR Prusa Tairona i3", "name": "makeR Prusa Tairona i3",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "makeit_pro_l",
"version": 2, "version": 2,
"name": "MAKEiT Pro-L", "name": "MAKEiT Pro-L",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "makeit_pro_m",
"version": 2, "version": 2,
"name": "MAKEiT Pro-M", "name": "MAKEiT Pro-M",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "maker_starter",
"version": 2, "version": 2,
"name": "3DMaker Starter", "name": "3DMaker Starter",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "makerbotreplicator",
"name": "MakerBotReplicator", "name": "MakerBotReplicator",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "mankati_fullscale_xt_plus",
"version": 2, "version": 2,
"name": "Mankati Fullscale XT Plus", "name": "Mankati Fullscale XT Plus",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "mendel90",
"name": "Mendel90", "name": "Mendel90",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "ord",
"name": "RoVa3D", "name": "RoVa3D",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "peopoly_moai",
"version": 2, "version": 2,
"name": "Peopoly Moai", "name": "Peopoly Moai",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "printrbot_play",
"version": 2, "version": 2,
"name": "Printrbot Play", "name": "Printrbot Play",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "printrbot_play_heated",
"version": 2, "version": 2,
"name": "Printrbot Play (Heated Bed)", "name": "Printrbot Play (Heated Bed)",
"inherits": "printrbot_play", "inherits": "printrbot_play",

View file

@ -1,5 +1,4 @@
{ {
"id": "printrbot_simple",
"version": 2, "version": 2,
"name": "Printrbot Simple", "name": "Printrbot Simple",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "printrbot_simple_extended",
"version": 2, "version": 2,
"name": "Printrbot Simple Metal Extended", "name": "Printrbot Simple Metal Extended",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "prusa_i3",
"version": 2, "version": 2,
"name": "Prusa i3", "name": "Prusa i3",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "prusa_i3_mk2",
"version": 2, "version": 2,
"name": "Prusa i3 Mk2", "name": "Prusa i3 Mk2",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "prusa_i3_xl",
"version": 2, "version": 2,
"name": "Prusa i3 xl", "name": "Prusa i3 xl",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "punchtec_connect_xl",
"name": "Punchtec Connect XL", "name": "Punchtec Connect XL",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "raise3D_N2_dual",
"version": 2, "version": 2,
"name": "Raise3D N2 Dual", "name": "Raise3D N2 Dual",
"inherits": "fdmprinter", "inherits": "fdmprinter",
@ -33,7 +32,7 @@
}, },
"machine_heated_bed": { "machine_heated_bed": {
"default_value": true "default_value": true
}, },
"machine_nozzle_size": { "machine_nozzle_size": {
"default_value": 0.4 "default_value": 0.4
}, },
@ -55,9 +54,6 @@
"machine_min_cool_heat_time_window": { "machine_min_cool_heat_time_window": {
"default_value": 3600 "default_value": 3600
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"material_diameter": { "material_diameter": {
"default_value": 1.75 "default_value": 1.75
}, },

View file

@ -1,5 +1,4 @@
{ {
"id": "raise3D_N2_plus_dual",
"version": 2, "version": 2,
"name": "Raise3D N2 Plus Dual", "name": "Raise3D N2 Plus Dual",
"inherits": "fdmprinter", "inherits": "fdmprinter",
@ -55,9 +54,6 @@
"machine_min_cool_heat_time_window": { "machine_min_cool_heat_time_window": {
"default_value": 3600 "default_value": 3600
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"material_diameter": { "material_diameter": {
"default_value": 1.75 "default_value": 1.75
}, },

View file

@ -1,5 +1,4 @@
{ {
"id": "raise3D_N2_plus_single",
"version": 2, "version": 2,
"name": "Raise3D N2 Plus Single", "name": "Raise3D N2 Plus Single",
"inherits": "fdmprinter", "inherits": "fdmprinter",
@ -50,9 +49,6 @@
"machine_min_cool_heat_time_window": { "machine_min_cool_heat_time_window": {
"default_value": 3600 "default_value": 3600
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"material_diameter": { "material_diameter": {
"default_value": 1.75 "default_value": 1.75
}, },

View file

@ -1,5 +1,4 @@
{ {
"id": "raise3D_N2_single",
"version": 2, "version": 2,
"name": "Raise3D N2 Single", "name": "Raise3D N2 Single",
"inherits": "fdmprinter", "inherits": "fdmprinter",
@ -50,9 +49,6 @@
"machine_min_cool_heat_time_window": { "machine_min_cool_heat_time_window": {
"default_value": 3600 "default_value": 3600
}, },
"machine_nozzle_size": {
"default_value": 0.4
},
"material_diameter": { "material_diameter": {
"default_value": 1.75 "default_value": 1.75
}, },

View file

@ -1,5 +1,4 @@
{ {
"id": "RF100",
"version": 2, "version": 2,
"name": "Renkforce RF100", "name": "Renkforce RF100",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "rigid3d",
"name": "Rigid3D", "name": "Rigid3D",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "rigid3d_3rdgen",
"name": "Rigid3D 3rdGen", "name": "Rigid3D 3rdGen",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "rigid3d_hobby",
"name": "Rigid3D Hobby", "name": "Rigid3D Hobby",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "rigid3d_zero",
"name": "Rigid3D Zero", "name": "Rigid3D Zero",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "rigid3d_zero2",
"name": "Rigid3D Zero2", "name": "Rigid3D Zero2",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "rigidbot",
"version": 2, "version": 2,
"name": "RigidBot", "name": "RigidBot",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "rigidbotbig",
"version": 2, "version": 2,
"name": "RigidBotBig", "name": "RigidBotBig",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "robo_3d_r1",
"name": "Robo 3D R1", "name": "Robo 3D R1",
"version": 2, "version": 2,
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "typeamachines",
"version": 2, "version": 2,
"name": "Type A Machines Series 1 2014", "name": "Type A Machines Series 1 2014",
"inherits": "fdmprinter", "inherits": "fdmprinter",

View file

@ -1,5 +1,4 @@
{ {
"id": "tevo_tarantula",
"version": 2, "version": 2,
"name": "Tevo Tarantula", "name": "Tevo Tarantula",
"inherits": "fdmprinter", "inherits": "fdmprinter",

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