Skip to content

Commit

Permalink
Merge branch 'main' into CURA-12081_scarf-seam
Browse files Browse the repository at this point in the history
  • Loading branch information
wawanbreton authored Sep 27, 2024
2 parents 2692b47 + 485b94f commit deda5b2
Show file tree
Hide file tree
Showing 37 changed files with 762 additions and 103 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/printer-linter-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/printer-linter-pr-diagnose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 2

Expand Down Expand Up @@ -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/
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release-process_release-candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/[email protected]
if: ${{ inputs.publish_release_description }}
with:
target: ${{ needs.create-tags.outputs.main_commit }}
tag: ${{ inputs.cura_version }}
Expand Down
2 changes: 1 addition & 1 deletion conandata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions cura/API/Account.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ def _onLoginStateChanged(self, logged_in: bool = False, error_message: Optional[

def _onProfileChanged(self, profile: Optional[UserProfile]) -> None:
self._user_profile = profile
self._updatePermissions()
self.userProfileChanged.emit()

def _sync(self) -> None:
Expand Down
3 changes: 2 additions & 1 deletion cura/Settings/CuraContainerStack.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
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
Expand Down Expand Up @@ -237,6 +237,7 @@ def setProperty(self, key: str, property_name: str, property_value: Any, contain
: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)

Expand Down
3 changes: 2 additions & 1 deletion cura/Settings/ExtruderStack.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

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
Expand Down Expand Up @@ -86,6 +86,7 @@ def getCompatibleMaterialDiameter(self) -> float:
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()

Expand Down
189 changes: 140 additions & 49 deletions plugins/CuraEngineBackend/StartSliceJob.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
# ```
Expand All @@ -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 format(self, text: str) -> str:
remaining_text: str = text
result: 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])

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
result += self._process_code(next_code_match)

def get_value(self, expression: str, args: [str], kwargs: dict) -> str:
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
Expand All @@ -87,53 +128,100 @@ def get_value(self, expression: str, args: [str], kwargs: dict) -> str:
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 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:
if self._condition_state == GcodeConditionState.OutsideCondition:
raise SyntaxError("Condition should start with an 'if' statement")

if condition == "if":
# First instruction, just evaluate it
instruction = GcodeInstruction.Evaluate

if extruder_nr_expr.isdigit():
extruder_nr = extruder_nr_expr
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 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()

# 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

if extruder_nr == "-1":
container_stack = CuraApplication.getInstance().getGlobalContainerStack()
else:
container_stack = ExtruderManager.getInstance().getExtruderStack(extruder_nr)
if not container_stack:
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 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):
Expand Down Expand Up @@ -470,6 +558,9 @@ def _buildReplacementTokens(self, stack: ContainerStack) -> Dict[str, Any]:
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):
Expand Down
16 changes: 14 additions & 2 deletions plugins/MachineSettingsAction/MachineSettingsExtruderTab.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
}
}
}
16 changes: 15 additions & 1 deletion plugins/MachineSettingsAction/MachineSettingsPrinterTab.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
Loading

0 comments on commit deda5b2

Please sign in to comment.