def _loadAll(self) -> None: container_registry = ContainerRegistry.getInstance() if not self.machine.has_materials: self.materials["empty_material"] = MaterialNode("empty_material", variant = self) return # There should not be any materials loaded for this printer. # Find all the materials for this variant's name. else: # Printer has its own material profiles. Look for material profiles with this printer's definition. base_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = "fdmprinter") printer_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id, variant_name = None) variant_specific_materials = container_registry.findInstanceContainersMetadata(type = "material", definition = self.machine.container_id, variant_name = self.variant_name) # If empty_variant, this won't return anything. materials_per_base_file = {material["base_file"]: material for material in base_materials} materials_per_base_file.update({material["base_file"]: material for material in printer_specific_materials}) # Printer-specific profiles override global ones. materials_per_base_file.update({material["base_file"]: material for material in variant_specific_materials}) # Variant-specific profiles override all of those. materials = list(materials_per_base_file.values()) # Filter materials based on the exclude_materials property. filtered_materials = [material for material in materials if material["id"] not in self.machine.exclude_materials] for material in filtered_materials: base_file = material["base_file"] if base_file not in self.materials: self.materials[base_file] = MaterialNode(material["id"], variant = self) self.materials[base_file].materialChanged.connect(self.materialsChanged) if not self.materials: self.materials["empty_material"] = MaterialNode("empty_material", variant = self)
def test_onMetadataChanged_wrongContainer(container_registry): variant_node = MagicMock() variant_node.variant_name = "variant_1" variant_node.machine.has_machine_quality = True variant_node.machine.quality_definition = "machine_1" with patch("cura.Machines.MaterialNode.QualityNode"): with patch( "UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)): node = MaterialNode("material_1", variant_node) # We only do this now since we do want it to be constructed but not actually re-evaluated. node._loadAll = MagicMock() container = createMockedInstanceContainer("material_2") container.getMetaData = MagicMock( return_value={ "base_file": "new_base_file", "material": "new_material_type", "GUID": "new_guid" }) node._onMetadataChanged(container) assert node.material_type == "test_material_type" assert node.guid == "omg zomg" assert node.base_file == "material_1"
def _materialRemoved(self, container: ContainerInterface) -> None: if container.getMetaDataEntry("type") != "material": return # Only interested in materials. base_file = container.getMetaDataEntry("base_file") if base_file not in self.materials: return # We don't track this material anyway. No need to remove it. original_node = self.materials[base_file] del self.materials[base_file] self.materialsChanged.emit(original_node) # Now a different material from the same base file may have been hidden because it was not as specific as the one we deleted. # Search for any submaterials from that base file that are still left. materials_same_base_file = ContainerRegistry.getInstance().findContainersMetadata(base_file = base_file) if materials_same_base_file: most_specific_submaterial = None for submaterial in materials_same_base_file: if submaterial["definition"] == self.machine.container_id: if submaterial.get("variant_name", "empty") == self.variant_name: most_specific_submaterial = submaterial break # most specific match possible if submaterial.get("variant_name", "empty") == "empty": most_specific_submaterial = submaterial if most_specific_submaterial is None: Logger.log("w", "Material %s removed, but no suitable replacement found", base_file) else: Logger.log("i", "Material %s (%s) overridden by %s", base_file, self.variant_name, most_specific_submaterial.get("id")) self.materials[base_file] = MaterialNode(most_specific_submaterial["id"], variant = self) self.materialsChanged.emit(self.materials[base_file]) if not self.materials: # The last available material just got deleted and there is nothing with the same base file to replace it. self.materials["empty_material"] = MaterialNode("empty_material", variant = self) self.materialsChanged.emit(self.materials["empty_material"])
def _materialAdded(self, container: ContainerInterface) -> None: if container.getMetaDataEntry("type") != "material": return # Not interested. if not self.machine.has_materials: return # We won't add any materials. material_definition = container.getMetaDataEntry("definition") base_file = container.getMetaDataEntry("base_file") if base_file in self.machine.exclude_materials: return # Material is forbidden for this printer. if base_file not in self.materials: # Completely new base file. Always better than not having a file as long as it matches our set-up. if material_definition != "fdmprinter" and material_definition != self.machine.container_id: return material_variant = container.getMetaDataEntry("variant_name", "empty") if material_variant != "empty" and material_variant != self.variant_name: return else: # We already have this base profile. Replace the base profile if the new one is more specific. new_definition = container.getMetaDataEntry("definition") if new_definition == "fdmprinter": return # Just as unspecific or worse. if new_definition != self.machine.container_id: return # Doesn't match this set-up. original_metadata = ContainerRegistry.getInstance().findContainersMetadata(id = self.materials[base_file].container_id)[0] original_variant = original_metadata.get("variant_name", "empty") if original_variant != "empty" or container.getMetaDataEntry("variant_name", "empty") == "empty": return # Original was already specific or just as unspecific as the new one. if "empty_material" in self.materials: del self.materials["empty_material"] self.materials[base_file] = MaterialNode(container.getId(), variant = self) self.materials[base_file].materialChanged.connect(self.materialsChanged) self.materialsChanged.emit(self.materials[base_file])
def test_onRemoved_rightContainer(container_registry): variant_node = MagicMock() variant_node.variant_name = "variant_1" variant_node.machine.has_machine_quality = True variant_node.machine.quality_definition = "machine_1" with patch("cura.Machines.MaterialNode.QualityNode"): with patch( "UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)): node = MaterialNode("material_1", variant_node) container = createMockedInstanceContainer("material_1") variant_node.materials = {"material_1": MagicMock()} node._onRemoved(container) assert "material_1" not in variant_node.materials
def test_materialNodeInit_noMachineQuality(container_registry): variant_node = MagicMock() variant_node.variant_name = "variant_1" variant_node.machine.has_machine_quality = False with patch("cura.Machines.MaterialNode.QualityNode"): with patch( "UM.Settings.ContainerRegistry.ContainerRegistry.getInstance", MagicMock(return_value=container_registry)): node = MaterialNode("material_1", variant_node) assert len(node.qualities) == 1 assert "quality_1" in node.qualities
def _materialAdded(self, container: ContainerInterface) -> None: if container.getMetaDataEntry("type") != "material": return # Not interested. if not ContainerRegistry.getInstance().findContainersMetadata( id=container.getId()): # CURA-6889 # containerAdded and removed signals may be triggered in the next event cycle. If a container gets added # and removed in the same event cycle, in the next cycle, the connections should just ignore the signals. # The check here makes sure that the container in the signal still exists. Logger.log( "d", "Got container added signal for container [%s] but it no longer exists, do nothing.", container.getId()) return if not self.machine.has_materials: return # We won't add any materials. material_definition = container.getMetaDataEntry("definition") base_file = container.getMetaDataEntry("base_file") if base_file in self.machine.exclude_materials: return # Material is forbidden for this printer. if base_file not in self.materials: # Completely new base file. Always better than not having a file as long as it matches our set-up. if material_definition != "fdmprinter" and material_definition != self.machine.container_id: return material_variant = container.getMetaDataEntry("variant_name") if material_variant is not None and material_variant != self.variant_name: return else: # We already have this base profile. Replace the base profile if the new one is more specific. new_definition = container.getMetaDataEntry("definition") if new_definition == "fdmprinter": return # Just as unspecific or worse. material_variant = container.getMetaDataEntry("variant_name") if new_definition != self.machine.container_id or material_variant != self.variant_name: return # Doesn't match this set-up. original_metadata = ContainerRegistry.getInstance( ).findContainersMetadata( id=self.materials[base_file].container_id)[0] if "variant_name" in original_metadata or material_variant is None: return # Original was already specific or just as unspecific as the new one. if "empty_material" in self.materials: del self.materials["empty_material"] self.materials[base_file] = MaterialNode(container.getId(), variant=self) self.materials[base_file].materialChanged.connect( self.materialsChanged) self.materialsChanged.emit(self.materials[base_file])
class TestSendMaterialJob(TestCase): # version 1 _LOCAL_MATERIAL_WHITE = { "type": "material", "status": "unknown", "id": "generic_pla_white", "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA", "brand": "Generic", "material": "PLA", "color_name": "White", "GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "1", "color_code": "#ffffff", "description": "Test PLA White", "adhesion_info": "Use glue.", "approximate_diameter": "3", "properties": { "density": "1.00", "diameter": "2.85", "weight": "750" }, "definition": "fdmprinter", "compatible": True } # version 2 _LOCAL_MATERIAL_WHITE_NEWER = { "type": "material", "status": "unknown", "id": "generic_pla_white", "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA", "brand": "Generic", "material": "PLA", "color_name": "White", "GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "2", "color_code": "#ffffff", "description": "Test PLA White", "adhesion_info": "Use glue.", "approximate_diameter": "3", "properties": { "density": "1.00", "diameter": "2.85", "weight": "750" }, "definition": "fdmprinter", "compatible": True } # invalid version: "one" _LOCAL_MATERIAL_WHITE_INVALID_VERSION = { "type": "material", "status": "unknown", "id": "generic_pla_white", "base_file": "generic_pla_white", "setting_version": "5", "name": "White PLA", "brand": "Generic", "material": "PLA", "color_name": "White", "GUID": "badb0ee7-87c8-4f3f-9398-938587b67dce", "version": "one", "color_code": "#ffffff", "description": "Test PLA White", "adhesion_info": "Use glue.", "approximate_diameter": "3", "properties": { "density": "1.00", "diameter": "2.85", "weight": "750" }, "definition": "fdmprinter", "compatible": True } _LOCAL_MATERIAL_WHITE_ALL_RESULT = { "generic_pla_white": MaterialGroup("generic_pla_white", MaterialNode(_LOCAL_MATERIAL_WHITE)) } _LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT = { "generic_pla_white": MaterialGroup("generic_pla_white", MaterialNode(_LOCAL_MATERIAL_WHITE_NEWER)) } _LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT = { "generic_pla_white": MaterialGroup("generic_pla_white", MaterialNode(_LOCAL_MATERIAL_WHITE_INVALID_VERSION)) } _LOCAL_MATERIAL_BLACK = { "type": "material", "status": "unknown", "id": "generic_pla_black", "base_file": "generic_pla_black", "setting_version": "5", "name": "Yellow CPE", "brand": "Ultimaker", "material": "CPE", "color_name": "Black", "GUID": "5fbb362a-41f9-4818-bb43-15ea6df34aa4", "version": "1", "color_code": "#000000", "description": "Test PLA Black", "adhesion_info": "Use glue.", "approximate_diameter": "3", "properties": { "density": "1.01", "diameter": "2.85", "weight": "750" }, "definition": "fdmprinter", "compatible": True } _LOCAL_MATERIAL_BLACK_ALL_RESULT = { "generic_pla_black": MaterialGroup("generic_pla_black", MaterialNode(_LOCAL_MATERIAL_BLACK)) } _REMOTE_MATERIAL_WHITE = { "guid": "badb0ee7-87c8-4f3f-9398-938587b67dce", "material": "PLA", "brand": "Generic", "version": 1, "color": "White", "density": 1.00 } _REMOTE_MATERIAL_BLACK = { "guid": "5fbb362a-41f9-4818-bb43-15ea6df34aa4", "material": "PLA", "brand": "Generic", "version": 2, "color": "Black", "density": 1.00 } def test_run(self): device_mock = MagicMock() job = SendMaterialJob(device_mock) job.run() # We expect the materials endpoint to be called when the job runs. device_mock.get.assert_called_with( "materials/", on_finished=job._onGetRemoteMaterials) def test__onGetRemoteMaterials_withFailedRequest(self): reply_mock = MagicMock() device_mock = MagicMock() reply_mock.attribute.return_value = 404 job = SendMaterialJob(device_mock) job._onGetRemoteMaterials(reply_mock) # We expect the device not to be called for any follow up. self.assertEqual(0, device_mock.createFormPart.call_count) def test__onGetRemoteMaterials_withWrongEncoding(self): reply_mock = MagicMock() device_mock = MagicMock() reply_mock.attribute.return_value = 200 reply_mock.readAll.return_value = QByteArray( json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("cp500")) job = SendMaterialJob(device_mock) job._onGetRemoteMaterials(reply_mock) # Given that the parsing fails we do no expect the device to be called for any follow up. self.assertEqual(0, device_mock.createFormPart.call_count) def test__onGetRemoteMaterials_withBadJsonAnswer(self): reply_mock = MagicMock() device_mock = MagicMock() reply_mock.attribute.return_value = 200 reply_mock.readAll.return_value = QByteArray( b"Six sick hicks nick six slick bricks with picks and sticks.") job = SendMaterialJob(device_mock) job._onGetRemoteMaterials(reply_mock) # Given that the parsing fails we do no expect the device to be called for any follow up. self.assertEqual(0, device_mock.createFormPart.call_count) def test__onGetRemoteMaterials_withMissingGuidInRemoteMaterial(self): reply_mock = MagicMock() device_mock = MagicMock() reply_mock.attribute.return_value = 200 remote_material_without_guid = self._REMOTE_MATERIAL_WHITE.copy() del remote_material_without_guid["guid"] reply_mock.readAll.return_value = QByteArray( json.dumps([remote_material_without_guid]).encode("ascii")) job = SendMaterialJob(device_mock) job._onGetRemoteMaterials(reply_mock) # Given that parsing fails we do not expect the device to be called for any follow up. self.assertEqual(0, device_mock.createFormPart.call_count) @patch("cura.Machines.MaterialManager.MaterialManager") @patch("cura.Settings.CuraContainerRegistry") @patch("UM.Application") def test__onGetRemoteMaterials_withInvalidVersionInLocalMaterial( self, application_mock, container_registry_mock, material_manager_mock): reply_mock = MagicMock() device_mock = MagicMock() application_mock.getContainerRegistry.return_value = container_registry_mock application_mock.getMaterialManager.return_value = material_manager_mock reply_mock.attribute.return_value = 200 reply_mock.readAll.return_value = QByteArray( json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii")) material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_INVALID_VERSION_ALL_RESULT.copy( ) with mock.patch.object(Application, "getInstance", new=lambda: application_mock): job = SendMaterialJob(device_mock) job._onGetRemoteMaterials(reply_mock) self.assertEqual(0, device_mock.createFormPart.call_count) @patch("UM.Application.Application.getInstance") def test__onGetRemoteMaterials_withNoUpdate(self, application_mock): reply_mock = MagicMock() device_mock = MagicMock() container_registry_mock = application_mock.getContainerRegistry.return_value material_manager_mock = application_mock.getMaterialManager.return_value device_mock.createFormPart.return_value = "_xXx_" material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy( ) reply_mock.attribute.return_value = 200 reply_mock.readAll.return_value = QByteArray( json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii")) with mock.patch.object(Application, "getInstance", new=lambda: application_mock): job = SendMaterialJob(device_mock) job._onGetRemoteMaterials(reply_mock) self.assertEqual(0, device_mock.createFormPart.call_count) self.assertEqual(0, device_mock.postFormWithParts.call_count) @patch("UM.Application.Application.getInstance") def test__onGetRemoteMaterials_withUpdatedMaterial(self, get_instance_mock): reply_mock = MagicMock() device_mock = MagicMock() application_mock = get_instance_mock.return_value container_registry_mock = application_mock.getContainerRegistry.return_value material_manager_mock = application_mock.getMaterialManager.return_value container_registry_mock.getContainerFilePathById = lambda x: _FILES_MAP.get( x) device_mock.createFormPart.return_value = "_xXx_" material_manager_mock.getAllMaterialGroups.return_value = self._LOCAL_MATERIAL_WHITE_NEWER_ALL_RESULT.copy( ) reply_mock.attribute.return_value = 200 reply_mock.readAll.return_value = QByteArray( json.dumps([self._REMOTE_MATERIAL_WHITE]).encode("ascii")) job = SendMaterialJob(device_mock) job._onGetRemoteMaterials(reply_mock) self.assertEqual(1, device_mock.createFormPart.call_count) self.assertEqual(1, device_mock.postFormWithParts.call_count) self.assertEqual([ call.createFormPart( "name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", "<xml></xml>"), call.postFormWithParts(target="materials/", parts=["_xXx_"], on_finished=job.sendingFinished) ], device_mock.method_calls) @patch("UM.Application.Application.getInstance") def test__onGetRemoteMaterials_withNewMaterial(self, application_mock): reply_mock = MagicMock() device_mock = MagicMock() container_registry_mock = application_mock.getContainerRegistry.return_value material_manager_mock = application_mock.getMaterialManager.return_value container_registry_mock.getContainerFilePathById = lambda x: _FILES_MAP.get( x) device_mock.createFormPart.return_value = "_xXx_" all_results = self._LOCAL_MATERIAL_WHITE_ALL_RESULT.copy() for key, value in self._LOCAL_MATERIAL_BLACK_ALL_RESULT.items(): all_results[key] = value material_manager_mock.getAllMaterialGroups.return_value = all_results reply_mock.attribute.return_value = 200 reply_mock.readAll.return_value = QByteArray( json.dumps([self._REMOTE_MATERIAL_BLACK]).encode("ascii")) with mock.patch.object(Application, "getInstance", new=lambda: application_mock): job = SendMaterialJob(device_mock) job._onGetRemoteMaterials(reply_mock) self.assertEqual(1, device_mock.createFormPart.call_count) self.assertEqual(1, device_mock.postFormWithParts.call_count) self.assertEqual([ call.createFormPart( "name=\"file\"; filename=\"generic_pla_white.xml.fdm_material\"", "<xml></xml>"), call.postFormWithParts(target="materials/", parts=["_xXx_"], on_finished=job.sendingFinished) ], device_mock.method_calls)