diff --git a/AutoTowersGenerator.py b/AutoTowersGenerator.py index c65eae9..a96c891 100644 --- a/AutoTowersGenerator.py +++ b/AutoTowersGenerator.py @@ -35,6 +35,7 @@ from .Controllers.RetractTowerController import RetractTowerController from .Controllers.SpeedTowerController import SpeedTowerController from .Controllers.TempTowerController import TempTowerController +from .Controllers.LinearAdvanceTowerController import LinearAdvanceTowerController # not sure it's necessar i18n could be store in a different place ? Resources.addSearchPath( @@ -49,7 +50,7 @@ class AutoTowersGenerator(QObject, Extension): # Add additional controller classes to this list - _controllerClasses = [BedLevelPatternController, FanTowerController, FlowTowerController, RetractTowerController, SpeedTowerController, TempTowerController] + _controllerClasses = [BedLevelPatternController, FanTowerController, FlowTowerController, RetractTowerController, SpeedTowerController, TempTowerController, LinearAdvanceTowerController] diff --git a/Controllers/LinearAdvanceTowerController.py b/Controllers/LinearAdvanceTowerController.py new file mode 100644 index 0000000..2eb1c36 --- /dev/null +++ b/Controllers/LinearAdvanceTowerController.py @@ -0,0 +1,139 @@ +# Import the correct version of PyQt +try: + from PyQt6.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty +except ImportError: + from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, pyqtProperty + +import os + +from UM.Logger import Logger +from UM.i18n import i18nCatalog +from UM.Resources import Resources + +from .ControllerBase import ControllerBase +from ..Models.LinearAdvanceTowerModel import LinearAdvanceTowerModel + +# Import the script that does the actual post-processing +from ..Postprocessing import LinearAdvanceTower_PostProcessing + +Resources.addSearchPath( + os.path.join(os.path.join(os.path.abspath(os.path.dirname(__file__)),'..'),'Resources') +) # Plugin translation file import +catalog = i18nCatalog("autotowers") + +class LinearAdvanceTowerController(ControllerBase): + + _openScadFilename = 'latower.scad' + _qmlFilename = 'LATowerDialog.qml' + + _criticalPropertiesTable = { + 'adaptive_layer_height_enabled': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, False), + 'layer_height': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, None), + 'meshfix_union_all_remove_holes': (ControllerBase.ContainerId.ACTIVE_EXTRUDER_STACK, False), + 'support_enable': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, False), + 'top_bottom_thickness': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 0), + 'top_thickness': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 0), + 'bottom_thickness': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 0), + 'top_layers': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 0), + 'bottom_layers': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 0), + 'wall_thickness': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 0), + 'wall_line_count': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 2), + 'infill_sparse_density': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 0), + 'infill_wall_line_count': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 0), + 'adhesion_type': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, "brim"), + 'brim_line_count': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 10), + 'brim_width': (ControllerBase.ContainerId.GLOBAL_CONTAINER_STACK, 4), + + } + + + + def __init__(self, guiDir, stlDir, loadStlCallback, generateStlCallback, pluginName): + dataModel = LinearAdvanceTowerModel(stlDir=stlDir) + super().__init__(name=catalog.i18nc("@test", "Linear Advance Tower"), guiDir=guiDir, loadStlCallback=loadStlCallback, generateStlCallback=generateStlCallback, qmlFilename=self._qmlFilename, criticalPropertiesTable=self._criticalPropertiesTable, dataModel=dataModel, pluginName=pluginName) + + + + @pyqtSlot() + def dialogAccepted(self)->None: + ''' This method is called by the dialog when the "Generate" button is clicked ''' + + if self._dataModel.presetSelected: + # Load a preset tower + self._loadPresetPATower() + else: + # Generate a custom tower using the user's settings + self._generateCustomPATower() + + + + # This function is called by the main script when it's time to post-process the tower model + def postProcess(self, gcode, enable_lcd_messages=False, enable_advanced_gcode_comments=True)->list: + ''' This method is called to post-process the gcode before it is sent to the printer or disk ''' + + # Collect the post-processing data + baseHeight = 0 + sectionHeight = self._dataModel.optimalSectionHeight + initialLayerHeight = self._dataModel.initialLayerHeight + layerHeight = self._dataModel.layerHeight + startKfactor = self._dataModel.startKfactor + kfactorChange = self._dataModel.kfactorChange + + # Call the post-processing script + gcode = LinearAdvanceTower_PostProcessing.execute( + gcode=gcode, + base_height=baseHeight, + section_height=sectionHeight, + initial_layer_height=initialLayerHeight, + layer_height=layerHeight, + start_kfactor=startKfactor, + kfactor_change=kfactorChange, + enable_lcd_messages=enable_lcd_messages, + enable_advanced_gcode_comments = enable_advanced_gcode_comments, + enable_smooth_change=True + ) + + return gcode + + + + def _loadPresetPATower(self)->None: + ''' Load a preset tower ''' + + # Determine the path of the STL file to load + stlFilePath = self._dataModel.presetFilePath + + # Determine the tower name + towerName = f'Preset {self._dataModel.presetName}' + + # Use the callback to load the preset STL file + self._loadStlCallback(self, towerName, stlFilePath, self.postProcess) + + + + def _generateCustomPATower(self)->None: + ''' Generate a custom tower ''' + + # Collect data from the data model + openScadFilename = self._openScadFilename + startKfactor = self._dataModel.startKfactor + endKfactor = self._dataModel.endKfactor + kfactorChange = self._dataModel.kfactorChange + sectionHeight = self._dataModel.optimalSectionHeight + towerLabel = self._dataModel.towerLabel + towerDescription = self._dataModel.towerDescription + + # Compile the parameters to send to OpenSCAD + openScadParameters = {} + openScadParameters ['Starting_Value'] = startKfactor + openScadParameters ['Ending_Value'] = endKfactor + openScadParameters ['Value_Change'] = kfactorChange + openScadParameters ['Section_Height'] = sectionHeight + openScadParameters ['Column_Label'] = towerLabel + openScadParameters ['Tower_Label'] = towerDescription + + # Determine the tower name + towerName = f'Custom Linear Advance Tower - {startKfactor}-{endKfactor}x{kfactorChange}' + + # Send the filename and parameters to the model callback + self._generateStlCallback(self, towerName, self._openScadFilename, openScadParameters, self.postProcess) diff --git a/Models/LinearAdvanceTowerModel.py b/Models/LinearAdvanceTowerModel.py new file mode 100644 index 0000000..ee1c575 --- /dev/null +++ b/Models/LinearAdvanceTowerModel.py @@ -0,0 +1,190 @@ +# Import the correct version of PyQt +try: + from PyQt6.QtCore import pyqtSignal, pyqtProperty +except ImportError: + from PyQt5.QtCore import pyqtSignal, pyqtProperty + +import os + +from UM.Logger import Logger +from UM.i18n import i18nCatalog +from UM.Resources import Resources + +from .ModelBase import ModelBase + +Resources.addSearchPath( + os.path.join(os.path.join(os.path.abspath(os.path.dirname(__file__)),'..'),'Resources') +) # Plugin translation file import +catalog = i18nCatalog("autotowers") + +class LinearAdvanceTowerModel(ModelBase): + + # The available pa tower presets + _presetsTable = [ + {'name': catalog.i18nc("@model", "K-factor 0.0 - 0.2") , 'filename': 'Linear Advance Tower - K 0.0-0.2.stl', 'start K': '0.0', 'K change': '0.02'}, + {'name': catalog.i18nc("@model", "K-factor - 0.0 - 2.0") , 'filename': 'Linear Advance Tower - K 0.0-2.0.stl', 'start K': '0.0', 'K change': '0.2'}, + ] + + + # Make the presets availabe to QML + presetsModelChanged = pyqtSignal() + + @pyqtProperty(list, notify=presetsModelChanged) + def presetsModel(self): + return self._presetsTable + + + + # The selected fan tower preset index + _presetIndex = 0 + + presetIndexChanged = pyqtSignal() + + def setPresetIndex(self, value)->None: + self._presetIndex = int(value) + self.presetIndexChanged.emit() + + @pyqtProperty(int, notify=presetIndexChanged, fset=setPresetIndex) + def presetIndex(self)->int: + return self._presetIndex + + @pyqtProperty(bool, notify=presetIndexChanged) + def presetSelected(self)->bool: + return self._presetIndex < len(self._presetsTable) + + @pyqtProperty(str, notify=presetIndexChanged) + def presetName(self)->str: + return self._presetsTable[self.presetIndex]['name'] + + @pyqtProperty(str, notify=presetIndexChanged) + def presetFileName(self)->str: + return self._presetsTable[self.presetIndex]['filename'] + + @pyqtProperty(str, notify=presetIndexChanged) + def presetFilePath(self)->str: + return self._buildStlFilePath(self.presetFileName) + + @pyqtProperty(str, notify=presetIndexChanged) + def presetStartKfactorStr(self)->str: + return self._presetsTable[self.presetIndex]['start K'] + + @pyqtProperty(float, notify=presetIndexChanged) + def presetStartKfactor(self)->float: + return float(self.presetStartKfactorStr) + + @pyqtProperty(str, notify=presetIndexChanged) + def presetKfactorChangeStr(self)->str: + return self._presetsTable[self.presetIndex]['K change'] + + @pyqtProperty(float, notify=presetIndexChanged) + def presetKfactorChange(self)->float: + return float(self.presetKfactorChangeStr) + + + + # The icon to display on the dialog + dialogIconChanged = pyqtSignal() + + @pyqtProperty(str, notify=dialogIconChanged) + def dialogIcon(self)->str: + return 'latower_icon.png' + + + + # The starting K-factor value for the tower + _startKfactorStr = '0.0' + + startKfactorStrChanged = pyqtSignal() + + def setStartKfactorStr(self, value)->None: + self._startKfactorStr = value + self.startKfactorStrChanged.emit() + + @pyqtProperty(str, notify=startKfactorStrChanged, fset=setStartKfactorStr) + def startKfactorStr(self)->str: + # Allow the preset to override this setting + if self.presetSelected: + return self.presetStartKfactorStr + else: + return self._startKfactorStr + + @pyqtProperty(float, notify=startKfactorStrChanged) + def startKfactor(self)->float: + return float(self.startKfactorStr) + + + + # The ending K-factor value for the tower + _endKfactorStr = '0.2' + + endKfactorStrChanged = pyqtSignal() + + def setEndKfactorStr(self, value)->None: + self._endKfactorStr = value + self.endKfactorStrChanged.emit() + + @pyqtProperty(str, notify=endKfactorStrChanged, fset=setEndKfactorStr) + def endKfactorStr(self)->str: + return self._endKfactorStr + + @pyqtProperty(float, notify=endKfactorStrChanged) + def endKfactor(self)->float: + return float(self.endKfactorStr) + + + + # The amount to change the K-factor between tower sections + _kfactorChangeStr = '0.02' + + kfactorChangeStrChanged = pyqtSignal() + + def setKfactorChangeStr(self, value)->None: + self._kfactorChangeStr = value + self.kfactorChangeStrChanged.emit() + + @pyqtProperty(str, notify=kfactorChangeStrChanged, fset=setKfactorChangeStr) + def kfactorChangeStr(self)->str: + # Allow the preset to override this setting + if self.presetSelected: + return self.presetKfactorChangeStr + else: + return self._kfactorChangeStr + + @pyqtProperty(float, notify=kfactorChangeStrChanged) + def kfactorChange(self)->float: + return float(self.kfactorChangeStr) + + + + # The label to add to the tower + _towerLabel = '' + + towerLabelChanged = pyqtSignal() + + def setTowerLabel(self, value)->None: + self._towerLabel = value + self.towerLabelChanged.emit() + + @pyqtProperty(str, notify=towerLabelChanged, fset=setTowerLabel) + def towerLabel(self)->str: + return self._towerLabel + + + + # The description to carve up the side of the tower + _towerDescription = 'K-factor' + + towerDescriptionChanged = pyqtSignal() + + def setTowerDescription(self, value)->None: + self._towerDescription = value + self.towerDescriptionChanged.emit() + + @pyqtProperty(str, notify=towerDescriptionChanged, fset=setTowerDescription) + def towerDescription(self)->str: + return self._towerDescription + + + + def __init__(self, stlDir): + super().__init__(stlDir=stlDir) diff --git a/Postprocessing/FanTower_PostProcessing.py b/Postprocessing/FanTower_PostProcessing.py index 81153f6..979feb8 100644 --- a/Postprocessing/FanTower_PostProcessing.py +++ b/Postprocessing/FanTower_PostProcessing.py @@ -63,7 +63,7 @@ def execute(gcode, base_height:float, section_height:float, initial_layer_height after_bridge = False # Iterate over each line in the g-code - for line_index, line, lines, start_of_new_section in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): + for line_index, line, lines, start_of_new_section, _, _ in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): # Handle each new tower section if start_of_new_section: diff --git a/Postprocessing/FlowTower_PostProcessing.py b/Postprocessing/FlowTower_PostProcessing.py index 0ac1d00..23d8f65 100644 --- a/Postprocessing/FlowTower_PostProcessing.py +++ b/Postprocessing/FlowTower_PostProcessing.py @@ -63,7 +63,7 @@ def execute(gcode, base_height:float, section_height:float, initial_layer_height updated_extrusion_position = None # Iterate over each line in the g-code - for line_index, line, lines, start_of_new_section in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): + for line_index, line, lines, start_of_new_section, _, _ in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): # Handle each new tower section if start_of_new_section: diff --git a/Postprocessing/LinearAdvanceTower_PostProcessing.py b/Postprocessing/LinearAdvanceTower_PostProcessing.py new file mode 100644 index 0000000..ad965ad --- /dev/null +++ b/Postprocessing/LinearAdvanceTower_PostProcessing.py @@ -0,0 +1,88 @@ +# This script was adapted (copied) from TempTower_PostProcessing +# +# Version 0.1 - 25 Jul 2024: +# Created for https://github.com/kartchnb/AutoTowersGenerator/issues/41 +__version__ = '0.1' + +from UM.Logger import Logger + +from . import PostProcessingCommon as Common + + + +def execute(gcode, base_height:float, section_height:float, initial_layer_height:float, layer_height:float, start_kfactor:float, kfactor_change:float, enable_lcd_messages:bool, enable_advanced_gcode_comments:bool, enable_smooth_change:bool): + + # Log the post-processing settings + Logger.log('d', f'Beginning PA Tower post-processing script version {__version__}') + Logger.log('d', f'Base height = {base_height} mm') + Logger.log('d', f'Section height = {section_height} mm') + Logger.log('d', f'Initial printed layer height = {initial_layer_height}') + Logger.log('d', f'Printed layer height = {layer_height} mm') + Logger.log('d', f'Starting K-factor = {start_kfactor} C') + Logger.log('d', f'K-factor change = {kfactor_change} C') + Logger.log('d', f'Enable LCD messages = {enable_lcd_messages}') + Logger.log('d', f'Advanced Gcode Comments = {enable_advanced_gcode_comments}') + Logger.log('d', f'Smooth K-factor change = {enable_smooth_change}') + + # Document the settings in the g-code + gcode[0] += f'{Common.comment_prefix} PA Tower post-processing script version {__version__}\n' + gcode[0] += f'{Common.comment_prefix} Base height = {base_height} mm\n' + gcode[0] += f'{Common.comment_prefix} Section height = {section_height} mm\n' + gcode[0] += f'{Common.comment_prefix} Initial printed layer height = {initial_layer_height} mm\n' + gcode[0] += f'{Common.comment_prefix} Printed layer height = {layer_height} mm\n' + gcode[0] += f'{Common.comment_prefix} Starting K-factor = {start_kfactor} C\n' + gcode[0] += f'{Common.comment_prefix} K-factor change = {kfactor_change} C\n' + gcode[0] += f'{Common.comment_prefix} Enable LCD messages = {enable_lcd_messages}\n' + gcode[0] += f'{Common.comment_prefix} Advanced Gcode comments = {enable_advanced_gcode_comments}\n' + gcode[0] += f'{Common.comment_prefix} Smooth K-factor change = {enable_smooth_change}\n' + + # Start at the selected starting K-factor + current_kfactor = start_kfactor - kfactor_change # The current kfactor will be incremented when the first section is encountered + + # Iterate over each line in the g-code + for line_index, line, lines, start_of_new_section, start_of_new_layer, current_print_height in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments, enable_smooth_change): + + # Handle each new tower section + if not enable_smooth_change and start_of_new_section: + + # Increment the K-factor for this new tower section + current_kfactor += kfactor_change + + # Configure the new K-factor in the gcode + if enable_advanced_gcode_comments : + lines.insert(2, f'M900 K{current_kfactor} {Common.comment_prefix} setting K-factor to {current_kfactor}') + else : + lines.insert(2, f'M900 K{current_kfactor}') + + # Display the new K-factor on the printer's LCD + if enable_lcd_messages: + lines.insert(3, f'M117 K {current_kfactor}') + if enable_advanced_gcode_comments : + lines.insert(3, f'{Common.comment_prefix} Displaying "K {current_kfactor}" on the LCD') + elif enable_smooth_change and start_of_new_layer: + # Calculate K-factor from height + current_kfactor = start_kfactor + kfactor_change * float(current_print_height) / section_height + current_kfactor = round(current_kfactor, 4) + # Configure the new K-factor in the gcode + if enable_advanced_gcode_comments: + lines.insert(2, f'M900 K{current_kfactor} {Common.comment_prefix} setting K-factor to {current_kfactor}') + else: + lines.insert(2, f'M900 K{current_kfactor}') + + # Display the new K-factor on the printer's LCD + if enable_lcd_messages: + lines.insert(3, f'M117 K {current_kfactor}') + if enable_advanced_gcode_comments: + lines.insert(3, f'{Common.comment_prefix} Displaying "K {current_kfactor}" on the LCD') + # Handle lines within each section + else: + if Common.IsLinAdvChangeLine(line): + # Comment out the line + new_line = f';{line}' + if enable_advanced_gcode_comments: + new_line += f' {Common.comment_prefix} preventing K-factor change within the tower section' + lines[line_index] = line + + Logger.log('d', 'AutoTowersGenerator completing PA Tower post-processing') + + return gcode \ No newline at end of file diff --git a/Postprocessing/MiscSpeedTower_PostProcessing.py b/Postprocessing/MiscSpeedTower_PostProcessing.py index ee769dc..a93ba2b 100644 --- a/Postprocessing/MiscSpeedTower_PostProcessing.py +++ b/Postprocessing/MiscSpeedTower_PostProcessing.py @@ -57,7 +57,7 @@ def execute(gcode, base_height: float, section_height: float, initial_layer_heig current_speed = start_speed - speed_change # The current speed will be corrected when the first section is encountered # Iterate over each line in the g-code - for line_index, line, lines, start_of_new_section in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): + for line_index, line, lines, start_of_new_section, _, _ in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): # Handle each new tower section if start_of_new_section: diff --git a/Postprocessing/PostProcessingCommon.py b/Postprocessing/PostProcessingCommon.py index 04cced2..878cf16 100644 --- a/Postprocessing/PostProcessingCommon.py +++ b/Postprocessing/PostProcessingCommon.py @@ -18,7 +18,7 @@ -def LayerEnumerate(gcode, base_height:float, section_height:float, initial_layer_height:float, layer_height:float, enable_advanced_gcode_comments:bool): +def LayerEnumerate(gcode, base_height:float, section_height:float, initial_layer_height:float, layer_height:float, enable_advanced_gcode_comments:bool, enable_smooth_change:bool = False): ''' Iterates over the lines in the gcode that is passed in skipping Cura's comment layer and the user-specified start gcode and ignoring post-printing layers ''' @@ -31,6 +31,8 @@ def LayerEnumerate(gcode, base_height:float, section_height:float, initial_layer # Keep track of whether a line marks the start of a new layer start_of_new_section = False + + start_of_new_layer = False # Keep track of the current print height current_print_height = Decimal('0') @@ -99,11 +101,19 @@ def LayerEnumerate(gcode, base_height:float, section_height:float, initial_layer # Indicate this is NOT the start of a new tower section start_of_new_section = False + # If this is not the start of a new layer and smooth changing + if enable_smooth_change: + start_of_new_layer = True + else: + start_of_new_layer = False + # Yield the values for this line - yield line_index, line, lines, start_of_new_section + yield line_index, line, lines, start_of_new_section, start_of_new_layer, current_print_height # Once the first line in a new tower section has been processed, remove the new section indicator start_of_new_section = False + # Once the first line in a new layer has been processed, remove the new layer indicator + start_of_new_layer = False # Reassemble the clump gcode[clump_index] = '\n'.join(lines) @@ -191,3 +201,8 @@ def IsResetExtruderLine(line: str) -> bool: def IsTemperatureChangeLine(line: str) -> bool: ''' Check if the given line changes the printing temperature ''' return line.strip().startswith('M104') or line.strip().startswith('M109') + + +def IsLinAdvChangeLine(line: str) -> bool: + ''' Check if the given line changes the printing temperature ''' + return line.strip().startswith('M900') diff --git a/Postprocessing/PrintSpeedTower_PostProcessing.py b/Postprocessing/PrintSpeedTower_PostProcessing.py index f8cb139..6cd3770 100644 --- a/Postprocessing/PrintSpeedTower_PostProcessing.py +++ b/Postprocessing/PrintSpeedTower_PostProcessing.py @@ -77,7 +77,7 @@ def execute(gcode, base_height:float, section_height:float, initial_layer_height first_section = True # Iterate over each line in the g-code - for line_index, line, lines, start_of_new_section in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): + for line_index, line, lines, start_of_new_section, _, _ in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): # Handle each new section if start_of_new_section: diff --git a/Postprocessing/RetractDistanceTower_PostProcessing.py b/Postprocessing/RetractDistanceTower_PostProcessing.py index ad6b0f1..c475d5e 100644 --- a/Postprocessing/RetractDistanceTower_PostProcessing.py +++ b/Postprocessing/RetractDistanceTower_PostProcessing.py @@ -68,7 +68,7 @@ def execute(gcode, base_height:float, section_height:float, initial_layer_height reference_extrusion_position = None # Iterate over each line in the g-code - for line_index, line, lines, start_of_new_section in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): + for line_index, line, lines, start_of_new_section, _, _ in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): # Handle each new tower section if start_of_new_section: diff --git a/Postprocessing/RetractSpeedTower_PostProcessing.py b/Postprocessing/RetractSpeedTower_PostProcessing.py index 3bda793..b923bcf 100644 --- a/Postprocessing/RetractSpeedTower_PostProcessing.py +++ b/Postprocessing/RetractSpeedTower_PostProcessing.py @@ -63,7 +63,7 @@ def execute(gcode, base_height:float, section_height:float, initial_layer_height current_retract_speed = start_retract_speed - retract_speed_change # The current retract value will be corrected when the first section is encountered # Iterate over each line in the g-code - for line_index, line, lines, start_of_new_section in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): + for line_index, line, lines, start_of_new_section, _, _ in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): # Handle each new tower section if start_of_new_section: diff --git a/Postprocessing/TempTower_PostProcessing.py b/Postprocessing/TempTower_PostProcessing.py index 77d89a1..caa60f7 100644 --- a/Postprocessing/TempTower_PostProcessing.py +++ b/Postprocessing/TempTower_PostProcessing.py @@ -54,7 +54,7 @@ def execute(gcode, base_height:float, section_height:float, initial_layer_height current_temp = start_temp - temp_change # The current temp will be incremented when the first section is encountered # Iterate over each line in the g-code - for line_index, line, lines, start_of_new_section in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): + for line_index, line, lines, start_of_new_section, _, _ in Common.LayerEnumerate(gcode, base_height, section_height, initial_layer_height, layer_height, enable_advanced_gcode_comments): # Handle each new tower section if start_of_new_section: diff --git a/Resources/Images/latower_icon.png b/Resources/Images/latower_icon.png new file mode 100644 index 0000000..93a2f6d Binary files /dev/null and b/Resources/Images/latower_icon.png differ diff --git a/Resources/OpenScad/latower.json b/Resources/OpenScad/latower.json new file mode 100644 index 0000000..14b2d04 --- /dev/null +++ b/Resources/OpenScad/latower.json @@ -0,0 +1,79 @@ +{ + "parameterSets": { + "screenshot": { + "Base_Height": "0.84", + "Column_Label": "", + "Column_Label_Height_Multiplier": "0.3", + "Ending_Value": "4", + "Font": "Arial:style=Bold", + "Iota": "0.001", + "Label_Sections": "false", + "Orient_for_Screenshot": "true", + "Preview_Quality_Value": "24", + "Render_Quality_Value": "24", + "Screenshot_Vpd": "140", + "Screenshot_Vpf": "22.5", + "Screenshot_Vpr": "[75, 0, 300]", + "Screenshot_Vpt": "[0, 0, 15]", + "Section_Height": "8", + "Section_Label_Height_Multiplier": "0.3", + "Section_Label_Prefix": "", + "Section_Label_Suffix": "", + "Starting_Value": "1", + "Tower_Label": "", + "Tower_Label_Height_Multiplier": "0.6", + "Tower_Width_Multiplier": "5", + "Value_Change": "1", + "Wall_Thickness": "0.6" + }, + "Linear Advance Tower - K 0.0-0.2": { + "Column_Label": "0.0 - 0.2", + "Column_Label_Height_Multiplier": "0.301", + "Ending_Value": "0.2", + "Font": "Arial:style=Bold", + "Iota": "0.001", + "Label_Sections": "true", + "Orient_for_Screenshot": "false", + "Preview_Quality_Value": "24", + "Render_Quality_Value": "24", + "Screenshot_Vpd": "140", + "Screenshot_Vpf": "22.5", + "Screenshot_Vpr": "[75, 0, 300]", + "Screenshot_Vpt": "[0, 0, 15]", + "Section_Height": "8.4", + "Section_Label_Height_Multiplier": "0.401", + "Section_Label_Prefix": "", + "Section_Label_Suffix": "", + "Starting_Value": "0", + "Tower_Label": "LINEAR ADV", + "Tower_Label_Height_Multiplier": "0.601", + "Tower_Size": "70", + "Value_Change": "0.02" + }, + "Linear Advance Tower - K 0.0-2.0": { + "Column_Label": "0.0 - 2.0", + "Column_Label_Height_Multiplier": "0.301", + "Ending_Value": "2", + "Font": "Arial:style=Bold", + "Iota": "0.001", + "Label_Sections": "true", + "Orient_for_Screenshot": "false", + "Preview_Quality_Value": "24", + "Render_Quality_Value": "24", + "Screenshot_Vpd": "140", + "Screenshot_Vpf": "22.5", + "Screenshot_Vpr": "[75, 0, 300]", + "Screenshot_Vpt": "[0, 0, 15]", + "Section_Height": "8.4", + "Section_Label_Height_Multiplier": "0.401", + "Section_Label_Prefix": "", + "Section_Label_Suffix": "", + "Starting_Value": "0", + "Tower_Label": "LINEAR ADV", + "Tower_Label_Height_Multiplier": "0.601", + "Tower_Size": "70", + "Value_Change": "0.2" + } + }, + "fileFormatVersion": "1" +} diff --git a/Resources/OpenScad/latower.scad b/Resources/OpenScad/latower.scad new file mode 100644 index 0000000..86e9722 --- /dev/null +++ b/Resources/OpenScad/latower.scad @@ -0,0 +1,221 @@ +/* [General Parameters] */ +// The label to add to the tower +Tower_Label = "LINEAR ADV"; + +// The label to add the to right column +Column_Label = ""; + +// Text to prefix to the section labels +Section_Label_Prefix = ""; + +// Text to suffix to the section labels +Section_Label_Suffix = ""; + +// The starting value (temperature or fan speed) +Starting_Value = 0; + +// The ending value (temperature or fan speed) of the tower +Ending_Value = 0.2; + +// The amount to change the value (temperature or fan speed) between sections +Value_Change = 0.02; + +// The height of each section of the tower +Section_Height = 8.401; + +// Size of the tower +Tower_Size = 70; + + +/* [Advanced Parameters] */ +// The font to use for tower text +Font = "Arial:style=Bold"; + +// Should sections be labeled? +Label_Sections = true; + +// The height of the section labels in relation to the height of each section +Section_Label_Height_Multiplier = 0.401; + +// The height of the tower label in relation to the length of the column +Tower_Label_Height_Multiplier = 0.601; + +// The height of the column label in relation to the height of each section +Column_Label_Height_Multiplier = 0.301; + +// The value to use for creating the model preview (lower is faster) +Preview_Quality_Value = 24; + +// The value to use for creating the final model render (higher is more detailed) +Render_Quality_Value = 24; + +// A small value used to improve rendering in preview mode +Iota = 0.001; + + + +/* [Development Parameters] */ +// Orient the model for creating a screenshot +Orient_for_Screenshot = false; + +// The viewport distance for the screenshot +Screenshot_Vpd = 140.00; + +// The viewport field of view for the screenshot +Screenshot_Vpf = 22.50; + +// The viewport rotation for the screenshot +Screenshot_Vpr = [ 75.00, 0.00, 300.00 ]; + +// The viewport translation for the screenshot +Screenshot_Vpt = [ 0.00, 0.00, 15.00 ]; + + +/* [Calculated parameters] */ +// Calculate the rendering quality +$fn = $preview ? Preview_Quality_Value : Render_Quality_Value; + +// Ensure the value change has the correct sign +Value_Change_Corrected = Ending_Value > Starting_Value + ? abs(Value_Change) + : -abs(Value_Change); + +// Determine how many sections to generate +Section_Count = ceil(abs(Ending_Value - Starting_Value) / abs(Value_Change)); + +// Calculate the font size +Section_Label_Font_Size = Section_Height * Section_Label_Height_Multiplier; +Tower_Label_Font_Size = Section_Height * Tower_Label_Height_Multiplier; +Column_Label_Font_Size = Section_Height * Column_Label_Height_Multiplier; + +// Calculate the depth of the labels +Label_Depth = 0.401/2; + + + +// Generate the model +module Generate_Model() +{ + // Generate the tower proper by iteritively generating a section for each retraction value + module Generate_Tower() + { + // Create each section + for (section = [0: Section_Count - 1]) + { + // Determine the value for this section + value = Starting_Value + (Value_Change_Corrected * section); + + // Determine the offset of the section + z_offset = section*Section_Height; + + // Generate the section itself and move it into place + translate([0, 0, z_offset]) + Generate_Section(str(value)); + } + + } + + + + // Generate a single section of the tower with a given label + module Generate_Section(label) + { + difference() + { + union() { + // Create lines between sections + Generate_Extrusion(Section_Height-0.5); + scale([0.995, 0.995, 1]) + Generate_Extrusion(Section_Height); + } + + // Carve out the label for this section + if (Label_Sections) + Generate_SectionLabel(label); + } + } + + // Generate extruded polygon for tower segment + module Generate_Extrusion(extrusion_height) + { + linear_extrude( + height = extrusion_height , + center = true, + convexity = 10, + twist = 0) + polygon(points=[ + [-Tower_Size/2,Tower_Size/2], + [Tower_Size/2,Tower_Size/2], + [Tower_Size/2,0], + [0,-Tower_Size/2], + [-Tower_Size/2,0]], + paths=[[0,1,2,3,4]]); + }; + + // Generate the text that will be carved into the square section column + module Generate_SectionLabel(label) + { + full_label = str(Section_Label_Prefix, label, Section_Label_Suffix); + translate([Tower_Size/2 + Iota, Section_Height*2, 0]) + rotate([90, 0, 90]) + translate([0, 0, -Label_Depth]) + linear_extrude(Label_Depth + Iota) + text(text=full_label, font=Font, size=Section_Label_Font_Size, halign="center", valign="top"); + } + + + + // Generate the text that will be carved along the left side of the tower + module Generate_TowerLabel(label) + { + translate([-Tower_Size/2 - Iota, Tower_Size/4 + Section_Height/2, Section_Height/2]) + rotate([-90, -90, 90]) + translate([0, 0, -Label_Depth]) + linear_extrude(Label_Depth + Iota) + text(text=label, font=Font, size=Tower_Label_Font_Size, halign="left", valign="center"); + } + + + + // Generate the curved text that will be carved into the first rounded section column + module Generate_ColumnLabel(label) + { + translate([-Tower_Size/2 - Iota, Tower_Size/4 - Section_Height/2, Section_Height/2]) + rotate([-90, -90, 90]) + translate([0, 0, -Label_Depth]) + linear_extrude(Label_Depth + Iota) + text(text=label, font=Font, size=Column_Label_Font_Size, halign="left", valign="center"); + } + + + + module Generate() + { + + difference() + { + Generate_Tower(); + + // Create the tower label + Generate_TowerLabel(Tower_Label); + + // Create the column label + Generate_ColumnLabel(Column_Label); + } + } + + + + Generate(); +} + + + +// Generate the model +Generate_Model(); + +// Orient the viewport +$vpd = Orient_for_Screenshot ? Screenshot_Vpd : $vpd; +$vpf = Orient_for_Screenshot ? Screenshot_Vpf : $vpf; +$vpr = Orient_for_Screenshot ? Screenshot_Vpr : $vpr; +$vpt = Orient_for_Screenshot ? Screenshot_Vpt : $vpt; diff --git a/Resources/QML/QT5/LATowerDialog.qml b/Resources/QML/QT5/LATowerDialog.qml new file mode 100644 index 0000000..be84561 --- /dev/null +++ b/Resources/QML/QT5/LATowerDialog.qml @@ -0,0 +1,188 @@ +import QtQuick 2.11 +import QtQuick.Controls 2.11 +import QtQuick.Layouts 1.11 + +import UM 1.2 as UM + +UM.Dialog +{ + id: dialog + + property variant catalog: UM.I18nCatalog { name: "autotowers" } + + title: catalog.i18nc("@title", "Linear Advance Tower") + + minimumWidth: screenScaleFactor * 500 + minimumHeight: (screenScaleFactor * contents.childrenRect.height) + (2 * UM.Theme.getSize('default_margin').height) + UM.Theme.getSize('button').height + maximumHeight: minimumHeight + width: minimumWidth + height: minimumHeight + + // Define the width of the number input text boxes + property int numberInputWidth: UM.Theme.getSize('button').width + + + + RowLayout + { + id: contents + width: dialog.width - 2 * UM.Theme.getSize('default_margin').width + spacing: UM.Theme.getSize('default_margin').width + + Rectangle + { + Layout.preferredWidth: icon.width + Layout.preferredHeight: icon.height + Layout.fillHeight: true + color: UM.Theme.getColor('primary_button') + + Image + { + id: icon + source: Qt.resolvedUrl('../../Images/latower_icon') + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + GridLayout + { + columns: 2 + rowSpacing: UM.Theme.getSize('default_lining').height + columnSpacing: UM.Theme.getSize('default_margin').width + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignTop + + // Preset option + Label + { + text: catalog.i18nc("@label", "Preset") + } + ComboBox + { + Layout.fillWidth: true + model: enableCustom ? dataModel.presetsModel.concat({'name': 'Custom'}) : dataModel.presetsModel + textRole: 'name' + currentIndex: dataModel.presetIndex + + onCurrentIndexChanged: + { + dataModel.presetIndex = currentIndex + } + } + + // Starting value + Label + { + text: catalog.i18nc("@label", "Starting K-factor") + visible: !dataModel.presetSelected + } + TextField + { + Layout.preferredWidth: numberInputWidth + validator: RegExpValidator { regExp: /[0-9]*(\.[0-9]+)?/ } + text: dataModel.startKfactorStr + visible: !dataModel.presetSelected + + onTextChanged: + { + if (dataModel.startKfactorStr != text) dataModel.startKfactorStr = text + } + } + + // Ending + Label + { + text: catalog.i18nc("@label", "Ending K-factor") + visible: !dataModel.presetSelected + } + TextField + { + Layout.preferredWidth: numberInputWidth + validator: RegExpValidator { regExp: /[0-9]*(\.[0-9]+)?/ } + text: dataModel.endKfactorStr + visible: !dataModel.presetSelected + + onTextChanged: + { + if (dataModel.endKfactorStr != text) dataModel.endKfactorStr = text + } + } + + // Value change + Label + { + text: catalog.i18nc("@label", "K-factor Change") + visible: !dataModel.presetSelected + } + TextField + { + Layout.preferredWidth: numberInputWidth + validator: RegExpValidator { regExp: /[+-]?[0-9]*(\.[0-9]+)?/ } + text: dataModel.kfactorChangeStr + visible: !dataModel.presetSelected + + onTextChanged: + { + if (dataModel.kfactorChangeStr != text) dataModel.kfactorChangeStr = text + } + } + + // Tower label + Label + { + text: catalog.i18nc("@label", "Tower Label") + visible: !dataModel.presetSelected + } + TextField + { + Layout.preferredWidth: numberInputWidth + validator: RegExpValidator { regExp: /.{0,3}/ } + text: dataModel.towerLabel + visible: !dataModel.presetSelected + + onTextChanged: + { + if (dataModel.towerLabel != text) dataModel.towerLabel = text + } + } + + // Tower description + Label + { + text: catalog.i18nc("@label", "Tower Description") + visible: !dataModel.presetSelected + } + TextField + { + Layout.fillWidth: true + text: dataModel.towerDescription + visible: !dataModel.presetSelected + + onTextChanged: + { + if (dataModel.towerDescription != text) dataModel.towerDescription = text + } + } + } + } + + rightButtons: Button + { + text: catalog.i18nc("@button", "OK") + onClicked: dialog.accept() + } + + leftButtons: Button + { + text: catalog.i18nc("@button", "Cancel") + onClicked: dialog.reject() + } + + onAccepted: + { + controller.dialogAccepted() + } + +} diff --git a/Resources/QML/QT6/LATowerDialog.qml b/Resources/QML/QT6/LATowerDialog.qml new file mode 100644 index 0000000..62216ff --- /dev/null +++ b/Resources/QML/QT6/LATowerDialog.qml @@ -0,0 +1,253 @@ +import QtQuick 6.0 +import QtQuick.Controls 6.0 +import QtQuick.Layouts 6.0 + +import UM 1.6 as UM +import Cura 1.7 as Cura + +UM.Dialog +{ + id: dialog + + property variant catalog: UM.I18nCatalog { name: "autotowers" } + + title: catalog.i18nc("@title", "Linear Advance Tower") + + buttonSpacing: UM.Theme.getSize('default_margin').width + minimumWidth: screenScaleFactor * 445 + minimumHeight: (screenScaleFactor * contents.childrenRect.height) + (2 * UM.Theme.getSize('default_margin').height) + UM.Theme.getSize('button').height + maximumHeight: minimumHeight + width: minimumWidth + height: minimumHeight + + // Define the width of the number input text boxes + property int numberInputWidth: UM.Theme.getSize('button').width + + + RowLayout + { + id: contents + width: dialog.width - 2 * UM.Theme.getSize('default_margin').width + spacing: UM.Theme.getSize('default_margin').width + + // Display the icon for this tower + Rectangle + { + Layout.preferredWidth: icon.width + Layout.preferredHeight: icon.height + Layout.fillHeight: true + color: UM.Theme.getColor('primary_button') + + Image + { + id: icon + source: Qt.resolvedUrl('../../Images/' + dataModel.dialogIcon) + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + GridLayout + { + columns: 2 + rowSpacing: UM.Theme.getSize('default_lining').height + columnSpacing: UM.Theme.getSize('default_margin').width + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignTop + + // Preset option + UM.Label + { + text: catalog.i18nc("@label", "Linear Advance Tower Preset") + MouseArea + { + id: preset_mouse_area + anchors.fill: parent + hoverEnabled: true + } + } + Cura.ComboBox + { + Layout.fillWidth: true + model: enableCustom ? dataModel.presetsModel.concat({'name': catalog.i18nc("@model", "Custom")}) : dataModel.presetsModel + textRole: 'name' + currentIndex: dataModel.presetIndex + + onCurrentIndexChanged: + { + dataModel.presetIndex = currentIndex + } + } + + // Start temp + UM.Label + { + text: catalog.i18nc("@label", "Starting K-factor") + visible: !dataModel.presetSelected + MouseArea + { + id: starting_temperature_mouse_area + anchors.fill: parent + hoverEnabled: true + } + } + Cura.TextField + { + Layout.preferredWidth: numberInputWidth + validator: RegularExpressionValidator { regularExpression: /[0-9]*(\.[0-9]+)?/ } + text: dataModel.startKfactorStr + visible: !dataModel.presetSelected + + onTextChanged: + { + if (dataModel.startKfactorStr != text) dataModel.startKfactorStr = text + } + } + UM.ToolTip + { + text: catalog.i18nc("@tooltip", "The K-factor for the bottom of the tower.

