diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py
index 2fa5234ec3..03b4c181dd 100644
--- a/cura/Settings/SettingOverrideDecorator.py
+++ b/cura/Settings/SettingOverrideDecorator.py
@@ -94,6 +94,12 @@ class SettingOverrideDecorator(SceneNodeDecorator):
#
# \return An extruder's position, or None if no position info is available.
def getActiveExtruderPosition(self):
+ # for support_meshes, always use the support_extruder
+ if self.getStack().getProperty("support_mesh", "value"):
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ if global_container_stack:
+ return str(global_container_stack.getProperty("support_extruder_nr", "value"))
+
containers = ContainerRegistry.getInstance().findContainers(id = self.getActiveExtruder())
if containers:
container_stack = containers[0]
diff --git a/cura/UI/ObjectsModel.py b/cura/UI/ObjectsModel.py
index 6378ebcd1b..659732e895 100644
--- a/cura/UI/ObjectsModel.py
+++ b/cura/UI/ObjectsModel.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2019 Ultimaker B.V.
+# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger
import re
@@ -38,6 +38,9 @@ class ObjectsModel(ListModel):
OutsideAreaRole = Qt.UserRole + 3
BuilplateNumberRole = Qt.UserRole + 4
NodeRole = Qt.UserRole + 5
+ PerObjectSettingsCountRole = Qt.UserRole + 6
+ MeshTypeRole = Qt.UserRole + 7
+ ExtruderNumberRole = Qt.UserRole + 8
def __init__(self, parent = None) -> None:
super().__init__(parent)
@@ -46,6 +49,9 @@ class ObjectsModel(ListModel):
self.addRoleName(self.SelectedRole, "selected")
self.addRoleName(self.OutsideAreaRole, "outside_build_area")
self.addRoleName(self.BuilplateNumberRole, "buildplate_number")
+ self.addRoleName(self.ExtruderNumberRole, "extruder_number")
+ self.addRoleName(self.PerObjectSettingsCountRole, "per_object_settings_count")
+ self.addRoleName(self.MeshTypeRole, "mesh_type")
self.addRoleName(self.NodeRole, "node")
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSceneDelayed)
@@ -172,11 +178,47 @@ class ObjectsModel(ListModel):
node_build_plate_number = node.callDecoration("getBuildPlateNumber")
+ node_mesh_type = ""
+ per_object_settings_count = 0
+
+ per_object_stack = node.callDecoration("getStack")
+ if per_object_stack:
+ per_object_settings_count = per_object_stack.getTop().getNumInstances()
+
+ for mesh_type in ["anti_overhang_mesh", "infill_mesh", "cutting_mesh", "support_mesh"]:
+ if per_object_stack.getProperty(mesh_type, "value"):
+ node_mesh_type = mesh_type
+ per_object_settings_count -= 1 # do not count this mesh type setting
+ break
+
+ if per_object_settings_count > 0:
+ if node_mesh_type == "support_mesh":
+ # support meshes only allow support settings
+ per_object_settings_count = 0
+ for key in per_object_stack.getTop().getAllKeys():
+ if per_object_stack.getTop().getInstance(key).definition.isAncestor("support"):
+ per_object_settings_count += 1
+ elif node_mesh_type == "anti_overhang_mesh":
+ # anti overhang meshes ignore per model settings
+ per_object_settings_count = 0
+
+ extruder_position = node.callDecoration("getActiveExtruderPosition")
+ if extruder_position is None:
+ extruder_number = -1
+ else:
+ extruder_number = int(extruder_position)
+ if node_mesh_type == "anti_overhang_mesh" or node.callDecoration("isGroup"):
+ # for anti overhang meshes and groups the extruder nr is irrelevant
+ extruder_number = -1
+
nodes.append({
"name": node.getName(),
"selected": Selection.isSelected(node),
"outside_build_area": is_outside_build_area,
"buildplate_number": node_build_plate_number,
+ "extruder_number": extruder_number,
+ "per_object_settings_count": per_object_settings_count,
+ "mesh_type": node_mesh_type,
"node": node
})
diff --git a/resources/qml/ObjectItemButton.qml b/resources/qml/ObjectItemButton.qml
index b454fd929a..9244fa6391 100644
--- a/resources/qml/ObjectItemButton.qml
+++ b/resources/qml/ObjectItemButton.qml
@@ -1,4 +1,4 @@
-// Copyright (c) 2018 Ultimaker B.V.
+// Copyright (c) 2020 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
@@ -14,33 +14,11 @@ Button
width: parent.width
height: UM.Theme.getSize("action_button").height
leftPadding: UM.Theme.getSize("thin_margin").width
- rightPadding: UM.Theme.getSize("thin_margin").width
+ rightPadding: perObjectSettingsInfo.visible ? UM.Theme.getSize("default_lining").width : UM.Theme.getSize("thin_margin").width
checkable: true
hoverEnabled: true
- contentItem: Item
- {
- width: objectItemButton.width - objectItemButton.leftPadding
- height: UM.Theme.getSize("action_button").height
-
- Label
- {
- id: buttonText
- anchors
- {
- left: parent.left
- right: parent.right
- verticalCenter: parent.verticalCenter
- }
- text: objectItemButton.text
- font: UM.Theme.getFont("default")
- color: UM.Theme.getColor("text_scene")
- visible: text != ""
- renderType: Text.NativeRendering
- verticalAlignment: Text.AlignVCenter
- elide: Text.ElideRight
- }
- }
+ onClicked: Cura.SceneController.changeSelection(index)
background: Rectangle
{
@@ -51,6 +29,149 @@ Button
border.color: objectItemButton.checked ? UM.Theme.getColor("primary") : "transparent"
}
+ contentItem: Item
+ {
+ width: objectItemButton.width - objectItemButton.leftPadding
+ height: UM.Theme.getSize("action_button").height
+
+ UM.RecolorImage
+ {
+ id: swatch
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ width: height
+ height: parent.height - UM.Theme.getSize("narrow_margin").height
+ source: UM.Theme.getIcon("extruder_button")
+ color: extruderColor
+ visible: showExtruderSwatches && extruderColor != ""
+ }
+
+ Label
+ {
+ id: buttonText
+ anchors
+ {
+ left: showExtruderSwatches ? swatch.right : parent.left
+ leftMargin: showExtruderSwatches ? UM.Theme.getSize("narrow_margin").width : 0
+ right: perObjectSettingsInfo.visible ? perObjectSettingsInfo.left : parent.right
+ verticalCenter: parent.verticalCenter
+ }
+ text: objectItemButton.text
+ font: UM.Theme.getFont("default")
+ color: UM.Theme.getColor("text_scene")
+ opacity: (outsideBuildArea) ? 0.5 : 1.0
+ visible: text != ""
+ renderType: Text.NativeRendering
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ Button
+ {
+ id: perObjectSettingsInfo
+
+ anchors
+ {
+ right: parent.right
+ rightMargin: 0
+ }
+ width: childrenRect.width
+ height: parent.height
+ padding: 0
+ leftPadding: UM.Theme.getSize("thin_margin").width
+ visible: meshType != "" || perObjectSettingsCount > 0
+
+ onClicked:
+ {
+ Cura.SceneController.changeSelection(index)
+ UM.Controller.setActiveTool("PerObjectSettingsTool")
+ }
+
+ property string tooltipText:
+ {
+ var result = "";
+ if (!visible)
+ {
+ return result;
+ }
+ if (meshType != "")
+ {
+ result += "
";
+ switch (meshType) {
+ case "support_mesh":
+ result += catalog.i18nc("@label", "Is printed as support.");
+ break;
+ case "cutting_mesh":
+ result += catalog.i18nc("@label", "Other models overlapping with this model are modified.");
+ break;
+ case "infill_mesh":
+ result += catalog.i18nc("@label", "Infill overlapping with this model is modified.");
+ break;
+ case "anti_overhang_mesh":
+ result += catalog.i18nc("@label", "Overlaps with this model are not supported.");
+ break;
+ }
+ }
+ if (perObjectSettingsCount != "")
+ {
+ result += "
" + catalog.i18ncp(
+ "@label", "Overrides %1 setting.", "Overrides %1 settings.", perObjectSettingsCount
+ ).arg(perObjectSettingsCount);
+ }
+ return result;
+ }
+
+ contentItem: Item
+ {
+ height: parent.height
+ width: meshTypeIcon.width + perObjectSettingsCountLabel.width + UM.Theme.getSize("narrow_margin").width
+
+ Cura.NotificationIcon
+ {
+ id: perObjectSettingsCountLabel
+ anchors
+ {
+ right: parent.right
+ rightMargin: 0
+ }
+ visible: perObjectSettingsCount > 0
+ color: UM.Theme.getColor("text_scene")
+ labelText: perObjectSettingsCount.toString()
+ }
+
+ UM.RecolorImage
+ {
+ id: meshTypeIcon
+ anchors
+ {
+ right: perObjectSettingsCountLabel.left
+ rightMargin: UM.Theme.getSize("narrow_margin").width
+ }
+
+ width: parent.height
+ height: parent.height
+ color: UM.Theme.getColor("text_scene")
+ visible: meshType != ""
+ source:
+ {
+ switch (meshType) {
+ case "support_mesh":
+ return UM.Theme.getIcon("pos_print_as_support");
+ case "cutting_mesh":
+ case "infill_mesh":
+ return UM.Theme.getIcon("pos_modify_overlaps");
+ case "anti_overhang_mesh":
+ return UM.Theme.getIcon("pos_modify_dont_support_overlap");
+ }
+ return "";
+ }
+ }
+ }
+
+ background: Item {}
+ }
+ }
+
TextMetrics
{
id: buttonTextMetrics
@@ -63,9 +184,13 @@ Button
Cura.ToolTip
{
id: tooltip
- tooltipText: objectItemButton.text
- visible: objectItemButton.hovered && buttonTextMetrics.elidedText != buttonText.text
+ tooltipText: objectItemButton.text + perObjectSettingsInfo.tooltipText
+ visible: objectItemButton.hovered && (buttonTextMetrics.elidedText != buttonText.text || perObjectSettingsInfo.visible)
}
- onClicked: Cura.SceneController.changeSelection(index)
+ UM.I18nCatalog
+ {
+ id: catalog
+ name: "cura"
+ }
}
diff --git a/resources/qml/ObjectSelector.qml b/resources/qml/ObjectSelector.qml
index 6757863e1c..15cb476e08 100644
--- a/resources/qml/ObjectSelector.qml
+++ b/resources/qml/ObjectSelector.qml
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 Ultimaker B.V.
+// Copyright (c) 2020 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10
@@ -87,6 +87,17 @@ Item
anchors.bottom: parent.bottom
+ property var extrudersModel: CuraApplication.getExtrudersModel()
+ UM.SettingPropertyProvider
+ {
+ id: machineExtruderCount
+
+ containerStack: Cura.MachineManager.activeMachine
+ key: "machine_extruder_count"
+ watchedProperties: [ "value" ]
+ storeIndex: 0
+ }
+
ListView
{
id: listView
@@ -120,6 +131,19 @@ Item
}
text: model.name
width: listView.width
+ property bool outsideBuildArea: model.outside_build_area
+ property int perObjectSettingsCount: model.per_object_settings_count
+ property string meshType: model.mesh_type
+ property int extruderNumber: model.extruder_number
+ property string extruderColor:
+ {
+ if (model.extruder_number == -1)
+ {
+ return "";
+ }
+ return contents.extrudersModel.getItem(model.extruder_number).color;
+ }
+ property bool showExtruderSwatches: machineExtruderCount.properties.value > 1
}
}
}
diff --git a/tests/TestObjectsModel.py b/tests/TestObjectsModel.py
index caed4741bb..543334cea6 100644
--- a/tests/TestObjectsModel.py
+++ b/tests/TestObjectsModel.py
@@ -125,7 +125,16 @@ class Test_Update:
application_with_mocked_scene.getController().getScene().getRoot = MagicMock(return_value = group_scene_node)
with patch("UM.Application.Application.getInstance", MagicMock(return_value=application_with_mocked_scene)):
objects_model._update()
- assert objects_model.items == [{'name': 'Group #1', 'selected': False, 'outside_build_area': False, 'buildplate_number': None, 'node': group_scene_node}]
+ assert objects_model.items == [{
+ 'name': 'Group #1',
+ 'selected': False,
+ 'outside_build_area': False,
+ 'buildplate_number': None,
+ 'node': group_scene_node,
+ "extruder_number": -1,
+ "per_object_settings_count": 0,
+ "mesh_type": ""
+ }]
def test_updateWithNonGroup(self, objects_model, application_with_mocked_scene, slicable_scene_node):
objects_model._shouldNodeBeHandled = MagicMock(return_value=True)
@@ -133,7 +142,16 @@ class Test_Update:
application_with_mocked_scene.getController().getScene().getRoot = MagicMock(return_value=slicable_scene_node)
with patch("UM.Application.Application.getInstance", MagicMock(return_value=application_with_mocked_scene)):
objects_model._update()
- assert objects_model.items == [{'name': 'YAY(1)', 'selected': False, 'outside_build_area': False, 'buildplate_number': None, 'node': slicable_scene_node}]
+ assert objects_model.items == [{
+ 'name': 'YAY(1)',
+ 'selected': False,
+ 'outside_build_area': False,
+ 'buildplate_number': None,
+ 'node': slicable_scene_node,
+ "extruder_number": -1,
+ "per_object_settings_count": 0,
+ "mesh_type": ""
+ }]
def test_updateWithNonTwoNodes(self, objects_model, application_with_mocked_scene, slicable_scene_node):
objects_model._shouldNodeBeHandled = MagicMock(return_value=True)
@@ -143,7 +161,25 @@ class Test_Update:
application_with_mocked_scene.getController().getScene().getRoot = MagicMock(return_value=slicable_scene_node)
with patch("UM.Application.Application.getInstance", MagicMock(return_value=application_with_mocked_scene)):
objects_model._update()
- assert objects_model.items == [{'name': 'YAY', 'selected': False, 'outside_build_area': False, 'buildplate_number': None, 'node': slicable_scene_node}, {'name': 'YAY(1)', 'selected': False, 'outside_build_area': False, 'buildplate_number': None, 'node': copied_node}]
+ assert objects_model.items == [{
+ 'name': 'YAY',
+ 'selected': False,
+ 'outside_build_area': False,
+ 'buildplate_number': None,
+ 'node': slicable_scene_node,
+ "extruder_number": -1,
+ "per_object_settings_count": 0,
+ "mesh_type": ""
+ }, {
+ 'name': 'YAY(1)',
+ 'selected': False,
+ 'outside_build_area': False,
+ 'buildplate_number': None,
+ 'node': copied_node,
+ "extruder_number": -1,
+ "per_object_settings_count": 0,
+ "mesh_type": ""
+ }]
def test_updateWithNonTwoGroups(self, objects_model, application_with_mocked_scene, group_scene_node):
objects_model._shouldNodeBeHandled = MagicMock(return_value=True)
@@ -153,7 +189,25 @@ class Test_Update:
application_with_mocked_scene.getController().getScene().getRoot = MagicMock(return_value=group_scene_node)
with patch("UM.Application.Application.getInstance", MagicMock(return_value=application_with_mocked_scene)):
objects_model._update()
- assert objects_model.items == [{'name': 'Group #1', 'selected': False, 'outside_build_area': False, 'buildplate_number': None, 'node': group_scene_node}, {'name': 'Group #2', 'selected': False, 'outside_build_area': False, 'buildplate_number': None, 'node': copied_node}]
+ assert objects_model.items == [{
+ 'name': 'Group #1',
+ 'selected': False,
+ 'outside_build_area': False,
+ 'buildplate_number': None,
+ 'node': group_scene_node,
+ "extruder_number": -1,
+ "per_object_settings_count": 0,
+ "mesh_type": ""
+ }, {
+ 'name': 'Group #2',
+ 'selected': False,
+ 'outside_build_area': False,
+ 'buildplate_number': None,
+ 'node': copied_node,
+ "extruder_number": -1,
+ "per_object_settings_count": 0,
+ "mesh_type": ""
+ }]
def test_updateOutsideBuildplate(self, objects_model, application_with_mocked_scene, group_scene_node):
objects_model._shouldNodeBeHandled = MagicMock(return_value=True)
@@ -162,5 +216,14 @@ class Test_Update:
application_with_mocked_scene.getController().getScene().getRoot = MagicMock(return_value=group_scene_node)
with patch("UM.Application.Application.getInstance", MagicMock(return_value=application_with_mocked_scene)):
objects_model._update()
- assert objects_model.items == [{'name': 'Group #1', 'selected': False, 'outside_build_area': True, 'buildplate_number': None, 'node': group_scene_node}]
+ assert objects_model.items == [{
+ 'name': 'Group #1',
+ 'selected': False,
+ 'outside_build_area': True,
+ 'buildplate_number': None,
+ 'node': group_scene_node,
+ "extruder_number": -1,
+ "per_object_settings_count": 0,
+ "mesh_type": ""
+ }]