Merge pull request #5993 from Ultimaker/feature_intent_model_per_category

Create an intent model per intent category
This commit is contained in:
Lipu Fei 2019-07-09 13:12:02 +02:00 committed by GitHub
commit 9c3d65a6c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 163 additions and 72 deletions

View File

@ -61,7 +61,6 @@ from cura.Arranging.Arrange import Arrange
from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
from cura.Arranging.ShapeArray import ShapeArray
from cura.Machines.Models.IntentModel import IntentModel
from cura.Operations.SetParentOperation import SetParentOperation
@ -92,6 +91,8 @@ from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfile
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
from cura.Machines.Models.SettingVisibilityPresetsModel import SettingVisibilityPresetsModel
from cura.Machines.Models.UserChangesModel import UserChangesModel
from cura.Machines.Models.IntentModel import IntentModel
from cura.Machines.Models.IntentCategoryModel import IntentCategoryModel
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
@ -104,6 +105,7 @@ from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.MachineManager import MachineManager
from cura.Settings.MachineNameValidator import MachineNameValidator
from cura.Settings.IntentManager import IntentManager
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel
@ -937,6 +939,9 @@ class CuraApplication(QtApplication):
def getQualityManager(self, *args) -> "QualityManager":
return self._quality_manager
def getIntentManager(self, *args) -> IntentManager:
return IntentManager.getInstance()
def getObjectsModel(self, *args):
if self._object_manager is None:
self._object_manager = ObjectsModel(self)
@ -1036,6 +1041,7 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, "SceneController", self.getCuraSceneController)
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
qmlRegisterSingletonType(IntentManager, "Cura", 1, 6, "IntentManager", self.getIntentManager)
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
@ -1070,6 +1076,7 @@ class CuraApplication(QtApplication):
"CustomQualityProfilesDropDownMenuModel", self.getCustomQualityProfilesDropDownMenuModel)
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
qmlRegisterType(IntentModel, "Cura", 1, 6, "IntentModel")
qmlRegisterType(IntentCategoryModel, "Cura", 1, 6, "IntentCategoryModel")
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
qmlRegisterType(SettingVisibilityPresetsModel, "Cura", 1, 0, "SettingVisibilityPresetsModel")

View File

@ -3,9 +3,14 @@
from PyQt5.QtCore import Qt
import collections
from typing import TYPE_CHECKING
from cura.Settings.IntentManager import IntentManager
from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry #To update the list if anything changes.
if TYPE_CHECKING:
from UM.Settings.ContainerRegistry import ContainerInterface
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
@ -34,6 +39,17 @@ class IntentCategoryModel(ListModel):
self.addRoleName(self.IntentCategoryRole, "intent_category")
self.addRoleName(self.WeightRole, "weight")
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerChange)
ContainerRegistry.getInstance().containerRemoved.connect(self._onContainerChange)
IntentManager.getInstance().configurationChanged.connect(self.update)
self.update()
## Updates the list of intents if an intent profile was added or removed.
def _onContainerChange(self, container: "ContainerInterface") -> None:
if container.getMetaDataEntry("type") == "intent":
self.update()
## Updates the list of intents.
def update(self) -> None:
available_categories = IntentManager.getInstance().currentAvailableIntentCategories()
@ -42,6 +58,6 @@ class IntentCategoryModel(ListModel):
result.append({
"name": self.name_translation.get(category, catalog.i18nc("@label", "Unknown")),
"intent_category": category,
"weight": list(self.name_translation.items()).index(category)
"weight": list(self.name_translation.keys()).index(category)
})
super().update(result)
self.setItems(result)

View File