It is good practice to start with 0.

Narrow range for better results.") + visible: starting_temperature_mouse_area.containsMouse + } + + // End temp + UM.Label + { + text: catalog.i18nc("@label", "Ending K-factor") + visible: !dataModel.presetSelected + MouseArea + { + id: ending_temperature_mouse_area + anchors.fill: parent + hoverEnabled: true + } + } + Cura.TextField + { + Layout.preferredWidth: numberInputWidth + validator: RegularExpressionValidator { regularExpression: /[0-9]*(\.[0-9]+)?/ } + text: dataModel.endKfactorStr + visible: !dataModel.presetSelected + + onTextChanged: + { + if (dataModel.endKfactorStr != text) dataModel.endKfactorStr = text + } + } + UM.ToolTip + { + text: catalog.i18nc("@tooltip", "The K-factor for the top of the tower.

For Bowden extruder, start with 1.0 and for direct extruder 0.2.

Narrow range for better results.") + visible: ending_temperature_mouse_area.containsMouse + } + + // Temp change + UM.Label + { + text: catalog.i18nc("@label", "K-factor Change") + visible: !dataModel.presetSelected + MouseArea + { + id: temperature_change_mouse_area + anchors.fill: parent + hoverEnabled: true + } + } + Cura.TextField + { + Layout.preferredWidth: numberInputWidth + validator: RegularExpressionValidator { regularExpression: /[+-]?[0-9]*(\.[0-9]+)?/ } + text: dataModel.kfactorChangeStr + visible: !dataModel.presetSelected + + onTextChanged: + { + if (dataModel.kfactorChangeStr != text) dataModel.kfactorChangeStr = text + } + } + UM.ToolTip + { + text: catalog.i18nc("@tooltip", "The amount to change the K-factor between sections.

