diff --git a/cura/API/Backups.py b/cura/API/Backups.py index 5964557264..f31933c844 100644 --- a/cura/API/Backups.py +++ b/cura/API/Backups.py @@ -13,6 +13,7 @@ from cura.Backups.BackupsManager import BackupsManager # api = CuraAPI() # api.backups.createBackup() # api.backups.restoreBackup(my_zip_file, {"cura_release": "3.1"})`` + class Backups: manager = BackupsManager() # Re-used instance of the backups manager. diff --git a/cura/API/Interface/Settings.py b/cura/API/Interface/Settings.py new file mode 100644 index 0000000000..2889db7022 --- /dev/null +++ b/cura/API/Interface/Settings.py @@ -0,0 +1,33 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from cura.CuraApplication import CuraApplication + +## The Interface.Settings API provides a version-proof bridge between Cura's +# (currently) sidebar UI and plug-ins that hook into it. +# +# Usage: +# ``from cura.API import CuraAPI +# api = CuraAPI() +# api.interface.settings.getContextMenuItems() +# data = { +# "name": "My Plugin Action", +# "iconName": "my-plugin-icon", +# "actions": my_menu_actions, +# "menu_item": MyPluginAction(self) +# } +# api.interface.settings.addContextMenuItem(data)`` + +class Settings: + # Re-used instance of Cura: + application = CuraApplication.getInstance() # type: CuraApplication + + ## Add items to the sidebar context menu. + # \param menu_item dict containing the menu item to add. + def addContextMenuItem(self, menu_item: dict) -> None: + self.application.addSidebarCustomMenuItem(menu_item) + + ## Get all custom items currently added to the sidebar context menu. + # \return List containing all custom context menu items. + def getContextMenuItems(self) -> list: + return self.application.getSidebarCustomMenuItems() \ No newline at end of file diff --git a/cura/API/Interface/__init__.py b/cura/API/Interface/__init__.py new file mode 100644 index 0000000000..b38118949b --- /dev/null +++ b/cura/API/Interface/__init__.py @@ -0,0 +1,24 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from UM.PluginRegistry import PluginRegistry +from cura.API.Interface.Settings import Settings + +## The Interface class serves as a common root for the specific API +# methods for each interface element. +# +# Usage: +# ``from cura.API import CuraAPI +# api = CuraAPI() +# api.interface.settings.addContextMenuItem() +# api.interface.viewport.addOverlay() # Not implemented, just a hypothetical +# api.interface.toolbar.getToolButtonCount() # Not implemented, just a hypothetical +# # etc.`` + +class Interface: + + # For now we use the same API version to be consistent. + VERSION = PluginRegistry.APIVersion + + # API methods specific to the settings portion of the UI + settings = Settings() diff --git a/cura/API/__init__.py b/cura/API/__init__.py index 13f6722336..64d636903d 100644 --- a/cura/API/__init__.py +++ b/cura/API/__init__.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the LGPLv3 or higher. from UM.PluginRegistry import PluginRegistry from cura.API.Backups import Backups +from cura.API.Interface import Interface ## The official Cura API that plug-ins can use to interact with Cura. # @@ -9,10 +10,14 @@ from cura.API.Backups import Backups # this API provides a version-safe interface with proper deprecation warnings # etc. Usage of any other methods than the ones provided in this API can cause # plug-ins to be unstable. + class CuraAPI: # For now we use the same API version to be consistent. VERSION = PluginRegistry.APIVersion - # Backups API. + # Backups API backups = Backups() + + # Interface API + interface = Interface() diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 6164fc8756..2680c9a89c 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -104,6 +104,7 @@ from cura.Settings.UserChangesModel import UserChangesModel from cura.Settings.ExtrudersModel import ExtrudersModel from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler from cura.Settings.ContainerManager import ContainerManager +from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel from cura.ObjectsModel import ObjectsModel @@ -226,6 +227,8 @@ class CuraApplication(QtApplication): self._need_to_show_user_agreement = True + self._sidebar_custom_menu_items = [] # type: list # Keeps list of custom menu items for the side bar + self._plugins_loaded = False # Backups @@ -944,6 +947,7 @@ class CuraApplication(QtApplication): qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel") qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance) + qmlRegisterType(SidebarCustomMenuItemsModel, "Cura", 1, 0, "SidebarCustomMenuItemsModel") # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) @@ -1731,3 +1735,11 @@ class CuraApplication(QtApplication): @pyqtSlot() def showMoreInformationDialogForAnonymousDataCollection(self): cast(SliceInfo, self._plugin_registry.getPluginObject("SliceInfoPlugin")).showMoreInfoDialog() + + + def addSidebarCustomMenuItem(self, menu_item: list) -> None: + self._sidebar_custom_menu_items.append(menu_item) + + def getSidebarCustomMenuItems(self) -> list: + return self._sidebar_custom_menu_items + diff --git a/cura/Settings/SidebarCustomMenuItemsModel.py b/cura/Settings/SidebarCustomMenuItemsModel.py new file mode 100644 index 0000000000..d2a4d1a2b1 --- /dev/null +++ b/cura/Settings/SidebarCustomMenuItemsModel.py @@ -0,0 +1,41 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +from typing import Any + +from UM.Qt.ListModel import ListModel +from PyQt5.QtCore import pyqtSlot, Qt + + +class SidebarCustomMenuItemsModel(ListModel): + NameRole = Qt.UserRole + 1 + ActionsRole = Qt.UserRole + 2 + MenuItemRole = Qt.UserRole + 3 + MenuItemIconNameRole = Qt.UserRole + 5 + + def __init__(self, parent=None): + super().__init__(parent) + self.addRoleName(self.NameRole, "name") + self.addRoleName(self.ActionsRole, "actions") + self.addRoleName(self.MenuItemRole, "menu_item") + self.addRoleName(self.MenuItemIconNameRole, "iconName") + self._updateExtensionList() + + def _updateExtensionList(self)-> None: + from cura.CuraApplication import CuraApplication + for menu_item in CuraApplication.getInstance().getSidebarCustomMenuItems(): + + self.appendItem({ + "name": menu_item["name"], + "iconName": menu_item["iconName"], + "actions": menu_item["actions"], + "menu_item": menu_item["menu_item"] + }) + + @pyqtSlot(str, "QVariantList", "QVariantMap") + def callMenuItemMethod(self, menu_item_name: str, menu_item_actions: list, kwargs: Any)-> None: + for item in self._items: + if menu_item_name == item["name"]: + for method in menu_item_actions: + getattr(item["menu_item"], method)(kwargs) + break \ No newline at end of file diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 1b3f0cbc20..88876123fe 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -561,6 +561,28 @@ Item visible: machineExtruderCount.properties.value > 1 } + Instantiator + { + id: customMenuItems + model: Cura.SidebarCustomMenuItemsModel { } + MenuItem + { + text: model.name + iconName: model.iconName + onTriggered: + { + customMenuItems.model.callMenuItemMethod(name, model.actions, {"key": contextMenu.key}) + } + } + onObjectAdded: contextMenu.insertItem(index, object) + onObjectRemoved: contextMenu.removeItem(object) + } + + MenuSeparator + { + visible: customMenuItems.count > 0 + } + MenuItem { //: Settings context menu action