@ -1,38 +1,65 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from PyQt5.QtCore import QObject
from UM.Qt.ListModel import ListModel
from PyQt5.QtCore import Qt
from typing import Optional, List, Dict, Any
from PyQt5.QtCore import Qt, QObject, pyqtProperty, pyqtSignal
from UM.Qt.ListModel import ListModel
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Settings.IntentManager import IntentManager
import cura.CuraApplication
class IntentModel(ListModel):
NameRole = Qt.UserRole + 1
IdRole = Qt.UserRole + 2
ContainerRole = Qt.UserRole + 3
QualityTypeRole = Qt.UserRole + 2
def __init__(self, parent: Optional[QObject] = None) -> None:
super().__init__(parent)
self.addRoleName(self.NameRole, "name")
self.addRoleName(self.IdRole, "id")
self.addRoleName(self.ContainerRole, "container")
self.addRoleName(self.QualityTypeRole, "quality_type")
self._intent_category = "engineering"
ContainerRegistry.getInstance().containerAdded.connect(self._onChanged)
ContainerRegistry.getInstance().containerRemoved.connect(self._onChanged)
self._update()
intentCategoryChanged = pyqtSignal()
def setIntentCategory(self, new_category: str) -> None:
if self._intent_category != new_category:
self._intent_category = new_category
self.intentCategoryChanged.emit()
self._update()
@pyqtProperty(str, fset = setIntentCategory, notify = intentCategoryChanged)
def intentCategory(self) -> str:
return self._intent_category
def _onChanged(self, container):
if container.getMetaDataEntry("type") == "intent":
self._update()
def _update(self) -> None:
new_items = []
for container in ContainerRegistry.getInstance().findInstanceContainers(type="intent"):
new_items.append({"name": container.getName(), "id": container.getId(), "container": container})
new_items = [] # type: List[Dict[str, Any]]
application = cura.CuraApplication.CuraApplication.getInstance()
quality_manager = application.getQualityManager()
global_stack = application.getGlobalContainerStack()
if not global_stack:
self.setItems(new_items)
return
quality_groups = quality_manager.getQualityGroups(global_stack)
for intent_category, quality_type in IntentManager.getInstance().getCurrentAvailableIntents():
if intent_category == self._intent_category:
new_items.append({"name": quality_groups[quality_type].name, "quality_type": quality_type})
if self._intent_category == "default": #For Default we always list all quality types. We can't filter on available profiles since the empty intent is not a specific quality type.
for quality_type in quality_groups.keys():
new_items.append({"name": quality_groups[quality_type].name, "quality_type": quality_type})
self.setItems(new_items)

View File

