1# Copyright (c) 2019 Ultimaker B.V. 2# Cura is released under the terms of the LGPLv3 or higher. 3 4from typing import Optional 5 6from UM.ConfigurationErrorMessage import ConfigurationErrorMessage 7from UM.Logger import Logger 8from UM.Settings.Interfaces import DefinitionContainerInterface 9from UM.Settings.InstanceContainer import InstanceContainer 10 11from cura.Machines.ContainerTree import ContainerTree 12from .GlobalStack import GlobalStack 13from .ExtruderStack import ExtruderStack 14 15 16class CuraStackBuilder: 17 """Contains helper functions to create new machines.""" 18 19 @classmethod 20 def createMachine(cls, name: str, definition_id: str, machine_extruder_count: Optional[int] = None) -> Optional[GlobalStack]: 21 """Create a new instance of a machine. 22 23 :param name: The name of the new machine. 24 :param definition_id: The ID of the machine definition to use. 25 :param machine_extruder_count: The number of extruders in the machine. 26 27 :return: The new global stack or None if an error occurred. 28 """ 29 30 from cura.CuraApplication import CuraApplication 31 application = CuraApplication.getInstance() 32 registry = application.getContainerRegistry() 33 container_tree = ContainerTree.getInstance() 34 35 definitions = registry.findDefinitionContainers(id = definition_id) 36 if not definitions: 37 ConfigurationErrorMessage.getInstance().addFaultyContainers(definition_id) 38 Logger.log("w", "Definition {definition} was not found!", definition = definition_id) 39 return None 40 41 machine_definition = definitions[0] 42 machine_node = container_tree.machines[machine_definition.getId()] 43 44 generated_name = registry.createUniqueName("machine", "", name, machine_definition.getName()) 45 # Make sure the new name does not collide with any definition or (quality) profile 46 # createUniqueName() only looks at other stacks, but not at definitions or quality profiles 47 # Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true 48 if registry.findContainersMetadata(id = generated_name): 49 generated_name = registry.uniqueName(generated_name) 50 51 new_global_stack = cls.createGlobalStack( 52 new_stack_id = generated_name, 53 definition = machine_definition, 54 variant_container = application.empty_variant_container, 55 material_container = application.empty_material_container, 56 quality_container = machine_node.preferredGlobalQuality().container, 57 ) 58 new_global_stack.setName(generated_name) 59 60 # Create ExtruderStacks 61 extruder_dict = machine_definition.getMetaDataEntry("machine_extruder_trains") 62 for position in extruder_dict: 63 try: 64 cls.createExtruderStackWithDefaultSetup(new_global_stack, position) 65 except IndexError as e: 66 Logger.logException("e", "Failed to create an extruder stack for position {pos}: {err}".format(pos = position, err = str(e))) 67 return None 68 69 # If given, set the machine_extruder_count when creating the machine, or else the extruderList used bellow will 70 # not return the correct extruder list (since by default, the machine_extruder_count is 1) in machines with 71 # settable number of extruders. 72 if machine_extruder_count and 0 <= machine_extruder_count <= len(extruder_dict): 73 new_global_stack.setProperty("machine_extruder_count", "value", machine_extruder_count) 74 75 # Only register the extruders if we're sure that all of them are correct. 76 for new_extruder in new_global_stack.extruderList: 77 registry.addContainer(new_extruder) 78 79 # Register the global stack after the extruder stacks are created. This prevents the registry from adding another 80 # extruder stack because the global stack didn't have one yet (which is enforced since Cura 3.1). 81 registry.addContainer(new_global_stack) 82 83 return new_global_stack 84 85 @classmethod 86 def createExtruderStackWithDefaultSetup(cls, global_stack: "GlobalStack", extruder_position: int) -> None: 87 """Create a default Extruder Stack 88 89 :param global_stack: The global stack this extruder refers to. 90 :param extruder_position: The position of the current extruder. 91 """ 92 93 from cura.CuraApplication import CuraApplication 94 application = CuraApplication.getInstance() 95 registry = application.getContainerRegistry() 96 97 # Get the extruder definition. 98 extruder_definition_dict = global_stack.getMetaDataEntry("machine_extruder_trains") 99 extruder_definition_id = extruder_definition_dict[str(extruder_position)] 100 try: 101 extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0] 102 except IndexError: 103 # It still needs to break, but we want to know what extruder ID made it break. 104 msg = "Unable to find extruder definition with the id [%s]" % extruder_definition_id 105 Logger.logException("e", msg) 106 raise IndexError(msg) 107 108 # Find out what filament diameter we need. 109 approximate_diameter = round(extruder_definition.getProperty("material_diameter", "value")) # Can't be modified by definition changes since we are just initialising the stack here. 110 111 # Find the preferred containers. 112 machine_node = ContainerTree.getInstance().machines[global_stack.definition.getId()] 113 extruder_variant_node = machine_node.variants.get(machine_node.preferred_variant_name) 114 if not extruder_variant_node: 115 Logger.log("w", "Could not find preferred nozzle {nozzle_name}. Falling back to {fallback}.".format(nozzle_name = machine_node.preferred_variant_name, fallback = next(iter(machine_node.variants)))) 116 extruder_variant_node = next(iter(machine_node.variants.values())) 117 extruder_variant_container = extruder_variant_node.container 118 material_node = extruder_variant_node.preferredMaterial(approximate_diameter) 119 material_container = material_node.container 120 quality_node = material_node.preferredQuality() 121 122 new_extruder_id = registry.uniqueName(extruder_definition_id) 123 new_extruder = cls.createExtruderStack( 124 new_extruder_id, 125 extruder_definition = extruder_definition, 126 machine_definition_id = global_stack.definition.getId(), 127 position = extruder_position, 128 variant_container = extruder_variant_container, 129 material_container = material_container, 130 quality_container = quality_node.container 131 ) 132 new_extruder.setNextStack(global_stack) 133 134 registry.addContainer(new_extruder) 135 136 @classmethod 137 def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, 138 machine_definition_id: str, 139 position: int, 140 variant_container: "InstanceContainer", 141 material_container: "InstanceContainer", 142 quality_container: "InstanceContainer") -> ExtruderStack: 143 144 """Create a new Extruder stack 145 146 :param new_stack_id: The ID of the new stack. 147 :param extruder_definition: The definition to base the new stack on. 148 :param machine_definition_id: The ID of the machine definition to use for the user container. 149 :param position: The position the extruder occupies in the machine. 150 :param variant_container: The variant selected for the current extruder. 151 :param material_container: The material selected for the current extruder. 152 :param quality_container: The quality selected for the current extruder. 153 154 :return: A new Extruder stack instance with the specified parameters. 155 """ 156 157 from cura.CuraApplication import CuraApplication 158 application = CuraApplication.getInstance() 159 registry = application.getContainerRegistry() 160 161 stack = ExtruderStack(new_stack_id) 162 stack.setName(extruder_definition.getName()) 163 stack.setDefinition(extruder_definition) 164 165 stack.setMetaDataEntry("position", str(position)) 166 167 user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id, 168 is_global_stack = False) 169 170 stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings") 171 stack.variant = variant_container 172 stack.material = material_container 173 stack.quality = quality_container 174 stack.intent = application.empty_intent_container 175 stack.qualityChanges = application.empty_quality_changes_container 176 stack.userChanges = user_container 177 178 # Only add the created containers to the registry after we have set all the other 179 # properties. This makes the create operation more transactional, since any problems 180 # setting properties will not result in incomplete containers being added. 181 registry.addContainer(user_container) 182 183 return stack 184 185 @classmethod 186 def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, 187 variant_container: "InstanceContainer", 188 material_container: "InstanceContainer", 189 quality_container: "InstanceContainer") -> GlobalStack: 190 191 """Create a new Global stack 192 193 :param new_stack_id: The ID of the new stack. 194 :param definition: The definition to base the new stack on. 195 :param variant_container: The variant selected for the current stack. 196 :param material_container: The material selected for the current stack. 197 :param quality_container: The quality selected for the current stack. 198 199 :return: A new Global stack instance with the specified parameters. 200 """ 201 202 from cura.CuraApplication import CuraApplication 203 application = CuraApplication.getInstance() 204 registry = application.getContainerRegistry() 205 206 stack = GlobalStack(new_stack_id) 207 stack.setDefinition(definition) 208 209 # Create user container 210 user_container = cls.createUserChangesContainer(new_stack_id + "_user", definition.getId(), new_stack_id, 211 is_global_stack = True) 212 213 stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings") 214 stack.variant = variant_container 215 stack.material = material_container 216 stack.quality = quality_container 217 stack.intent = application.empty_intent_container 218 stack.qualityChanges = application.empty_quality_changes_container 219 stack.userChanges = user_container 220 221 registry.addContainer(user_container) 222 223 return stack 224 225 @classmethod 226 def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str, 227 is_global_stack: bool) -> "InstanceContainer": 228 from cura.CuraApplication import CuraApplication 229 application = CuraApplication.getInstance() 230 registry = application.getContainerRegistry() 231 232 unique_container_name = registry.uniqueName(container_name) 233 234 container = InstanceContainer(unique_container_name) 235 container.setDefinition(definition_id) 236 container.setMetaDataEntry("type", "user") 237 container.setMetaDataEntry("setting_version", CuraApplication.SettingVersion) 238 239 metadata_key_to_add = "machine" if is_global_stack else "extruder" 240 container.setMetaDataEntry(metadata_key_to_add, stack_id) 241 242 return container 243 244 @classmethod 245 def createDefinitionChangesContainer(cls, container_stack, container_name): 246 from cura.CuraApplication import CuraApplication 247 application = CuraApplication.getInstance() 248 registry = application.getContainerRegistry() 249 250 unique_container_name = registry.uniqueName(container_name) 251 252 definition_changes_container = InstanceContainer(unique_container_name) 253 definition_changes_container.setDefinition(container_stack.getBottom().getId()) 254 definition_changes_container.setMetaDataEntry("type", "definition_changes") 255 definition_changes_container.setMetaDataEntry("setting_version", CuraApplication.SettingVersion) 256 257 registry.addContainer(definition_changes_container) 258 container_stack.definitionChanges = definition_changes_container 259 260 return definition_changes_container 261