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