diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsModel.py b/plugins/PerObjectSettingsTool/PerObjectSettingsModel.py new file mode 100644 index 0000000000..22ebfbc4be --- /dev/null +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsModel.py @@ -0,0 +1,109 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Uranium is released under the terms of the AGPLv3 or higher. + +from PyQt5.QtCore import Qt, pyqtSlot, QUrl + +from UM.Application import Application +from UM.Qt.ListModel import ListModel +from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator +from UM.Scene.SceneNode import SceneNode +from UM.Settings.SettingOverrideDecorator import SettingOverrideDecorator +from UM.Settings.ProfileOverrideDecorator import ProfileOverrideDecorator + +from . import SettingOverrideModel + +class PerObjectSettingsModel(ListModel): + IdRole = Qt.UserRole + 1 + XRole = Qt.UserRole + 2 + YRole = Qt.UserRole + 3 + MaterialRole = Qt.UserRole + 4 + ProfileRole = Qt.UserRole + 5 + SettingsRole = Qt.UserRole + 6 + + def __init__(self, parent = None): + super().__init__(parent) + self._scene = Application.getInstance().getController().getScene() + self._root = self._scene.getRoot() + self._root.transformationChanged.connect(self._updatePositions) + self._root.childrenChanged.connect(self._updateNodes) + self._updateNodes(None) + + self.addRoleName(self.IdRole,"id") + self.addRoleName(self.XRole,"x") + self.addRoleName(self.YRole,"y") + self.addRoleName(self.MaterialRole, "material") + self.addRoleName(self.ProfileRole, "profile") + self.addRoleName(self.SettingsRole, "settings") + + @pyqtSlot("quint64", str) + def setObjectProfile(self, object_id, profile_name): + self.setProperty(self.find("id", object_id), "profile", profile_name) + + profile = None + if profile_name != "global": + profile = Application.getInstance().getMachineManager().findProfile(profile_name) + + node = self._scene.findObject(object_id) + if profile: + if not node.getDecorator(ProfileOverrideDecorator): + node.addDecorator(ProfileOverrideDecorator()) + node.callDecoration("setProfile", profile) + else: + if node.getDecorator(ProfileOverrideDecorator): + node.removeDecorator(ProfileOverrideDecorator) + + @pyqtSlot("quint64", str) + def addSettingOverride(self, object_id, key): + machine = Application.getInstance().getMachineManager().getActiveMachineInstance() + if not machine: + return + + node = self._scene.findObject(object_id) + if not node.getDecorator(SettingOverrideDecorator): + node.addDecorator(SettingOverrideDecorator()) + + node.callDecoration("addSetting", key) + + @pyqtSlot("quint64", str) + def removeSettingOverride(self, object_id, key): + node = self._scene.findObject(object_id) + node.callDecoration("removeSetting", key) + + if len(node.callDecoration("getAllSettings")) == 0: + node.removeDecorator(SettingOverrideDecorator) + + def _updatePositions(self, source): + camera = Application.getInstance().getController().getScene().getActiveCamera() + for node in BreadthFirstIterator(self._root): + if type(node) is not SceneNode or not node.getMeshData(): + continue + + projected_position = camera.project(node.getWorldPosition()) + + index = self.find("id", id(node)) + self.setProperty(index, "x", float(projected_position[0])) + self.setProperty(index, "y", float(projected_position[1])) + + def _updateNodes(self, source): + self.clear() + camera = Application.getInstance().getController().getScene().getActiveCamera() + for node in BreadthFirstIterator(self._root): + if type(node) is not SceneNode or not node.getMeshData() or not node.isSelectable(): + continue + + projected_position = camera.project(node.getWorldPosition()) + + node_profile = node.callDecoration("getProfile") + if not node_profile: + node_profile = "global" + else: + node_profile = node_profile.getName() + + self.appendItem({ + "id": id(node), + "x": float(projected_position[0]), + "y": float(projected_position[1]), + "material": "", + "profile": node_profile, + "settings": SettingOverrideModel.SettingOverrideModel(node) + }) diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml new file mode 100644 index 0000000000..117830b03b --- /dev/null +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -0,0 +1,339 @@ +// Copyright (c) 2015 Ultimaker B.V. +// Uranium is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Window 2.2 + +import UM 1.1 as UM + +Item { + id: base; + + width: 0; + height: 0; + + property variant position: mapToItem(null, 0, 0) + + property real viewportWidth: UM.Application.mainWindow.width * UM.Application.mainWindow.viewportRect.width; + property real viewportHeight: UM.Application.mainWindow.height * UM.Application.mainWindow.viewportRect.height; + + property int currentIndex; + + Rectangle { + id: settingsPanel; + + z: 3; + + width: UM.Theme.sizes.per_object_settings_panel.width; + height: items.height + UM.Theme.sizes.default_margin.height * 2; + + opacity: 0; + Behavior on opacity { NumberAnimation { } } + + border.width: UM.Theme.sizes.per_object_settings_panel_border.width; + border.color: UM.Theme.colors.per_object_settings_panel_border; + + color: UM.Theme.colors.per_object_settings_panel_background; + + DropArea { + anchors.fill: parent; + } + + Column { + id: items + anchors.top: parent.top; + anchors.topMargin: UM.Theme.sizes.default_margin.height; + + spacing: UM.Theme.sizes.default_lining.height; + + UM.SettingItem { + id: profileSelection + + x: UM.Theme.sizes.per_object_settings_panel_border.width + 1 + + width: UM.Theme.sizes.setting.width; + height: UM.Theme.sizes.setting.height; + + name: catalog.i18nc("@label", "Profile") + type: "enum" + perObjectSetting: true + + style: UM.Theme.styles.setting_item; + + options: UM.ProfilesModel { addUseGlobal: true } + + value: UM.ActiveTool.properties.Model.getItem(base.currentIndex).profile + + onItemValueChanged: { + var item = UM.ActiveTool.properties.Model.getItem(base.currentIndex); + UM.ActiveTool.properties.Model.setObjectProfile(item.id, value) + } + } + + Repeater { + id: settings; + + model: UM.ActiveTool.properties.Model.getItem(base.currentIndex).settings + + UM.SettingItem { + width: UM.Theme.sizes.setting.width; + height: UM.Theme.sizes.setting.height; + x: UM.Theme.sizes.per_object_settings_panel_border.width + 1 + + name: model.label; + type: model.type; + value: model.value; + description: model.description; + unit: model.unit; + valid: model.valid; + perObjectSetting: true + dismissable: true + + style: UM.Theme.styles.setting_item; + + onItemValueChanged: { + settings.model.setSettingValue(model.key, value) + } + +// Button { +// anchors.left: parent.right; +// text: "x"; +// +// width: UM.Theme.sizes.setting.height; +// height: UM.Theme.sizes.setting.height; +// +// opacity: parent.hovered || hovered ? 1 : 0; +// onClicked: UM.ActiveTool.properties.Model.removeSettingOverride(UM.ActiveTool.properties.Model.getItem(base.currentIndex).id, model.key) +// +// style: ButtonStyle { } +// } + } + } + + Item + { + height: UM.Theme.sizes.default_margin.height / 2 + width: parent.width + } + + Button + { + anchors.right: profileSelection.right; + + text: catalog.i18nc("@action:button", "Customize Settings"); + + style: ButtonStyle + { + background: Rectangle + { + width: control.width; + height: control.height; + color: control.hovered ? UM.Theme.colors.load_save_button_hover : UM.Theme.colors.load_save_button; + } + label: Label + { + text: control.text; + color: UM.Theme.colors.load_save_button_text; + } + } + + onClicked: settingPickDialog.visible = true; + } + } + + UM.I18nCatalog { id: catalog; name: "uranium"; } + } + + Repeater { + model: UM.ActiveTool.properties.Model; + delegate: Button { + x: ((model.x + 1.0) / 2.0) * base.viewportWidth - base.position.x - width / 2 + y: -((model.y + 1.0) / 2.0) * base.viewportHeight + (base.viewportHeight - base.position.y) + height / 2 + + width: UM.Theme.sizes.per_object_settings_button.width + height: UM.Theme.sizes.per_object_settings_button.height + + tooltip: catalog.i18nc("@info:tooltip", "Customise settings for this object"); + + checkable: true; + onClicked: { + base.currentIndex = index; + + settingsPanel.anchors.left = right; + settingsPanel.anchors.top = top; + + settingsPanel.opacity = 1; + } + + style: ButtonStyle + { + background: Rectangle + { + width: control.width; + height: control.height; + + color: control.hovered ? UM.Theme.colors.button_active : UM.Theme.colors.button_hover; + } + label: Image { + width: control.width; + height: control.height; + sourceSize.width: width; + sourceSize.height: height; + source: UM.Theme.icons.plus; + } + } + } + } + + UM.Dialog { + id: settingPickDialog + + title: catalog.i18nc("@title:window", "Pick a Setting to Customize") + + TextField { + id: filter; + + anchors { + top: parent.top; + left: parent.left; + right: parent.right; + } + + placeholderText: catalog.i18nc("@label:textbox", "Filter..."); + + onTextChanged: settingCategoriesModel.filter(text); + } + + ScrollView { + id: view; + anchors { + top: filter.bottom; + left: parent.left; + right: parent.right; + bottom: parent.bottom; + } + + Column { + width: view.width - UM.Theme.sizes.default_margin.width * 2; + height: childrenRect.height; + + Repeater { + id: settingList; + + model: UM.SettingCategoriesModel { id: settingCategoriesModel; } + + delegate: Item { + id: delegateItem; + + width: parent.width; + height: childrenRect.height; + + ToolButton { + id: categoryHeader; + text: model.name; + checkable: true; + width: parent.width; + onCheckedChanged: settingsColumn.state != "" ? settingsColumn.state = "" : settingsColumn.state = "collapsed"; + + style: ButtonStyle { + background: Rectangle + { + width: control.width; + height: control.height; + color: control.hovered ? palette.highlight : "transparent"; + } + label: Row + { + spacing: UM.Theme.sizes.default_margin.width; + Image + { + anchors.verticalCenter: parent.verticalCenter; + source: control.checked ? UM.Theme.icons.arrow_right : UM.Theme.icons.arrow_bottom; + } + Label + { + text: control.text; + font.bold: true; + color: control.hovered ? palette.highlightedText : palette.text; + } + } + } + } + + property variant settingsModel: model.settings; + + visible: model.visible; + + Column { + id: settingsColumn; + + anchors.top: categoryHeader.bottom; + + property real childrenHeight: + { + var h = 0.0; + for(var i in children) + { + var item = children[i]; + h += children[i].height; + if(item.settingVisible) + { + if(i > 0) + { + h += spacing; + } + } + } + return h; + } + + width: childrenRect.width; + height: childrenHeight; + Repeater { + model: delegateItem.settingsModel; + + delegate: ToolButton { + id: button; + x: model.depth * UM.Theme.sizes.default_margin.width; + text: model.name; + tooltip: model.description; + + onClicked: { + var object_id = UM.ActiveTool.properties.Model.getItem(base.currentIndex).id; + UM.ActiveTool.properties.Model.addSettingOverride(object_id, model.key); + settingPickDialog.visible = false; + } + + states: State { + name: "filtered" + when: model.filtered || !model.visible || !model.enabled + PropertyChanges { target: button; height: 0; opacity: 0; } + } + } + } + + states: State { + name: "collapsed"; + + PropertyChanges { target: settingsColumn; opacity: 0; height: 0; } + } + } + } + } + } + } + + rightButtons: [ + Button { + text: catalog.i18nc("@action:button", "Cancel"); + onClicked: { + settingPickDialog.visible = false; + } + } + ] + } + + SystemPalette { id: palette; } +} diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py new file mode 100644 index 0000000000..664fe0c61d --- /dev/null +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -0,0 +1,18 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Uranium is released under the terms of the AGPLv3 or higher. + +from UM.Tool import Tool + +from . import PerObjectSettingsModel + +class PerObjectSettingsTool(Tool): + def __init__(self): + super().__init__() + + self.setExposedProperties("Model") + + def event(self, event): + return False + + def getModel(self): + return PerObjectSettingsModel.PerObjectSettingsModel() diff --git a/plugins/PerObjectSettingsTool/SettingOverrideModel.py b/plugins/PerObjectSettingsTool/SettingOverrideModel.py new file mode 100644 index 0000000000..9fd84324d5 --- /dev/null +++ b/plugins/PerObjectSettingsTool/SettingOverrideModel.py @@ -0,0 +1,76 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Uranium is released under the terms of the AGPLv3 or higher. + +from PyQt5.QtCore import Qt, pyqtSlot, QUrl + +from UM.Application import Application +from UM.Qt.ListModel import ListModel +from UM.Settings.SettingOverrideDecorator import SettingOverrideDecorator + +class SettingOverrideModel(ListModel): + KeyRole = Qt.UserRole + 1 + LabelRole = Qt.UserRole + 2 + DescriptionRole = Qt.UserRole + 3 + ValueRole = Qt.UserRole + 4 + TypeRole = Qt.UserRole + 5 + UnitRole = Qt.UserRole + 6 + ValidRole = Qt.UserRole + 7 + + def __init__(self, node, parent = None): + super().__init__(parent) + + self._ignore_setting_change = None + + self._node = node + self._node.decoratorsChanged.connect(self._onDecoratorsChanged) + self._onDecoratorsChanged(None) + + self.addRoleName(self.KeyRole, "key") + self.addRoleName(self.LabelRole, "label") + self.addRoleName(self.DescriptionRole, "description") + self.addRoleName(self.ValueRole,"value") + self.addRoleName(self.TypeRole, "type") + self.addRoleName(self.UnitRole, "unit") + self.addRoleName(self.ValidRole, "valid") + + @pyqtSlot(str, "QVariant") + def setSettingValue(self, key, value): + if not self._decorator: + return + + self._ignore_setting_change = key + self._decorator.setSettingValue(key, value) + self._ignore_setting_change = None + + def _onDecoratorsChanged(self, node): + if not self._node.getDecorator(SettingOverrideDecorator): + self.clear() + return + + self._decorator = self._node.getDecorator(SettingOverrideDecorator) + self._decorator.settingAdded.connect(self._onSettingsChanged) + self._decorator.settingRemoved.connect(self._onSettingsChanged) + self._decorator.settingValueChanged.connect(self._onSettingValueChanged) + self._onSettingsChanged() + + def _onSettingsChanged(self): + self.clear() + + for key, setting in self._decorator.getAllSettings().items(): + value = self._decorator.getSettingValue(key) + self.appendItem({ + "key": key, + "label": setting.getLabel(), + "description": setting.getDescription(), + "value": str(value), + "type": setting.getType(), + "unit": setting.getUnit(), + "valid": setting.validate(value) + }) + + def _onSettingValueChanged(self, setting): + index = self.find("key", setting.getKey()) + if index != -1 and self._ignore_setting_change != setting.getKey(): + value = self._decorator.getSettingValue(setting.getKey()) + self.setProperty(index, "value", str(value)) + self.setProperty(index, "valid", setting.validate(value)) diff --git a/plugins/PerObjectSettingsTool/__init__.py b/plugins/PerObjectSettingsTool/__init__.py new file mode 100644 index 0000000000..c3cea197e1 --- /dev/null +++ b/plugins/PerObjectSettingsTool/__init__.py @@ -0,0 +1,27 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Uranium is released under the terms of the AGPLv3 or higher. + +from . import PerObjectSettingsTool + +from UM.i18n import i18nCatalog +i18n_catalog = i18nCatalog("uranium") + +def getMetaData(): + return { + "plugin": { + "name": i18n_catalog.i18nc("@label", "Per Object Settings Tool"), + "author": "Ultimaker", + "version": "1.0", + "description": i18n_catalog.i18nc("@info:whatsthis", "Provides the Per Object Settings."), + "api": 2 + }, + "tool": { + "name": i18n_catalog.i18nc("@label", "Per Object Settings"), + "description": i18n_catalog.i18nc("@info:tooltip", "Configure Per Object Settings"), + "icon": "setting_per_object", + "tool_panel": "PerObjectSettingsPanel.qml" + }, + } + +def register(app): + return { "tool": PerObjectSettingsTool.PerObjectSettingsTool() }