@ -1,10 +1,10 @@
#Copyright (c) 2019 Ultimaker B.V.
#Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtSignal
from typing import Any, Dict, List, Set, Tuple, TYPE_CHECKING
from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
from typing import Any, Dict, List, Optional, Set, Tuple, TYPE_CHECKING
import cura.CuraApplication
from cura.Settings.cura_empty_instance_containers import empty_intent_container
from UM.Settings.InstanceContainer import InstanceContainer
if TYPE_CHECKING:
@ -20,7 +20,7 @@ class IntentManager(QObject):
def __init__(self) -> None:
super().__init__()
CuraApplication.getInstance().getMachineManager().activeStackChanged.connect(self.configurationChanged)
cura.CuraApplication.CuraApplication.getInstance().getMachineManager().activeStackChanged.connect(self.configurationChanged)
self.configurationChanged.connect(self.selectDefaultIntent)
pass
@ -31,7 +31,8 @@ class IntentManager(QObject):
cls.__instance = IntentManager()
return cls.__instance
configurationChanged = pyqtSignal()
configurationChanged = pyqtSignal() #Triggered when something changed in the rest of the stack.
intentCategoryChanged = pyqtSignal() #Triggered when we switch categories.
## Gets the metadata dictionaries of all intent profiles for a given
# configuration.
@ -42,7 +43,7 @@ class IntentManager(QObject):
# \return A list of metadata dictionaries matching the search criteria, or
# an empty list if nothing was found.
def intentMetadatas(self, definition_id: str, nozzle_name: str, material_id: str) -> List[Dict[str, Any]]:
registry = CuraApplication.getInstance().getContainerRegistry()
registry = cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry()
return registry.findContainersMetadata(type = "intent", definition = definition_id, variant = nozzle_name, material = material_id)
## Collects and returns all intent categories available for the given
@ -66,8 +67,8 @@ class IntentManager(QObject):
#
# \return A list of tuples of intent_category and quality_type. The actual
# instance may vary per extruder.
def currentAvailableIntents(self) -> List[Tuple[str, str]]:
application = CuraApplication.getInstance()
def getCurrentAvailableIntents(self) -> List[Tuple[str, str]]:
application = cura.CuraApplication.CuraApplication.getInstance()
global_stack = application.getGlobalContainerStack()
if global_stack is None:
return [("default", "normal")]
@ -79,7 +80,7 @@ class IntentManager(QObject):
final_intent_ids = set() # type: Set[str]
current_definition_id = global_stack.definition.getMetaDataEntry("id")
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
for extruder_stack in global_stack.extruderList:
nozzle_name = extruder_stack.variant.getMetaDataEntry("name")
material_id = extruder_stack.material.getMetaDataEntry("base_file")
final_intent_ids |= {metadata["id"] for metadata in self.intentMetadatas(current_definition_id, nozzle_name, material_id) if metadata["quality_type"] in available_quality_types}
@ -100,12 +101,12 @@ class IntentManager(QObject):
# \return List of all categories in the current configurations of all
# extruders.
def currentAvailableIntentCategories(self) -> List[str]:
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
if global_stack is None:
return ["default"]
current_definition_id = global_stack.definition.getMetaDataEntry("id")
final_intent_categories = set() # type: Set[str]
for extruder_stack in ExtruderManager.getInstance().getUsedExtruderStacks():
for extruder_stack in global_stack.extruderList:
nozzle_name = extruder_stack.variant.getMetaDataEntry("name")
material_id = extruder_stack.material.getMetaDataEntry("base_file")
final_intent_categories.update(self.intentCategories(current_definition_id, nozzle_name, material_id))
@ -115,16 +116,26 @@ class IntentManager(QObject):
# the configuration, an extruder can't match the intent that the user
# selects, or just when creating a new printer.
def getDefaultIntent(self) -> InstanceContainer:
return CuraApplication.getInstance().empty_intent_container
return empty_intent_container
@pyqtProperty(str, notify = intentCategoryChanged)
def currentIntentCategory(self) -> str:
application = cura.CuraApplication.CuraApplication.getInstance()
active_extruder_stack = application.getMachineManager().activeStack
if active_extruder_stack is None:
return ""
return active_extruder_stack.intent.getMetaDataEntry("intent_category", "")
## Apply intent on the stacks.
@pyqtSlot(str, str)
def selectIntent(self, intent_category: str, quality_type: str) -> None:
application = CuraApplication.getInstance()
old_intent_category = self.currentIntentCategory
application = cura.CuraApplication.CuraApplication.getInstance()
global_stack = application.getGlobalContainerStack()
if global_stack is None:
return
current_definition_id = global_stack.definition.getMetaDataEntry("id")
for extruder_stack in ExtruderManager.getInstance().getUsedExtruderStacks():
for extruder_stack in global_stack.extruderList:
nozzle_name = extruder_stack.variant.getMetaDataEntry("name")
material_id = extruder_stack.material.getMetaDataEntry("base_file")
intent = application.getContainerRegistry().findContainers(definition = current_definition_id, variant = nozzle_name, material = material_id, quality_type = quality_type, intent_category = intent_category)
@ -134,8 +145,14 @@ class IntentManager(QObject):
extruder_stack.intent = self.getDefaultIntent()
application.getMachineManager().setQualityGroupByQualityType(quality_type)
if old_intent_category != intent_category:
self.intentCategoryChanged.emit()
## Selects the default intents on every extruder.
def selectDefaultIntent(self) -> None:
for extruder_stack in ExtruderManager.getInstance().getUsedExtruderStacks():
application = cura.CuraApplication.CuraApplication.getInstance()
global_stack = application.getGlobalContainerStack()
if global_stack is None:
return
for extruder_stack in global_stack.extruderList:
extruder_stack.intent = self.getDefaultIntent()

