diff --git a/cura/CuraActions.py b/cura/CuraActions.py index df26a9a9a6..5c89232639 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -1,10 +1,21 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + from PyQt5.QtCore import QObject, QUrl from PyQt5.QtGui import QDesktopServices from UM.FlameProfiler import pyqtSlot from UM.Event import CallFunctionEvent from UM.Application import Application +from UM.Math.Vector import Vector +from UM.Scene.Selection import Selection +from UM.Operations.GroupedOperation import GroupedOperation +from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation +from UM.Operations.SetTransformOperation import SetTransformOperation +from cura.SetParentOperation import SetParentOperation +from cura.MultiplyObjectsJob import MultiplyObjectsJob +from cura.Settings.SetObjectExtruderOperation import SetObjectExtruderOperation class CuraActions(QObject): def __init__(self, parent = None): @@ -23,5 +34,58 @@ class CuraActions(QObject): event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {}) Application.getInstance().functionEvent(event) + ## Center all objects in the selection + @pyqtSlot() + def centerSelection(self) -> None: + operation = GroupedOperation() + for node in Selection.getAllSelectedObjects(): + current_node = node + while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): + current_node = current_node.getParent() + + center_operation = SetTransformOperation(current_node, Vector()) + operation.addOperation(center_operation) + operation.push() + + ## Multiply all objects in the selection + # + # \param count The number of times to multiply the selection. + @pyqtSlot(int) + def multiplySelection(self, count: int) -> None: + job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, 8) + job.start() + + ## Delete all selected objects. + @pyqtSlot() + def deleteSelection(self) -> None: + if not Application.getInstance().getController().getToolsEnabled(): + return + + removed_group_nodes = [] + op = GroupedOperation() + nodes = Selection.getAllSelectedObjects() + for node in nodes: + op.addOperation(RemoveSceneNodeOperation(node)) + group_node = node.getParent() + if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes: + remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes)) + if len(remaining_nodes_in_group) == 1: + removed_group_nodes.append(group_node) + op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent())) + op.addOperation(RemoveSceneNodeOperation(group_node)) + op.push() + + ## Set the extruder that should be used to print the selection. + # + # \param extruder_id The ID of the extruder stack to use for the selected objects. + @pyqtSlot(str) + def setExtruderForSelection(self, extruder_id: str) -> None: + operation = GroupedOperation() + for node in Selection.getAllSelectedObjects(): + if node.callDecoration("getActiveExtruder") == extruder_id: + continue + operation.addOperation(SetObjectExtruderOperation(node, extruder_id)) + operation.push() + def _openUrl(self, url): - QDesktopServices.openUrl(url) \ No newline at end of file + QDesktopServices.openUrl(url) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index ce2d029ae4..5f71495f77 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -26,6 +26,7 @@ from UM.Message import Message from UM.i18n import i18nCatalog from UM.Workspace.WorkspaceReader import WorkspaceReader from UM.Platform import Platform +from UM.Decorators import deprecated from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation @@ -218,6 +219,7 @@ class CuraApplication(QtApplication): self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity) self.getController().toolOperationStopped.connect(self._onToolOperationStopped) + self.getController().contextMenuRequested.connect(self._onContextMenuRequested) Resources.addType(self.ResourceTypes.QmlFiles, "qml") Resources.addType(self.ResourceTypes.Firmware, "firmware") @@ -807,6 +809,7 @@ class CuraApplication(QtApplication): # Remove all selected objects from the scene. @pyqtSlot() + @deprecated("Moved to CuraActions", "2.6") def deleteSelection(self): if not self.getController().getToolsEnabled(): return @@ -827,6 +830,7 @@ class CuraApplication(QtApplication): ## Remove an object from the scene. # Note that this only removes an object if it is selected. @pyqtSlot("quint64") + @deprecated("Use deleteSelection instead", "2.6") def deleteObject(self, object_id): if not self.getController().getToolsEnabled(): return @@ -854,13 +858,22 @@ class CuraApplication(QtApplication): # \param count number of copies # \param min_offset minimum offset to other objects. @pyqtSlot("quint64", int) + @deprecated("Use CuraActions::multiplySelection", "2.6") def multiplyObject(self, object_id, count, min_offset = 8): - job = MultiplyObjectsJob(object_id, count, min_offset) + node = self.getController().getScene().findObject(object_id) + if not node: + node = Selection.getSelectedObject(0) + + while node.getParent() and node.getParent().callDecoration("isGroup"): + node = node.getParent() + + job = MultiplyObjectsJob([node], count, min_offset) job.start() return ## Center object on platform. @pyqtSlot("quint64") + @deprecated("Use CuraActions::centerSelection", "2.6") def centerObject(self, object_id): node = self.getController().getScene().findObject(object_id) if not node and object_id != 0: # Workaround for tool handles overlapping the selected object @@ -1320,3 +1333,10 @@ class CuraApplication(QtApplication): except Exception as e: Logger.log("e", "Could not check file %s: %s", file_url, e) return False + + def _onContextMenuRequested(self, x: float, y: float) -> None: + # Ensure we select the object if we request a context menu over an object without having a selection. + if not Selection.hasSelection(): + node = self.getController().getScene().findObject(self.getRenderer().getRenderPass("selection").getIdAtPosition(x, y)) + if node: + Selection.add(node) diff --git a/cura/MultiplyObjectsJob.py b/cura/MultiplyObjectsJob.py index 40dbc221d6..a795e0bc10 100644 --- a/cura/MultiplyObjectsJob.py +++ b/cura/MultiplyObjectsJob.py @@ -24,9 +24,9 @@ from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation class MultiplyObjectsJob(Job): - def __init__(self, object_id, count, min_offset = 8): + def __init__(self, objects, count, min_offset = 8): super().__init__() - self._object_id = object_id + self._objects = objects self._count = count self._min_offset = min_offset @@ -35,38 +35,42 @@ class MultiplyObjectsJob(Job): dismissable=False, progress=0) status_message.show() scene = Application.getInstance().getController().getScene() - node = scene.findObject(self._object_id) - if not node and self._object_id != 0: # Workaround for tool handles overlapping the selected object - node = Selection.getSelectedObject(0) - - # If object is part of a group, multiply group - current_node = node - while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): - current_node = current_node.getParent() + total_progress = len(self._objects) * self._count + current_progress = 0 root = scene.getRoot() arranger = Arrange.create(scene_root=root) - node_too_big = False - if node.getBoundingBox().width < 300 or node.getBoundingBox().depth < 300: - offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset) - else: - node_too_big = True nodes = [] - found_solution_for_all = True - for i in range(self._count): - # We do place the nodes one by one, as we want to yield in between. - if not node_too_big: - node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr) - if node_too_big or not solution_found: - found_solution_for_all = False - new_location = node.getPosition() - new_location = new_location.set(z = 100 - i * 20) - node.setPosition(new_location) + for node in self._objects: + # If object is part of a group, multiply group + current_node = node + while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): + current_node = current_node.getParent() + + node_too_big = False + if node.getBoundingBox().width < 300 or node.getBoundingBox().depth < 300: + offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset) + else: + node_too_big = True + + found_solution_for_all = True + for i in range(self._count): + # We do place the nodes one by one, as we want to yield in between. + if not node_too_big: + node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr) + if node_too_big or not solution_found: + found_solution_for_all = False + new_location = node.getPosition() + new_location = new_location.set(z = 100 - i * 20) + node.setPosition(new_location) + + nodes.append(node) + current_progress += 1 + status_message.setProgress((current_progress / total_progress) * 100) + Job.yieldThread() - nodes.append(node) Job.yieldThread() - status_message.setProgress((i + 1) / self._count * 100) if nodes: op = GroupedOperation() diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index d21480b11b..75fa1ece3a 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -8,12 +8,13 @@ from UM.Application import Application #To get the global container stack to fin from UM.Logger import Logger from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode +from UM.Scene.Selection import Selection from UM.Settings.ContainerRegistry import ContainerRegistry #Finding containers by ID. from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.SettingFunction import SettingFunction from UM.Settings.ContainerStack import ContainerStack from UM.Settings.DefinitionContainer import DefinitionContainer -from typing import Optional +from typing import Optional, List ## Manages all existing extruder stacks. # @@ -34,10 +35,13 @@ class ExtruderManager(QObject): super().__init__(parent) self._extruder_trains = { } #Per machine, a dictionary of extruder container stack IDs. self._active_extruder_index = 0 + self._selected_object_extruders = [] Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged) self._global_container_stack_definition_id = None self._addCurrentMachineExtruders() + Selection.selectionChanged.connect(self.resetSelectedObjectExtruders) + ## Gets the unique identifier of the currently active extruder stack. # # The currently active extruder stack is the stack that is currently being @@ -117,6 +121,34 @@ class ExtruderManager(QObject): except IndexError: return "" + ## Emitted whenever the selectedObjectExtruders property changes. + selectedObjectExtrudersChanged = pyqtSignal() + + ## Provides a list of extruder IDs used by the current selected objects. + @pyqtProperty("QVariantList", notify = selectedObjectExtrudersChanged) + def selectedObjectExtruders(self) -> List[str]: + if not self._selected_object_extruders: + object_extruders = set() + for node in Selection.getAllSelectedObjects(): + extruder = node.callDecoration("getActiveExtruder") + if extruder: + object_extruders.add(extruder) + else: + global_stack = Application.getInstance().getGlobalContainerStack() + object_extruders.add(self._extruder_trains[global_stack.getId()]["0"].getId()) + + self._selected_object_extruders = list(object_extruders) + + return self._selected_object_extruders + + ## Reset the internal list used for the selectedObjectExtruders property + # + # This will trigger a recalculation of the extruders used for the + # selection. + def resetSelectedObjectExtruders(self) -> None: + self._selected_object_extruders = [] + self.selectedObjectExtrudersChanged.emit() + def getActiveExtruderStack(self) -> ContainerStack: global_container_stack = Application.getInstance().getGlobalContainerStack() @@ -444,6 +476,8 @@ class ExtruderManager(QObject): self.globalContainerStackDefinitionChanged.emit() self.activeExtruderChanged.emit() + self.resetSelectedObjectExtruders() + ## Adds the extruders of the currently active machine. def _addCurrentMachineExtruders(self) -> None: global_stack = Application.getInstance().getGlobalContainerStack() diff --git a/cura/Settings/ExtrudersModel.py b/cura/Settings/ExtrudersModel.py index 7f4a77eb5f..62b1f0af2c 100644 --- a/cura/Settings/ExtrudersModel.py +++ b/cura/Settings/ExtrudersModel.py @@ -1,7 +1,7 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty +from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty, pyqtSlot import UM.Qt.ListModel from UM.Application import Application @@ -33,6 +33,12 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): # The ID of the definition of the extruder. DefinitionRole = Qt.UserRole + 5 + # The material of the extruder. + MaterialRole = Qt.UserRole + 6 + + # The variant of the extruder. + VariantRole = Qt.UserRole + 7 + ## List of colours to display if there is no material or the material has no known # colour. defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"] @@ -49,6 +55,8 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): self.addRoleName(self.ColorRole, "color") self.addRoleName(self.IndexRole, "index") self.addRoleName(self.DefinitionRole, "definition") + self.addRoleName(self.MaterialRole, "material") + self.addRoleName(self.VariantRole, "variant") self._add_global = False self._simple_names = False @@ -140,6 +148,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): for extruder in manager.getMachineExtruders(global_container_stack.getId()): extruder_name = extruder.getName() material = extruder.findContainer({ "type": "material" }) + variant = extruder.findContainer({"type": "variant"}) position = extruder.getMetaDataEntry("position", default = "0") # Get the position try: position = int(position) @@ -152,7 +161,9 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel): "name": extruder_name, "color": color, "index": position, - "definition": extruder.getBottom().getId() + "definition": extruder.getBottom().getId(), + "material": material.getName() if material else "", + "variant": variant.getName() if variant else "", } items.append(item) changed = True diff --git a/cura/Settings/SetObjectExtruderOperation.py b/cura/Settings/SetObjectExtruderOperation.py new file mode 100644 index 0000000000..31c996529a --- /dev/null +++ b/cura/Settings/SetObjectExtruderOperation.py @@ -0,0 +1,27 @@ +# Copyright (c) 2017 Ultimaker B.V. +# Cura is released under the terms of the AGPLv3 or higher. + +from UM.Scene.SceneNode import SceneNode +from UM.Operations.Operation import Operation + +from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator + +## Simple operation to set the extruder a certain object should be printed with. +class SetObjectExtruderOperation(Operation): + def __init__(self, node: SceneNode, extruder_id: str) -> None: + self._node = node + self._extruder_id = extruder_id + self._previous_extruder_id = None + self._decorator_added = False + + def undo(self): + if self._previous_extruder_id: + self._node.callDecoration("setActiveExtruder", self._previous_extruder_id) + + def redo(self): + stack = self._node.callDecoration("getStack") #Don't try to get the active extruder since it may be None anyway. + if not stack: + self._node.addDecorator(SettingOverrideDecorator()) + + self._previous_extruder_id = self._node.callDecoration("getActiveExtruder") + self._node.callDecoration("setActiveExtruder", self._extruder_id) diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index 76c155cb99..d754b6bc6d 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -109,6 +109,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): def setActiveExtruder(self, extruder_stack_id): self._extruder_stack = extruder_stack_id self._updateNextStack() + ExtruderManager.getInstance().resetSelectedObjectExtruders() self.activeExtruderChanged.emit() def getStack(self): diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index b5f5823ece..b9eef11a55 100755 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -18,6 +18,8 @@ Item property alias redo: redoAction; property alias deleteSelection: deleteSelectionAction; + property alias centerSelection: centerSelectionAction; + property alias multiplySelection: multiplySelectionAction; property alias deleteObject: deleteObjectAction; property alias centerObject: centerObjectAction; @@ -181,11 +183,28 @@ Item Action { id: deleteSelectionAction; - text: catalog.i18nc("@action:inmenu menubar:edit","Delete &Selection"); - enabled: UM.Controller.toolsEnabled; + text: catalog.i18ncp("@action:inmenu menubar:edit", "Delete &Selected Model", "Delete &Selected Models", UM.Selection.selectionCount); + enabled: UM.Controller.toolsEnabled && UM.Selection.hasSelection; iconName: "edit-delete"; shortcut: StandardKey.Delete; - onTriggered: CuraApplication.deleteSelection(); + onTriggered: CuraActions.deleteSelection(); + } + + Action + { + id: centerSelectionAction; + text: catalog.i18ncp("@action:inmenu menubar:edit", "Center Selected Model", "Center Selected Models", UM.Selection.selectionCount); + enabled: UM.Controller.toolsEnabled && UM.Selection.hasSelection; + iconName: "align-vertical-center"; + onTriggered: CuraActions.centerSelection(); + } + + Action + { + id: multiplySelectionAction; + text: catalog.i18ncp("@action:inmenu menubar:edit", "Multiply Selected Model", "Multiply Selected Models", UM.Selection.selectionCount); + enabled: UM.Controller.toolsEnabled && UM.Selection.hasSelection; + iconName: "edit-duplicate"; } Action diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index b0e6d09080..0a48725011 100755 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -594,102 +594,8 @@ UM.MainWindow } } - Menu - { - id: objectContextMenu; - - property variant objectId: -1; - MenuItem { action: Cura.Actions.centerObject; } - MenuItem { action: Cura.Actions.deleteObject; } - MenuItem { action: Cura.Actions.multiplyObject; } - MenuSeparator { } - MenuItem { action: Cura.Actions.selectAll; } - MenuItem { action: Cura.Actions.arrangeAll; } - MenuItem { action: Cura.Actions.deleteAll; } - MenuItem { action: Cura.Actions.reloadAll; } - MenuItem { action: Cura.Actions.resetAllTranslation; } - MenuItem { action: Cura.Actions.resetAll; } - MenuSeparator { } - MenuItem { action: Cura.Actions.groupObjects; } - MenuItem { action: Cura.Actions.mergeObjects; } - MenuItem { action: Cura.Actions.unGroupObjects; } - - Connections - { - target: Cura.Actions.deleteObject - onTriggered: - { - if(objectContextMenu.objectId != 0) - { - CuraApplication.deleteObject(objectContextMenu.objectId); - objectContextMenu.objectId = 0; - } - } - } - - MultiplyObjectOptions - { - id: multiplyObjectOptions - } - - Connections - { - target: Cura.Actions.multiplyObject - onTriggered: - { - if(objectContextMenu.objectId != 0) - { - multiplyObjectOptions.objectId = objectContextMenu.objectId; - multiplyObjectOptions.visible = true; - multiplyObjectOptions.reset(); - objectContextMenu.objectId = 0; - } - } - } - - Connections - { - target: Cura.Actions.centerObject - onTriggered: - { - if(objectContextMenu.objectId != 0) - { - CuraApplication.centerObject(objectContextMenu.objectId); - objectContextMenu.objectId = 0; - } - } - } - } - - Menu - { - id: contextMenu; - MenuItem { action: Cura.Actions.selectAll; } - MenuItem { action: Cura.Actions.arrangeAll; } - MenuItem { action: Cura.Actions.deleteAll; } - MenuItem { action: Cura.Actions.reloadAll; } - MenuItem { action: Cura.Actions.resetAllTranslation; } - MenuItem { action: Cura.Actions.resetAll; } - MenuSeparator { } - MenuItem { action: Cura.Actions.groupObjects; } - MenuItem { action: Cura.Actions.mergeObjects; } - MenuItem { action: Cura.Actions.unGroupObjects; } - } - - Connections - { - target: UM.Controller - onContextMenuRequested: - { - if(objectId == 0) - { - contextMenu.popup(); - } else - { - objectContextMenu.objectId = objectId; - objectContextMenu.popup(); - } - } + ContextMenu { + id: contextMenu } Connections diff --git a/resources/qml/Menus/ContextMenu.qml b/resources/qml/Menus/ContextMenu.qml new file mode 100644 index 0000000000..43867a4cc1 --- /dev/null +++ b/resources/qml/Menus/ContextMenu.qml @@ -0,0 +1,138 @@ +// Copyright (c) 2016 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Dialogs 1.2 +import QtQuick.Window 2.1 + +import UM 1.2 as UM +import Cura 1.0 as Cura + +Menu +{ + id: base + + property bool shouldShowExtruders: machineExtruderCount.properties.value > 1; + + // Selection-related actions. + MenuItem { action: Cura.Actions.centerSelection; } + MenuItem { action: Cura.Actions.deleteSelection; } + MenuItem { action: Cura.Actions.multiplySelection; } + + // Extruder selection - only visible if there is more than 1 extruder + MenuSeparator { visible: base.shouldShowExtruders } + MenuItem { id: extruderHeader; text: catalog.i18ncp("@label", "Print Selected Model With:", "Print Selected Models With:", UM.Selection.selectionCount); enabled: false; visible: base.shouldShowExtruders } + Instantiator + { + model: Cura.ExtrudersModel { id: extrudersModel } + MenuItem { + text: "%1: %2 - %3".arg(model.name).arg(model.material).arg(model.variant) + visible: base.shouldShowExtruders + enabled: UM.Selection.hasSelection + checkable: true + checked: ExtruderManager.selectedObjectExtruders.indexOf(model.id) != -1 + onTriggered: CuraActions.setExtruderForSelection(model.id) + shortcut: "Ctrl+" + (model.index + 1) + } + onObjectAdded: base.insertItem(base.findItemIndex(extruderHeader) + index, object) + onObjectRemoved: base.removeItem(object) + } + + // Global actions + MenuSeparator { } + MenuItem { action: Cura.Actions.selectAll; } + MenuItem { action: Cura.Actions.arrangeAll; } + MenuItem { action: Cura.Actions.deleteAll; } + MenuItem { action: Cura.Actions.reloadAll; } + MenuItem { action: Cura.Actions.resetAllTranslation; } + MenuItem { action: Cura.Actions.resetAll; } + + // Group actions + MenuSeparator { } + MenuItem { action: Cura.Actions.groupObjects; } + MenuItem { action: Cura.Actions.mergeObjects; } + MenuItem { action: Cura.Actions.unGroupObjects; } + + Connections + { + target: UM.Controller + onContextMenuRequested: base.popup(); + } + + Connections + { + target: Cura.Actions.multiplySelection + onTriggered: multiplyDialog.open() + } + + UM.SettingPropertyProvider + { + id: machineExtruderCount + + containerStackId: Cura.MachineManager.activeMachineId + key: "machine_extruder_count" + watchedProperties: [ "value" ] + } + + Dialog + { + id: multiplyDialog + + title: catalog.i18ncp("@title:window", "Multiply Selected Model", "Multiply Selected Models", UM.Selection.selectionCount) + + width: 400 * Screen.devicePixelRatio + height: 80 * Screen.devicePixelRatio + + onAccepted: CuraActions.multiplySelection(copiesField.value) + + signal reset() + onReset: + { + copiesField.value = 1; + copiesField.focus = true; + } + + standardButtons: StandardButton.Ok | StandardButton.Cancel + + Row + { + spacing: UM.Theme.getSize("default_margin").width + + Label + { + text: catalog.i18nc("@label", "Number of Copies") + anchors.verticalCenter: copiesField.verticalCenter + } + + SpinBox + { + id: copiesField + minimumValue: 1 + maximumValue: 99 + } + } + } + + // Find the index of an item in the list of child items of this menu. + // + // This is primarily intended as a helper function so we do not have to + // hard-code the position of the extruder selection actions. + // + // \param item The item to find the index of. + // + // \return The index of the item or -1 if it was not found. + function findItemIndex(item) + { + for(var i in base.items) + { + if(base.items[i] == item) + { + return i; + } + } + return -1; + } + + UM.I18nCatalog { id: catalog; name: "cura" } +}