Merge branch 'Ultimaker:main' into zyyx_profile_addition

This commit is contained in:
Theodor Hansson 2024-10-02 10:20:45 +02:00 committed by GitHub
commit 78bface21a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 684 additions and 105 deletions

View File

@ -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:

View File

@ -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/

View File

@ -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 }}

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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<expression>.*)\s*,\s*(?P<extruder_nr_expr>.*)\s*$")
_instruction_regex = re.compile(r"{(?P<condition>if|else|elif|endif)?\s*(?P<expression>.*?)\s*(?:,\s*(?P<extruder_nr_expr>.*))?\s*}(?P<end_of_line>\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):

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -1 +1 @@
version: "5.8.0"
version: "5.9.0-alpha.0"

View File

@ -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,

View File

@ -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 },

View File

@ -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 },

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,6 +1,6 @@
[general]
definition = ultimaker_factor4
name = Sprint
name = Extra Fast
version = 4
[metadata]

View File

@ -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:

View File

@ -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