diff --git a/CMakeLists.txt b/CMakeLists.txt index 035191ebf7..39628b8fa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,24 +30,16 @@ configure_file(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desk configure_file(cura/CuraVersion.py.in CuraVersion.py @ONLY) -# FIXME: Remove the code for CMake <3.12 once we have switched over completely. -# FindPython3 is a new module since CMake 3.12. It deprecates FindPythonInterp and FindPythonLibs. The FindPython3 -# module is copied from the CMake repository here so in CMake <3.12 we can still use it. -if(${CMAKE_VERSION} VERSION_LESS 3.12) - # Use FindPythonInterp and FindPythonLibs for CMake <3.12 - find_package(PythonInterp 3 REQUIRED) +# FIXME: The new FindPython3 finds the system's Python3.6 reather than the Python3.5 that we built for Cura's environment. +# So we're using the old method here, with FindPythonInterp for now. +find_package(PythonInterp 3 REQUIRED) - set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE}) - - set(Python3_VERSION ${PYTHON_VERSION_STRING}) - set(Python3_VERSION_MAJOR ${PYTHON_VERSION_MAJOR}) - set(Python3_VERSION_MINOR ${PYTHON_VERSION_MINOR}) - set(Python3_VERSION_PATCH ${PYTHON_VERSION_PATCH}) -else() - # Use FindPython3 for CMake >=3.12 - find_package(Python3 REQUIRED COMPONENTS Interpreter Development) -endif() +set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE}) +set(Python3_VERSION ${PYTHON_VERSION_STRING}) +set(Python3_VERSION_MAJOR ${PYTHON_VERSION_MAJOR}) +set(Python3_VERSION_MINOR ${PYTHON_VERSION_MINOR}) +set(Python3_VERSION_PATCH ${PYTHON_VERSION_PATCH}) if(NOT ${URANIUM_DIR} STREQUAL "") set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${URANIUM_DIR}/cmake") diff --git a/cmake/CuraTests.cmake b/cmake/CuraTests.cmake index 251bec5781..0e62b84efa 100644 --- a/cmake/CuraTests.cmake +++ b/cmake/CuraTests.cmake @@ -4,18 +4,11 @@ include(CTest) include(CMakeParseArguments) -# FIXME: Remove the code for CMake <3.12 once we have switched over completely. -# FindPython3 is a new module since CMake 3.12. It deprecates FindPythonInterp and FindPythonLibs. The FindPython3 -# module is copied from the CMake repository here so in CMake <3.12 we can still use it. -if(${CMAKE_VERSION} VERSION_LESS 3.12) - # Use FindPythonInterp and FindPythonLibs for CMake <3.12 - find_package(PythonInterp 3 REQUIRED) +# FIXME: The new FindPython3 finds the system's Python3.6 reather than the Python3.5 that we built for Cura's environment. +# So we're using the old method here, with FindPythonInterp for now. +find_package(PythonInterp 3 REQUIRED) - set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE}) -else() - # Use FindPython3 for CMake >=3.12 - find_package(Python3 REQUIRED COMPONENTS Interpreter Development) -endif() +set(Python3_EXECUTABLE ${PYTHON_EXECUTABLE}) add_custom_target(test-verbose COMMAND ${CMAKE_CTEST_COMMAND} --verbose) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 09a6bb5bb6..80a0d64474 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -122,6 +122,8 @@ class ContainerManager(QObject): root_material.setMetaDataEntry(entry_name, entry_value) if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed. root_material.metaDataChanged.emit(root_material) + + cura.CuraApplication.CuraApplication.getInstance().getMachineManager().updateUponMaterialMetadataChange() return True @pyqtSlot(str, result = str) diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index 4595bf3996..f594ad3d0c 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -1,7 +1,7 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Any, cast, List, Optional +from typing import Any, cast, List, Optional, Dict from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject from UM.Application import Application @@ -60,6 +60,8 @@ class CuraContainerStack(ContainerStack): import cura.CuraApplication #Here to prevent circular imports. self.setMetaDataEntry("setting_version", cura.CuraApplication.CuraApplication.SettingVersion) + self._settable_per_extruder_cache = {} # type: Dict[str, Any] + self.setDirty(False) # This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted. @@ -387,6 +389,18 @@ class CuraContainerStack(ContainerStack): value = int(Application.getInstance().getMachineManager().defaultExtruderPosition) return value + def getProperty(self, key: str, property_name: str, context = None) -> Any: + if property_name == "settable_per_extruder": + # Setable per extruder isn't a value that can ever change. So once we requested it once, we can just keep + # that in memory. + try: + return self._settable_per_extruder_cache[key] + except KeyError: + self._settable_per_extruder_cache[key] = super().getProperty(key, property_name, context) + return self._settable_per_extruder_cache[key] + + return super().getProperty(key, property_name, context) + class _ContainerIndexes: """Private helper class to keep track of container positions and their types.""" diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index aadfd15f1a..efc447b2cf 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -16,13 +16,13 @@ from .ExtruderStack import ExtruderStack class CuraStackBuilder: """Contains helper functions to create new machines.""" - @classmethod - def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]: + def createMachine(cls, name: str, definition_id: str, machine_extruder_count: Optional[int] = None) -> Optional[GlobalStack]: """Create a new instance of a machine. :param name: The name of the new machine. :param definition_id: The ID of the machine definition to use. + :param machine_extruder_count: The number of extruders in the machine. :return: The new global stack or None if an error occurred. """ @@ -66,7 +66,14 @@ class CuraStackBuilder: Logger.logException("e", "Failed to create an extruder stack for position {pos}: {err}".format(pos = position, err = str(e))) return None - for new_extruder in new_global_stack.extruderList: # Only register the extruders if we're sure that all of them are correct. + # If given, set the machine_extruder_count when creating the machine, or else the extruderList used bellow will + # not return the correct extruder list (since by default, the machine_extruder_count is 1) in machines with + # settable number of extruders. + if machine_extruder_count and 0 <= machine_extruder_count <= len(extruder_dict): + new_global_stack.setProperty("machine_extruder_count", "value", machine_extruder_count) + + # Only register the extruders if we're sure that all of them are correct. + for new_extruder in new_global_stack.extruderList: registry.addContainer(new_extruder) # Register the global stack after the extruder stacks are created. This prevents the registry from adding another diff --git a/cura/Settings/ExtruderStack.py b/cura/Settings/ExtruderStack.py index bb35b336c7..2a9838c671 100644 --- a/cura/Settings/ExtruderStack.py +++ b/cura/Settings/ExtruderStack.py @@ -131,13 +131,13 @@ class ExtruderStack(CuraContainerStack): if not self._next_stack: raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id)) - if context is None: - context = PropertyEvaluationContext() - context.pushContainer(self) + if context: + context.pushContainer(self) if not super().getProperty(key, "settable_per_extruder", context): result = self.getNextStack().getProperty(key, property_name, context) - context.popContainer() + if context: + context.popContainer() return result limit_to_extruder = super().getProperty(key, "limit_to_extruder", context) @@ -150,13 +150,15 @@ class ExtruderStack(CuraContainerStack): try: result = self.getNextStack().extruderList[int(limit_to_extruder)].getProperty(key, property_name, context) if result is not None: - context.popContainer() + if context: + context.popContainer() return result except IndexError: pass result = super().getProperty(key, property_name, context) - context.popContainer() + if context: + context.popContainer() return result @override(CuraContainerStack) diff --git a/cura/Settings/GlobalStack.py b/cura/Settings/GlobalStack.py index a9164d0fb9..2c7cbf5e25 100755 --- a/cura/Settings/GlobalStack.py +++ b/cura/Settings/GlobalStack.py @@ -211,9 +211,8 @@ class GlobalStack(CuraContainerStack): if not self.definition.findDefinitions(key = key): return None - if context is None: - context = PropertyEvaluationContext() - context.pushContainer(self) + if context: + context.pushContainer(self) # Handle the "resolve" property. #TODO: Why the hell does this involve threading? @@ -238,13 +237,15 @@ class GlobalStack(CuraContainerStack): if super().getProperty(key, "settable_per_extruder", context): result = self._extruders[str(limit_to_extruder)].getProperty(key, property_name, context) if result is not None: - context.popContainer() + if context: + context.popContainer() return result else: Logger.log("e", "Setting {setting} has limit_to_extruder but is not settable per extruder!", setting = key) result = super().getProperty(key, property_name, context) - context.popContainer() + if context: + context.popContainer() return result @override(ContainerStack) @@ -256,8 +257,6 @@ class GlobalStack(CuraContainerStack): raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!") - # protected: - # Determine whether or not we should try to get the "resolve" property instead of the # requested property. def _shouldResolve(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> bool: @@ -265,6 +264,10 @@ class GlobalStack(CuraContainerStack): # Do not try to resolve anything but the "value" property return False + if not self.definition.getProperty(key, "resolve"): + # If there isn't a resolve set for this setting, there isn't anything to do here. + return False + current_thread = threading.current_thread() if key in self._resolving_settings[current_thread.name]: # To prevent infinite recursion, if getProperty is called with the same key as @@ -273,10 +276,8 @@ class GlobalStack(CuraContainerStack): # track all settings that are being resolved. return False - setting_state = super().getProperty(key, "state", context = context) - if setting_state is not None and setting_state != InstanceState.Default: - # When the user has explicitly set a value, we should ignore any resolve and - # just return that value. + if self.hasUserValue(key): + # When the user has explicitly set a value, we should ignore any resolve and just return that value. return False return True diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index da1d13aa4e..c7c3fcee18 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -1703,7 +1703,7 @@ class MachineManager(QObject): return False return global_stack.qualityChanges != empty_quality_changes_container - def _updateUponMaterialMetadataChange(self) -> None: + def updateUponMaterialMetadataChange(self) -> None: if self._global_container_stack is None: return with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 89b4209b56..3ed005f131 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -506,6 +506,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Now we know which material is in which extruder. Let's use that to sort the material_labels according to # their extruder position material_labels = [material_name for pos, material_name in sorted(materials_in_extruders_dict.items())] + machine_extruder_count = self._getMachineExtruderCount() + if machine_extruder_count: + material_labels = material_labels[:machine_extruder_count] num_visible_settings = 0 try: @@ -665,7 +668,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # We need to create a new machine machine_name = self._container_registry.uniqueName(self._machine_info.name) - global_stack = CuraStackBuilder.createMachine(machine_name, self._machine_info.definition_id) + # Printers with modifiable number of extruders (such as CFFF) will specify a machine_extruder_count in their + # quality_changes file. If that's the case, take the extruder count into account when creating the machine + # or else the extruderList will return only the first extruder, leading to missing non-global settings in + # the other extruders. + machine_extruder_count = self._getMachineExtruderCount() # type: Optional[int] + global_stack = CuraStackBuilder.createMachine(machine_name, self._machine_info.definition_id, machine_extruder_count) if global_stack: # Only switch if creating the machine was successful. extruder_stack_dict = {str(position): extruder for position, extruder in enumerate(global_stack.extruderList)} @@ -922,6 +930,29 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._machine_info.quality_changes_info.name = quality_changes_name + def _getMachineExtruderCount(self) -> Optional[int]: + """ + Extracts the machine extruder count from the definition_changes file of the printer. If it is not specified in + the file, None is returned instead. + + :return: The count of the machine's extruders + """ + machine_extruder_count = None + if self._machine_info \ + and self._machine_info.definition_changes_info \ + and "values" in self._machine_info.definition_changes_info.parser \ + and "machine_extruder_count" in self._machine_info.definition_changes_info.parser["values"]: + try: + # Theoretically, if the machine_extruder_count is a setting formula (e.g. "=3"), this will produce a + # value error and the project file loading will load the settings in the first extruder only. + # This is not expected to happen though, since all machine definitions define the machine_extruder_count + # as an integer. + machine_extruder_count = int(self._machine_info.definition_changes_info.parser["values"]["machine_extruder_count"]) + except ValueError: + Logger.log("w", "'machine_extruder_count' in file '{file_name}' is not a number." + .format(file_name = self._machine_info.definition_changes_info.file_name)) + return machine_extruder_count + def _createNewQualityChanges(self, quality_type: str, intent_category: Optional[str], name: str, global_stack: GlobalStack, extruder_stack: Optional[ExtruderStack]) -> InstanceContainer: """Helper class to create a new quality changes profile. diff --git a/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py b/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py index 7865e7b6ae..78e0e71626 100644 --- a/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py +++ b/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py @@ -51,7 +51,7 @@ # M207 S F - set the retract length or feed rate # M117 - output the current changes -from typing import List, Optional, Dict +from typing import List, Dict from ..Script import Script import re @@ -336,7 +336,7 @@ class ChangeAtZ(Script): caz_instance = ChangeAtZProcessor() - caz_instance.TargetValues = {} + caz_instance.targetValues = {} # copy over our settings to our change z class self.setIntSettingIfEnabled(caz_instance, "e1_Change_speed", "speed", "e2_speed") @@ -352,23 +352,23 @@ class ChangeAtZ(Script): self.setFloatSettingIfEnabled(caz_instance, "caz_change_retractlength", "retractlength", "caz_retractlength") # is this mod enabled? - caz_instance.IsEnabled = self.getSettingValueByKey("caz_enabled") + caz_instance.enabled = self.getSettingValueByKey("caz_enabled") # are we emitting data to the LCD? - caz_instance.IsDisplayingChangesToLcd = self.getSettingValueByKey("caz_output_to_display") + caz_instance.displayChangesToLcd = self.getSettingValueByKey("caz_output_to_display") # are we doing linear move retractions? - caz_instance.IsLinearRetraction = self.getSettingValueByKey("caz_retractstyle") == "linear" + caz_instance.linearRetraction = self.getSettingValueByKey("caz_retractstyle") == "linear" # see if we're applying to a single layer or to all layers hence forth - caz_instance.IsApplyToSingleLayer = self.getSettingValueByKey("c_behavior") == "single_layer" + caz_instance.applyToSingleLayer = self.getSettingValueByKey("c_behavior") == "single_layer" # used for easy reference of layer or height targeting - caz_instance.IsTargetByLayer = self.getSettingValueByKey("a_trigger") == "layer_no" + caz_instance.targetByLayer = self.getSettingValueByKey("a_trigger") == "layer_no" # change our target based on what we're targeting - caz_instance.TargetLayer = self.getIntSettingByKey("b_targetL", None) - caz_instance.TargetZ = self.getFloatSettingByKey("b_targetZ", None) + caz_instance.targetLayer = self.getIntSettingByKey("b_targetL", None) + caz_instance.targetZ = self.getFloatSettingByKey("b_targetZ", None) # run our script return caz_instance.execute(data) @@ -388,7 +388,7 @@ class ChangeAtZ(Script): return # set our value in the target settings - caz_instance.TargetValues[target] = value + caz_instance.targetValues[target] = value # Sets the given TargetValue in the ChangeAtZ instance if the trigger is specified def setFloatSettingIfEnabled(self, caz_instance, trigger, target, setting): @@ -405,7 +405,7 @@ class ChangeAtZ(Script): return # set our value in the target settings - caz_instance.TargetValues[target] = value + caz_instance.targetValues[target] = value # Returns the given settings value as an integer or the default if it cannot parse it def getIntSettingByKey(self, key, default): @@ -430,13 +430,13 @@ class ChangeAtZ(Script): class GCodeCommand: # The GCode command itself (ex: G10) - Command = None, + command = None, # Contains any arguments passed to the command. The key is the argument name, the value is the value of the argument. - Arguments = {} + arguments = {} # Contains the components of the command broken into pieces - Components = [] + components = [] # Constructor. Sets up defaults def __init__(self): @@ -468,10 +468,10 @@ class GCodeCommand: return None # stores all the components of the command within the class for later - command.Components = command_pieces + command.components = command_pieces # set the actual command - command.Command = command_pieces[0] + command.command = command_pieces[0] # stop here if we don't have any parameters if len(command_pieces) == 1: @@ -488,15 +488,15 @@ class GCodeCommand: linear_command = GCodeCommand.getFromLine(line) # if it's not a linear move, we don't care - if linear_command is None or (linear_command.Command != "G0" and linear_command.Command != "G1"): + if linear_command is None or (linear_command.command != "G0" and linear_command.command != "G1"): return None # convert our values to floats (or defaults) - linear_command.Arguments["F"] = linear_command.getArgumentAsFloat("F", None) - linear_command.Arguments["X"] = linear_command.getArgumentAsFloat("X", None) - linear_command.Arguments["Y"] = linear_command.getArgumentAsFloat("Y", None) - linear_command.Arguments["Z"] = linear_command.getArgumentAsFloat("Z", None) - linear_command.Arguments["E"] = linear_command.getArgumentAsFloat("E", None) + linear_command.arguments["F"] = linear_command.getArgumentAsFloat("F", None) + linear_command.arguments["X"] = linear_command.getArgumentAsFloat("X", None) + linear_command.arguments["Y"] = linear_command.getArgumentAsFloat("Y", None) + linear_command.arguments["Z"] = linear_command.getArgumentAsFloat("Z", None) + linear_command.arguments["E"] = linear_command.getArgumentAsFloat("E", None) # return our new command return linear_command @@ -508,11 +508,11 @@ class GCodeCommand: self.parseArguments() # if we don't have the parameter, return the default - if name not in self.Arguments: + if name not in self.arguments: return default # otherwise return the value - return self.Arguments[name] + return self.arguments[name] # Gets the value of a parameter as a float or returns the default def getArgumentAsFloat(self, name: str, default: float = None) -> float: @@ -593,14 +593,14 @@ class GCodeCommand: def parseArguments(self): # stop here if we don't have any remaining components - if len(self.Components) <= 1: + if len(self.components) <= 1: return None # iterate and index all of our parameters, skip the first component as it's the command - for i in range(1, len(self.Components)): + for i in range(1, len(self.components)): # get our component - component = self.Components[i] + component = self.components[i] # get the first character of the parameter, which is the name component_name = component[0] @@ -613,10 +613,10 @@ class GCodeCommand: component_value = component[1:] # index the argument - self.Arguments[component_name] = component_value + self.arguments[component_name] = component_value # clear the components to we don't process again - self.Components = [] + self.components = [] # Easy function for replacing any GCODE parameter variable in a given GCODE command @staticmethod @@ -625,8 +625,8 @@ class GCodeCommand: # Resets the model back to defaults def reset(self): - self.Command = None - self.Arguments = {} + self.command = None + self.arguments = {} # The primary ChangeAtZ class that does all the gcode editing. This was broken out into an @@ -634,55 +634,55 @@ class GCodeCommand: class ChangeAtZProcessor: # Holds our current height - CurrentZ = None + currentZ = None # Holds our current layer number - CurrentLayer = None + currentLayer = None # Indicates if we're only supposed to apply our settings to a single layer or multiple layers - IsApplyToSingleLayer = False + applyToSingleLayer = False # Indicates if this should emit the changes as they happen to the LCD - IsDisplayingChangesToLcd = False + displayChangesToLcd = False # Indicates that this mod is still enabled (or not) - IsEnabled = True + enabled = True # Indicates if we're processing inside the target layer or not - IsInsideTargetLayer = False + insideTargetLayer = False # Indicates if we have restored the previous values from before we started our pass - IsLastValuesRestored = False + lastValuesRestored = False # Indicates if the user has opted for linear move retractions or firmware retractions - IsLinearRetraction = True + linearRetraction = True # Indicates if we're targetting by layer or height value - IsTargetByLayer = True + targetByLayer = True # Indicates if we have injected our changed values for the given layer yet - IsTargetValuesInjected = False + targetValuesInjected = False # Holds the last extrusion value, used with detecting when a retraction is made - LastE = None + lastE = None # An index of our gcodes which we're monitoring - LastValues = {} + lastValues = {} # The detected layer height from the gcode - LayerHeight = None + layerHeight = None # The target layer - TargetLayer = None + targetLayer = None # Holds the values the user has requested to change - TargetValues = {} + targetValues = {} # The target height in mm - TargetZ = None + targetZ = None # Used to track if we've been inside our target layer yet - WasInsideTargetLayer = False + wasInsideTargetLayer = False # boots up the class with defaults def __init__(self): @@ -692,7 +692,7 @@ class ChangeAtZProcessor: def execute(self, data): # short cut the whole thing if we're not enabled - if not self.IsEnabled: + if not self.enabled: return data # our layer cursor @@ -750,14 +750,14 @@ class ChangeAtZProcessor: # for each of our target values, get the value to restore # no point in restoring values we haven't changed - for key in self.TargetValues: + for key in self.targetValues: # skip target values we can't restore - if key not in self.LastValues: + if key not in self.lastValues: continue # save into our changed - changed[key] = self.LastValues[key] + changed[key] = self.lastValues[key] # return our collection of changed values return changed @@ -766,7 +766,7 @@ class ChangeAtZProcessor: def getDisplayChangesFromValues(self, values: Dict[str, any]) -> str: # stop here if we're not outputting data - if not self.IsDisplayingChangesToLcd: + if not self.displayChangesToLcd: return "" # will hold all the default settings for the target layer @@ -833,7 +833,7 @@ class ChangeAtZProcessor: def getTargetDisplayValues(self) -> str: # convert our target values to something we can output - return self.getDisplayChangesFromValues(self.TargetValues) + return self.getDisplayChangesFromValues(self.targetValues) # Builds the the relevant GCODE lines from the given collection of values def getCodeFromValues(self, values: Dict[str, any]) -> str: @@ -898,7 +898,7 @@ class ChangeAtZProcessor: # set retract rate if "retractfeedrate" in values: - if self.IsLinearRetraction: + if self.linearRetraction: codes.append(";RETRACTFEEDRATE " + str(values["retractfeedrate"] * 60) + "") else: codes.append("M207 F" + str(values["retractfeedrate"] * 60) + "") @@ -906,7 +906,7 @@ class ChangeAtZProcessor: # set retract length if "retractlength" in values: - if self.IsLinearRetraction: + if self.linearRetraction: codes.append(";RETRACTLENGTH " + str(values["retractlength"]) + "") else: codes.append("M207 S" + str(values["retractlength"]) + "") @@ -923,19 +923,19 @@ class ChangeAtZProcessor: def getInjectCode(self) -> str: # if we're now outside of our target layer and haven't restored our last values, do so now - if not self.IsInsideTargetLayer and self.WasInsideTargetLayer and not self.IsLastValuesRestored: + if not self.insideTargetLayer and self.wasInsideTargetLayer and not self.lastValuesRestored: # mark that we've injected the last values - self.IsLastValuesRestored = True + self.lastValuesRestored = True # inject the defaults return self.getLastValues() + "\n" + self.getLastDisplayValues() # if we're inside our target layer but haven't added our values yet, do so now - if self.IsInsideTargetLayer and not self.IsTargetValuesInjected: + if self.insideTargetLayer and not self.targetValuesInjected: # mark that we've injected the target values - self.IsTargetValuesInjected = True + self.targetValuesInjected = True # inject the defaults return self.getTargetValues() + "\n" + self.getTargetDisplayValues() @@ -960,35 +960,35 @@ class ChangeAtZProcessor: def getTargetValues(self) -> str: # build the gcode to change our current values - return self.getCodeFromValues(self.TargetValues) + return self.getCodeFromValues(self.targetValues) # Determines if the current line is at or below the target required to start modifying def isTargetLayerOrHeight(self) -> bool: # target selected by layer no. - if self.IsTargetByLayer: + if self.targetByLayer: # if we don't have a current layer, we're not there yet - if self.CurrentLayer is None: + if self.currentLayer is None: return False # if we're applying to a single layer, stop if our layer is not identical - if self.IsApplyToSingleLayer: - return self.CurrentLayer == self.TargetLayer + if self.applyToSingleLayer: + return self.currentLayer == self.targetLayer else: - return self.CurrentLayer >= self.TargetLayer + return self.currentLayer >= self.targetLayer else: # if we don't have a current Z, we're not there yet - if self.CurrentZ is None: + if self.currentZ is None: return False # if we're applying to a single layer, stop if our Z is not identical - if self.IsApplyToSingleLayer: - return self.CurrentZ == self.TargetZ + if self.applyToSingleLayer: + return self.currentZ == self.targetZ else: - return self.CurrentZ >= self.TargetZ + return self.currentZ >= self.targetZ # Marks any current ChangeZ layer defaults in the layer for deletion @staticmethod @@ -999,7 +999,7 @@ class ChangeAtZProcessor: def processLayerHeight(self, line: str): # stop here if we haven't entered a layer yet - if self.CurrentLayer is None: + if self.currentLayer is None: return # get our gcode command @@ -1010,7 +1010,7 @@ class ChangeAtZProcessor: return # stop here if this isn't a linear move command - if command.Command != "G0" and command.Command != "G1": + if command.command != "G0" and command.command != "G1": return # get our value from the command @@ -1021,15 +1021,15 @@ class ChangeAtZProcessor: return # stop if there's no change - if current_z == self.CurrentZ: + if current_z == self.currentZ: return # set our current Z value - self.CurrentZ = current_z + self.currentZ = current_z # if we don't have a layer height yet, set it based on the current Z value - if self.LayerHeight is None: - self.LayerHeight = self.CurrentZ + if self.layerHeight is None: + self.layerHeight = self.currentZ # Grabs the current layer number def processLayerNumber(self, line: str): @@ -1042,11 +1042,11 @@ class ChangeAtZProcessor: current_layer = GCodeCommand.getDirectArgumentAsInt(line, ";LAYER:", None) # this should never happen, but if our layer number hasn't changed, stop here - if current_layer == self.CurrentLayer: + if current_layer == self.currentLayer: return # update our current layer - self.CurrentLayer = current_layer + self.currentLayer = current_layer # Makes any linear move changes and also injects either target or restored values depending on the plugin state def processLine(self, line: str) -> str: @@ -1059,10 +1059,10 @@ class ChangeAtZProcessor: # if we're not inside the target layer, simply read the any # settings we can and revert any ChangeAtZ deletions - if not self.IsInsideTargetLayer: + if not self.insideTargetLayer: # read any settings if we haven't hit our target layer yet - if not self.WasInsideTargetLayer: + if not self.wasInsideTargetLayer: self.processSetting(line) # if we haven't hit our target yet, leave the defaults as is (unmark them for deletion) @@ -1074,7 +1074,7 @@ class ChangeAtZProcessor: modified_gcode += self.getInjectCode() # modify our command if we're still inside our target layer, otherwise pass unmodified - if self.IsInsideTargetLayer: + if self.insideTargetLayer: modified_gcode += self.processLinearMove(line) + "\n" else: modified_gcode += line + "\n" @@ -1104,11 +1104,11 @@ class ChangeAtZProcessor: return line # get our linear move parameters - feed_rate = linear_command.Arguments["F"] - x_coord = linear_command.Arguments["X"] - y_coord = linear_command.Arguments["Y"] - z_coord = linear_command.Arguments["Z"] - extrude_length = linear_command.Arguments["E"] + feed_rate = linear_command.arguments["F"] + x_coord = linear_command.arguments["X"] + y_coord = linear_command.arguments["Y"] + z_coord = linear_command.arguments["Z"] + extrude_length = linear_command.arguments["E"] # set our new line to our old line new_line = line @@ -1124,7 +1124,7 @@ class ChangeAtZProcessor: new_line = self.processPrintSpeed(feed_rate, new_line) # set our current extrude position - self.LastE = extrude_length if extrude_length is not None else self.LastE + self.lastE = extrude_length if extrude_length is not None else self.lastE # if no changes have been made, stop here if new_line == line: @@ -1137,11 +1137,11 @@ class ChangeAtZProcessor: def processPrintSpeed(self, feed_rate: float, new_line: str) -> str: # if we're not setting print speed or we don't have a feed rate, stop here - if "printspeed" not in self.TargetValues or feed_rate is None: + if "printspeed" not in self.targetValues or feed_rate is None: return new_line # get our requested print speed - print_speed = int(self.TargetValues["printspeed"]) + print_speed = int(self.targetValues["printspeed"]) # if they requested no change to print speed (ie: 100%), stop here if print_speed == 100: @@ -1157,11 +1157,11 @@ class ChangeAtZProcessor: def processRetractLength(self, extrude_length: float, feed_rate: float, new_line: str, x_coord: float, y_coord: float, z_coord: float) -> str: # if we don't have a retract length in the file we can't add one - if "retractlength" not in self.LastValues or self.LastValues["retractlength"] == 0: + if "retractlength" not in self.lastValues or self.lastValues["retractlength"] == 0: return new_line # if we're not changing retraction length, stop here - if "retractlength" not in self.TargetValues: + if "retractlength" not in self.targetValues: return new_line # retractions are only F (feed rate) and E (extrude), at least in cura @@ -1173,22 +1173,22 @@ class ChangeAtZProcessor: return new_line # stop here if we don't know our last extrude value - if self.LastE is None: + if self.lastE is None: return new_line # if there's no change in extrude we have nothing to change - if self.LastE == extrude_length: + if self.lastE == extrude_length: return new_line # if our last extrude was lower than our current, we're restoring, so skip - if self.LastE < extrude_length: + if self.lastE < extrude_length: return new_line # get our desired retract length - retract_length = float(self.TargetValues["retractlength"]) + retract_length = float(self.targetValues["retractlength"]) # subtract the difference between the default and the desired - extrude_length -= (retract_length - self.LastValues["retractlength"]) + extrude_length -= (retract_length - self.lastValues["retractlength"]) # replace our extrude amount return GCodeCommand.replaceDirectArgument(new_line, "E", extrude_length) @@ -1197,7 +1197,7 @@ class ChangeAtZProcessor: def processRetractLengthSetting(self, line: str): # skip if we're not doing linear retractions - if not self.IsLinearRetraction: + if not self.linearRetraction: return # get our command from the line @@ -1208,11 +1208,11 @@ class ChangeAtZProcessor: return # get our linear move parameters - feed_rate = linear_command.Arguments["F"] - x_coord = linear_command.Arguments["X"] - y_coord = linear_command.Arguments["Y"] - z_coord = linear_command.Arguments["Z"] - extrude_length = linear_command.Arguments["E"] + feed_rate = linear_command.arguments["F"] + x_coord = linear_command.arguments["X"] + y_coord = linear_command.arguments["Y"] + z_coord = linear_command.arguments["Z"] + extrude_length = linear_command.arguments["E"] # the command we're looking for only has extrude and feed rate if x_coord is not None or y_coord is not None or z_coord is not None: @@ -1230,17 +1230,17 @@ class ChangeAtZProcessor: return # what ever the last negative retract length is it wins - self.LastValues["retractlength"] = extrude_length + self.lastValues["retractlength"] = extrude_length # Handles any changes to retraction feed rate for the given linear motion command def processRetractFeedRate(self, extrude_length: float, feed_rate: float, new_line: str, x_coord: float, y_coord: float, z_coord: float) -> str: # skip if we're not doing linear retractions - if not self.IsLinearRetraction: + if not self.linearRetraction: return new_line # if we're not changing retraction length, stop here - if "retractfeedrate" not in self.TargetValues: + if "retractfeedrate" not in self.targetValues: return new_line # retractions are only F (feed rate) and E (extrude), at least in cura @@ -1252,7 +1252,7 @@ class ChangeAtZProcessor: return new_line # get our desired retract feed rate - retract_feed_rate = float(self.TargetValues["retractfeedrate"]) + retract_feed_rate = float(self.targetValues["retractfeedrate"]) # convert to units/min retract_feed_rate *= 60 @@ -1264,7 +1264,7 @@ class ChangeAtZProcessor: def processSetting(self, line: str): # if we're in layers already we're out of settings - if self.CurrentLayer is not None: + if self.currentLayer is not None: return # check our retract length @@ -1277,16 +1277,16 @@ class ChangeAtZProcessor: if not self.isTargetLayerOrHeight(): # flag that we're outside our target layer - self.IsInsideTargetLayer = False + self.insideTargetLayer = False # skip to the next line return # flip if we hit our target layer - self.WasInsideTargetLayer = True + self.wasInsideTargetLayer = True # flag that we're inside our target layer - self.IsInsideTargetLayer = True + self.insideTargetLayer = True # Removes all the ChangeZ layer defaults from the given layer @staticmethod @@ -1296,22 +1296,22 @@ class ChangeAtZProcessor: # Resets the class contents to defaults def reset(self): - self.TargetValues = {} - self.IsApplyToSingleLayer = False - self.LastE = None - self.CurrentZ = None - self.CurrentLayer = None - self.IsTargetByLayer = True - self.TargetLayer = None - self.TargetZ = None - self.LayerHeight = None - self.LastValues = {} - self.IsLinearRetraction = True - self.IsInsideTargetLayer = False - self.IsTargetValuesInjected = False - self.IsLastValuesRestored = False - self.WasInsideTargetLayer = False - self.IsEnabled = True + self.targetValues = {} + self.applyToSingleLayer = False + self.lastE = None + self.currentZ = None + self.currentLayer = None + self.targetByLayer = True + self.targetLayer = None + self.targetZ = None + self.layerHeight = None + self.lastValues = {} + self.linearRetraction = True + self.insideTargetLayer = False + self.targetValuesInjected = False + self.lastValuesRestored = False + self.wasInsideTargetLayer = False + self.enabled = True # Sets the original GCODE line in a given GCODE command @staticmethod @@ -1341,31 +1341,31 @@ class ChangeAtZProcessor: return # handle retract length changes - if command.Command == "M207": + if command.command == "M207": # get our retract length if provided - if "S" in command.Arguments: - self.LastValues["retractlength"] = command.getArgumentAsFloat("S") + if "S" in command.arguments: + self.lastValues["retractlength"] = command.getArgumentAsFloat("S") # get our retract feedrate if provided, convert from mm/m to mm/s - if "F" in command.Arguments: - self.LastValues["retractfeedrate"] = command.getArgumentAsFloat("F") / 60.0 + if "F" in command.arguments: + self.lastValues["retractfeedrate"] = command.getArgumentAsFloat("F") / 60.0 # move to the next command return # handle bed temp changes - if command.Command == "M140" or command.Command == "M190": + if command.command == "M140" or command.command == "M190": # get our bed temp if provided - if "S" in command.Arguments: - self.LastValues["bedTemp"] = command.getArgumentAsFloat("S") + if "S" in command.arguments: + self.lastValues["bedTemp"] = command.getArgumentAsFloat("S") # move to the next command return # handle extruder temp changes - if command.Command == "M104" or command.Command == "M109": + if command.command == "M104" or command.command == "M109": # get our tempurature tempurature = command.getArgumentAsFloat("S") @@ -1379,26 +1379,26 @@ class ChangeAtZProcessor: # set our extruder temp based on the extruder if extruder is None or extruder == 0: - self.LastValues["extruderOne"] = tempurature + self.lastValues["extruderOne"] = tempurature if extruder is None or extruder == 1: - self.LastValues["extruderTwo"] = tempurature + self.lastValues["extruderTwo"] = tempurature # move to the next command return # handle fan speed changes - if command.Command == "M106": + if command.command == "M106": # get our bed temp if provided - if "S" in command.Arguments: - self.LastValues["fanSpeed"] = (command.getArgumentAsInt("S") / 255.0) * 100 + if "S" in command.arguments: + self.lastValues["fanSpeed"] = (command.getArgumentAsInt("S") / 255.0) * 100 # move to the next command return # handle flow rate changes - if command.Command == "M221": + if command.command == "M221": # get our flow rate tempurature = command.getArgumentAsFloat("S") @@ -1412,21 +1412,21 @@ class ChangeAtZProcessor: # set our extruder temp based on the extruder if extruder is None: - self.LastValues["flowrate"] = tempurature + self.lastValues["flowrate"] = tempurature elif extruder == 1: - self.LastValues["flowrateOne"] = tempurature + self.lastValues["flowrateOne"] = tempurature elif extruder == 1: - self.LastValues["flowrateTwo"] = tempurature + self.lastValues["flowrateTwo"] = tempurature # move to the next command return # handle print speed changes - if command.Command == "M220": + if command.command == "M220": # get our speed if provided - if "S" in command.Arguments: - self.LastValues["speed"] = command.getArgumentAsInt("S") + if "S" in command.arguments: + self.lastValues["speed"] = command.getArgumentAsInt("S") # move to the next command return diff --git a/resources/definitions/cocoon_create.def.json b/resources/definitions/cocoon_create.def.json new file mode 100644 index 0000000000..a3b47361c7 --- /dev/null +++ b/resources/definitions/cocoon_create.def.json @@ -0,0 +1,79 @@ +{ + "name": "Cocoon Create", + "version": 2, + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "Thushan Fernando", + "manufacturer": "Cocoon Create", + "file_formats": "text/x-gcode", + "preferred_quality_type": "fine", + "has_materials": true, + "platform": "wanhao_200_200_platform.obj", + "platform_texture": "Cocoon-backplate.png", + "machine_extruder_trains": { + "0": "cocoon_create_extruder_0" + }, + "platform_offset": [ + 0, + -28, + 0 + ] + }, + "overrides": { + "machine_name": { + "default_value": "Cocoon Create" + }, + "machine_width": { + "default_value": 200 + }, + "machine_height": { + "default_value": 180 + }, + "machine_depth": { + "default_value": 200 + }, + "machine_heated_bed": { + "default_value": true + }, + "machine_gcode_flavor": { + "default_value": "RepRap (Marlin/Sprinter)" + }, + "machine_start_gcode": { + "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{speed_travel} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E3 ;extrude 3mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{speed_travel} \n ;Put printing message on LCD screen\n M117 Printing..." + }, + "machine_end_gcode": { + "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning" + }, + "material_diameter": { + "default_value": 1.75 + }, + "layer_height": { + "default_value": 0.10 + }, + "layer_height_0": { + "default_value": 0.2 + }, + "wall_thickness": { + "value": "0.8" + }, + "top_bottom_thickness": { + "default_value": 0.6 + }, + "speed_print": { + "default_value": 50 + }, + "support_enable": { + "default_value": true + }, + "retraction_enable": { + "default_value": true + }, + "retraction_amount": { + "default_value": 4.5 + }, + "retraction_speed": { + "default_value": 25 + } + } +} \ No newline at end of file diff --git a/resources/definitions/cocoon_create_touch.def.json b/resources/definitions/cocoon_create_touch.def.json new file mode 100644 index 0000000000..da5905c047 --- /dev/null +++ b/resources/definitions/cocoon_create_touch.def.json @@ -0,0 +1,79 @@ +{ + "name": "Cocoon Create Touch", + "version": 2, + "inherits": "fdmprinter", + "metadata": { + "visible": true, + "author": "Thushan Fernando", + "manufacturer": "Cocoon Create", + "file_formats": "text/x-gcode", + "preferred_quality_type": "fine", + "has_materials": true, + "platform": "wanhao_200_200_platform.obj", + "platform_texture": "Cocoon-backplate.png", + "machine_extruder_trains": { + "0": "cocoon_create_touch_extruder_0" + }, + "platform_offset": [ + 0, + -28, + 0 + ] + }, + "overrides": { + "machine_name": { + "default_value": "Cocoon Create Touch" + }, + "machine_width": { + "default_value": 200 + }, + "machine_height": { + "default_value": 180 + }, + "machine_depth": { + "default_value": 200 + }, + "machine_heated_bed": { + "default_value": true + }, + "machine_gcode_flavor": { + "default_value": "RepRap (Marlin/Sprinter)" + }, + "machine_start_gcode": { + "default_value": "G21 ;metric values\n G90 ;absolute positioning\n M82 ;set extruder to absolute mode\n M107 ;start with the fan off\n G28 X0 Y0 ;move X/Y to min endstops\n G28 Z0 ;move Z to min endstops\n G1 Z15.0 F{speed_travel} ;move the platform down 15mm\n G92 E0 ;zero the extruded length\n G1 F200 E3 ;extrude 3mm of feed stock\n G92 E0 ;zero the extruded length again\n G1 F{speed_travel} \n ;Put printing message on LCD screen\n M117 Printing..." + }, + "machine_end_gcode": { + "default_value": "M104 S0 ;extruder heater off \n G91 ;relative positioning\n G1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n G1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\n G28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\n M84 ;steppers off\n G90 ;absolute positioning" + }, + "material_diameter": { + "default_value": 1.75 + }, + "layer_height": { + "default_value": 0.10 + }, + "layer_height_0": { + "default_value": 0.2 + }, + "wall_thickness": { + "value": "0.8" + }, + "top_bottom_thickness": { + "default_value": 0.6 + }, + "speed_print": { + "default_value": 50 + }, + "support_enable": { + "default_value": true + }, + "retraction_enable": { + "default_value": true + }, + "retraction_amount": { + "default_value": 4.5 + }, + "retraction_speed": { + "default_value": 25 + } + } +} \ No newline at end of file diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index c21864ad8d..3736ff609b 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -4299,7 +4299,7 @@ "type": "bool", "default_value": false, "value": "support_pattern == 'cross' or support_pattern == 'gyroid'", - "enabled": "(support_enable or support_meshes_present) and (support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'cross' or support_pattern == 'gyroid')", + "enabled": "(support_enable or support_meshes_present) and (support_pattern == 'lines' or support_pattern == 'grid' or support_pattern == 'triangles' or support_pattern == 'cross' or support_pattern == 'gyroid')", "limit_to_extruder": "support_infill_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true diff --git a/resources/extruders/cocoon_create_extruder_0.def.json b/resources/extruders/cocoon_create_extruder_0.def.json new file mode 100644 index 0000000000..edca4b33bb --- /dev/null +++ b/resources/extruders/cocoon_create_extruder_0.def.json @@ -0,0 +1,15 @@ +{ + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": { + "machine": "cocoon_create", + "position": "0" + }, + + "overrides": { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} diff --git a/resources/extruders/cocoon_create_touch_extruder_0.def.json b/resources/extruders/cocoon_create_touch_extruder_0.def.json new file mode 100644 index 0000000000..ca7c3e38af --- /dev/null +++ b/resources/extruders/cocoon_create_touch_extruder_0.def.json @@ -0,0 +1,15 @@ +{ + "version": 2, + "name": "Extruder 1", + "inherits": "fdmextruder", + "metadata": { + "machine": "cocoon_create_touch", + "position": "0" + }, + + "overrides": { + "extruder_nr": { "default_value": 0 }, + "machine_nozzle_size": { "default_value": 0.4 }, + "material_diameter": { "default_value": 1.75 } + } +} diff --git a/resources/images/Cocoon-backplate.png b/resources/images/Cocoon-backplate.png new file mode 100644 index 0000000000..577edfcf0d Binary files /dev/null and b/resources/images/Cocoon-backplate.png differ diff --git a/resources/qml/PrintMonitor.qml b/resources/qml/PrintMonitor.qml index 9274bf80ad..19c2562874 100644 --- a/resources/qml/PrintMonitor.qml +++ b/resources/qml/PrintMonitor.qml @@ -77,7 +77,7 @@ Item Repeater { id: extrudersRepeater - model: activePrinter != null ? activePrinter.extruderList : null + model: activePrinter != null ? activePrinter.extruders : null ExtruderBox { diff --git a/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml b/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml index 3ae180f133..c879ff53fd 100644 --- a/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml +++ b/resources/qml/PrintSetupSelector/Custom/QualitiesWithIntentMenu.qml @@ -104,16 +104,6 @@ Popup anchors.left: parent.left anchors.right: parent.right - // We set it by means of a binding, since then we can use the when condition, which we need to - // prevent a binding loop. - Binding - { - target: parent - property: "height" - value: parent.childrenRect.height - when: parent.visibleChildren.length > 0 - } - // Add the qualities that belong to the intent Repeater { @@ -148,11 +138,7 @@ Popup Item { height: childrenRect.height - anchors - { - left: parent.left - right: parent.right - } + width: popup.contentWidth Label { diff --git a/tests/Settings/TestGlobalStack.py b/tests/Settings/TestGlobalStack.py index ab9c034e24..79d326ddae 100755 --- a/tests/Settings/TestGlobalStack.py +++ b/tests/Settings/TestGlobalStack.py @@ -410,13 +410,13 @@ def test_getPropertyInstancesBeforeResolve(global_stack): value = unittest.mock.MagicMock() #Sets just the value. value.getProperty = unittest.mock.MagicMock(side_effect = getValueProperty) - value.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality") + value.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality_changes") resolve = unittest.mock.MagicMock() #Sets just the resolve. resolve.getProperty = unittest.mock.MagicMock(side_effect = getResolveProperty) with unittest.mock.patch("cura.Settings.CuraContainerStack.DefinitionContainer", unittest.mock.MagicMock): #To guard against the type checking. global_stack.definition = resolve - global_stack.quality = value + global_stack.qualityChanges = value assert global_stack.getProperty("material_bed_temperature", "value") == 10