1# Copyright (c) 2020 Ultimaker B.V. 2# Cura is released under the terms of the LGPLv3 or higher. 3 4from UM.Logger import Logger 5from UM.Tool import Tool 6from UM.Scene.Selection import Selection 7from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator 8from UM.Application import Application 9from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator 10from cura.Settings.ExtruderManager import ExtruderManager 11from UM.Settings.SettingInstance import SettingInstance 12from UM.Event import Event 13 14 15class PerObjectSettingsTool(Tool): 16 """This tool allows the user to add & change settings per node in the scene. 17 18 The settings per object are kept in a ContainerStack, which is linked to a node by decorator. 19 """ 20 def __init__(self): 21 super().__init__() 22 self._model = None 23 24 self.setExposedProperties("SelectedObjectId", "ContainerID", "SelectedActiveExtruder", "MeshType") 25 26 self._multi_extrusion = False 27 self._single_model_selected = False 28 self.visibility_handler = None 29 30 Selection.selectionChanged.connect(self.propertyChanged) 31 Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) 32 self._onGlobalContainerChanged() 33 Selection.selectionChanged.connect(self._updateEnabled) 34 35 def event(self, event): 36 super().event(event) 37 if event.type == Event.MousePressEvent and self._controller.getToolsEnabled(): 38 self.operationStopped.emit(self) 39 return False 40 41 def getSelectedObjectId(self): 42 selected_object = Selection.getSelectedObject(0) 43 selected_object_id = id(selected_object) 44 return selected_object_id 45 46 def getContainerID(self): 47 selected_object = Selection.getSelectedObject(0) 48 try: 49 return selected_object.callDecoration("getStack").getId() 50 except AttributeError: 51 return "" 52 53 def getSelectedActiveExtruder(self): 54 """Gets the active extruder of the currently selected object. 55 56 :return: The active extruder of the currently selected object. 57 """ 58 59 selected_object = Selection.getSelectedObject(0) 60 return selected_object.callDecoration("getActiveExtruder") 61 62 def setSelectedActiveExtruder(self, extruder_stack_id): 63 """Changes the active extruder of the currently selected object. 64 65 :param extruder_stack_id: The ID of the extruder to print the currently 66 selected object with. 67 """ 68 69 selected_object = Selection.getSelectedObject(0) 70 stack = selected_object.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway. 71 if not stack: 72 selected_object.addDecorator(SettingOverrideDecorator()) 73 selected_object.callDecoration("setActiveExtruder", extruder_stack_id) 74 75 def setMeshType(self, mesh_type: str) -> bool: 76 """Returns True when the mesh_type was changed, False when current mesh_type == mesh_type""" 77 78 old_mesh_type = self.getMeshType() 79 if old_mesh_type == mesh_type: 80 return False 81 82 selected_object = Selection.getSelectedObject(0) 83 if selected_object is None: 84 Logger.log("w", "Tried setting the mesh type of the selected object, but no object was selected") 85 return False 86 87 stack = selected_object.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway. 88 if not stack: 89 selected_object.addDecorator(SettingOverrideDecorator()) 90 stack = selected_object.callDecoration("getStack") 91 92 settings_visibility_changed = False 93 settings = stack.getTop() 94 for property_key in ["infill_mesh", "cutting_mesh", "support_mesh", "anti_overhang_mesh"]: 95 if property_key != mesh_type: 96 if settings.getInstance(property_key): 97 settings.removeInstance(property_key) 98 else: 99 if not (settings.getInstance(property_key) and settings.getProperty(property_key, "value")): 100 definition = stack.getSettingDefinition(property_key) 101 new_instance = SettingInstance(definition, settings) 102 new_instance.setProperty("value", True) 103 new_instance.resetState() # Ensure that the state is not seen as a user state. 104 settings.addInstance(new_instance) 105 106 for property_key in ["top_bottom_thickness", "wall_thickness", "wall_line_count"]: 107 if mesh_type == "infill_mesh": 108 if settings.getInstance(property_key) is None: 109 definition = stack.getSettingDefinition(property_key) 110 new_instance = SettingInstance(definition, settings) 111 # We just want the wall_line count to be there in case it was overriden in the global stack. 112 # as such, we don't need to set a value. 113 if property_key != "wall_line_count": 114 new_instance.setProperty("value", 0) 115 new_instance.resetState() # Ensure that the state is not seen as a user state. 116 settings.addInstance(new_instance) 117 settings_visibility_changed = True 118 119 elif old_mesh_type == "infill_mesh" and settings.getInstance(property_key) and (settings.getProperty(property_key, "value") == 0 or property_key == "wall_line_count"): 120 settings.removeInstance(property_key) 121 settings_visibility_changed = True 122 123 if settings_visibility_changed: 124 self.visibility_handler.forceVisibilityChanged() 125 126 self.propertyChanged.emit() 127 return True 128 129 def getMeshType(self): 130 selected_object = Selection.getSelectedObject(0) 131 stack = selected_object.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway. 132 if not stack: 133 return "" 134 135 settings = stack.getTop() 136 for property_key in ["infill_mesh", "cutting_mesh", "support_mesh", "anti_overhang_mesh"]: 137 if settings.getInstance(property_key) and settings.getProperty(property_key, "value"): 138 return property_key 139 140 return "" 141 142 def _onGlobalContainerChanged(self): 143 global_container_stack = Application.getInstance().getGlobalContainerStack() 144 if global_container_stack: 145 146 # used for enabling or disabling per extruder settings per object 147 self._multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 148 149 extruder_stack = ExtruderManager.getInstance().getExtruderStack(0) 150 151 if extruder_stack: 152 root_node = Application.getInstance().getController().getScene().getRoot() 153 for node in DepthFirstIterator(root_node): 154 new_stack_id = extruder_stack.getId() 155 # Get position of old extruder stack for this node 156 old_extruder_pos = node.callDecoration("getActiveExtruderPosition") 157 if old_extruder_pos is not None: 158 # Fetch current (new) extruder stack at position 159 new_stack = ExtruderManager.getInstance().getExtruderStack(old_extruder_pos) 160 if new_stack: 161 new_stack_id = new_stack.getId() 162 node.callDecoration("setActiveExtruder", new_stack_id) 163 164 self._updateEnabled() 165 166 def _updateEnabled(self): 167 selected_objects = Selection.getAllSelectedObjects() 168 if len(selected_objects)> 1: 169 self._single_model_selected = False 170 elif len(selected_objects) == 1 and selected_objects[0].callDecoration("isGroup"): 171 self._single_model_selected = False # Group is selected, so tool needs to be disabled 172 else: 173 self._single_model_selected = True 174 Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._single_model_selected) 175