View File

@ -1,11 +1,15 @@
[general]
version = 4
name = Smooth (TEST INTENT)
definition = fdmprinter
definition = ultimaker3
[metadata]
setting_version = 7
setting_version = 8
type = intent
intent_category = smooth
quality_type = draft
variant = AA 0.4
material = generic_pla
[values]
infill_sparse_density = 10

View File

@ -1,15 +1,15 @@
[general]
version = 4
name = Strong (TEST INTENT)
definition = fdmprinter
definition = ultimaker3
[metadata]
setting_version = 7
setting_version = 8
type = intent
intent_category = engineering
quality_type = draft
material = generic_abs
variant = AA 0.4
material = generic_pla
[values]
infill_sparse_density = 50

View File

@ -14,27 +14,47 @@ Menu
property int extruderIndex: 0
Cura.IntentModel
Cura.IntentCategoryModel
{
id: intentModel
id: intentCategoryModel
}
Instantiator
{
model: intentModel
model: intentCategoryModel
MenuItem
MenuItem //Section header.
{
text: model.name
checkable: true
enabled: false
checked: false
Binding on checked
property var per_category_intents: Cura.IntentModel
{
when: Cura.MachineManager.activeStack != null
value: Cura.MachineManager.activeStack.intent == model.container
id: intentModel
intentCategory: model.intent_category
}
property var intent_instantiator: Instantiator
{
model: intentModel
MenuItem
{
text: model.name
checkable: true
checked: false
Binding on checked
{
when: Cura.MachineManager.activeStack != null
value: Cura.MachineManager.activeStack.intent.metaData["intent_category"] == intentModel.intentCategory && Cura.MachineManager.activeStack.quality.metaData["quality_type"] == model.quality_type
}
exclusiveGroup: group
onTriggered: Cura.IntentManager.selectIntent(intentModel.intentCategory, model.quality_type)
}
onObjectAdded: menu.insertItem(index, object)
onObjectRemoved: menu.removeItem(object)
}
exclusiveGroup: group
onTriggered: Cura.MachineManager.activeStack.intent = model.container
}
onObjectAdded: menu.insertItem(index, object)

View File

@ -12,8 +12,8 @@ from tests.Settings.MockContainer import MockContainer
@pytest.fixture()
def quality_manager(application, container_registry, global_stack) -> QualityManager:
application.getGlobalContainerStack = MagicMock(return_value = global_stack)
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value = application)):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
manager = QualityManager(application)
return manager
@ -24,8 +24,8 @@ def intent_manager(application, extruder_manager, machine_manager, quality_manag
application.getGlobalContainerStack = MagicMock(return_value = global_stack)
application.getMachineManager = MagicMock(return_value = machine_manager)
application.getQualityManager = MagicMock(return_value = quality_manager)
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value = application)):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
manager = IntentManager()
return manager
@ -65,16 +65,16 @@ def mockFindContainers(**kwargs) -> List[MockContainer]:
def doSetup(application, extruder_manager, quality_manager, container_registry, global_stack) -> None:
container_registry.findContainersMetadata = MagicMock(side_effect=mockFindMetadata)
container_registry.findContainers = MagicMock(side_effect=mockFindContainers)
container_registry.findContainersMetadata = MagicMock(side_effect = mockFindMetadata)
container_registry.findContainers = MagicMock(side_effect = mockFindContainers)
quality_manager.getQualityGroups = MagicMock(return_value=mocked_qualitygroup_metadata)
quality_manager.getQualityGroups = MagicMock(return_value = mocked_qualitygroup_metadata)
for _, qualitygroup in mocked_qualitygroup_metadata.items():
qualitygroup.node_for_global = MagicMock(name="Node for global")
application.getQualityManager = MagicMock(return_value=quality_manager)
qualitygroup.node_for_global = MagicMock(name = "Node for global")
application.getQualityManager = MagicMock(return_value = quality_manager)
global_stack.definition = MockContainer({"id": "ultimaker3"})
application.getGlobalContainerStack = MagicMock(return_value=global_stack)
application.getGlobalContainerStack = MagicMock(return_value = global_stack)
extruder_stack_a = MockContainer({"id": "Extruder The First"})
extruder_stack_a.variant = MockContainer({"name": "AA 0.4"})
@ -83,33 +83,33 @@ def doSetup(application, extruder_manager, quality_manager, container_registry,
extruder_stack_b.variant = MockContainer({"name": "AA 0.4"})
extruder_stack_b.material = MockContainer({"base_file": "generic_pla"})
extruder_manager.getUsedExtruderStacks = MagicMock(return_value=[extruder_stack_a, extruder_stack_b])
application.getGlobalContainerStack().extruderList = [extruder_stack_a, extruder_stack_b]
extruder_manager.getUsedExtruderStacks = MagicMock(return_value = [extruder_stack_a, extruder_stack_b])
def test_intentCategories(application, intent_manager, container_registry):
# Mock .findContainersMetadata so we also test .intentMetadatas (the latter is mostly a wrapper around the former).
container_registry.findContainersMetadata = MagicMock(return_value=mocked_intent_metadata)
container_registry.findContainersMetadata = MagicMock(return_value = mocked_intent_metadata)
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value = application)):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
categories = intent_manager.intentCategories("ultimaker3", "AA 0.4", "generic_pla") # type:List[str]
assert "default" in categories, "default should always be in categories"
assert "strong" in categories, "strong should be in categories"
assert "smooth" in categories, "smooth should be in categories"
def test_currentAvailableIntents(application, extruder_manager, quality_manager, intent_manager, container_registry, global_stack):
def test_getCurrentAvailableIntents(application, extruder_manager, quality_manager, intent_manager, container_registry, global_stack):
doSetup(application, extruder_manager, quality_manager, container_registry, global_stack)
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
with patch("cura.Settings.ExtruderManager.ExtruderManager.getInstance", MagicMock(return_value=extruder_manager)):
intents = intent_manager.currentAvailableIntents()
assert ("smooth", "normal") in intents
assert ("strong", "abnorm") in intents
#assert ("default", "normal") in intents # Pending to-do in 'IntentManager'.
#assert ("default", "abnorm") in intents # Pending to-do in 'IntentManager'.
assert len(intents) == 2 # Or 4? pending to-do in 'IntentManager'.
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value = application)):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value = container_registry)):
intents = intent_manager.getCurrentAvailableIntents()
assert ("smooth", "normal") in intents
assert ("strong", "abnorm") in intents
#assert ("default", "normal") in intents # Pending to-do in 'IntentManager'.
#assert ("default", "abnorm") in intents # Pending to-do in 'IntentManager'.
assert len(intents) == 2 # Or 4? pending to-do in 'IntentManager'.
def test_currentAvailableIntentCategories(application, extruder_manager, quality_manager, intent_manager, container_registry, global_stack):
@ -131,7 +131,7 @@ def test_selectIntent(application, extruder_manager, quality_manager, intent_man
with patch("cura.CuraApplication.CuraApplication.getInstance", MagicMock(return_value=application)):
with patch("UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)):
with patch("cura.Settings.ExtruderManager.ExtruderManager.getInstance", MagicMock(return_value=extruder_manager)):
intents = intent_manager.currentAvailableIntents()
intents = intent_manager.getCurrentAvailableIntents()
for intent, quality in intents:
intent_manager.selectIntent(intent, quality)
extruder_stacks = extruder_manager.getUsedExtruderStacks()