Merge branch 'main' into bump-to-5.9.0-alpha.1

This commit is contained in:
Remco Burema 2024-08-06 12:54:14 +02:00 committed by GitHub
commit 5b9b26f28e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 256 additions and 128 deletions

View File

@ -1,7 +1,13 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING
from dataclasses import asdict
from typing import cast, Dict, TYPE_CHECKING
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction
from cura.Settings.GlobalStack import GlobalStack
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
@ -47,3 +53,57 @@ class Settings:
"""
return self.application.getSidebarCustomMenuItems()
def getSliceMetadata(self) -> Dict[str, Dict[str, Dict[str, str]]]:
"""Get all changed settings and all settings. For each extruder and the global stack"""
print_information = self.application.getPrintInformation()
machine_manager = self.application.getMachineManager()
settings = {
"material": {
"length": print_information.materialLengths,
"weight": print_information.materialWeights,
"cost": print_information.materialCosts,
},
"global": {
"changes": {},
"all_settings": {},
},
"quality": asdict(machine_manager.activeQualityDisplayNameMap()),
}
def _retrieveValue(container: InstanceContainer, setting_: str):
value_ = container.getProperty(setting_, "value")
for _ in range(0, 1024): # Prevent possibly endless loop by not using a limit.
if not isinstance(value_, SettingFunction):
return value_ # Success!
value_ = value_(container)
return 0 # Fallback value after breaking possibly endless loop.
global_stack = cast(GlobalStack, self.application.getGlobalContainerStack())
# Add global user or quality changes
global_flattened_changes = InstanceContainer.createMergedInstanceContainer(global_stack.userChanges, global_stack.qualityChanges)
for setting in global_flattened_changes.getAllKeys():
settings["global"]["changes"][setting] = _retrieveValue(global_flattened_changes, setting)
# Get global all settings values without user or quality changes
for setting in global_stack.getAllKeys():
settings["global"]["all_settings"][setting] = _retrieveValue(global_stack, setting)
for i, extruder in enumerate(global_stack.extruderList):
# Add extruder fields to settings dictionary
settings[f"extruder_{i}"] = {
"changes": {},
"all_settings": {},
}
# Add extruder user or quality changes
extruder_flattened_changes = InstanceContainer.createMergedInstanceContainer(extruder.userChanges, extruder.qualityChanges)
for setting in extruder_flattened_changes.getAllKeys():
settings[f"extruder_{i}"]["changes"][setting] = _retrieveValue(extruder_flattened_changes, setting)
# Get extruder all settings values without user or quality changes
for setting in extruder.getAllKeys():
settings[f"extruder_{i}"]["all_settings"][setting] = _retrieveValue(extruder, setting)
return settings

View File

@ -0,0 +1,106 @@
# Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Resources import Resources
import json
from typing import Dict, List, Optional
class FormatMaps:
# A map from the printer-type in their native file-formats to the internal name we use.
PRINTER_TYPE_NAME = {
"fire_e": "ultimaker_method",
"lava_f": "ultimaker_methodx",
"magma_10": "ultimaker_methodxl",
"sketch": "ultimaker_sketch"
}
# A map from the extruder-name in their native file-formats to the internal name we use.
EXTRUDER_NAME_MAP = {
"mk14_hot": "1XA",
"mk14_hot_s": "2XA",
"mk14_c": "1C",
"mk14": "1A",
"mk14_s": "2A",
"mk14_e": "LABS"
}
# A map from the material-name in their native file-formats to some info, including the internal name we use.
MATERIAL_MAP = {
"abs": {"name": "ABS", "guid": "2780b345-577b-4a24-a2c5-12e6aad3e690"},
"abs-cf10": {"name": "ABS-CF", "guid": "495a0ce5-9daf-4a16-b7b2-06856d82394d"},
"abs-wss1": {"name": "ABS-R", "guid": "88c8919c-6a09-471a-b7b6-e801263d862d"},
"asa": {"name": "ASA", "guid": "f79bc612-21eb-482e-ad6c-87d75bdde066"},
"nylon12-cf": {"name": "Nylon 12 CF", "guid": "3c6f2877-71cc-4760-84e6-4b89ab243e3b"},
"nylon": {"name": "Nylon", "guid": "283d439a-3490-4481-920c-c51d8cdecf9c"},
"pc": {"name": "PC", "guid": "62414577-94d1-490d-b1e4-7ef3ec40db02"},
"petg": {"name": "PETG", "guid": "69386c85-5b6c-421a-bec5-aeb1fb33f060"},
"pla": {"name": "PLA", "guid": "abb9c58e-1f56-48d1-bd8f-055fde3a5b56"},
"pva": {"name": "PVA", "guid": "add51ef2-86eb-4c39-afd5-5586564f0715"},
"wss1": {"name": "RapidRinse", "guid": "a140ef8f-4f26-4e73-abe0-cfc29d6d1024"},
"sr30": {"name": "SR-30", "guid": "77873465-83a9-4283-bc44-4e542b8eb3eb"},
"bvoh": {"name": "BVOH", "guid": "923e604c-8432-4b09-96aa-9bbbd42207f4"},
"cpe": {"name": "CPE", "guid": "da1872c1-b991-4795-80ad-bdac0f131726"},
"hips": {"name": "HIPS", "guid": "a468d86a-220c-47eb-99a5-bbb47e514eb0"},
"tpu": {"name": "TPU 95A", "guid": "19baa6a9-94ff-478b-b4a1-8157b74358d2"},
"im-pla": {"name": "Tough", "guid": "de031137-a8ca-4a72-bd1b-17bb964033ad"}
}
__inverse_printer_name: Optional[Dict[str, str]] = None
__inverse_extruder_type: Optional[Dict[str, str]] = None
__inverse_material_map: Optional[Dict[str, str]] = None
__product_to_id_map: Optional[Dict[str, List[str]]] = None
@classmethod
def getInversePrinterNameMap(cls) -> Dict[str, str]:
"""Returns the inverse of the printer name map, that is, from the internal name to the name used in output."""
if cls.__inverse_printer_name is not None:
return cls.__inverse_printer_name
cls.__inverse_printer_name = {}
for key, value in cls.PRINTER_TYPE_NAME.items():
cls.__inverse_printer_name[value] = key
return cls.__inverse_printer_name
@classmethod
def getInverseExtruderTypeMap(cls) -> Dict[str, str]:
"""Returns the inverse of the extruder type map, that is, from the internal name to the name used in output."""
if cls.__inverse_extruder_type is not None:
return cls.__inverse_extruder_type
cls.__inverse_extruder_type = {}
for key, value in cls.EXTRUDER_NAME_MAP.items():
cls.__inverse_extruder_type[value] = key
return cls.__inverse_extruder_type
@classmethod
def getInverseMaterialMap(cls) -> Dict[str, str]:
"""Returns the inverse of the material map, that is, from the internal name to the name used in output.
Note that this drops the extra info saved in the non-inverse material map, use that if you need it.
"""
if cls.__inverse_material_map is not None:
return cls.__inverse_material_map
cls.__inverse_material_map = {}
for key, value in cls.MATERIAL_MAP.items():
cls.__inverse_material_map[value["name"]] = key
return cls.__inverse_material_map
@classmethod
def getProductIdMap(cls) -> Dict[str, List[str]]:
"""Gets a mapping from product names (for example, in the XML files) to their definition IDs.
This loads the mapping from a file.
"""
if cls.__product_to_id_map is not None:
return cls.__product_to_id_map
product_to_id_file = Resources.getPath(Resources.Texts, "product_to_id.json")
with open(product_to_id_file, encoding = "utf-8") as f:
contents = ""
for line in f:
contents += line if "#" not in line else "".join([line.replace("#", str(n)) for n in range(1, 12)])
cls.__product_to_id_map = json.loads(contents)
cls.__product_to_id_map = {key: [value] for key, value in cls.__product_to_id_map.items()}
#This also loads "Ultimaker S5" -> "ultimaker_s5" even though that is not strictly necessary with the default to change spaces into underscores.
#However it is not always loaded with that default; this mapping is also used in serialize() without that default.
return cls.__product_to_id_map

View File

@ -1,9 +1,10 @@
# Copyright (c) 2018 Ultimaker B.V.
# Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from PyQt6.QtCore import pyqtProperty, QObject, pyqtSignal
from cura.PrinterOutput.FormatMaps import FormatMaps
from .MaterialOutputModel import MaterialOutputModel
@ -45,16 +46,8 @@ class ExtruderConfigurationModel(QObject):
@staticmethod
def applyNameMappingHotend(hotendId) -> str:
_EXTRUDER_NAME_MAP = {
"mk14_hot":"1XA",
"mk14_hot_s":"2XA",
"mk14_c":"1C",
"mk14":"1A",
"mk14_s":"2A",
"mk14_e": "LABS"
}
if hotendId in _EXTRUDER_NAME_MAP:
return _EXTRUDER_NAME_MAP[hotendId]
if hotendId in FormatMaps.EXTRUDER_NAME_MAP:
return FormatMaps.EXTRUDER_NAME_MAP[hotendId]
return hotendId
@pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)

View File

@ -1,9 +1,10 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional
from PyQt6.QtCore import pyqtProperty, QObject
from cura.PrinterOutput.FormatMaps import FormatMaps
class MaterialOutputModel(QObject):
@ -23,30 +24,9 @@ class MaterialOutputModel(QObject):
@staticmethod
def getMaterialFromDefinition(guid, type, brand, name):
_MATERIAL_MAP = { "abs" :{"name" :"ABS" ,"guid": "2780b345-577b-4a24-a2c5-12e6aad3e690"},
"abs-cf10" :{"name": "ABS-CF" ,"guid": "495a0ce5-9daf-4a16-b7b2-06856d82394d"},
"abs-wss1" :{"name" :"ABS-R" ,"guid": "88c8919c-6a09-471a-b7b6-e801263d862d"},
"asa" :{"name" :"ASA" ,"guid": "f79bc612-21eb-482e-ad6c-87d75bdde066"},
"nylon12-cf":{"name": "Nylon 12 CF" ,"guid": "3c6f2877-71cc-4760-84e6-4b89ab243e3b"},
"nylon" :{"name" :"Nylon" ,"guid": "283d439a-3490-4481-920c-c51d8cdecf9c"},
"pc" :{"name" :"PC" ,"guid": "62414577-94d1-490d-b1e4-7ef3ec40db02"},
"petg" :{"name" :"PETG" ,"guid": "69386c85-5b6c-421a-bec5-aeb1fb33f060"},
"pla" :{"name" :"PLA" ,"guid": "abb9c58e-1f56-48d1-bd8f-055fde3a5b56"},
"pva" :{"name" :"PVA" ,"guid": "add51ef2-86eb-4c39-afd5-5586564f0715"},
"wss1" :{"name" :"RapidRinse" ,"guid": "a140ef8f-4f26-4e73-abe0-cfc29d6d1024"},
"sr30" :{"name" :"SR-30" ,"guid": "77873465-83a9-4283-bc44-4e542b8eb3eb"},
"bvoh" :{"name" :"BVOH" ,"guid": "923e604c-8432-4b09-96aa-9bbbd42207f4"},
"cpe" :{"name" :"CPE" ,"guid": "da1872c1-b991-4795-80ad-bdac0f131726"},
"hips" :{"name" :"HIPS" ,"guid": "a468d86a-220c-47eb-99a5-bbb47e514eb0"},
"tpu" :{"name" :"TPU 95A" ,"guid": "19baa6a9-94ff-478b-b4a1-8157b74358d2"},
"im-pla" :{"name": "Tough" ,"guid": "de031137-a8ca-4a72-bd1b-17bb964033ad"}
}
if guid is None and brand != "empty" and type in _MATERIAL_MAP:
name = _MATERIAL_MAP[type]["name"]
guid = _MATERIAL_MAP[type]["guid"]
if guid is None and brand != "empty" and type in FormatMaps.MATERIAL_MAP:
name = FormatMaps.MATERIAL_MAP[type]["name"]
guid = FormatMaps.MATERIAL_MAP[type]["guid"]
return name, guid

View File

@ -1,4 +1,4 @@
# Copyright (c) 2021 Ultimaker B.V.
# Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher.
from UM.FileHandler.FileHandler import FileHandler #For typing.
@ -6,6 +6,7 @@ from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode #For typing.
from cura.API import Account
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.FormatMaps import FormatMaps
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
@ -419,14 +420,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
@staticmethod
def applyPrinterTypeMapping(printer_type):
_PRINTER_TYPE_NAME = {
"fire_e": "ultimaker_method",
"lava_f": "ultimaker_methodx",
"magma_10": "ultimaker_methodxl",
"sketch": "ultimaker_sketch"
}
if printer_type in _PRINTER_TYPE_NAME:
return _PRINTER_TYPE_NAME[printer_type]
if printer_type in FormatMaps.PRINTER_TYPE_NAME:
return FormatMaps.PRINTER_TYPE_NAME[printer_type]
return printer_type
@pyqtProperty(str, constant = True)

View File

@ -1,4 +1,4 @@
# Copyright (c) 2023 UltiMaker
# Copyright (c) 2024 UltiMaker
# Cura is released under the terms of the LGPLv3 or higher.
from io import StringIO, BufferedIOBase
import json
@ -18,6 +18,7 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.i18n import i18nCatalog
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.FormatMaps import FormatMaps
from cura.Snapshot import Snapshot
from cura.Utils.Threading import call_on_qt_thread
from cura.CuraVersion import ConanInstalls
@ -137,6 +138,30 @@ class MakerbotWriter(MeshWriter):
for png_file in png_files:
file, data = png_file["file"], png_file["data"]
zip_stream.writestr(file, data)
api = CuraApplication.getInstance().getCuraAPI()
metadata_json = api.interface.settings.getSliceMetadata()
# All the mapping stuff we have to do:
product_to_id_map = FormatMaps.getProductIdMap()
printer_name_map = FormatMaps.getInversePrinterNameMap()
extruder_type_map = FormatMaps.getInverseExtruderTypeMap()
material_map = FormatMaps.getInverseMaterialMap()
for key, value in metadata_json.items():
if "all_settings" in value:
if "machine_name" in value["all_settings"]:
machine_name = value["all_settings"]["machine_name"]
if machine_name in product_to_id_map:
machine_name = product_to_id_map[machine_name][0]
value["all_settings"]["machine_name"] = printer_name_map.get(machine_name, machine_name)
if "machine_nozzle_id" in value["all_settings"]:
extruder_type = value["all_settings"]["machine_nozzle_id"]
value["all_settings"]["machine_nozzle_id"] = extruder_type_map.get(extruder_type, extruder_type)
if "material_type" in value["all_settings"]:
material_type = value["all_settings"]["material_type"]
value["all_settings"]["material_type"] = material_map.get(material_type, material_type)
slice_metadata = json.dumps(metadata_json, separators=(", ", ": "), indent=4)
zip_stream.writestr("slicemetadata.json", slice_metadata)
except (IOError, OSError, BadZipFile) as ex:
Logger.log("e", f"Could not write to (.makerbot) file because: '{ex}'.")
self.setInformation(catalog.i18nc("@error", "MakerbotWriter could not save to the designated path."))

View File

@ -24,6 +24,7 @@ from UM.Settings.InstanceContainer import InstanceContainer
from cura.CuraApplication import CuraApplication
from cura.Settings.GlobalStack import GlobalStack
from cura.Utils.Threading import call_on_qt_thread
from cura.API import CuraAPI
from UM.i18n import i18nCatalog
@ -85,7 +86,8 @@ class UFPWriter(MeshWriter):
try:
archive.addContentType(extension="json", mime_type="application/json")
setting_textio = StringIO()
json.dump(self._getSliceMetadata(), setting_textio, separators=(", ", ": "), indent=4)
api = CuraApplication.getInstance().getCuraAPI()
json.dump(api.interface.settings.getSliceMetadata(), setting_textio, separators=(", ", ": "), indent=4)
steam = archive.getStream(SLICE_METADATA_PATH)
steam.write(setting_textio.getvalue().encode("UTF-8"))
except EnvironmentError as e:
@ -210,57 +212,3 @@ class UFPWriter(MeshWriter):
return [{"name": item.getName()}
for item in DepthFirstIterator(node)
if item.getMeshData() is not None and not item.callDecoration("isNonPrintingMesh")]
def _getSliceMetadata(self) -> Dict[str, Dict[str, Dict[str, str]]]:
"""Get all changed settings and all settings. For each extruder and the global stack"""
print_information = CuraApplication.getInstance().getPrintInformation()
machine_manager = CuraApplication.getInstance().getMachineManager()
settings = {
"material": {
"length": print_information.materialLengths,
"weight": print_information.materialWeights,
"cost": print_information.materialCosts,
},
"global": {
"changes": {},
"all_settings": {},
},
"quality": asdict(machine_manager.activeQualityDisplayNameMap()),
}
def _retrieveValue(container: InstanceContainer, setting_: str):
value_ = container.getProperty(setting_, "value")
for _ in range(0, 1024): # Prevent possibly endless loop by not using a limit.
if not isinstance(value_, SettingFunction):
return value_ # Success!
value_ = value_(container)
return 0 # Fallback value after breaking possibly endless loop.
global_stack = cast(GlobalStack, Application.getInstance().getGlobalContainerStack())
# Add global user or quality changes
global_flattened_changes = InstanceContainer.createMergedInstanceContainer(global_stack.userChanges, global_stack.qualityChanges)
for setting in global_flattened_changes.getAllKeys():
settings["global"]["changes"][setting] = _retrieveValue(global_flattened_changes, setting)
# Get global all settings values without user or quality changes
for setting in global_stack.getAllKeys():
settings["global"]["all_settings"][setting] = _retrieveValue(global_stack, setting)
for i, extruder in enumerate(global_stack.extruderList):
# Add extruder fields to settings dictionary
settings[f"extruder_{i}"] = {
"changes": {},
"all_settings": {},
}
# Add extruder user or quality changes
extruder_flattened_changes = InstanceContainer.createMergedInstanceContainer(extruder.userChanges, extruder.qualityChanges)
for setting in extruder_flattened_changes.getAllKeys():
settings[f"extruder_{i}"]["changes"][setting] = _retrieveValue(extruder_flattened_changes, setting)
# Get extruder all settings values without user or quality changes
for setting in extruder.getAllKeys():
settings[f"extruder_{i}"]["all_settings"][setting] = _retrieveValue(extruder, setting)
return settings

View File

@ -17,6 +17,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.FormatMaps import FormatMaps
from cura.Machines.VariantType import VariantType
try:
@ -249,7 +250,7 @@ class XmlMaterialProfile(InstanceContainer):
machine_variant_map[definition_id][variant_name] = variant_dict
# Map machine human-readable names to IDs
product_id_map = self.getProductIdMap()
product_id_map = FormatMaps.getProductIdMap()
for definition_id, container in machine_container_map.items():
definition_id = container.getMetaDataEntry("definition")
@ -647,7 +648,7 @@ class XmlMaterialProfile(InstanceContainer):
self._dirty = False
# Map machine human-readable names to IDs
product_id_map = self.getProductIdMap()
product_id_map = FormatMaps.getProductIdMap()
machines = data.iterfind("./um:settings/um:machine", self.__namespaces)
for machine in machines:
@ -923,7 +924,7 @@ class XmlMaterialProfile(InstanceContainer):
result_metadata.append(base_metadata)
# Map machine human-readable names to IDs
product_id_map = cls.getProductIdMap()
product_id_map = FormatMaps.getProductIdMap()
for machine in data.iterfind("./um:settings/um:machine", cls.__namespaces):
machine_compatibility = common_compatibility
@ -1128,29 +1129,6 @@ class XmlMaterialProfile(InstanceContainer):
id_list = list(id_list)
return id_list
__product_to_id_map: Optional[Dict[str, List[str]]] = None
@classmethod
def getProductIdMap(cls) -> Dict[str, List[str]]:
"""Gets a mapping from product names in the XML files to their definition IDs.
This loads the mapping from a file.
"""
if cls.__product_to_id_map is not None:
return cls.__product_to_id_map
plugin_path = cast(str, PluginRegistry.getInstance().getPluginPath("XmlMaterialProfile"))
product_to_id_file = os.path.join(plugin_path, "product_to_id.json")
with open(product_to_id_file, encoding = "utf-8") as f:
contents = ""
for line in f:
contents += line if "#" not in line else "".join([line.replace("#", str(n)) for n in range(1, 12)])
cls.__product_to_id_map = json.loads(contents)
cls.__product_to_id_map = {key: [value] for key, value in cls.__product_to_id_map.items()}
#This also loads "Ultimaker S5" -> "ultimaker_s5" even though that is not strictly necessary with the default to change spaces into underscores.
#However it is not always loaded with that default; this mapping is also used in serialize() without that default.
return cls.__product_to_id_map
@staticmethod
def _parseCompatibleValue(value: str):
"""Parse the value of the "material compatible" property."""

View File

@ -28,6 +28,18 @@
"icon": "Printer",
"children":
{
"build_volume_fan_nr":
{
"label": "Build volume fan number",
"description": "The number of the fan that cools the build volume. If this is set to 0, it's means that there is no build volume fan",
"default_value": 0,
"minimum_value": "0",
"maximum_value": "999999",
"type": "int",
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
},
"machine_name":
{
"label": "Machine Type",
@ -4467,6 +4479,37 @@
"settable_per_mesh": false,
"settable_per_extruder": true
},
"build_fan_full_at_height":
{
"label": "Build Fan Speed at Height",
"description": "The height at which the fans spin on regular fan speed. At the layers below the fan speed gradually increases from Initial Fan Speed to Regular Fan Speed.",
"unit": "mm",
"type": "float",
"default_value": 0,
"minimum_value": "0",
"enabled": "build_volume_fan_nr != 0",
"maximum_value_warning": "10.0",
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false,
"children":
{
"build_fan_full_layer":
{
"label": "Build Fan Speed at Layer",
"description": "The layer at which the build fans spin on full fan speed. This value is calculated and rounded to a whole number.",
"type": "int",
"default_value": 0,
"minimum_value": "0",
"enabled": "build_volume_fan_nr != 0",
"maximum_value_warning": "10 / resolveOrValue('layer_height')",
"value": "max(1, int(math.floor((build_fan_full_at_height - resolveOrValue('layer_height_0')) / resolveOrValue('layer_height')) + 2))",
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
}
}
},
"cool_fan_speed":
{
"label": "Fan Speed",