In combination with the starting and ending K-factors, this determines the number of sections in the tower.

Try to keep number of sections below 20.") + visible: temperature_change_mouse_area.containsMouse + } + + // Tower label + UM.Label + { + text: catalog.i18nc("@label", "Tower Label") + visible: !dataModel.presetSelected + MouseArea + { + id: tower_label_mouse_area + anchors.fill: parent + hoverEnabled: true + } + } + Cura.TextField + { + Layout.preferredWidth: numberInputWidth + validator: RegularExpressionValidator { regularExpression: /.{0,4}/ } + text: dataModel.towerLabel + visible: !dataModel.presetSelected + + onTextChanged: + { + if (dataModel.towerLabel != text) dataModel.towerLabel = text + } + } + UM.ToolTip + { + text: catalog.i18nc("@tooltip", "An optional short label to carve into the base of the left of the tower.

This must be four characters or less.") + visible: tower_label_mouse_area.containsMouse + } + + // Tower description + UM.Label + { + text: catalog.i18nc("@label", "Tower Description") + visible: !dataModel.presetSelected + MouseArea + { + id: tower_description_mouse_area + anchors.fill: parent + hoverEnabled: true + } + } + Cura.TextField + { + Layout.fillWidth: true + text: dataModel.towerDescription + visible: !dataModel.presetSelected + + onTextChanged: + { + if (dataModel.towerDescription != text) dataModel.towerDescription = text + } + } + UM.ToolTip + { + text: catalog.i18nc("@tooltip", "An optional label to carve up the left side of the tower.

This can be used, for example, to identify the purpose of the tower or the material being printed.") + visible: tower_description_mouse_area.containsMouse + } + } + } + + rightButtons: + [ + Cura.SecondaryButton + { + text: catalog.i18nc("@button", "Cancel") + onClicked: dialog.reject() + }, + Cura.PrimaryButton + { + text: catalog.i18nc("@button", "OK") + onClicked: dialog.accept() + } + ] + + onAccepted: + { + controller.dialogAccepted() + } + +} diff --git a/Resources/STL/Linear Advance Tower - K 0.0-0.2.stl b/Resources/STL/Linear Advance Tower - K 0.0-0.2.stl new file mode 100644 index 0000000..5358a71 Binary files /dev/null and b/Resources/STL/Linear Advance Tower - K 0.0-0.2.stl differ diff --git a/Resources/STL/Linear Advance Tower - K 0.0-2.0.stl b/Resources/STL/Linear Advance Tower - K 0.0-2.0.stl new file mode 100644 index 0000000..cc3fef5 Binary files /dev/null and b/Resources/STL/Linear Advance Tower - K 0.0-2.0.stl differ