Merge branch 'feature_objectlist_improvements' of https://github.com/fieldOfView/Cura

This commit is contained in:
Jaime van Kessel 2020-04-28 15:40:08 +02:00
commit 9e68bcb7a0
No known key found for this signature in database
GPG key ID: 3710727397403C91
5 changed files with 295 additions and 35 deletions

View file

@ -94,6 +94,12 @@ class SettingOverrideDecorator(SceneNodeDecorator):
# #
# \return An extruder's position, or None if no position info is available. # \return An extruder's position, or None if no position info is available.
def getActiveExtruderPosition(self): 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()) containers = ContainerRegistry.getInstance().findContainers(id = self.getActiveExtruder())
if containers: if containers:
container_stack = containers[0] container_stack = containers[0]

View file

@ -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. # Cura is released under the terms of the LGPLv3 or higher.
from UM.Logger import Logger from UM.Logger import Logger
import re import re
@ -38,6 +38,9 @@ class ObjectsModel(ListModel):
OutsideAreaRole = Qt.UserRole + 3 OutsideAreaRole = Qt.UserRole + 3
BuilplateNumberRole = Qt.UserRole + 4 BuilplateNumberRole = Qt.UserRole + 4
NodeRole = Qt.UserRole + 5 NodeRole = Qt.UserRole + 5
PerObjectSettingsCountRole = Qt.UserRole + 6
MeshTypeRole = Qt.UserRole + 7
ExtruderNumberRole = Qt.UserRole + 8
def __init__(self, parent = None) -> None: def __init__(self, parent = None) -> None:
super().__init__(parent) super().__init__(parent)
@ -46,6 +49,9 @@ class ObjectsModel(ListModel):
self.addRoleName(self.SelectedRole, "selected") self.addRoleName(self.SelectedRole, "selected")
self.addRoleName(self.OutsideAreaRole, "outside_build_area") self.addRoleName(self.OutsideAreaRole, "outside_build_area")
self.addRoleName(self.BuilplateNumberRole, "buildplate_number") 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") self.addRoleName(self.NodeRole, "node")
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSceneDelayed) Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSceneDelayed)
@ -172,11 +178,47 @@ class ObjectsModel(ListModel):
node_build_plate_number = node.callDecoration("getBuildPlateNumber") 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({ nodes.append({
"name": node.getName(), "name": node.getName(),
"selected": Selection.isSelected(node), "selected": Selection.isSelected(node),
"outside_build_area": is_outside_build_area, "outside_build_area": is_outside_build_area,
"buildplate_number": node_build_plate_number, "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 "node": node
}) })

View file

@ -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. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10 import QtQuick 2.10
@ -14,33 +14,11 @@ Button
width: parent.width width: parent.width
height: UM.Theme.getSize("action_button").height height: UM.Theme.getSize("action_button").height
leftPadding: UM.Theme.getSize("thin_margin").width 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 checkable: true
hoverEnabled: true hoverEnabled: true
contentItem: Item onClicked: Cura.SceneController.changeSelection(index)
{
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
}
}
background: Rectangle background: Rectangle
{ {
@ -51,6 +29,149 @@ Button
border.color: objectItemButton.checked ? UM.Theme.getColor("primary") : "transparent" 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 += "<br>";
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 += "<br>" + 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 TextMetrics
{ {
id: buttonTextMetrics id: buttonTextMetrics
@ -63,9 +184,13 @@ Button
Cura.ToolTip Cura.ToolTip
{ {
id: tooltip id: tooltip
tooltipText: objectItemButton.text tooltipText: objectItemButton.text + perObjectSettingsInfo.tooltipText
visible: objectItemButton.hovered && buttonTextMetrics.elidedText != buttonText.text visible: objectItemButton.hovered && (buttonTextMetrics.elidedText != buttonText.text || perObjectSettingsInfo.visible)
} }
onClicked: Cura.SceneController.changeSelection(index) UM.I18nCatalog
{
id: catalog
name: "cura"
}
} }

View file

@ -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. // Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.10 import QtQuick 2.10
@ -87,6 +87,17 @@ Item
anchors.bottom: parent.bottom 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 ListView
{ {
id: listView id: listView
@ -120,6 +131,19 @@ Item
} }
text: model.name text: model.name
width: listView.width 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
} }
} }
} }

View file

@ -125,7 +125,16 @@ class Test_Update:
application_with_mocked_scene.getController().getScene().getRoot = MagicMock(return_value = group_scene_node) 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)): with patch("UM.Application.Application.getInstance", MagicMock(return_value=application_with_mocked_scene)):
objects_model._update() 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): def test_updateWithNonGroup(self, objects_model, application_with_mocked_scene, slicable_scene_node):
objects_model._shouldNodeBeHandled = MagicMock(return_value=True) 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) 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)): with patch("UM.Application.Application.getInstance", MagicMock(return_value=application_with_mocked_scene)):
objects_model._update() 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): def test_updateWithNonTwoNodes(self, objects_model, application_with_mocked_scene, slicable_scene_node):
objects_model._shouldNodeBeHandled = MagicMock(return_value=True) 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) 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)): with patch("UM.Application.Application.getInstance", MagicMock(return_value=application_with_mocked_scene)):
objects_model._update() 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): def test_updateWithNonTwoGroups(self, objects_model, application_with_mocked_scene, group_scene_node):
objects_model._shouldNodeBeHandled = MagicMock(return_value=True) 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) 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)): with patch("UM.Application.Application.getInstance", MagicMock(return_value=application_with_mocked_scene)):
objects_model._update() 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): def test_updateOutsideBuildplate(self, objects_model, application_with_mocked_scene, group_scene_node):
objects_model._shouldNodeBeHandled = MagicMock(return_value=True) 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) 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)): with patch("UM.Application.Application.getInstance", MagicMock(return_value=application_with_mocked_scene)):
objects_model._update() 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": ""
}]