diff --git a/.github/workflows/printer-linter-format.yml b/.github/workflows/printer-linter-format.yml index 8e65a481c4..d8e136f156 100644 --- a/.github/workflows/printer-linter-format.yml +++ b/.github/workflows/printer-linter-format.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: technote-space/get-diff-action@v6 with: diff --git a/.github/workflows/printer-linter-pr-diagnose.yml b/.github/workflows/printer-linter-pr-diagnose.yml index ed51f8b2f8..e70cabce7d 100644 --- a/.github/workflows/printer-linter-pr-diagnose.yml +++ b/.github/workflows/printer-linter-pr-diagnose.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 @@ -55,7 +55,7 @@ jobs: echo ${{ github.event.pull_request.head.repo.full_name }} > printer-linter-result/pr-head-repo.txt echo ${{ github.event.pull_request.head.sha }} > printer-linter-result/pr-head-sha.txt - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: printer-linter-result path: printer-linter-result/ diff --git a/.github/workflows/release-process_release-candidate.yml b/.github/workflows/release-process_release-candidate.yml index d14701d278..1afb8aad04 100644 --- a/.github/workflows/release-process_release-candidate.yml +++ b/.github/workflows/release-process_release-candidate.yml @@ -9,6 +9,11 @@ on: required: true type: string + publish_release_description: + description: 'Create the GitHub release (if existing, the description will be overridden based on the changelog)' + required: true + type: boolean + jobs: parse-version: name: Parse input version string @@ -153,10 +158,12 @@ jobs: ref: ${{ needs.parse-version.outputs.branch_name }} - name: Extract changelog + if: ${{ inputs.publish_release_description }} run: python ./scripts/extract_changelog.py --version ${{ needs.parse-version.outputs.version_major }}.${{ needs.parse-version.outputs.version_minor }}.${{ needs.parse-version.outputs.version_patch }} --changelog ./resources/texts/change_log.txt > formatted_changelog.txt - name: Create release uses: notpeelz/action-gh-create-release@v5.0.1 + if: ${{ inputs.publish_release_description }} with: target: ${{ needs.create-tags.outputs.main_commit }} tag: ${{ inputs.cura_version }} diff --git a/conandata.yml b/conandata.yml index 3a2f90bbe1..eda0a7c164 100644 --- a/conandata.yml +++ b/conandata.yml @@ -10,7 +10,7 @@ requirements: - "pynest2d/5.3.0" - "native_cad_plugin/2.0.0" requirements_internal: - - "fdm_materials/(latest)@internal/testing" + - "fdm_materials/5.8.1" - "cura_private_data/(latest)@internal/testing" urls: default: diff --git a/cura/Machines/Models/QualityManagementModel.py b/cura/Machines/Models/QualityManagementModel.py index 86e35f6b28..92551dbddf 100644 --- a/cura/Machines/Models/QualityManagementModel.py +++ b/cura/Machines/Models/QualityManagementModel.py @@ -127,13 +127,12 @@ class QualityManagementModel(ListModel): # have no container for the global stack, because "my_profile" just got renamed to "my_new_profile". This results # in crashes because the rest of the system assumes that all data in a QualityChangesGroup will be correct. # - # Renaming the container for the global stack in the end seems to be ok, because the assumption is mostly based - # on the quality changes container for the global stack. + # This is why we use the "supress_signals" flag for the set name. This basically makes the change silent. for metadata in quality_changes_group.metadata_per_extruder.values(): extruder_container = cast(InstanceContainer, container_registry.findContainers(id = metadata["id"])[0]) - extruder_container.setName(new_name) + extruder_container.setName(new_name, supress_signals=True) global_container = cast(InstanceContainer, container_registry.findContainers(id = quality_changes_group.metadata_for_global["id"])[0]) - global_container.setName(new_name) + global_container.setName(new_name, supress_signals=True) quality_changes_group.name = new_name diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index b5b8a1b721..ef1c9561af 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -5,7 +5,7 @@ from typing import Any, cast, List, Optional, Dict from PyQt6.QtCore import pyqtProperty, pyqtSignal, QObject from UM.Application import Application -from UM.Decorators import override +from UM.Decorators import CachedMemberFunctions, override from UM.FlameProfiler import pyqtSlot from UM.Logger import Logger from UM.Settings.ContainerStack import ContainerStack, InvalidContainerStackError @@ -237,6 +237,7 @@ class CuraContainerStack(ContainerStack): :param new_value: The new value to set the property to. """ + CachedMemberFunctions.clearInstanceCache(self) container_index = _ContainerIndexes.UserChanges self._containers[container_index].setProperty(key, property_name, property_value, container, set_from_cache) diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index eba41569b7..645abeb0f6 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -5,7 +5,7 @@ from typing import Any, Dict, TYPE_CHECKING, Optional from PyQt6.QtCore import pyqtProperty, pyqtSignal -from UM.Decorators import override +from UM.Decorators import CachedMemberFunctions, override from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerRegistry import ContainerRegistry @@ -86,6 +86,7 @@ class ExtruderStack(CuraContainerStack): def setCompatibleMaterialDiameter(self, value: float) -> None: old_approximate_diameter = self.getApproximateMaterialDiameter() if self.getCompatibleMaterialDiameter() != value: + CachedMemberFunctions.clearInstanceCache(self) self.definitionChanges.setProperty("material_diameter", "value", value) self.compatibleMaterialDiameterChanged.emit() diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index dd7987bc42..a7126af14e 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -197,7 +197,8 @@ class CuraEngineBackend(QObject, Backend): self._slicing_error_message.actionTriggered.connect(self._reportBackendError) self._resetLastSliceTimeStats() - self._snapshot: Optional[QImage] = None + self._snapshot: Optional[QImage] = None + self._last_socket_error: Optional[Arcus.Error] = None application.initializationFinished.connect(self.initialize) @@ -569,7 +570,19 @@ class CuraEngineBackend(QObject, Backend): return # Preparation completed, send it to the backend. - self._socket.sendMessage(job.getSliceMessage()) + if not self._socket.sendMessage(job.getSliceMessage()): + if self._last_socket_error is not None and self._last_socket_error.getErrorCode() == Arcus.ErrorCode.MessageTooBigError: + error_txt = catalog.i18nc("@info:status", "Unable to send the model data to the engine. Please try to use a less detailed model, or reduce the number of instances.") + else: + error_txt = catalog.i18nc("@info:status", "Unable to send the model data to the engine. Please try again, or contact support.") + + self._error_message = Message(error_txt, + title=catalog.i18nc("@info:title", "Unable to slice"), + message_type=Message.MessageType.WARNING) + self._error_message.show() + self.setState(BackendState.Error) + self.backendError.emit(job) + return # Notify the user that it's now up to the backend to do its job self.setState(BackendState.Processing) @@ -691,6 +704,7 @@ class CuraEngineBackend(QObject, Backend): if error.getErrorCode() == Arcus.ErrorCode.Debug: return + self._last_socket_error = error self._terminate() self._createSocket() diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index 6da0edb2a7..0789e8a684 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -49,7 +49,20 @@ class StartJobResult(IntEnum): ObjectsWithDisabledExtruder = 8 -class GcodeStartEndFormatter(Formatter): +class GcodeConditionState(IntEnum): + OutsideCondition = 1 + ConditionFalse = 2 + ConditionTrue = 3 + ConditionDone = 4 + + +class GcodeInstruction(IntEnum): + Skip = 1 + Evaluate = 2 + EvaluateAndWrite = 3 + + +class GcodeStartEndFormatter: # Formatter class that handles token expansion in start/end gcode # Example of a start/end gcode string: # ``` @@ -63,22 +76,50 @@ class GcodeStartEndFormatter(Formatter): # will be used. Alternatively, if the expression is formatted as "{[expression], [extruder_nr]}", # then the expression will be evaluated with the extruder stack of the specified extruder_nr. - _extruder_regex = re.compile(r"^\s*(?P.*)\s*,\s*(?P.*)\s*$") + _instruction_regex = re.compile(r"{(?Pif|else|elif|endif)?\s*(?P.*?)\s*(?:,\s*(?P.*))?\s*}(?P\n?)") - def __init__(self, all_extruder_settings: Dict[str, Any], default_extruder_nr: int = -1) -> None: + def __init__(self, all_extruder_settings: Dict[str, Dict[str, Any]], default_extruder_nr: int = -1) -> None: super().__init__() - self._all_extruder_settings: Dict[str, Any] = all_extruder_settings + self._all_extruder_settings: Dict[str, Dict[str, Any]] = all_extruder_settings self._default_extruder_nr: int = default_extruder_nr + self._cura_application = CuraApplication.getInstance() + self._extruder_manager = ExtruderManager.getInstance() - def get_field(self, field_name, args: [str], kwargs: dict) -> Tuple[str, str]: - # get_field method parses all fields in the format-string and parses them individually to the get_value method. - # e.g. for a string "Hello {foo.bar}" would the complete field "foo.bar" would be passed to get_field, and then - # the individual parts "foo" and "bar" would be passed to get_value. This poses a problem for us, because want - # to parse the entire field as a single expression. To solve this, we override the get_field method and return - # the entire field as the expression. - return self.get_value(field_name, args, kwargs), field_name + def format(self, text: str) -> str: + remaining_text: str = text + result: str = "" - def get_value(self, expression: str, args: [str], kwargs: dict) -> str: + self._condition_state: GcodeConditionState = GcodeConditionState.OutsideCondition + + while len(remaining_text) > 0: + next_code_match = self._instruction_regex.search(remaining_text) + if next_code_match is not None: + expression_start, expression_end = next_code_match.span() + + if expression_start > 0: + result += self._process_statement(remaining_text[:expression_start]) + + result += self._process_code(next_code_match) + + remaining_text = remaining_text[expression_end:] + + else: + result += self._process_statement(remaining_text) + remaining_text = "" + + return result + + def _process_statement(self, statement: str) -> str: + if self._condition_state in [GcodeConditionState.OutsideCondition, GcodeConditionState.ConditionTrue]: + return statement + else: + return "" + + def _process_code(self, code: re.Match) -> str: + condition: Optional[str] = code.group("condition") + expression: Optional[str] = code.group("expression") + extruder_nr_expr: Optional[str] = code.group("extruder_nr_expr") + end_of_line: Optional[str] = code.group("end_of_line") # The following variables are not settings, but only become available after slicing. # when these variables are encountered, we return them as-is. They are replaced later @@ -87,53 +128,100 @@ class GcodeStartEndFormatter(Formatter): if expression in post_slice_data_variables: return f"{{{expression}}}" - extruder_nr = str(self._default_extruder_nr) + extruder_nr: str = str(self._default_extruder_nr) + instruction: GcodeInstruction = GcodeInstruction.Skip # The settings may specify a specific extruder to use. This is done by # formatting the expression as "{expression}, {extruder_nr_expr}". If the # expression is formatted like this, we extract the extruder_nr and use # it to get the value from the correct extruder stack. - match = self._extruder_regex.match(expression) - if match: - expression = match.group("expression") - extruder_nr_expr = match.group("extruder_nr_expr") - - if extruder_nr_expr.isdigit(): - extruder_nr = extruder_nr_expr + if condition is None: + # This is a classic statement + if self._condition_state in [GcodeConditionState.OutsideCondition, GcodeConditionState.ConditionTrue]: + # Skip and move to next + instruction = GcodeInstruction.EvaluateAndWrite + else: + # This is a condition statement, first check validity + if condition == "if": + if self._condition_state != GcodeConditionState.OutsideCondition: + raise SyntaxError("Nested conditions are not supported") else: - # We get the value of the extruder_nr_expr from `_all_extruder_settings` dictionary - # rather than the global container stack. The `_all_extruder_settings["-1"]` is a - # dict-representation of the global container stack, with additional properties such - # as `initial_extruder_nr`. As users may enter such expressions we can't use the - # global container stack. - extruder_nr = str(self._all_extruder_settings["-1"].get(extruder_nr_expr, "-1")) + if self._condition_state == GcodeConditionState.OutsideCondition: + raise SyntaxError("Condition should start with an 'if' statement") - if extruder_nr in self._all_extruder_settings: - additional_variables = self._all_extruder_settings[extruder_nr].copy() - else: - Logger.warning(f"Extruder {extruder_nr} does not exist, using global settings") - additional_variables = self._all_extruder_settings["-1"].copy() + if condition == "if": + # First instruction, just evaluate it + instruction = GcodeInstruction.Evaluate - # Add the arguments and keyword arguments to the additional settings. These - # are currently _not_ used, but they are added for consistency with the - # base Formatter class. - for key, value in enumerate(args): - additional_variables[key] = value - for key, value in kwargs.items(): - additional_variables[key] = value + else: + if self._condition_state == GcodeConditionState.ConditionTrue: + # We have reached the next condition after a valid one has been found, skip the rest + self._condition_state = GcodeConditionState.ConditionDone - if extruder_nr == "-1": - container_stack = CuraApplication.getInstance().getGlobalContainerStack() - else: - container_stack = ExtruderManager.getInstance().getExtruderStack(extruder_nr) - if not container_stack: + if condition == "elif": + if self._condition_state == GcodeConditionState.ConditionFalse: + # New instruction, and valid condition has not been reached so far => evaluate it + instruction = GcodeInstruction.Evaluate + else: + # New instruction, but valid condition has already been reached => skip it + instruction = GcodeInstruction.Skip + + elif condition == "else": + instruction = GcodeInstruction.Skip # Never evaluate, expression should be empty + if self._condition_state == GcodeConditionState.ConditionFalse: + # Fallback instruction, and valid condition has not been reached so far => active next + self._condition_state = GcodeConditionState.ConditionTrue + + elif condition == "endif": + instruction = GcodeInstruction.Skip # Never evaluate, expression should be empty + self._condition_state = GcodeConditionState.OutsideCondition + + if instruction >= GcodeInstruction.Evaluate and extruder_nr_expr is not None: + extruder_nr_function = SettingFunction(extruder_nr_expr) + container_stack = self._cura_application.getGlobalContainerStack() + + # We add the variables contained in `_all_extruder_settings["-1"]`, which is a dict-representation of the + # global container stack, with additional properties such as `initial_extruder_nr`. As users may enter such + # expressions we can't use the global container stack. The variables contained in the global container stack + # will then be inserted twice, which is not optimal but works well. + extruder_nr = str(extruder_nr_function(container_stack, additional_variables=self._all_extruder_settings["-1"])) + + if instruction >= GcodeInstruction.Evaluate: + if extruder_nr in self._all_extruder_settings: + additional_variables = self._all_extruder_settings[extruder_nr].copy() + else: Logger.warning(f"Extruder {extruder_nr} does not exist, using global settings") - container_stack = CuraApplication.getInstance().getGlobalContainerStack() + additional_variables = self._all_extruder_settings["-1"].copy() - setting_function = SettingFunction(expression) - value = setting_function(container_stack, additional_variables=additional_variables) + if extruder_nr == "-1": + container_stack = self._cura_application.getGlobalContainerStack() + else: + container_stack = self._extruder_manager.getExtruderStack(extruder_nr) + if not container_stack: + Logger.warning(f"Extruder {extruder_nr} does not exist, using global settings") + container_stack = self._cura_application.getGlobalContainerStack() - return value + setting_function = SettingFunction(expression) + value = setting_function(container_stack, additional_variables=additional_variables) + + if instruction == GcodeInstruction.Evaluate: + if value: + self._condition_state = GcodeConditionState.ConditionTrue + else: + self._condition_state = GcodeConditionState.ConditionFalse + + return "" + else: + value_str = str(value) + + if end_of_line is not None: + # If we are evaluating an expression that is not a condition, restore the end of line + value_str += end_of_line + + return value_str + + else: + return "" class StartSliceJob(Job): @@ -470,6 +558,9 @@ class StartSliceJob(Job): result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))] result["initial_extruder_nr"] = CuraApplication.getInstance().getExtruderManager().getInitialExtruderNr() + # If adding or changing a setting here, please update the associated wiki page + # https://github.com/Ultimaker/Cura/wiki/Start-End-G%E2%80%90Code + return result def _cacheAllExtruderSettings(self): diff --git a/plugins/MachineSettingsAction/MachineSettingsExtruderTab.qml b/plugins/MachineSettingsAction/MachineSettingsExtruderTab.qml index 5e1ddc2f3f..ab7442bd4e 100644 --- a/plugins/MachineSettingsAction/MachineSettingsExtruderTab.qml +++ b/plugins/MachineSettingsAction/MachineSettingsExtruderTab.qml @@ -182,7 +182,7 @@ Item Cura.GcodeTextArea // "Extruder Start G-code" { anchors.top: parent.top - anchors.bottom: parent.bottom + anchors.bottom: buttonLearnMore.top anchors.bottomMargin: UM.Theme.getSize("default_margin").height anchors.left: parent.left width: base.columnWidth - UM.Theme.getSize("default_margin").width @@ -196,7 +196,7 @@ Item Cura.GcodeTextArea // "Extruder End G-code" { anchors.top: parent.top - anchors.bottom: parent.bottom + anchors.bottom: buttonLearnMore.top anchors.bottomMargin: UM.Theme.getSize("default_margin").height anchors.right: parent.right width: base.columnWidth - UM.Theme.getSize("default_margin").width @@ -206,5 +206,17 @@ Item settingKey: "machine_extruder_end_code" settingStoreIndex: propertyStoreIndex } + + Cura.TertiaryButton + { + id: buttonLearnMore + + text: catalog.i18nc("@button", "Learn more") + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + onClicked: Qt.openUrlExternally("https://github.com/Ultimaker/Cura/wiki/Start-End-G%E2%80%90Code") + anchors.bottom: parent.bottom + anchors.right: parent.right + } } } diff --git a/plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml b/plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml index 740e248828..b01060c1df 100644 --- a/plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml +++ b/plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml @@ -376,7 +376,7 @@ Item anchors { top: upperBlock.bottom - bottom: parent.bottom + bottom: buttonLearnMore.top left: parent.left right: parent.right margins: UM.Theme.getSize("default_margin").width @@ -403,5 +403,19 @@ Item settingKey: "machine_end_gcode" settingStoreIndex: propertyStoreIndex } + + } + + Cura.TertiaryButton + { + id: buttonLearnMore + + text: catalog.i18nc("@button", "Learn more") + iconSource: UM.Theme.getIcon("LinkExternal") + isIconOnRightSide: true + onClicked: Qt.openUrlExternally("https://github.com/Ultimaker/Cura/wiki/Start-End-G%E2%80%90Code") + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: UM.Theme.getSize("default_margin").width } } diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 13111c1c7a..eb2bb2a788 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -11,6 +11,7 @@ import xml.etree.ElementTree as ET from UM.PluginRegistry import PluginRegistry from UM.Resources import Resources from UM.Logger import Logger +from UM.Decorators import CachedMemberFunctions import UM.Dictionary from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry @@ -71,6 +72,8 @@ class XmlMaterialProfile(InstanceContainer): Logger.log("w", "Can't change metadata {key} of material {material_id} because it's read-only.".format(key = key, material_id = self.getId())) return + CachedMemberFunctions.clearInstanceCache(self) + # Some metadata such as diameter should also be instantiated to be a setting. Go though all values for the # "properties" field and apply the new values to SettingInstances as well. new_setting_values_dict = {} @@ -480,6 +483,7 @@ class XmlMaterialProfile(InstanceContainer): first.append(element) def clearData(self): + CachedMemberFunctions.clearInstanceCache(self) self._metadata = { "id": self.getId(), "name": "" @@ -519,6 +523,8 @@ class XmlMaterialProfile(InstanceContainer): def deserialize(self, serialized, file_name = None): """Overridden from InstanceContainer""" + CachedMemberFunctions.clearInstanceCache(self) + containers_to_add = [] # update the serialized data first from UM.Settings.Interfaces import ContainerInterface diff --git a/resources/conandata.yml b/resources/conandata.yml index d43b7d4dd2..9e4e8b5271 100644 --- a/resources/conandata.yml +++ b/resources/conandata.yml @@ -1 +1 @@ -version: "5.8.0" +version: "5.9.0-alpha.0" diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json index a0fffeeaec..101efee3b1 100644 --- a/resources/definitions/fdmextruder.def.json +++ b/resources/definitions/fdmextruder.def.json @@ -172,7 +172,6 @@ "default_value": 3, "description": "The height difference between the tip of the nozzle and the lowest part of the print head.", "label": "Nozzle Length", - "settable_globally": false, "settable_per_extruder": true, "settable_per_mesh": false, "settable_per_meshgroup": false, diff --git a/resources/definitions/ultimaker.def.json b/resources/definitions/ultimaker.def.json index 21e905fdf8..14c7f6d0e0 100644 --- a/resources/definitions/ultimaker.def.json +++ b/resources/definitions/ultimaker.def.json @@ -70,14 +70,12 @@ "machine_max_feedrate_e": { "default_value": 45 }, "material_bed_temperature": { - "maximum_value": "140", - "maximum_value_warning": "120", + "maximum_value": "120", "minimum_value": "0" }, "material_bed_temperature_layer_0": { - "maximum_value": "140", - "maximum_value_warning": "120", + "maximum_value": "120", "minimum_value": "0" }, "material_print_temp_wait": { "value": false }, diff --git a/resources/definitions/ultimaker_factor4.def.json b/resources/definitions/ultimaker_factor4.def.json index 573b26cf50..5945d3a262 100644 --- a/resources/definitions/ultimaker_factor4.def.json +++ b/resources/definitions/ultimaker_factor4.def.json @@ -112,10 +112,10 @@ "machine_head_with_fans_polygon": { "default_value": [ - [-30, -80], - [-30, 20], - [50, 20], - [50, -80] + [-35, -80], + [-35, 30], + [55, 30], + [55, -80] ] }, "machine_heated_bed": { "default_value": true }, diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index 7acf39ecb2..0c673a9409 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -62,7 +62,6 @@ Item property alias showProfileFolder: showProfileFolderAction property alias documentation: documentationAction - property alias showTroubleshooting: showTroubleShootingAction property alias openSponsershipPage: openSponsershipPageAction property alias reportBug: reportBugAction property alias whatsNew: whatsNewAction @@ -86,14 +85,6 @@ Item UM.I18nCatalog{id: catalog; name: "cura"} - - Action - { - id: showTroubleShootingAction - onTriggered: Qt.openUrlExternally("https://ultimaker.com/en/troubleshooting?utm_source=cura&utm_medium=software&utm_campaign=dropdown-troubleshooting") - text: catalog.i18nc("@action:inmenu", "Show Online Troubleshooting") - } - Action { id: openSponsershipPageAction diff --git a/resources/qml/MachineSettings/GcodeTextArea.qml b/resources/qml/MachineSettings/GcodeTextArea.qml index 2538cd9f65..05d3711bae 100644 --- a/resources/qml/MachineSettings/GcodeTextArea.qml +++ b/resources/qml/MachineSettings/GcodeTextArea.qml @@ -12,20 +12,16 @@ import Cura 1.1 as Cura // // TextArea widget for editing Gcode in the Machine Settings dialog. // -UM.TooltipArea +Item { id: control UM.I18nCatalog { id: catalog; name: "cura"; } - text: tooltip - property alias containerStackId: propertyProvider.containerStackId property alias settingKey: propertyProvider.key property alias settingStoreIndex: propertyProvider.storeIndex - property string tooltip: propertyProvider.properties.description ? propertyProvider.properties.description : "" - property alias labelText: titleLabel.text property alias labelFont: titleLabel.font diff --git a/resources/qml/Menus/HelpMenu.qml b/resources/qml/Menus/HelpMenu.qml index 6a57a99515..c257db0527 100644 --- a/resources/qml/Menus/HelpMenu.qml +++ b/resources/qml/Menus/HelpMenu.qml @@ -14,7 +14,6 @@ Cura.Menu title: catalog.i18nc("@title:menu menubar:toplevel", "&Help") Cura.MenuItem { action: Cura.Actions.showProfileFolder } - Cura.MenuItem { action: Cura.Actions.showTroubleshooting} Cura.MenuItem { action: Cura.Actions.documentation } Cura.MenuItem { action: Cura.Actions.reportBug } Cura.MenuItem { action: Cura.Actions.openSponsershipPage } diff --git a/resources/quality/ultimaker_factor4/um_f4_aa0.8_tpu_0.3mm.inst.cfg b/resources/quality/ultimaker_factor4/um_f4_aa0.8_tpu_0.3mm.inst.cfg new file mode 100644 index 0000000000..7949c64afc --- /dev/null +++ b/resources/quality/ultimaker_factor4/um_f4_aa0.8_tpu_0.3mm.inst.cfg @@ -0,0 +1,26 @@ +[general] +definition = ultimaker_factor4 +name = Extra Fast +version = 4 + +[metadata] +material = generic_tpu +quality_type = verydraft +setting_version = 23 +type = quality +variant = AA 0.8 +weight = -3 + +[values] +gradual_flow_discretisation_step_size = 0.1 +gradual_flow_enabled = True +infill_pattern = ='zigzag' if infill_sparse_density > 50 else 'cross_3d' +infill_sparse_density = 20 +inset_direction = outside_in +material_print_temperature = =default_material_print_temperature + 5 +max_flow_acceleration = 1 +skin_material_flow = =material_flow * 0.9 +skin_material_flow_layer_0 = 90 +speed_layer_0 = 20 +speed_print = 35 + diff --git a/resources/quality/ultimaker_factor4/um_f4_aa0.8_tpu_0.4mm.inst.cfg b/resources/quality/ultimaker_factor4/um_f4_aa0.8_tpu_0.4mm.inst.cfg new file mode 100644 index 0000000000..4b1d25bd18 --- /dev/null +++ b/resources/quality/ultimaker_factor4/um_f4_aa0.8_tpu_0.4mm.inst.cfg @@ -0,0 +1,26 @@ +[general] +definition = ultimaker_factor4 +name = Sprint +version = 4 + +[metadata] +material = generic_tpu +quality_type = superdraft +setting_version = 23 +type = quality +variant = AA 0.8 +weight = -4 + +[values] +gradual_flow_discretisation_step_size = 0.1 +gradual_flow_enabled = True +infill_pattern = ='zigzag' if infill_sparse_density > 50 else 'cross_3d' +infill_sparse_density = 20 +inset_direction = outside_in +material_print_temperature = =default_material_print_temperature + 8 +max_flow_acceleration = 1 +skin_material_flow = =material_flow * 0.9 +skin_material_flow_layer_0 = 90 +speed_layer_0 = 20 +speed_print = 35 + diff --git a/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.15mm.inst.cfg b/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.15mm.inst.cfg index 3294a3a150..37e9bb8b73 100644 --- a/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.15mm.inst.cfg +++ b/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.15mm.inst.cfg @@ -12,20 +12,32 @@ variant = BB 0.4 weight = -1 [values] +acceleration_print = 1000.0 acceleration_support_bottom = 100 -acceleration_support_interface = 1500 +acceleration_support_interface = 1000 brim_replaces_support = False build_volume_temperature = =40 if extruders_enabled_count > 1 else 35 default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 gradual_flow_discretisation_step_size = 0.1 gradual_flow_enabled = True -initial_layer_line_width_factor = 150 +gradual_support_infill_steps = 0 +initial_layer_line_width_factor = 125 +jerk_print = 10 +material_flow_layer_0 = 90 max_flow_acceleration = 1 minimum_support_area = 4 +prime_tower_flow = 90 prime_tower_min_volume = 15 +retraction_min_travel = 5.0 +retraction_prime_speed = 10.0 skin_material_flow = =material_flow * 0.93 +speed_print = 30 +support_angle = 45 +support_infill_rate = 20 support_infill_sparse_thickness = =min(layer_height * 2, machine_nozzle_size * 3 / 4) if layer_height <= 0.15 / 0.4 * machine_nozzle_size else layer_height support_interface_offset = 1 support_offset = 3 +support_xy_distance = 2 support_z_distance = 0 +switch_extruder_prime_speed = 10.0 diff --git a/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.1mm.inst.cfg b/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.1mm.inst.cfg index 0f10e4f507..4c83276641 100644 --- a/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.1mm.inst.cfg +++ b/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.1mm.inst.cfg @@ -12,21 +12,32 @@ variant = BB 0.4 weight = 0 [values] +acceleration_print = 1000.0 acceleration_support_bottom = 100 -acceleration_support_interface = 1500 +acceleration_support_interface = 1000 brim_replaces_support = False build_volume_temperature = =40 if extruders_enabled_count > 1 else 35 default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 gradual_flow_discretisation_step_size = 0.1 gradual_flow_enabled = True -initial_layer_line_width_factor = 150 +gradual_support_infill_steps = 0 +initial_layer_line_width_factor = 125 +jerk_print = 10 +material_flow_layer_0 = 90 max_flow_acceleration = 1 minimum_support_area = 4 +prime_tower_flow = 90 prime_tower_min_volume = 15 +retraction_min_travel = 5.0 +retraction_prime_speed = 10.0 skin_material_flow = =material_flow * 0.93 -speed_print = 50 +speed_print = 30 +support_angle = 45 +support_infill_rate = 20 support_infill_sparse_thickness = =min(layer_height * 2, machine_nozzle_size * 3 / 4) if layer_height <= 0.15 / 0.4 * machine_nozzle_size else layer_height support_interface_offset = 1 support_offset = 3 +support_xy_distance = 2 support_z_distance = 0 +switch_extruder_prime_speed = 10.0 diff --git a/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.2mm.inst.cfg b/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.2mm.inst.cfg index ef920e6fd7..93f62df983 100644 --- a/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.2mm.inst.cfg +++ b/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.2mm.inst.cfg @@ -12,21 +12,32 @@ variant = BB 0.4 weight = -2 [values] +acceleration_print = 1000.0 acceleration_support_bottom = 100 -acceleration_support_interface = 1500 +acceleration_support_interface = 1000 brim_replaces_support = False build_volume_temperature = =40 if extruders_enabled_count > 1 else 35 default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 gradual_flow_discretisation_step_size = 0.1 gradual_flow_enabled = True -initial_layer_line_width_factor = 150 +gradual_support_infill_steps = 0 +initial_layer_line_width_factor = 125 +jerk_print = 10 +material_flow_layer_0 = 90 max_flow_acceleration = 1 minimum_support_area = 4 +prime_tower_flow = 90 prime_tower_min_volume = 15 +retraction_min_travel = 5.0 +retraction_prime_speed = 10.0 skin_material_flow = =material_flow * 0.93 -speed_print = 50 +speed_print = 30 +support_angle = 45 +support_infill_rate = 20 support_infill_sparse_thickness = =min(layer_height * 2, machine_nozzle_size * 3 / 4) if layer_height <= 0.15 / 0.4 * machine_nozzle_size else layer_height support_interface_offset = 1 support_offset = 3 +support_xy_distance = 2 support_z_distance = 0 +switch_extruder_prime_speed = 10.0 diff --git a/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.3mm.inst.cfg b/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.3mm.inst.cfg index 4165a27a58..0d946c0092 100644 --- a/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.3mm.inst.cfg +++ b/resources/quality/ultimaker_factor4/um_f4_bb0.4_pva_0.3mm.inst.cfg @@ -12,21 +12,32 @@ variant = BB 0.4 weight = -3 [values] +acceleration_print = 1000.0 acceleration_support_bottom = 100 -acceleration_support_interface = 1500 +acceleration_support_interface = 1000 brim_replaces_support = False build_volume_temperature = =40 if extruders_enabled_count > 1 else 35 default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 gradual_flow_discretisation_step_size = 0.1 gradual_flow_enabled = True -initial_layer_line_width_factor = 150 +gradual_support_infill_steps = 0 +initial_layer_line_width_factor = 125 +jerk_print = 10 +material_flow_layer_0 = 90 max_flow_acceleration = 1 minimum_support_area = 4 +prime_tower_flow = 90 prime_tower_min_volume = 15 +retraction_min_travel = 5.0 +retraction_prime_speed = 10.0 skin_material_flow = =material_flow * 0.93 -speed_print = 40 +speed_print = 30 +support_angle = 45 +support_infill_rate = 20 support_infill_sparse_thickness = =min(layer_height * 2, machine_nozzle_size * 3 / 4) if layer_height <= 0.15 / 0.4 * machine_nozzle_size else layer_height support_interface_offset = 1 support_offset = 3 +support_xy_distance = 2 support_z_distance = 0 +switch_extruder_prime_speed = 10.0 diff --git a/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.2mm.inst.cfg b/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.2mm.inst.cfg index 2963265eec..148a82c991 100644 --- a/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.2mm.inst.cfg +++ b/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.2mm.inst.cfg @@ -12,20 +12,32 @@ variant = BB 0.8 weight = -2 [values] +acceleration_print = 1000.0 acceleration_support_bottom = 100 -acceleration_support_interface = 1500 +acceleration_support_interface = 1000 brim_replaces_support = False build_volume_temperature = =40 if extruders_enabled_count > 1 else 35 default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 gradual_flow_discretisation_step_size = 0.1 gradual_flow_enabled = True -initial_layer_line_width_factor = 150 +gradual_support_infill_steps = 0 +initial_layer_line_width_factor = 125 +jerk_print = 10 +material_flow_layer_0 = 90 max_flow_acceleration = 1 minimum_support_area = 4 +prime_tower_flow = 90 prime_tower_min_volume = 15 +retraction_min_travel = 5.0 +retraction_prime_speed = 10.0 skin_material_flow = =material_flow * 0.93 +speed_print = 30 +support_angle = 45 +support_infill_rate = 20 support_infill_sparse_thickness = =min(layer_height * 2, machine_nozzle_size * 3 / 4) if layer_height <= 0.15 / 0.4 * machine_nozzle_size else layer_height support_interface_offset = 1 support_offset = 3 +support_xy_distance = 2 support_z_distance = 0 +switch_extruder_prime_speed = 10.0 diff --git a/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.3mm.inst.cfg b/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.3mm.inst.cfg index 0a1437fd30..8ab2146046 100644 --- a/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.3mm.inst.cfg +++ b/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.3mm.inst.cfg @@ -12,20 +12,32 @@ variant = BB 0.8 weight = -3 [values] +acceleration_print = 1000.0 acceleration_support_bottom = 100 -acceleration_support_interface = 1500 +acceleration_support_interface = 1000 brim_replaces_support = False build_volume_temperature = =40 if extruders_enabled_count > 1 else 35 default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 gradual_flow_discretisation_step_size = 0.1 gradual_flow_enabled = True -initial_layer_line_width_factor = 150 +gradual_support_infill_steps = 0 +initial_layer_line_width_factor = 125 +jerk_print = 10 +material_flow_layer_0 = 90 max_flow_acceleration = 1 minimum_support_area = 4 +prime_tower_flow = 90 prime_tower_min_volume = 15 +retraction_min_travel = 5.0 +retraction_prime_speed = 10.0 skin_material_flow = =material_flow * 0.93 +speed_print = 30 +support_angle = 45 +support_infill_rate = 20 support_infill_sparse_thickness = =min(layer_height * 2, machine_nozzle_size * 3 / 4) if layer_height <= 0.15 / 0.4 * machine_nozzle_size else layer_height support_interface_offset = 1 support_offset = 3 +support_xy_distance = 2 support_z_distance = 0 +switch_extruder_prime_speed = 10.0 diff --git a/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.4mm.inst.cfg b/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.4mm.inst.cfg index edd837f8fc..52134575b0 100644 --- a/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.4mm.inst.cfg +++ b/resources/quality/ultimaker_factor4/um_f4_bb0.8_pva_0.4mm.inst.cfg @@ -12,20 +12,32 @@ variant = BB 0.8 weight = -4 [values] +acceleration_print = 1000.0 acceleration_support_bottom = 100 -acceleration_support_interface = 1500 +acceleration_support_interface = 1000 brim_replaces_support = False build_volume_temperature = =40 if extruders_enabled_count > 1 else 35 default_material_bed_temperature = =0 if extruders_enabled_count > 1 else 60 gradual_flow_discretisation_step_size = 0.1 gradual_flow_enabled = True -initial_layer_line_width_factor = 150 +gradual_support_infill_steps = 0 +initial_layer_line_width_factor = 125 +jerk_print = 10 +material_flow_layer_0 = 90 max_flow_acceleration = 1 minimum_support_area = 4 +prime_tower_flow = 90 prime_tower_min_volume = 15 +retraction_min_travel = 5.0 +retraction_prime_speed = 10.0 skin_material_flow = =material_flow * 0.93 +speed_print = 30 +support_angle = 45 +support_infill_rate = 20 support_infill_sparse_thickness = =min(layer_height * 2, machine_nozzle_size * 3 / 4) if layer_height <= 0.15 / 0.4 * machine_nozzle_size else layer_height support_interface_offset = 1 support_offset = 3 +support_xy_distance = 2 support_z_distance = 0 +switch_extruder_prime_speed = 10.0 diff --git a/resources/quality/ultimaker_factor4/um_f4_global_Verydraft_Quality.inst.cfg b/resources/quality/ultimaker_factor4/um_f4_global_Verydraft_Quality.inst.cfg index 6aa1be79a3..48feef8723 100644 --- a/resources/quality/ultimaker_factor4/um_f4_global_Verydraft_Quality.inst.cfg +++ b/resources/quality/ultimaker_factor4/um_f4_global_Verydraft_Quality.inst.cfg @@ -1,6 +1,6 @@ [general] definition = ultimaker_factor4 -name = Sprint +name = Extra Fast version = 4 [metadata] diff --git a/resources/texts/change_log.txt b/resources/texts/change_log.txt index cb5aa3d2a6..237caedea9 100644 --- a/resources/texts/change_log.txt +++ b/resources/texts/change_log.txt @@ -1,3 +1,11 @@ +[5.8.1] + +* Bug fixes: +- Fixed an issue where materials could not be synced to UltiMaker Printers + +* Profile Improvements +- Significant improvements for PVA and TPU 95A profiles for UltiMaker Factor 4 + [5.8] * New Z Seam Settings: diff --git a/tests/Machines/TestStartEndGCode.py b/tests/Machines/TestStartEndGCode.py new file mode 100644 index 0000000000..13c14f5acc --- /dev/null +++ b/tests/Machines/TestStartEndGCode.py @@ -0,0 +1,310 @@ +# Copyright (c) 2024 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import pytest +from unittest.mock import MagicMock + +from plugins.CuraEngineBackend.StartSliceJob import GcodeStartEndFormatter + + +# def createMockedInstanceContainer(container_id): +# result = MagicMock() +# result.getId = MagicMock(return_value=container_id) +# result.getMetaDataEntry = MagicMock(side_effect=getMetadataEntrySideEffect) +# return result + +class MockValueProvider: + ## Creates a mock value provider. + # + # This initialises a dictionary with key-value pairs. + def __init__(self, values): + self._values = values + + ## Provides a value. + # + # \param name The key of the value to provide. + def getProperty(self, key, property_name, context = None): + if not (key in self._values): + return None + return self._values[key] + +extruder_0_values = { + "material_temperature": 190.0 +} + +extruder_1_values = { + "material_temperature": 210.0 +} + +global_values = { + "bed_temperature": 50.0, + "initial_extruder": 0 +} + +extruder_0_provider = MockValueProvider(extruder_0_values) +extruder_1_provider = MockValueProvider(extruder_1_values) + +all_extruder_settings = {"-1": global_values, "0": extruder_0_values, "1": extruder_1_values} + +test_cases = [ + ('Static code', None, 'G0', 'G0'), + + ('Basic replacement', None, 'M128 {bed_temperature}', 'M128 50.0'), + + ( + 'Conditional expression with global setting', + None, +'''{if bed_temperature > 30} +G123 +{else} +G456 +{endif}''', +'''G123 +''' + ), + + ( + 'Conditional expression with extruder setting directly specified by index 0', + None, +'''{if material_temperature > 200, 0} +G10 +{else} +G20 +{endif}''', +'''G20 +''' + ), + ( + 'Conditional expression with extruder setting directly specified by index 1', + None, +'''{if material_temperature > 200, 1} +G100 +{else} +G200 +{endif}''', +'''G100 +''' + ), + + ( + 'Conditional expression with extruder index specified by setting', + None, +'''{if material_temperature > 200, initial_extruder} +G1000 +{else} +G2000 +{endif}''', +'''G2000 +''' + ), + + ( + 'Conditional expression with extruder index specified by formula', + None, +'''{if material_temperature > 200, (initial_extruder + 1) % 2} +X1000 +{else} +X2000 +{endif}''', +'''X1000 +''' + ), + + ( + 'Conditional expression with elsif', + None, +'''{if bed_temperature < 30} +T30 +{elif bed_temperature >= 30 and bed_temperature < 40} +T40 +{elif bed_temperature >= 40 and bed_temperature < 50} +T50 +{elif bed_temperature >= 50 and bed_temperature < 60} +T60 +{elif bed_temperature >= 60 and bed_temperature < 70} +T70 +{else} +T-800 +{endif}''', +'''T60 +''' + ), + + ( + 'Formula inside a conditional expression', + None, +'''{if bed_temperature < 30} +Z000 +{else} +Z{bed_temperature + 10} +{endif}''', +'''Z60.0 +''' + ), + + ( + 'Other commands around conditional expression', + None, +''' +R000 +# My super initial command +R111 X123 Y456 Z789 +{if bed_temperature > 30} +R987 +R654 X321 +{else} +R963 X852 Y741 +R321 X654 Y987 +{endif} +# And finally, the end of the start at the beginning of the header +R369 +R357 X951 Y843''', +''' +R000 +# My super initial command +R111 X123 Y456 Z789 +R987 +R654 X321 +# And finally, the end of the start at the beginning of the header +R369 +R357 X951 Y843''' + ), + + ( + 'Multiple conditional expressions', + None, +''' +A999 +{if bed_temperature > 30} +A000 +{else} +A100 +{endif} +A888 +{if material_temperature > 200, 0} +A200 +{else} +A300 +{endif} +A777 +''', +''' +A999 +A000 +A888 +A300 +A777 +''' + ), + + ( + 'Nested condition expression', + SyntaxError, +'''{if bed_temperature < 30} +{if material_temperature < 30, 0} +M000 +{else} +M888 +{endif} +{else} +M{bed_temperature + 10} +{endif}''', + '' + ), + + ( + 'Wrong condition expression', + SyntaxError, +'''{of material_temperature > 200, 1} +G100 +{else} +G200 +{endif}''', + '' + ), + + ( + 'Condition expression without start', + SyntaxError, +''' +W100 +{else} +W200 +{endif}''', + '' + ), + + ( + 'Formula with non-existing variable', + None, + '{material_storage_temperature}', + '0' + ), + + ( + 'Missing formula end character', + None, + '{material_temperature, 0', + '{material_temperature, 0' + ), + + ( + 'Conditional expression with missing end character', + SyntaxError, +'''{if material_temperature > 200, 0 +Q1000 +{else} +Q2000 +{endif}''', + '' + ), + +( + 'Unexpected end character', + None, +'''{if material_temperature > 200, 0}} +S1000 +{else} +S2000 +{endif}''', +'''S2000 +''' + ), +] + +def pytest_generate_tests(metafunc): + if "original_gcode" in metafunc.fixturenames: + tests_ids = [test[0] for test in test_cases] + tests_data = [test[1:] for test in test_cases] + metafunc.parametrize("exception_type, original_gcode, expected_gcode", tests_data, ids = tests_ids) + +@pytest.fixture +def cura_application(): + result = MagicMock() + result.getGlobalContainerStack = MagicMock(return_value = MockValueProvider(global_values)) + return result + +@pytest.fixture +def extruder_manager(): + def get_extruder(extruder_nr: str): + if extruder_nr == "0": + return extruder_0_provider + elif extruder_nr == "1": + return extruder_1_provider + else: + return None + + result = MagicMock() + result.getExtruderStack = MagicMock(side_effect = get_extruder) + return result + +def test_startEndGCode_replace(cura_application, extruder_manager, exception_type, original_gcode, expected_gcode): + formatter = GcodeStartEndFormatter(all_extruder_settings, -1) + formatter._cura_application = cura_application + formatter._extruder_manager = extruder_manager + + if exception_type is not None: + with pytest.raises(exception_type): + formatter.format(original_gcode) + else: + assert formatter.format(original_gcode) == expected_gcode