├── .gitignore ├── AlterZhops.py ├── BridgeTemperatureAdjustment.py ├── ChangeFanValue.py ├── CheckFirstSpeed.py ├── CommentGCode.py ├── CreateJPEGThumbnail.py ├── CreateThumbnail.py ├── Cura_GCode.CSV ├── CustomTimeLapse.py ├── DiagonalZHop.py ├── DisplayPrintInfosOnLCD.py ├── FanIroning.py ├── FastFirstInfill.py ├── FlowTower.py ├── GCodeDocumentation.py ├── GradientInfill.py ├── GregVInsertAtLayerChange.py ├── InfillLast.py ├── InhibFan.py ├── InsertAtLayerChange.py ├── InsertAtLayerNumber.py ├── KlipperPrintArea.py ├── LICENSE ├── LevelingMeshOptimizer.py ├── MultiBrim.py ├── README.md ├── RepRapPrintInfos.py ├── RetractContinue.py ├── RetractTower.py ├── SlowZ.py ├── SpeedTower.py ├── SpoonOrder.py ├── TempFanTower.py ├── TemperatureTower.py ├── UPPostProcess.py ├── ZMoveG0.py ├── ZMoveIroning.py ├── ZOffsetBrim.py ├── images ├── AlterZhops.png ├── ChangeFanValue.jpg ├── CheckFirstSpeed.JPG ├── CheckFirstSpeedorigine.png ├── CheckFirstSpeedresult.png ├── ErrorZmoveG0.jpg ├── FanIroning.jpg ├── FanWithBridgeValue.jpg ├── FanWithoutBridgeValue.jpg ├── GcodeDocumentation.jpg ├── InfillLast.png ├── InhibFan.jpg ├── InsertAtLayerNumber.jpg ├── NotDetectedFanIroning.jpg ├── PrintInfos.jpg ├── SpoonOrder.png ├── ZmoveG0.jpg ├── ZmoveIroning.jpg ├── benchy.jpg ├── commentGcode.jpg ├── fastfirstinfill.jpg ├── gcode_temptower.jpg ├── gradient.jpg ├── gradient2.jpg ├── gradient3.jpg ├── multibrim.jpg ├── multilayerbrim.svg ├── origine.png ├── plugins.jpg ├── result.png ├── retract-tower.jpg ├── slowz.jpg ├── slowzsettings.jpg ├── speedtower.jpg └── tempfan.jpg └── startHeatingAtPercentage.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | live_script.py 3 | -------------------------------------------------------------------------------- /ChangeFanValue.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: 5axes 5 | # Date: January 13, 2020 6 | # 7 | # Description: postprocessing-script to manage Fan Value 8 | # 9 | # 10 | #------------------------------------------------------------------------------------------------------------------------------------ 11 | # 12 | # Version 1.1 9/01/2020 13 | # 14 | #------------------------------------------------------------------------------------------------------------------------------------ 15 | 16 | import string 17 | from ..Script import Script 18 | from UM.Application import Application # To get the current printer's settings. 19 | from cura.CuraVersion import CuraVersion # type: ignore 20 | from UM.Message import Message 21 | from UM.Logger import Logger 22 | from UM.i18n import i18nCatalog # Translation 23 | catalog = i18nCatalog("cura") 24 | 25 | __version__ = '1.1' 26 | 27 | class ChangeFanValue(Script): 28 | def __init__(self): 29 | super().__init__() 30 | 31 | def getSettingDataString(self): 32 | return """{ 33 | "name": "ChangeFanValue", 34 | "key": "ChangeFanValue", 35 | "metadata": {}, 36 | "version": 2, 37 | "settings": 38 | { 39 | "usefanvalue": 40 | { 41 | "label": "Set Fan Value on Minimum Time", 42 | "description": "Change the Fan Value on minimum Time situation", 43 | "type": "bool", 44 | "default_value": false 45 | }, 46 | "fanchange": 47 | { 48 | "label": "Fan values in %", 49 | "description": "The fan speed change of each block for minimum time Layer situation", 50 | "type": "int", 51 | "unit": "%", 52 | "default_value": 100, 53 | "minimum_value": 1, 54 | "maximum_value": 100, 55 | "minimum_value_warning": 50, 56 | "maximum_value_warning": 100 57 | } 58 | } 59 | }""" 60 | 61 | # Get the value 62 | def GetDataExtruder(self,id_ex,key,dec=0): 63 | 64 | # Deprecation Warning 65 | # extrud = list(Application.getInstance().getGlobalContainerStack().extruders.values()) 66 | extruder_stack = Application.getInstance().getExtruderManager().getActiveExtruderStacks() 67 | GetVal = extruder_stack[id_ex].getProperty(key, "value") 68 | #GetLabel = Application.getInstance().getGlobalContainerStack().getProperty(key, "label") 69 | #GetType = Application.getInstance().getGlobalContainerStack().getProperty(key, "type") 70 | #GetUnit = Application.getInstance().getGlobalContainerStack().getProperty(key, "unit") 71 | 72 | return GetVal 73 | 74 | def execute(self, data): 75 | 76 | usefan = False 77 | fanvalues = 0 78 | usefan = bool(self.getSettingValueByKey("usefanvalue")) 79 | fanvalues = int(self.getSettingValueByKey("fanchange")) 80 | 81 | # machine_extruder_count 82 | extruder_count=Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") 83 | extruder_count = extruder_count-1 84 | extruder_id=extruder_count 85 | 86 | # cool_min_layer_time 87 | self._cool_min_layer_time = float(self.GetDataExtruder(extruder_id,"cool_min_layer_time")) 88 | Logger.log('d', "cool_min_layer_time --> " + str(self._cool_min_layer_time) ) 89 | 90 | currentfan = 0 91 | Current_Fan_Value = 0 92 | Current_Layer = 0 93 | setfan = int((int(fanvalues)/100)*255) # 100% = 255 pour ventilateur 94 | #Logger.log('d', "setfan --> " + str(setfan) ) 95 | 96 | save_time=0 97 | Just_Modi=0 98 | idl=0 99 | 100 | for layer in data: 101 | layer_index = data.index(layer) 102 | 103 | lines = layer.split("\n") 104 | for line in lines: 105 | if line.startswith(";LAYER:0"): 106 | idl=0 107 | 108 | if line.startswith(";LAYER:"): 109 | Current_Layer = int(line.split(":")[1]) 110 | 111 | if line.startswith("; MODI_FAN"): 112 | Just_Modi=1 113 | 114 | if line.startswith("M106 S") and usefan : 115 | if Just_Modi==1 : 116 | Just_Modi=0 117 | else : 118 | line_index = lines.index(line) 119 | Current_Fan_Value = int(line.split("S")[1]) 120 | #Logger.log('d', "Current_Fan_Value --> " + str(Current_Fan_Value) ) 121 | if idl==1: 122 | lines[line_index] = "; " + line 123 | 124 | # M107: Eteindre les ventilateurs 125 | if line.startswith("M107") and (usefan): 126 | line_index = lines.index(line) 127 | Current_Fan_Value=0 128 | if idl==1: 129 | lines[line_index] = "; " + line 130 | 131 | if line.startswith(";TIME_ELAPSED:"): 132 | line_index = lines.index(line) 133 | total_time = float(line.split(":")[1]) 134 | Layer_time=total_time-save_time 135 | 136 | if Layer_time<=self._cool_min_layer_time : 137 | if idl==0: 138 | #Logger.log('d', "Time MODI --> " + str(Layer_time)) 139 | #Logger.log('d', "MODI LAYER--> " + str(Current_Layer)) 140 | lines.insert(line_index + 1, "; MODI_FAN") 141 | lines.insert(line_index + 2, "M106 S"+str(setfan)) 142 | idl=1 143 | Just_Modi=1 144 | else: 145 | 146 | if idl==1: 147 | #Logger.log('d', "Reset Time --> " + str(Layer_time) ) 148 | #Logger.log('d', "Reset FAN VALUE --> " + str(Current_Fan_Value)) 149 | Cline=lines[line_index] 150 | if Current_Fan_Value == 0: 151 | lines.insert(line_index + 1, "M107") 152 | else: 153 | lines.insert(line_index + 1, "M106 S"+str(Current_Fan_Value)) 154 | idl=0 155 | 156 | save_time=total_time 157 | 158 | 159 | result = "\n".join(lines) 160 | data[layer_index] = result 161 | 162 | return data 163 | -------------------------------------------------------------------------------- /CheckFirstSpeed.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: 5axes 5 | # Date: January 04, 2024 6 | # 7 | # Description: postprocessing script to modifiy the first layer infill and Check the first Wall Speed Bug Cura 5.6 8 | # 9 | #------------------------------------------------------------------------------------------------------------------------------------ 10 | # 11 | # Version 1.0 04/01/2024 12 | # 13 | #------------------------------------------------------------------------------------------------------------------------------------ 14 | 15 | import string 16 | import re # To perform the search 17 | from ..Script import Script 18 | from UM.Application import Application # To get the current printer's settings. 19 | from cura.CuraVersion import CuraVersion # type: ignore 20 | from UM.Logger import Logger 21 | 22 | from enum import Enum 23 | 24 | 25 | 26 | __version__ = '1.0' 27 | 28 | class Section(Enum): 29 | """Enum for section type.""" 30 | 31 | NOTHING = 0 32 | SKIRT = 1 33 | INNER_WALL = 2 34 | OUTER_WALL = 3 35 | INFILL = 4 36 | SKIN = 5 37 | SKIN2 = 6 38 | 39 | def is_begin_layer_line(line: str) -> bool: 40 | """Check if current line is the start of a layer section. 41 | 42 | Args: 43 | line (str): Gcode line 44 | 45 | Returns: 46 | bool: True if the line is the start of a layer section 47 | """ 48 | return line.startswith(";LAYER:") 49 | 50 | def is_begin_type_line(line: str) -> bool: 51 | """Check if current line is the start of a new type section. 52 | 53 | Args: 54 | line (str): Gcode line 55 | 56 | Returns: 57 | bool: True if the line is the start of a new type section 58 | """ 59 | return line.startswith(";TYPE:") 60 | 61 | def is_retract_line(line: str) -> bool: 62 | """Check if current line is a retract segment. 63 | 64 | Args: 65 | line (str): Gcode line 66 | 67 | Returns: 68 | bool: True if the line is a retract segment 69 | """ 70 | return "G1" in line and "F" in line and "E" in line and not "X" in line and not "Y" in line and not "Z" in line 71 | 72 | def is_extrusion_line(line: str) -> bool: 73 | """Check if current line is a standard printing segment. 74 | 75 | Args: 76 | line (str): Gcode line 77 | 78 | Returns: 79 | bool: True if the line is a standard printing segment 80 | """ 81 | return "G1" in line and "X" in line and "Y" in line and "E" in line 82 | 83 | def is_not_extrusion_line(line: str) -> bool: 84 | """Check if current line is a rapid movement segment. 85 | 86 | Args: 87 | line (str): Gcode line 88 | 89 | Returns: 90 | bool: True if the line is a standard printing segment 91 | """ 92 | return "G0" in line and "X" in line and "Y" in line and not "E" in line 93 | 94 | def is_begin_skin_segment_line(line: str) -> bool: 95 | """Check if current line is the start of an skin. 96 | 97 | Args: 98 | line (str): Gcode line 99 | 100 | Returns: 101 | bool: True if the line is the start of an skin section 102 | """ 103 | return line.startswith(";TYPE:SKIN") 104 | 105 | def is_begin_inner_wall_segment_line(line: str) -> bool: 106 | """Check if current line is the start of an inner wall. 107 | 108 | Args: 109 | line (str): Gcode line 110 | 111 | Returns: 112 | bool: True if the line is the start of an inner wall section 113 | """ 114 | return line.startswith(";TYPE:WALL-INNER") 115 | 116 | def is_begin_outer_wall_segment_line(line: str) -> bool: 117 | """Check if current line is the start of an outer wall. 118 | 119 | Args: 120 | line (str): Gcode line 121 | 122 | Returns: 123 | bool: True if the line is the start of an outer wall section 124 | """ 125 | return line.startswith(";TYPE:WALL-OUTER") 126 | 127 | class CheckFirstSpeed(Script): 128 | def __init__(self): 129 | super().__init__() 130 | 131 | def getSettingDataString(self): 132 | return """{ 133 | "name": "Check First Speed", 134 | "key": "CheckFirstSpeed", 135 | "metadata": {}, 136 | "version": 2, 137 | "settings": 138 | { 139 | "modifyinfillspeed": 140 | { 141 | "label": "Modify Infill Speed", 142 | "description": "Option to modify First layer infill speed value.", 143 | "type": "bool", 144 | "default_value": true 145 | }, 146 | "infillspeed": 147 | { 148 | "label": "First layer infill speed", 149 | "description": "First layer infill speed value.", 150 | "type": "float", 151 | "unit": "mm/s", 152 | "default_value": 30, 153 | "minimum_value": 1, 154 | "maximum_value": 100, 155 | "maximum_value_warning": 50, 156 | "enabled": "modifyinfillspeed" 157 | }, 158 | "replacewallspeed": 159 | { 160 | "label": "Replace Wall Speed", 161 | "description": "Option to replace wall speed on first layer (Cura 5.6 bug fix).", 162 | "type": "bool", 163 | "default_value": true 164 | } 165 | } 166 | }""" 167 | 168 | # Get the value 169 | def GetDataExtruder(self,id_ex,key,dec=0): 170 | 171 | extruder_stack = Application.getInstance().getExtruderManager().getActiveExtruderStacks() 172 | GetVal = extruder_stack[id_ex].getProperty(key, "value") 173 | 174 | return GetVal 175 | 176 | def execute(self, data): 177 | 178 | InfillSpeed = float(self.getSettingValueByKey("infillspeed")) * 60 179 | checkFirstWallSpeed = bool(self.getSettingValueByKey("replacewallspeed")) 180 | modifyFirstInfillSpeed = bool(self.getSettingValueByKey("modifyinfillspeed")) 181 | 182 | # machine_extruder_count 183 | extruder_count=int(Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value")) 184 | extruder_count = extruder_count-1 185 | extruder_stack = Application.getInstance().getExtruderManager().getActiveExtruderStacks() 186 | extruder_nr=len(extruder_stack) 187 | extruder_nr = int(Application.getInstance().getExtruderManager().getActiveExtruderStacks()[0].getProperty("extruder_nr", "value")) 188 | extruder_id=int(Application.getInstance().getGlobalContainerStack().getProperty("wall_extruder_nr", "value")) 189 | 190 | 191 | if extruder_id == -1 : 192 | extruder_id=extruder_nr 193 | 194 | if extruder_id>extruder_count : 195 | extruder_id=extruder_count 196 | 197 | # speed_print_layer_0 198 | self._speed_print_layer_0 = float(self.GetDataExtruder(extruder_id,"speed_print_layer_0")) 199 | Logger.log('d', "extruder_count --> " + str(extruder_count)) 200 | Logger.log('d', "extruder_nr --> " + str(extruder_nr)) 201 | Logger.log('d', "extruder_id --> " + str(extruder_id)) 202 | Logger.log('d', "speed_print_layer_0 --> " + str(self._speed_print_layer_0) ) 203 | 204 | idl=0 205 | 206 | for layer in data: 207 | layer_index = data.index(layer) 208 | 209 | lines = layer.split("\n") 210 | for line in lines: 211 | 212 | if is_begin_layer_line(line): 213 | # Logger.log('d', 'layer_index : {:d}'.format(layer_index)) 214 | # Logger.log('d', 'layer_lines : {}'.format(line)) 215 | if line.startswith(";LAYER:0"): 216 | idl=1 217 | else : 218 | idl=0 219 | 220 | if is_begin_type_line(line) and idl > 0: 221 | if is_begin_skin_segment_line(line) and modifyFirstInfillSpeed : 222 | idl=4 223 | ReplaceSpeedInstruction="F" + str(InfillSpeed) 224 | # Logger.log('d', 'Skin line : {}'.format(ReplaceSpeedInstruction)) 225 | elif is_begin_inner_wall_segment_line(line) and checkFirstWallSpeed : 226 | idl=3 227 | ReplaceSpeedInstruction="F" + str(self._speed_print_layer_0*60) 228 | # Logger.log('d', 'Inner Wall line : {}'.format(ReplaceSpeedInstruction)) 229 | elif is_begin_outer_wall_segment_line(line) and checkFirstWallSpeed : 230 | idl=2 231 | ReplaceSpeedInstruction="F" + str(self._speed_print_layer_0*60) 232 | # Logger.log('d', 'Outer Wall line : {}'.format(ReplaceSpeedInstruction)) 233 | else : 234 | idl=1 235 | 236 | if idl >= 2 and is_extrusion_line(line): 237 | searchF = re.search(r"F(\d*\.?\d*)", line) 238 | if searchF: 239 | line_index = lines.index(line) 240 | save_F=float(searchF.group(1)) 241 | instructionF="F"+str(searchF.group(1)) 242 | # Logger.log('d', 'save_F : {:f}'.format(save_F)) 243 | # Logger.log('d', 'line : {}'.format(line)) 244 | # Logger.log('d', 'line replace : {}'.format(line.replace(instructionF,ReplaceSpeedInstruction))) 245 | lines[line_index]=line.replace(instructionF,ReplaceSpeedInstruction) 246 | 247 | result = "\n".join(lines) 248 | data[layer_index] = result 249 | 250 | return data 251 | -------------------------------------------------------------------------------- /CommentGCode.py: -------------------------------------------------------------------------------- 1 | 2 | # Copyright (c) 2019 Lisa Erlingheuser 3 | # This Cura PostProcessing-Script is released under the terms of the AGPLv3 or higher. 4 | 5 | # This Cura Postprocessing Script adds comments to the G-Code. 6 | # The user can select or deselect comments for M-Commands and G-Commands separately. 7 | 8 | # G0 and G1 commands are only commented if a retract is included. 9 | 10 | # Command, description and parameters are read from a CSV file. If a command is not contained, the required data is determined once via the website http://marlinfw.org/docs/gcode/ 11 | # and added to the CSV file. 12 | 13 | import re #To perform the search and replace. 14 | import string 15 | import os 16 | import urllib.request 17 | 18 | from UM.Logger import Logger 19 | from UM.Message import Message 20 | from UM.i18n import i18nCatalog 21 | catalog = i18nCatalog("cura") 22 | 23 | from ..Script import Script 24 | 25 | 26 | class CommentGCode(Script): 27 | 28 | def getSettingDataString(self): 29 | return """{ 30 | "name": "Comment G Code", 31 | "key": "CommentGCode", 32 | "metadata": {}, 33 | "version": 2, 34 | "settings": 35 | { 36 | "is_MCmd": 37 | { 38 | "label": "M-Commands", 39 | "description": "When enabled, M-Commands will be commented.", 40 | "type": "bool", 41 | "default_value": true 42 | }, 43 | "is_GCmd": 44 | { 45 | "label": "G-Commands", 46 | "description": "When enabled, G-Commands will be commented, except G0 and G1.", 47 | "type": "bool", 48 | "default_value": true 49 | } 50 | } 51 | }""" 52 | 53 | def getVar(self): 54 | global bM, bG 55 | if hasattr(self, 'getSettingValueByKey'): 56 | bM = self.getSettingValueByKey("is_MCmd") 57 | bG = self.getSettingValueByKey("is_GCmd") 58 | else: 59 | bM = True 60 | bG = True 61 | return 62 | 63 | def getCmdParamTab(self, cmdparam): 64 | arret = [] 65 | #Logger.log('d', "CommentGCode --> " + 'getCmdParamTab cmdparam: ' + str(cmdparam)) 66 | for cp in cmdparam: 67 | if cp == '': 68 | break 69 | arp = cp.split("-") 70 | var1 = arp[0].strip() 71 | var1 = var1.strip('[') 72 | arp[0] = var1.split('<')[0] 73 | arp[1] = arp[1].strip() 74 | arret.append(arp) 75 | return arret 76 | 77 | def getCmdDescP(self, param, cmdparam): 78 | arcmd = self.getCmdParamTab(cmdparam) 79 | ret = '' 80 | for lp in param: 81 | for cp in arcmd: 82 | if lp[0] == cp[0]: 83 | if ret != '': 84 | ret = ret + ", " 85 | ret = ret + str(cp[1]) + '=' + str(lp[1:]) 86 | return ret 87 | 88 | def _restmit(self, data, text): 89 | ret = data[data.index(text):] 90 | return ret 91 | 92 | def _restbisohne(self, data, text): 93 | try: 94 | ret = data[0:data.index(text)] 95 | except: 96 | ret = "" 97 | return ret 98 | 99 | def _restbismit(self, data, text): 100 | ret = data[0:data.index(text)+len(text)+1] 101 | return ret 102 | 103 | def _restohne(self, data, text): 104 | try: 105 | ret = data[data.index(text)+ len(text):] 106 | except: 107 | ret = "" 108 | return ret 109 | 110 | 111 | def getCmdDescFromHTML(self, htmlData, cmd): 112 | desc = None 113 | param = None 114 | erg = htmlData 115 | erg = self._restmit(erg, '

') 116 | desc = self._restbisohne(erg, "

") 117 | desc = self._restohne(desc, "-") 118 | desc = desc.strip() 119 | retlist = [cmd, desc] 120 | erg = self._restmit(erg, "

Parameters

") 121 | erg = self._restmit(erg, "") 124 | for line in table: 125 | line = line.replace("<", "<") 126 | line = line.replace(">", ">") 127 | p = self._restohne(line, "") 128 | if p != "": 129 | p = self._restbisohne(p, "") 130 | p = p.strip() 131 | d = self._restohne(line, "

") 132 | d = self._restbisohne(d, "

") 133 | if '.' in d: 134 | d = self._restbisohne(d, ".") 135 | d = d.strip() 136 | retlist.append(p + " - " + d) 137 | ret = ";".join(retlist) 138 | #Logger.log('d', "CommentGCode --> " + 'getCmdDescFromHTML ret: ' + ret) 139 | return ret 140 | 141 | def getCmdDescUrl(self, cmd): 142 | result = None 143 | cmdB = cmd[0] 144 | cmdI = cmd[1:] 145 | scmdI = "%03i" % int(cmdI) 146 | url = "http://marlinfw.org/docs/gcode/" + cmdB + scmdI + ".html" 147 | #Logger.log('d', "CommentGCode --> " + 'getCmdDescUrl URL: ' + url) 148 | try: 149 | request = urllib.request.Request(url) 150 | response = urllib.request.urlopen(request) 151 | result = response.read().decode("utf-8") 152 | #Logger.log('d', "CommentGCode --> " + 'getCmdDescUrl result: ' + str(result)) 153 | except URLError: 154 | Logger.log('w', "CommentGCode --> " + 'getCmdDescUrl Error') 155 | return 156 | return result 157 | 158 | 159 | def getCmdDesc(self, cmd, cmdplist): 160 | global cmdtab, _msg 161 | cmddesc = '' 162 | cmdp = '' 163 | line = "" 164 | bgef = False 165 | if cmdtab == None: 166 | cmddir = os.path.join( os.path.dirname(__file__), 'Cura_GCode.CSV') 167 | cmdges = open(cmddir).read() 168 | tablines = cmdges.split("\n") 169 | if cmd + ";" not in cmdges: 170 | cmdhtml = self.getCmdDescUrl(cmd) 171 | if cmdhtml != None: 172 | newline = self.getCmdDescFromHTML(cmdhtml, cmd) 173 | #Logger.log('d', "CommentGCode --> " + 'getCmdDesc newline: ' + str(newline)) 174 | cmdges += newline 175 | tablines = cmdges.split("\n") 176 | try: 177 | fobj_out = open(cmddir,'w') 178 | fobj_out.write(cmdges) 179 | fobj_out.close() 180 | except: 181 | pass 182 | for line in tablines: 183 | cols = line.split(";") 184 | if cols[0] == cmd: 185 | bgef = True 186 | if len(cols) > 1 : 187 | cmddesc = cols[1] 188 | if len(cols) > 2: 189 | cmddp = cols[2:] 190 | if cmdplist != []: 191 | cmdp = self.getCmdDescP(cmdplist, cmddp) 192 | if cmdp != '': 193 | cmddesc = cmddesc + ', ' + cmdp 194 | if bgef == False: 195 | _msg += 'Command ' + str(cmd) + ' missing in G-Code file' + '\n' 196 | return cmddesc 197 | 198 | def addCmd(self, line, cmd, cmdplist): 199 | desc = self.getCmdDesc(cmd, cmdplist) 200 | if desc != '': 201 | line = line + '; --> ' + desc 202 | return line 203 | 204 | def execute(self, data): 205 | global bM, bG, bShort, _msg 206 | global cmdtab 207 | _msg = '' 208 | cmdtab = None 209 | self.getVar() 210 | lastE = 0 211 | lineNo = 0 212 | 213 | for layer_number, layer in enumerate(data): 214 | lines = layer.split("\n") 215 | i1 = 0 216 | for line in lines: 217 | lineNo = lineNo +1 218 | if line != '': 219 | if not ';' in line: 220 | arline = line.split(' ') 221 | cmd = arline[0] 222 | if cmd[0] == 'G': 223 | if cmd != 'G1' and cmd != 'G0': 224 | if bG == True: 225 | line = self.addCmd(line, cmd, arline[1:]) 226 | else: 227 | actE = Script.getValue(self, line = line, key = 'E') 228 | if actE != None: 229 | actE = float(actE) 230 | if actE < lastE: 231 | retract = actE - lastE 232 | line = line + '; --> Retract ' + str(round(retract, 2)) + ' mm' 233 | if i1 > 0: 234 | print(' line ' + str(lineNo-1) + ' : ' + lines[i1-1]) 235 | print(' line ' + str(lineNo) + ' : ' + line) 236 | else: 237 | print(' line ' + str(lineNo) + ' : ' + line) 238 | lastE = actE 239 | if cmd == 'G92': 240 | lastE = Script.getValue(self, line = line, key = 'E') 241 | if cmd == 'G91': 242 | lastE = 0 243 | elif cmd[0] == 'M': 244 | if bM == True: 245 | line = self.addCmd(line, cmd, arline[1:]) 246 | elif cmd[0] == 'T': 247 | line = line + '; --> Activation Extruder ' + cmd[1] 248 | lines[i1] = line 249 | i1 = i1 + 1 250 | sep = '\n' 251 | data[layer_number] = sep.join(lines) 252 | if _msg != None and _msg != '': 253 | Message("Info Comment G-Code:" + "\n" + _msg, title = catalog.i18nc("@info:title", "Post Processing")).show() 254 | return data 255 | -------------------------------------------------------------------------------- /CreateJPEGThumbnail.py: -------------------------------------------------------------------------------- 1 | #-------------------------------------- 2 | # Cura JPEG Thumbnail creator 3 | # Professional firmware for Ender3v2 4 | # Miguel A. Risco-Castillo 5 | # 2021-07-01 6 | # Contains code from: 7 | # https://github.com/Ultimaker/Cura/blob/master/plugins/PostProcessingPlugin/scripts/CreateThumbnail.py 8 | #-------------------------------------- 9 | 10 | import base64 11 | 12 | from UM.Logger import Logger 13 | from cura.Snapshot import Snapshot 14 | from PyQt5.QtCore import QByteArray, QIODevice, QBuffer 15 | 16 | from ..Script import Script 17 | 18 | 19 | class CreateJPEGThumbnail(Script): 20 | def __init__(self): 21 | super().__init__() 22 | 23 | def _createSnapshot(self, width, height): 24 | Logger.log("d", "Creating thumbnail image...") 25 | try: 26 | return Snapshot.snapshot(width, height) 27 | except Exception: 28 | Logger.logException("w", "Failed to create snapshot image") 29 | 30 | def _encodeSnapshot(self, snapshot): 31 | Logger.log("d", "Encoding thumbnail image...") 32 | try: 33 | thumbnail_buffer = QBuffer() 34 | thumbnail_buffer.open(QBuffer.ReadWrite) 35 | thumbnail_image = snapshot 36 | thumbnail_image.save(thumbnail_buffer, "JPG") 37 | base64_bytes = base64.b64encode(thumbnail_buffer.data()) 38 | base64_message = base64_bytes.decode('ascii') 39 | thumbnail_buffer.close() 40 | return base64_message 41 | except Exception: 42 | Logger.logException("w", "Failed to encode snapshot image") 43 | 44 | def _convertSnapshotToGcode(self, encoded_snapshot, width, height, chunk_size=78): 45 | gcode = [] 46 | 47 | encoded_snapshot_length = len(encoded_snapshot) 48 | gcode.append(";") 49 | gcode.append("; thumbnail begin {}x{} {}".format( 50 | width, height, encoded_snapshot_length)) 51 | 52 | chunks = ["; {}".format(encoded_snapshot[i:i+chunk_size]) 53 | for i in range(0, len(encoded_snapshot), chunk_size)] 54 | gcode.extend(chunks) 55 | 56 | gcode.append("; thumbnail end") 57 | gcode.append(";") 58 | gcode.append("") 59 | 60 | return gcode 61 | 62 | def getSettingDataString(self): 63 | return """{ 64 | "name": "Create JPEG Thumbnail", 65 | "key": "CreateJPEGThumbnail", 66 | "metadata": {}, 67 | "version": 2, 68 | "settings": 69 | { 70 | "width": 71 | { 72 | "label": "Width", 73 | "description": "Width of the generated thumbnail", 74 | "unit": "px", 75 | "type": "int", 76 | "default_value": 230, 77 | "minimum_value": "0", 78 | "minimum_value_warning": "12", 79 | "maximum_value_warning": "800" 80 | }, 81 | "height": 82 | { 83 | "label": "Height", 84 | "description": "Height of the generated thumbnail", 85 | "unit": "px", 86 | "type": "int", 87 | "default_value": 180, 88 | "minimum_value": "0", 89 | "minimum_value_warning": "12", 90 | "maximum_value_warning": "600" 91 | } 92 | } 93 | }""" 94 | 95 | def execute(self, data): 96 | width = self.getSettingValueByKey("width") 97 | height = self.getSettingValueByKey("height") 98 | 99 | snapshot = self._createSnapshot(width, height) 100 | if snapshot: 101 | encoded_snapshot = self._encodeSnapshot(snapshot) 102 | snapshot_gcode = self._convertSnapshotToGcode( 103 | encoded_snapshot, width, height) 104 | 105 | for layer in data: 106 | layer_index = data.index(layer) 107 | lines = data[layer_index].split("\n") 108 | for line in lines: 109 | if line.startswith(";Generated with Cura"): 110 | line_index = lines.index(line) 111 | insert_index = line_index + 1 112 | lines[insert_index:insert_index] = snapshot_gcode 113 | break 114 | 115 | final_lines = "\n".join(lines) 116 | data[layer_index] = final_lines 117 | 118 | return data -------------------------------------------------------------------------------- /CreateThumbnail.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # Cura JPEG Thumbnail creator 3 | # Professional firmware for Ender3v2 4 | # Miguel A. Risco-Castillo 5 | # version: 1.4 6 | # date: 2022-05-18 7 | # 8 | # Contains code from: 9 | # https://github.com/Ultimaker/Cura/blob/master/plugins/PostProcessingPlugin/scripts/CreateThumbnail.py 10 | #------------------------------------------------------------------------------ 11 | 12 | import base64 13 | 14 | from UM.Logger import Logger 15 | from cura.Snapshot import Snapshot 16 | from cura.CuraVersion import CuraVersion 17 | 18 | from ..Script import Script 19 | 20 | 21 | class CreateJPEGThumbnail(Script): 22 | def __init__(self): 23 | super().__init__() 24 | 25 | def _createSnapshot(self, width, height): 26 | Logger.log("d", "Creating thumbnail image...") 27 | try: 28 | return Snapshot.snapshot(width, height) 29 | except Exception: 30 | Logger.logException("w", "Failed to create snapshot image") 31 | 32 | def _encodeSnapshot(self, snapshot): 33 | 34 | Major=0 35 | Minor=0 36 | try: 37 | Major = int(CuraVersion.split(".")[0]) 38 | Minor = int(CuraVersion.split(".")[1]) 39 | except: 40 | pass 41 | 42 | if Major < 5 : 43 | from PyQt5.QtCore import QByteArray, QIODevice, QBuffer 44 | else : 45 | from PyQt6.QtCore import QByteArray, QIODevice, QBuffer 46 | 47 | Logger.log("d", "Encoding thumbnail image...") 48 | try: 49 | thumbnail_buffer = QBuffer() 50 | if Major < 5 : 51 | thumbnail_buffer.open(QBuffer.ReadWrite) 52 | else: 53 | thumbnail_buffer.open(QBuffer.OpenModeFlag.ReadWrite) 54 | thumbnail_image = snapshot 55 | thumbnail_image.save(thumbnail_buffer, "JPG") 56 | base64_bytes = base64.b64encode(thumbnail_buffer.data()) 57 | base64_message = base64_bytes.decode('ascii') 58 | thumbnail_buffer.close() 59 | return base64_message 60 | except Exception: 61 | Logger.logException("w", "Failed to encode snapshot image") 62 | 63 | def _convertSnapshotToGcode(self, encoded_snapshot, width, height, chunk_size=78): 64 | gcode = [] 65 | 66 | encoded_snapshot_length = len(encoded_snapshot) 67 | gcode.append(";") 68 | gcode.append("; thumbnail begin {}x{} {}".format( 69 | width, height, encoded_snapshot_length)) 70 | 71 | chunks = ["; {}".format(encoded_snapshot[i:i+chunk_size]) 72 | for i in range(0, len(encoded_snapshot), chunk_size)] 73 | gcode.extend(chunks) 74 | 75 | gcode.append("; thumbnail end") 76 | gcode.append(";") 77 | gcode.append("") 78 | 79 | return gcode 80 | 81 | def getSettingDataString(self): 82 | return """{ 83 | "name": "Create JPEG Thumbnail", 84 | "key": "CreateJPEGThumbnail", 85 | "metadata": {}, 86 | "version": 2, 87 | "settings": 88 | { 89 | "width": 90 | { 91 | "label": "Width", 92 | "description": "Width of the generated thumbnail", 93 | "unit": "px", 94 | "type": "int", 95 | "default_value": 230, 96 | "minimum_value": "0", 97 | "minimum_value_warning": "12", 98 | "maximum_value_warning": "800" 99 | }, 100 | "height": 101 | { 102 | "label": "Height", 103 | "description": "Height of the generated thumbnail", 104 | "unit": "px", 105 | "type": "int", 106 | "default_value": 180, 107 | "minimum_value": "0", 108 | "minimum_value_warning": "12", 109 | "maximum_value_warning": "600" 110 | } 111 | } 112 | }""" 113 | 114 | def execute(self, data): 115 | width = self.getSettingValueByKey("width") 116 | height = self.getSettingValueByKey("height") 117 | 118 | snapshot = self._createSnapshot(width, height) 119 | if snapshot: 120 | encoded_snapshot = self._encodeSnapshot(snapshot) 121 | snapshot_gcode = self._convertSnapshotToGcode( 122 | encoded_snapshot, width, height) 123 | 124 | for layer in data: 125 | layer_index = data.index(layer) 126 | lines = data[layer_index].split("\n") 127 | for line in lines: 128 | if line.startswith(";Generated with Cura"): 129 | line_index = lines.index(line) 130 | insert_index = line_index + 1 131 | lines[insert_index:insert_index] = snapshot_gcode 132 | break 133 | 134 | final_lines = "\n".join(lines) 135 | data[layer_index] = final_lines 136 | 137 | return data -------------------------------------------------------------------------------- /Cura_GCode.CSV: -------------------------------------------------------------------------------- 1 | Command;Title;Parameter;;;;;;; 2 | M140;Set Bed Temperature;[S] - Target temperature;;;;;;; 3 | M190;Wait for Bed Temperature;[R] - Target temperature;[S] - Target temperature;;;;;; 4 | M109;Wait for Hotend Temperature;[B] - With AUTOTEMP, the max auto-temperature;[F] - Autotemp flag. Omit to disable autotemp;[R] - Target temperature (wait for cooling or heating);[S] - Target temperature;[T] - Hotend index;;; 5 | M107;Fan Off ;[P] - Fan index;;;;;;; 6 | M106;Set Fan Speed;[P] - Fan index;[S] - Speed;[T] - Secondary speed;;;;; 7 | M84;Disable steppers;;;;;;;; 8 | M92;Set Axis Steps-per-unit;[E] - E steps per unit;[T] - Target extruder;[X] - X steps per unit;[Y] - Y steps per unit;[Z] - Z steps per unit;;; 9 | G29;Bed Leveling;;;;;;;; 10 | M18;Disable steppers;;;;;;;; 11 | M204;Set Starting Acceleration;[P] - Printing acceleration;[R] - Retract acceleration;[T] - Travel acceleration;;;;; 12 | M205;Set Advanced Settings;[B<µs>] - Minimum segment time (µs);[E] - E max jerk (units/s);"[J] - Junction deviation (requires JUNCTION_DEVIATION)";[S] - Minimum feedrate for print moves (units/s);[T] - Minimum feedrate for travel moves (units/s);[X] - X max jerk (units/s);[Y] - Y max jerk (units/s);[Z] - Z max jerk (units/s) 13 | M211;Software Endstops;[S] - Software endstops state;;;;;;; 14 | M203;Set Max Feedrate;[E] - E axis max feedrate;"[T] - Target extruder (Requires DISTINCT_E_FACTORS)";[X] - X axis max feedrate;[Y] - Y axis max feedrate;[Z] - Z axis max feedrate;;; 15 | G90;Absolute Positioning;;;;;;;; 16 | M82;Absolute Extrusion Mode;;;;;;;; 17 | M163;Set Mix Factor;[P] - Mix factor;[S] - Component index;;;;;; 18 | M164;Save Mix;S - Tool index (active virtual tool if omitted);;;;;;; 19 | M105;Report Temperatures;[T] - Hotend index;;;;;;; 20 | G92;Set Position;[E] - New extruder position;[X] - New X axis position;[Y] - New Y axis position;[Z] - New Z axis position;;;; 21 | M104;Set Hotend Temperature;[B] - AUTOTEMP: The max auto-temperature;[F] - AUTOTEMP: Autotemp flag;[S] - Target temperature;[T] - Hotend index -------------------------------------------------------------------------------- /CustomTimeLapse.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script Custom Time Lapse 4 | # Author : ???? 5 | # Modification : Arpadvezer1 : https://github.com/Arpadvezer1 6 | # Date : January 31, 2023 7 | # 8 | # Description : CustomTimelapse 9 | # 10 | # 11 | #------------------------------------------------------------------------------------------------------------------------------------ 12 | 13 | from ..Script import Script 14 | from UM.Logger import Logger 15 | 16 | __version__ = '1.0' 17 | 18 | class CustomTimeLapse(Script): 19 | def __init__(self): 20 | super().__init__() 21 | 22 | def getSettingDataString(self): 23 | return """{ 24 | "name": "Custom timelapse", 25 | "key": "CustomTimeLapse", 26 | "metadata": {}, 27 | "version": 2, 28 | "settings": 29 | { 30 | "activate_plugin": 31 | { 32 | "label": "Enable plugin", 33 | "description": "Select if you want the plugin to be active (allows you to desactivate without losing your configuration)", 34 | "type": "bool", 35 | "default_value": true 36 | }, 37 | "first_gcode": 38 | { 39 | "label": "GCODE for the first position(display position).", 40 | "description": "GCODE to add before or after layer change.", 41 | "type": "str", 42 | "default_value": "G0 Y235" 43 | }, 44 | "second_gcode": 45 | { 46 | "label": "GCODE for the second position(trigger position).", 47 | "description": "GCODE to add before or after layer change.", 48 | "type": "str", 49 | "default_value": "G0 X235" 50 | }, 51 | "enable_custom_return_speed": 52 | { 53 | "label": "Specify a return speed", 54 | "description": "Set the value below", 55 | "type": "bool", 56 | "default_value": false 57 | }, 58 | "return_speed": 59 | { 60 | "label": "return speed in mm/minutes", 61 | "description": "return speed in mm/minute as for the F gcode parameter.", 62 | "type": "int", 63 | "unit": "mm/m", 64 | "enabled": "enable_custom_return_speed" 65 | }, 66 | "pause_length": 67 | { 68 | "label": "Pause length", 69 | "description": "How long to wait (in ms) after camera was triggered.", 70 | "type": "int", 71 | "default_value": 700, 72 | "minimum_value": 0, 73 | "unit": "ms" 74 | }, 75 | "enable_retraction": 76 | { 77 | "label": "Enable retraction", 78 | "description": "Retract the filament before moving the head", 79 | "type": "bool", 80 | "default_value": true 81 | }, 82 | "retraction_distance": 83 | { 84 | "label": "Retraction distance", 85 | "description": "How much to retract the filament.", 86 | "unit": "mm", 87 | "type": "float", 88 | "default_value": 5, 89 | "enabled": "enable_retraction" 90 | }, 91 | "display_photo_number": 92 | { 93 | "label": "Display current photo number", 94 | "description": "Display the current photo number on the panel during the shots", 95 | "type": "bool", 96 | "default_value": false 97 | }, 98 | "send_photo_command": 99 | { 100 | "label": "Send camera command", 101 | "description": "Send a customisable G-code command for compatible printers", 102 | "type": "bool", 103 | "default_value": false 104 | }, 105 | "trigger_command": 106 | { 107 | "label": "Trigger camera command", 108 | "description": "Gcode command used to trigger camera.", 109 | "type": "str", 110 | "default_value": "M240", 111 | "enabled": "send_photo_command" 112 | } 113 | } 114 | }""" 115 | # Note : This function and some other bits of code comes from PauseAtHeight.py 116 | ## Get the X and Y values for a layer (will be used to get X and Y of the 117 | # layer after the pause). 118 | def getNextXY(self, layer): 119 | lines = layer.split("\n") 120 | for line in lines: 121 | if self.getValue(line, "X") is not None and self.getValue(line, "Y") is not None: 122 | x = self.getValue(line, "X") 123 | y = self.getValue(line, "Y") 124 | return x, y 125 | return 0, 0 126 | 127 | def execute(self, data): 128 | activate_plugin = self.getSettingValueByKey("activate_plugin") 129 | first_gcode = self.getSettingValueByKey("first_gcode") 130 | second_gcode = self.getSettingValueByKey("second_gcode") 131 | pause_length = self.getSettingValueByKey("pause_length") 132 | enable_custom_return_speed = self.getSettingValueByKey("enable_custom_return_speed") 133 | return_speed = self.getSettingValueByKey("return_speed") 134 | enable_retraction = self.getSettingValueByKey("enable_retraction") 135 | retraction_distance = self.getSettingValueByKey("retraction_distance") 136 | display_photo_number = self.getSettingValueByKey("display_photo_number") 137 | send_photo_command = self.getSettingValueByKey("send_photo_command") 138 | trigger_command = self.getSettingValueByKey("trigger_command") 139 | 140 | for layerIndex, layer in enumerate(data): 141 | # Check that a layer is being printed 142 | lines = layer.split("\n") 143 | for line in lines: 144 | if ";LAYER:" in line: 145 | index = data.index(layer) 146 | 147 | next_layer = data[layerIndex + 1] 148 | x, y = self.getNextXY(next_layer) 149 | 150 | gcode_to_append = "" 151 | 152 | if activate_plugin: 153 | gcode_to_append += ";CustomTimelapse Begin\n" 154 | 155 | if display_photo_number: 156 | gcode_to_append += "M117 Taking photo " + str(layerIndex) + "...\n" 157 | 158 | gcode_to_append += "; STEP 1 : retraction\n" 159 | gcode_to_append += self.putValue(M = 83) + " ; switch to relative E values for any needed retraction\n" 160 | if enable_retraction: 161 | gcode_to_append += self.putValue(G = 1, F = 1800, E = -retraction_distance) + ";Retraction\n" 162 | gcode_to_append += self.putValue(M = 82) + ";Switch back to absolute E values\n" 163 | 164 | gcode_to_append += "; STEP 2 : Move the head up a bit\n" 165 | gcode_to_append += self.putValue(G = 91) + ";Switch to relative positioning\n" 166 | gcode_to_append += self.putValue(G = 0, Z = 1) + ";Move Z axis up a bit\n" 167 | gcode_to_append += self.putValue(G = 90) + ";Switch back to absolute positioning\n" 168 | 169 | gcode_to_append += "; STEP 3 : Move the head to \"display\" position and wait\n" 170 | gcode_to_append += first_gcode + ";GCODE for the first position(display position)\n" 171 | gcode_to_append += second_gcode + ";GCODE for the second position(trigger position)\n" 172 | gcode_to_append += self.putValue(M = 400) + ";Wait for moves to finish\n" 173 | gcode_to_append += self.putValue(G = 4, P = pause_length) + ";Wait for camera\n" 174 | 175 | gcode_to_append += "; STEP 4 : send photo trigger command if set\n" 176 | if send_photo_command: 177 | gcode_to_append += trigger_command + " ;Snap Photo\n" 178 | 179 | # TODO skip steps 5 and 6 for the last layer 180 | gcode_to_append += "; STEP 5 : Move the head back in its original place\n" 181 | if enable_custom_return_speed: 182 | gcode_to_append += self.putValue(G = 0, X = x, Y = y, F = return_speed) + "\n" 183 | else: 184 | gcode_to_append += self.putValue(G = 0, X = x, Y = y) + "\n" 185 | 186 | gcode_to_append += "; STEP 6 : Move the head height back down\n" 187 | gcode_to_append += self.putValue(G = 91) + ";Switch to relative positioning\n" 188 | gcode_to_append += self.putValue(G = 0, Z = -1) + ";Restore Z axis position\n" 189 | gcode_to_append += self.putValue(G = 90) + ";Switch back to absolute positioning\n" 190 | 191 | gcode_to_append += ";CustomTimelapse End\n" 192 | 193 | 194 | layer += gcode_to_append 195 | 196 | data[index] = layer 197 | break 198 | return data -------------------------------------------------------------------------------- /DiagonalZHop.py: -------------------------------------------------------------------------------- 1 | # DiagonalZHop 2 | """ 3 | DiagonalZHop for 3D prints. 4 | 5 | Diagonal Z Hop 6 | 7 | Author: 5axes 8 | Version: 0.1 9 | 10 | Note : https://github.com/Ultimaker/Cura/issues/15583 11 | """ 12 | 13 | import re #To perform the search 14 | 15 | from ..Script import Script 16 | 17 | from UM.Application import Application 18 | from UM.Logger import Logger 19 | from UM.Message import Message 20 | from UM.i18n import i18nCatalog 21 | 22 | catalog = i18nCatalog("cura") 23 | 24 | __version__ = '0.1' 25 | 26 | class DiagonalZHop(Script): 27 | def getSettingDataString(self): 28 | return """{ 29 | "name": "Diagonal Z Hop", 30 | "key": "DiagonalZHop", 31 | "metadata": {}, 32 | "version": 2, 33 | "settings": 34 | { 35 | "extruder_nb": 36 | { 37 | "label": "Extruder Id", 38 | "description": "Define extruder Id in case of multi extruders", 39 | "unit": "", 40 | "type": "int", 41 | "default_value": 1 42 | } 43 | } 44 | }""" 45 | 46 | ## ----------------------------------------------------------------------------- 47 | # 48 | # Main Prog 49 | # 50 | ## ----------------------------------------------------------------------------- 51 | def execute(self, data): 52 | 53 | extruder_id = self.getSettingValueByKey("extruder_nb") 54 | extruder_id = extruder_id -1 55 | 56 | # machine_extruder_count 57 | extruder_count=Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") 58 | extruder_count = extruder_count-1 59 | if extruder_id>extruder_count : 60 | extruder_id=extruder_count 61 | 62 | extrud = Application.getInstance().getGlobalContainerStack().extruderList 63 | 64 | # Get the Cura retraction_hop and speed_z_hop as Zhop parameter 65 | retraction_hop = float(extrud[extruder_id].getProperty("retraction_hop", "value")) 66 | speed_z_hop = int(extrud[extruder_id].getProperty("speed_z_hop", "value")) 67 | speed_z_hop = speed_z_hop * 60 68 | 69 | # Check if Z hop is desactivated 70 | retraction_hop_enabled= extrud[extruder_id].getProperty("retraction_hop_enabled", "value") 71 | if retraction_hop_enabled == False: 72 | # 73 | Logger.log('d', 'Mode Z Hop must be activated !') 74 | Message(catalog.i18nc("@message", "Mode Z Hop must be activated !"), title = catalog.i18nc("@info:title", "Post Processing")).show() 75 | return data 76 | 77 | In_Zhop = False 78 | for layer_index, layer in enumerate(data): 79 | lines = layer.split("\n") 80 | 81 | for line_index, currentLine in enumerate(lines): 82 | 83 | # Zhop G1 84 | if currentLine.startswith("G1") and "Z" in currentLine and not "X" in currentLine and not "Y" in currentLine and not In_Zhop : 85 | In_Zhop = True 86 | lines[line_index] = ";" + currentLine + " ; Modified by DiagonalZhop" 87 | else : 88 | if currentLine.startswith("G1") and "Z" in currentLine and not "X" in currentLine and not "Y" in currentLine : 89 | In_Zhop = False 90 | 91 | # 92 | # end of analyse 93 | # 94 | 95 | final_lines = "\n".join(lines) 96 | data[layer_index] = final_lines 97 | return data 98 | -------------------------------------------------------------------------------- /DisplayPrintInfosOnLCD.py: -------------------------------------------------------------------------------- 1 | # 2 | # Cura PostProcessingPlugin 3 | # Author: Amanda de Castilho for the Layer part 4 | # Author: Mathias Lyngklip Kjeldgaard for the remaining time part 5 | # Author: 5axes 6 | # Date: Janvier 02 2020 7 | # Modified: Janvier 05 2020 Option Display LayerId 8 | # 9 | # Description: This script shows custom messages about your print on the Printer Panel... 10 | # Please look at the option 11 | # - LayerId: Uses the Layer ID encoded in the original file 12 | # 13 | from ..Script import Script 14 | from UM.Application import Application 15 | 16 | class DisplayPrintInfosOnLCD(Script): 17 | def __init__(self): 18 | super().__init__() 19 | 20 | def getSettingDataString(self): 21 | return """{ 22 | "name": "Display Print Infos On LCD", 23 | "key": "DisplayPrintInfosOnLCD", 24 | "metadata": {}, 25 | "version": 2, 26 | "settings": 27 | { 28 | "LayerId": 29 | { 30 | "label": "Use Layer Id G-Code", 31 | "description": "Uses the Layer Id encoded in the G-Code file. Must be used in Print Sequence : One at a time", 32 | "type": "bool", 33 | "default_value": false 34 | } 35 | } 36 | }""" 37 | 38 | def execute(self, data): 39 | max_layer = 0 40 | total_time = 0 41 | part = 0 42 | total_time_string = "" 43 | current_time_string = "" 44 | lcd_text = "M117 (" 45 | Id = 1 46 | for layer in data: 47 | display_text = lcd_text + str(Id) + "/" 48 | layer_index = data.index(layer) 49 | lines = layer.split("\n") 50 | for line in lines: 51 | if line.startswith(";LAYER_COUNT:"): 52 | max_layer = line.split(":")[1] # Recuperation Nb Layer Maxi 53 | elif line.startswith(";LAYER:"): 54 | line_index = lines.index(line) 55 | if part > 1: 56 | display_text = display_text + max_layer + ") " + current_time_string + " P" + str(part) 57 | else: 58 | display_text = display_text + max_layer + ") " + current_time_string 59 | 60 | lines.insert(line_index + 1, display_text) # Insert du code M117 apres les layers 61 | if self.getSettingValueByKey("LayerId"): 62 | Id = int(line.split(":")[1]) # Utilise le Layer dans G-Code ;LAYER:1 63 | if Id == 0: 64 | part += 1 # Incrémente le numero de pièce 65 | Id += 1 66 | else: 67 | Id += 1 # Incrémente le numero de Layer (sans utiliser celui du Gcode) 68 | if line.startswith(";TIME:"): 69 | line_index = lines.index(line) 70 | total_time = int(line.split(":")[1]) 71 | m, s = divmod(total_time, 60) # Decomposition en 72 | h, m = divmod(m, 60) # heures, minutes et secondes 73 | total_time_string = "{:d}h{:d}m{:d}s".format(int(h), int(m), int(s)) 74 | current_time_string = total_time_string 75 | display_text = lcd_text + total_time_string + ")" 76 | lines.insert(line_index + 1, display_text) 77 | elif line.startswith(";TIME_ELAPSED:"): 78 | line_index = lines.index(line) 79 | current_time = float(line.split(":")[1]) 80 | time_left = total_time - current_time # Calcul du temps restant 81 | m1, s1 = divmod(time_left, 60) # Decomposition en 82 | h1, m1 = divmod(m1, 60) # heures, minutes et secondes 83 | current_time_string = "{:d}h{:d}m{:d}s".format(int(h1), int(m1), int(s1)) 84 | 85 | final_lines = "\n".join(lines) 86 | data[layer_index] = final_lines 87 | 88 | return data 89 | -------------------------------------------------------------------------------- /FanIroning.py: -------------------------------------------------------------------------------- 1 | # FanIroning 2 | """ 3 | FanIroning for 3D prints. 4 | 5 | Set Fan value for ironing 6 | 7 | Author : 5axes 8 | Version : 1.0 9 | Date : 3/01/2023 10 | 11 | """ 12 | 13 | from ..Script import Script 14 | from UM.Logger import Logger 15 | from UM.Application import Application 16 | import re #To perform the search 17 | from cura.Settings.ExtruderManager import ExtruderManager 18 | from enum import Enum 19 | from UM.Message import Message 20 | from UM.i18n import i18nCatalog 21 | catalog = i18nCatalog("cura") 22 | 23 | __version__ = '1.0' 24 | 25 | class Section(Enum): 26 | """Enum for section type.""" 27 | 28 | NOTHING = 0 29 | SKIRT = 1 30 | INNER_WALL = 2 31 | OUTER_WALL = 3 32 | INFILL = 4 33 | SKIN = 5 34 | SKIN2 = 6 35 | PRIME_TOWER = 7 36 | BRIDGE = 8 37 | 38 | 39 | 40 | def is_begin_layer_line(line: str) -> bool: 41 | """Check if current line is the start of a layer section. 42 | 43 | Args: 44 | line (str): Gcode line 45 | 46 | Returns: 47 | bool: True if the line is the start of a layer section 48 | """ 49 | return line.startswith(";LAYER:") 50 | 51 | 52 | def is_extrusion_line(line: str) -> bool: 53 | """Check if current line is a standard printing segment. 54 | 55 | Args: 56 | line (str): Gcode line 57 | 58 | Returns: 59 | bool: True if the line is a standard printing segment 60 | """ 61 | return "G1" in line and "X" in line and "Y" in line and "E" in line 62 | 63 | def is_not_extrusion_line(line: str) -> bool: 64 | """Check if current line is a rapid movement segment. 65 | 66 | Args: 67 | line (str): Gcode line 68 | 69 | Returns: 70 | bool: True if the line is a standard printing segment 71 | """ 72 | return "G0" in line and "X" in line and "Y" in line and not "E" in line 73 | 74 | def is_begin_skin_segment_line(line: str) -> bool: 75 | """Check if current line is the start of an skin. 76 | 77 | Args: 78 | line (str): Gcode line 79 | 80 | Returns: 81 | bool: True if the line is the start of an skin section 82 | """ 83 | return line.startswith(";TYPE:SKIN") 84 | 85 | 86 | class FanIroning(Script): 87 | def getSettingDataString(self): 88 | return """{ 89 | "name": "Fan Ironing", 90 | "key": "FanIroning", 91 | "metadata": {}, 92 | "version": 2, 93 | "settings": 94 | { 95 | "extruder_nb": 96 | { 97 | "label": "Extruder Id", 98 | "description": "Define extruder Id in case of multi extruders", 99 | "unit": "", 100 | "type": "int", 101 | "default_value": 1 102 | }, 103 | "fan_value": 104 | { 105 | "label": "Fan Value", 106 | "description": "Fan value during ironing operation.", 107 | "type": "int", 108 | "default_value": 0, 109 | "minimum_value": 0, 110 | "maximum_value": 100 111 | } 112 | } 113 | }""" 114 | 115 | 116 | ## ----------------------------------------------------------------------------- 117 | # 118 | # Main Prog 119 | # 120 | ## ----------------------------------------------------------------------------- 121 | 122 | def execute(self, data): 123 | 124 | extruder_id = self.getSettingValueByKey("extruder_nb") 125 | extruder_id = extruder_id -1 126 | 127 | ironing_fan_value = int((float(self.getSettingValueByKey("fan_value"))/100)*255) 128 | 129 | # machine_extruder_count 130 | extruder_count=Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") 131 | extruder_count = extruder_count-1 132 | if extruder_id>extruder_count : 133 | extruder_id=extruder_count 134 | 135 | extrud = Application.getInstance().getGlobalContainerStack().extruderList 136 | 137 | ironingenabled = extrud[extruder_id].getProperty("ironing_enabled", "value") 138 | if ironingenabled == False: 139 | # 140 | Logger.log('d', 'Gcode must be generate with ironing mode') 141 | Message('Gcode must be generate with ironing mode', title = catalog.i18nc("@info:title", "Post Processing")).show() 142 | return None 143 | 144 | """Parse Gcode and modify infill portions with an extrusion width gradient.""" 145 | currentSection = Section.NOTHING 146 | set_ironing_fan_value = False 147 | current_Fan = 0 148 | Fan_On = False 149 | 150 | for layer_index, layer in enumerate(data): 151 | lines = layer.split("\n") 152 | for line_index, currentLine in enumerate(lines): 153 | # M107 - Set Fan Speed 154 | if currentLine.startswith("M106") and not set_ironing_fan_value : 155 | searchM106 = re.search(r"S(\d*\.?\d*)", currentLine) 156 | if searchM106: 157 | current_Fan=int(searchM106.group(1)) 158 | Logger.log('d', 'current_Fan :' + str(current_Fan)) 159 | Fan_On = True 160 | # M107 - Fan Off 161 | if currentLine.startswith("M107") : 162 | Fan_On = False 163 | 164 | if is_begin_skin_segment_line(currentLine) and not (currentSection == Section.SKIN): 165 | currentSection = Section.SKIN 166 | continue 167 | 168 | # SKIN After SKIN = Ironing operation 169 | if currentSection == Section.SKIN: 170 | if is_begin_skin_segment_line(currentLine): 171 | currentSection = Section.SKIN2 172 | set_ironing_fan_value = True 173 | outPutLine = "\nM106 S{:d}".format(ironing_fan_value) 174 | # Logger.log('d', 'outPutLine :' + str(outPutLine)) 175 | outPutLine = currentLine + outPutLine 176 | lines[line_index] = outPutLine 177 | elif currentLine.startswith(";TYPE:"): 178 | currentSection = Section.NOTHING 179 | if set_ironing_fan_value : 180 | set_ironing_fan_value = False 181 | if Fan_On == True : 182 | outPutLine = "\nM106 S{:d}".format(current_Fan) 183 | else: 184 | outPutLine = "\nM107" 185 | # Logger.log('d', 'Reset A outPutLine :' + str(outPutLine)) 186 | outPutLine = currentLine + outPutLine 187 | lines[line_index] = outPutLine 188 | 189 | # 190 | # comment like ;MESH:NONMESH 191 | # 192 | if currentLine.startswith(";MESH:"): 193 | currentSection = Section.NOTHING 194 | if set_ironing_fan_value : 195 | set_ironing_fan_value = False 196 | # Logger.log('d', 'Reset B outPutLine :' + str(outPutLine)) 197 | if Fan_On == True : 198 | outPutLine = "\nM106 S{:d}".format(current_Fan) 199 | else: 200 | outPutLine = "\nM107" 201 | outPutLine = currentLine + outPutLine 202 | lines[line_index] = outPutLine 203 | 204 | # 205 | # end of analyse 206 | # 207 | 208 | final_lines = "\n".join(lines) 209 | data[layer_index] = final_lines 210 | return data 211 | -------------------------------------------------------------------------------- /FastFirstInfill.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: 5axes 5 | # Date: November 06, 2021 6 | # 7 | # Description: postprocessing script to modifiy the first layer infill 8 | # 9 | #------------------------------------------------------------------------------------------------------------------------------------ 10 | # 11 | # Version 1.0 06/11/2021 12 | # Version 1.1 07/11/2021 Modification for Print Sequence 13 | # 14 | #------------------------------------------------------------------------------------------------------------------------------------ 15 | 16 | from ..Script import Script 17 | from UM.Logger import Logger 18 | from UM.Application import Application 19 | import re # To perform the search 20 | from enum import Enum 21 | 22 | __version__ = '1.1' 23 | 24 | class Section(Enum): 25 | """Enum for section type.""" 26 | 27 | NOTHING = 0 28 | SKIRT = 1 29 | INNER_WALL = 2 30 | OUTER_WALL = 3 31 | INFILL = 4 32 | SKIN = 5 33 | SKIN2 = 6 34 | 35 | def is_begin_layer_line(line: str) -> bool: 36 | """Check if current line is the start of a layer section. 37 | 38 | Args: 39 | line (str): Gcode line 40 | 41 | Returns: 42 | bool: True if the line is the start of a layer section 43 | """ 44 | return line.startswith(";LAYER:") 45 | 46 | def is_begin_type_line(line: str) -> bool: 47 | """Check if current line is the start of a new type section. 48 | 49 | Args: 50 | line (str): Gcode line 51 | 52 | Returns: 53 | bool: True if the line is the start of a new type section 54 | """ 55 | return line.startswith(";TYPE:") 56 | 57 | def is_retract_line(line: str) -> bool: 58 | """Check if current line is a retract segment. 59 | 60 | Args: 61 | line (str): Gcode line 62 | 63 | Returns: 64 | bool: True if the line is a retract segment 65 | """ 66 | return "G1" in line and "F" in line and "E" in line and not "X" in line and not "Y" in line and not "Z" in line 67 | 68 | def is_extrusion_line(line: str) -> bool: 69 | """Check if current line is a standard printing segment. 70 | 71 | Args: 72 | line (str): Gcode line 73 | 74 | Returns: 75 | bool: True if the line is a standard printing segment 76 | """ 77 | return "G1" in line and "X" in line and "Y" in line and "E" in line 78 | 79 | def is_not_extrusion_line(line: str) -> bool: 80 | """Check if current line is a rapid movement segment. 81 | 82 | Args: 83 | line (str): Gcode line 84 | 85 | Returns: 86 | bool: True if the line is a standard printing segment 87 | """ 88 | return "G0" in line and "X" in line and "Y" in line and not "E" in line 89 | 90 | def is_begin_skin_segment_line(line: str) -> bool: 91 | """Check if current line is the start of an skin. 92 | 93 | Args: 94 | line (str): Gcode line 95 | 96 | Returns: 97 | bool: True if the line is the start of an skin section 98 | """ 99 | return line.startswith(";TYPE:SKIN") 100 | 101 | class FastFirstInfill(Script): 102 | def __init__(self): 103 | super().__init__() 104 | 105 | def getSettingDataString(self): 106 | return """{ 107 | "name": "FastFirstInfill", 108 | "key": "FastFirstInfill", 109 | "metadata": {}, 110 | "version": 2, 111 | "settings": 112 | { 113 | "infillspeed": 114 | { 115 | "label": "First layer infill speed", 116 | "description": "First layer infill speed value.", 117 | "type": "float", 118 | "unit": "mm/s", 119 | "default_value": 30, 120 | "minimum_value": 1, 121 | "maximum_value": 100, 122 | "maximum_value_warning": 50 123 | } 124 | } 125 | }""" 126 | 127 | def execute(self, data): 128 | 129 | InfillSpeed = float(self.getSettingValueByKey("infillspeed")) * 60 130 | InfillSpeedInstruction = "F" + str(InfillSpeed) 131 | Logger.log('d', 'InfillSpeedInstruction : {}'.format(InfillSpeedInstruction)) 132 | 133 | idl=0 134 | 135 | for layer in data: 136 | layer_index = data.index(layer) 137 | 138 | lines = layer.split("\n") 139 | for line in lines: 140 | 141 | if is_begin_layer_line(line): 142 | # Logger.log('d', 'layer_index : {:d}'.format(layer_index)) 143 | # Logger.log('d', 'layer_lines : {}'.format(line)) 144 | if line.startswith(";LAYER:0"): 145 | idl=1 146 | else : 147 | idl=0 148 | 149 | if is_begin_type_line(line) and idl > 0: 150 | if is_begin_skin_segment_line(line): 151 | idl=2 152 | Logger.log('d', 'layer_lines : {}'.format(line)) 153 | else : 154 | idl=1 155 | 156 | if idl >= 2 and is_extrusion_line(line): 157 | searchF = re.search(r"F(\d*\.?\d*)", line) 158 | if searchF: 159 | line_index = lines.index(line) 160 | save_F=float(searchF.group(1)) 161 | instructionF="F"+str(searchF.group(1)) 162 | # Logger.log('d', 'save_F : {:f}'.format(save_F)) 163 | # Logger.log('d', 'line : {}'.format(line)) 164 | # Logger.log('d', 'line replace : {}'.format(line.replace(instructionF,InfillSpeedInstruction))) 165 | lines[line_index]=line.replace(instructionF,InfillSpeedInstruction) 166 | 167 | result = "\n".join(lines) 168 | data[layer_index] = result 169 | 170 | return data 171 | -------------------------------------------------------------------------------- /FlowTower.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: 5axes 5 | # Date: August 29, 2021 6 | # 7 | # Description: postprocessing script to easily define a Flow Tower 8 | # 9 | #------------------------------------------------------------------------------------------------------------------------------------ 10 | # 11 | # Version 1.0 29/08/2021 12 | # 13 | #------------------------------------------------------------------------------------------------------------------------------------ 14 | 15 | from ..Script import Script 16 | from UM.Application import Application 17 | from UM.Logger import Logger 18 | import re #To perform the search 19 | 20 | __version__ = '1.0' 21 | 22 | class FlowTower(Script): 23 | def __init__(self): 24 | super().__init__() 25 | 26 | def getSettingDataString(self): 27 | return """{ 28 | "name": "FlowTower", 29 | "key": "FlowTower", 30 | "metadata": {}, 31 | "version": 2, 32 | "settings": 33 | { 34 | "startValue": 35 | { 36 | "label": "Starting value", 37 | "description": "the starting value of the Tower.", 38 | "type": "float", 39 | "default_value": 110.0 40 | }, 41 | "valueChange": 42 | { 43 | "label": "Value Increment", 44 | "description": "the value change of each block, can be positive or negative. I you want 110 and then 108, you need to set this to -2.", 45 | "type": "float", 46 | "default_value": -2.0 47 | }, 48 | "changelayer": 49 | { 50 | "label": "Change Layer", 51 | "description": "how many layers needs to be printed before the value should be changed.", 52 | "type": "float", 53 | "default_value": 40, 54 | "minimum_value": 1, 55 | "maximum_value": 1000, 56 | "maximum_value_warning": 100 57 | }, 58 | "changelayeroffset": 59 | { 60 | "label": "Change Layer Offset", 61 | "description": "if the Tower has a base, put the layer high off it here", 62 | "type": "float", 63 | "default_value": 0, 64 | "minimum_value": 0, 65 | "maximum_value": 1000, 66 | "maximum_value_warning": 100 67 | }, 68 | "lcdfeedback": 69 | { 70 | "label": "Display details on LCD", 71 | "description": "This setting will insert M117 gcode instructions, to display current modification in the G-Code is being used.", 72 | "type": "bool", 73 | "default_value": true 74 | } 75 | } 76 | }""" 77 | 78 | def execute(self, data): 79 | 80 | UseLcd = self.getSettingValueByKey("lcdfeedback") 81 | StartValue = float(self.getSettingValueByKey("startValue")) 82 | ValueChange = float(self.getSettingValueByKey("valueChange")) 83 | ChangeLayer = self.getSettingValueByKey("changelayer") 84 | ChangeLayerOffset = self.getSettingValueByKey("changelayeroffset") 85 | ChangeLayerOffset += 2 # Modification to take into account the numbering offset in Gcode 86 | # layer_index = 0 for initial Block 1= Start Gcode normaly first layer = 0 87 | 88 | CurrentValue = StartValue 89 | Command="" 90 | 91 | idl=0 92 | 93 | for layer in data: 94 | layer_index = data.index(layer) 95 | 96 | lines = layer.split("\n") 97 | for line in lines: 98 | 99 | if line.startswith(";LAYER:"): 100 | line_index = lines.index(line) 101 | # Logger.log('d', 'Instruction : {}'.format(Instruction)) 102 | 103 | if (layer_index==ChangeLayerOffset): 104 | Command = "M221 S{:d}".format(int(CurrentValue)) 105 | lcd_gcode = "M117 Flow S{:d}".format(int(CurrentValue)) 106 | 107 | lines.insert(line_index + 1, ";TYPE:CUSTOM LAYER") 108 | lines.insert(line_index + 2, Command) 109 | if UseLcd == True : 110 | lines.insert(line_index + 3, lcd_gcode) 111 | 112 | if ((layer_index-ChangeLayerOffset) % ChangeLayer == 0) and ((layer_index-ChangeLayerOffset)>0): 113 | CurrentValue += ValueChange 114 | Command = "M221 S{:d}".format(int(CurrentValue)) 115 | lcd_gcode = "M117 Flow S{:d}".format(int(CurrentValue)) 116 | 117 | lines.insert(line_index + 1, ";TYPE:CUSTOM VALUE") 118 | lines.insert(line_index + 2, Command) 119 | if UseLcd == True : 120 | lines.insert(line_index + 3, lcd_gcode) 121 | 122 | result = "\n".join(lines) 123 | data[layer_index] = result 124 | 125 | return data 126 | -------------------------------------------------------------------------------- /GregVInsertAtLayerChange.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Ultimaker B.V. 2 | # Cura is released under the terms of the LGPLv3 or higher. 3 | # Created by Wayne Porter. 4 | # altered January of 2023 by GregValiant 5 | 6 | from ..Script import Script 7 | 8 | class GregVInsertAtLayerChange(Script): 9 | def __init__(self): 10 | super().__init__() 11 | 12 | def getSettingDataString(self): 13 | return """{ 14 | "name": "Greg's Insert at layer change", 15 | "key": "GregVInsertAtLayerChange", 16 | "metadata": {}, 17 | "version": 2, 18 | "settings": 19 | { 20 | "insert_location": 21 | { 22 | "label": "When to insert", 23 | "description": "Whether to insert code at the beginning or end of a layer.", 24 | "type": "enum", 25 | "options": {"before": "Beginning", "after": "End"}, 26 | "default_value": "before" 27 | }, 28 | "insert_frequency": 29 | { 30 | "label": "How often to insert", 31 | "description": "Every so many layers starting with the Start Layer.", 32 | "type": "enum", 33 | "options": {"once_only": "One insertion only", "every_layer": "Every Layer", "every_second": "Every 2nd", "every_third": "Every 3rd", "every_fifth": "Every 5th", "every_tenth": "Every 10th", "every_XXV": "Every 25th", "every_L": "Every 50th", "every_C": "Every 100th"}, 34 | "default_value": "every_layer" 35 | }, 36 | "start_layer": 37 | { 38 | "label": "Starting Layer", 39 | "description": "Layer to start the insertion at.", 40 | "type": "int", 41 | "default_value": 0, 42 | "minimum_value": 0, 43 | "enabled": "insert_frequency != 'once_only'" 44 | }, 45 | "end_layer_enabled": 46 | { 47 | "label": "Enable End Layer", 48 | "description": "Check to use an ending layer for the insertion.", 49 | "type": "bool", 50 | "default_value": false, 51 | "enabled": "insert_frequency != 'once_only'" 52 | }, 53 | "end_layer": 54 | { 55 | "label": "Ending Layer", 56 | "description": "Layer to end the insertion at. Enter 'End' for entire file (or disable this setting).", 57 | "type": "str", 58 | "default_value": "End", 59 | "enabled": "end_layer_enabled and insert_frequency != 'once_only'" 60 | }, 61 | "single_end_layer": 62 | { 63 | "label": "Layer # for Single Insertion", 64 | "description": "Layer for a single insertion of the Gcode.", 65 | "type": "str", 66 | "default_value": "", 67 | "enabled": "insert_frequency == 'once_only'" 68 | }, 69 | "gcode_to_add": 70 | { 71 | "label": "G-code to insert.", 72 | "description": "G-code to add before or after layer change. Use a comma to delimit multi-line commands. EX: G28 X Y,M220 S100,M117 HELL0. Note that all commands will be converted to upper-case.", 73 | "type": "str", 74 | "default_value": "" 75 | } 76 | } 77 | }""" 78 | 79 | def execute(self, data): 80 | MyCode = self.getSettingValueByKey("gcode_to_add").upper() 81 | the_start_layer = self.getSettingValueByKey("start_layer") 82 | the_end_layer = self.getSettingValueByKey("end_layer") 83 | the_end_is_enabled = self.getSettingValueByKey("end_layer_enabled") 84 | when_to_insert = self.getSettingValueByKey("insert_frequency") 85 | start_here = False 86 | RealNum = 0 87 | 88 | if the_end_layer == "End" or not the_end_is_enabled: 89 | the_end_layer = "9999999999" 90 | 91 | if "," in MyCode: 92 | gc = MyCode.split(",") 93 | gcode_to_add = "" 94 | for g_code in gc: 95 | gcode_to_add += g_code + "\n" 96 | 97 | else: 98 | gcode_to_add = MyCode + "\n" 99 | 100 | if when_to_insert == "every_layer": 101 | freq = 1 102 | 103 | if when_to_insert == "every_second": 104 | freq = 2 105 | 106 | if when_to_insert == "every_third": 107 | freq = 3 108 | 109 | if when_to_insert == "every_fifth": 110 | freq = 5 111 | 112 | if when_to_insert == "every_tenth": 113 | freq = 10 114 | 115 | if when_to_insert == "every_XXV": 116 | freq = 25 117 | 118 | if when_to_insert == "every_L": 119 | freq = 50 120 | 121 | if when_to_insert == "every_C": 122 | freq = 100 123 | 124 | if when_to_insert == "once_only": 125 | the_search_layer = self.getSettingValueByKey("single_end_layer") 126 | 127 | index = 0 128 | if when_to_insert == "once_only": 129 | for layer in data: 130 | lines = layer.split("\n") 131 | for line in lines: 132 | if ";LAYER:" in line: 133 | layer_number = int(line.split(":")[1]) 134 | if layer_number == int(the_search_layer): 135 | index = data.index(layer) 136 | if self.getSettingValueByKey("insert_location") == "before": 137 | layer = gcode_to_add + layer 138 | else: 139 | layer = layer + gcode_to_add 140 | 141 | data[index] = layer 142 | break 143 | 144 | if when_to_insert != "once_only": 145 | for layer in data: 146 | lines = layer.split("\n") 147 | for line in lines: 148 | if ";LAYER:" in line: 149 | layer_number = int(line.split(":")[1]) 150 | if layer_number >= int(the_start_layer) and layer_number <= int(the_end_layer): 151 | index = data.index(layer) 152 | RealNum = layer_number - int(the_start_layer) 153 | if int(RealNum / freq) - (RealNum / freq) == 0: 154 | if self.getSettingValueByKey("insert_location") == "before": 155 | layer = gcode_to_add + layer 156 | else: 157 | layer = layer + gcode_to_add 158 | 159 | data[index] = layer 160 | break 161 | 162 | return data 163 | -------------------------------------------------------------------------------- /InfillLast.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: 5axes 5 | # Date: March 13, 2023 6 | # 7 | # Description: InfillLast 8 | # 9 | #------------------------------------------------------------------------------------------------------------------------------------ 10 | # 11 | # Version 1.0 13/03/2023 first prototype right now must be use with the relative extrusion activated 12 | # Zhop Management must be include in this script ! Not manage in this release ! 13 | # 14 | #------------------------------------------------------------------------------------------------------------------------------------ 15 | 16 | from ..Script import Script 17 | from UM.Logger import Logger 18 | from UM.Application import Application 19 | from UM.Message import Message 20 | import re #To perform the search 21 | from enum import Enum 22 | 23 | from UM.i18n import i18nCatalog # Translation 24 | catalog = i18nCatalog("cura") 25 | 26 | __version__ = '1.0' 27 | 28 | class Section(Enum): 29 | """Enum for section type.""" 30 | 31 | NOTHING = 0 32 | SKIRT = 1 33 | INNER_WALL = 2 34 | OUTER_WALL = 3 35 | INFILL = 4 36 | SKIN = 5 37 | SKIN2 = 6 38 | 39 | def is_begin_layer_line(line: str) -> bool: 40 | """Check if current line is the start of a layer section. 41 | 42 | Args: 43 | line (str): Gcode line 44 | 45 | Returns: 46 | bool: True if the line is the start of a layer section 47 | """ 48 | return line.startswith(";LAYER:") 49 | 50 | def is_begin_skirt_line(line: str) -> bool: 51 | """Check if current line is the start of a SKIRT section. 52 | 53 | Args: 54 | line (str): Gcode line 55 | 56 | Returns: 57 | bool: True if the line is the start of a SKIRT section 58 | """ 59 | return line.startswith(";TYPE:SKIRT") 60 | 61 | def is_begin_type_line(line: str) -> bool: 62 | """Check if current line is the start of a type section. 63 | 64 | Args: 65 | line (str): Gcode line 66 | 67 | Returns: 68 | bool: True if the line is the start of a type section 69 | """ 70 | return line.startswith(";TYPE") 71 | 72 | def is_begin_mesh_line(line: str) -> bool: 73 | """Check if current line is the start of a new MESH. 74 | 75 | Args: 76 | line (str): Gcode line 77 | 78 | Returns: 79 | bool: True if the line is the start of a new MESH 80 | """ 81 | return line.startswith(";MESH:") 82 | 83 | def is_e_line(line: str) -> bool: 84 | """Check if current line is a an Extruder line 85 | 86 | Args: 87 | line (str): Gcode line 88 | 89 | Returns: 90 | bool: True if the line is an Extruder line segment 91 | """ 92 | return "G1" in line and "E" in line 93 | 94 | def is_relative_extrusion_line(line: str) -> bool: 95 | """Check if current line is a relative extrusion line 96 | 97 | Args: 98 | line (str): Gcode line 99 | 100 | Returns: 101 | bool: True if the line is a relative extrusion line 102 | """ 103 | return "M83" in line 104 | 105 | def is_absolute_extrusion_line(line: str) -> bool: 106 | """Check if current line is an absolute extrusion line 107 | 108 | Args: 109 | line (str): Gcode line 110 | 111 | Returns: 112 | bool: True if the line is an absolute extrusion line 113 | """ 114 | return "M82" in line 115 | 116 | def is_relative_instruction_line(line: str) -> bool: 117 | """Check if current line contain a M83 / G91 instruction 118 | 119 | Args: 120 | line (str): Gcode line 121 | 122 | Returns: 123 | bool: True contain a M83 / G91 instruction 124 | """ 125 | return "G91" in line or "M83" in line 126 | 127 | def is_not_relative_instruction_line(line: str) -> bool: 128 | """Check if current line contain a M82 / G90 instruction 129 | 130 | Args: 131 | line (str): Gcode line 132 | 133 | Returns: 134 | bool: True contain a M82 / G90 instruction 135 | """ 136 | return "G90" in line or "M82" in line 137 | 138 | def is_reset_extruder_line(line: str) -> bool: 139 | """Check if current line contain a G92 E0 140 | 141 | Args: 142 | line (str): Gcode line 143 | 144 | Returns: 145 | bool: True contain a G92 E0 instruction 146 | """ 147 | return "G92" in line and "E0" in line 148 | 149 | def is_begin_skin_segment_line(line: str) -> bool: 150 | """Check if current line is the start of an skin. 151 | 152 | Args: 153 | line (str): Gcode line 154 | 155 | Returns: 156 | bool: True if the line is the start of an skin section 157 | """ 158 | return line.startswith(";TYPE:SKIN") 159 | 160 | class InfillLast(Script): 161 | def __init__(self): 162 | super().__init__() 163 | 164 | def getSettingDataString(self): 165 | return """{ 166 | "name": "InfillLast", 167 | "key": "InfillLast", 168 | "metadata": {}, 169 | "version": 2, 170 | "settings": 171 | { 172 | "extruder_nb": 173 | { 174 | "label": "Extruder Id", 175 | "description": "Define extruder Id in case of multi extruders", 176 | "unit": "", 177 | "type": "int", 178 | "default_value": 1 179 | } 180 | } 181 | }""" 182 | 183 | def execute(self, data): 184 | 185 | extrud = Application.getInstance().getGlobalContainerStack().extruderList 186 | relative_extrusion = bool(extrud[0].getProperty("relative_extrusion", "value")) 187 | 188 | 189 | 190 | if not relative_extrusion: 191 | Message("Must be in mode Relative extrusion", title = catalog.i18nc("@info:title", "Post Processing Skin Last")).show() 192 | return data 193 | 194 | extruder_id = self.getSettingValueByKey("extruder_nb") 195 | extruder_id = extruder_id -1 196 | 197 | # machine_extruder_count 198 | extruder_count=Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") 199 | extruder_count = extruder_count-1 200 | if extruder_id>extruder_count : 201 | extruder_id=extruder_count 202 | 203 | extrud = Application.getInstance().getGlobalContainerStack().extruderList 204 | 205 | # Check if Z hop is desactivated 206 | retraction_hop_enabled= extrud[extruder_id].getProperty("retraction_hop_enabled", "value") 207 | 208 | # Get the Cura retraction_hop and speed_z_hop as Zhop parameter 209 | retraction_hop = float(extrud[extruder_id].getProperty("retraction_hop", "value")) 210 | speed_z_hop = int(extrud[extruder_id].getProperty("speed_z_hop", "value")) 211 | speed_z_hop = speed_z_hop * 60 212 | speed_travel = extrud[0].getProperty("speed_travel", "value") 213 | speed_travel = speed_travel * 60 214 | 215 | idl=0 216 | 217 | current_z = 0 218 | Zr = "Z0" 219 | Zc = "Z0" 220 | 221 | currentlayer=0 222 | CurrentE=0 223 | ResetE=0 224 | RelativeExtruder = False 225 | SaveE = -1 226 | In_G0 = False 227 | 228 | for layer in data: 229 | layer_index = data.index(layer) 230 | # Logger.log('d', "Layer_index founded : {}".format(layer_index)) 231 | lines_skin = [] 232 | lines_not_skin = [] 233 | 234 | lines = layer.split("\n") 235 | 236 | for line_index, line in enumerate(lines): 237 | 238 | if line.startswith("G0") and "Z" in line : 239 | searchZ = re.search(r"Z(\d*\.?\d*)", line) 240 | if searchZ: 241 | current_z=float(searchZ.group(1)) 242 | Zc = "Z"+searchZ.group(1) 243 | 244 | # Check if the line start with the Comment Char 245 | if is_relative_instruction_line(line) and line[0] != ";" : 246 | relative_extrusion = True 247 | # Logger.log('d', "Relative_extrusion founded : {}".format(line)) 248 | 249 | # Check if the line start with the Comment Char 250 | if is_not_relative_instruction_line(line) and line[0] != ";" : 251 | relative_extrusion = False 252 | 253 | if is_reset_extruder_line(line) and line[0] != ";" : 254 | # Logger.log('d', "Reset_extruder :" + str(CurrentE)) 255 | CurrentE = 0 256 | SaveE = 0 257 | 258 | if is_relative_extrusion_line(line): 259 | RelativeExtruder = True 260 | 261 | if is_absolute_extrusion_line(line): 262 | RelativeExtruder = False 263 | 264 | if line.startswith(";LAYER_COUNT:"): 265 | # Logger.log("w", "found LAYER_COUNT %s", line[13:]) 266 | layercount=int(line[13:]) 267 | 268 | # ;LAYER:X 269 | if is_begin_layer_line(line): 270 | Logger.log('d', "layer_lines : {}".format(line)) 271 | currentlayer=int(line[7:]) 272 | if currentlayer == 0 : 273 | # Logger.log('d', "CurrentLayer : {}".format(currentlayer)) 274 | idl=1 275 | else: 276 | idl=0 277 | 278 | if is_begin_type_line(line) and idl > 0: 279 | if is_begin_skin_segment_line(line): 280 | idl=2 281 | Logger.log('d', 'layer_lines : {}'.format(line)) 282 | if retraction_hop_enabled : 283 | Logger.log('d', 'Output_Z : {}'.format(Output_Z)) 284 | Output_Z=current_z+retraction_hop 285 | outPutLine = "G1 F{} Z{:.3f}\n".format(speed_z_hop,Output_Z) 286 | lines_skin.append(outPutLine) 287 | In_G0 = True 288 | # Must integrate Zhop Management 289 | else : 290 | idl=1 291 | 292 | #--------------------------------------- 293 | # Add the Skin line to the Skin path 294 | #--------------------------------------- 295 | if idl == 2 : 296 | if line.startswith("G1") and In_G0 : 297 | Output_Z=current_z+retraction_hop 298 | Logger.log('d', 'Current_z : {}'.format(current_z)) 299 | outPutLine = "G0 F{} Z{:.3f}\n".format(speed_z_hop,Output_Z) 300 | Zr = "Z{:.3f}".format(Output_Z) 301 | outPutLine=line.replace(Zc, Zr) 302 | lines_skin.append(outPutLine.replace("G1", "G0")) 303 | Output_Z=current_z 304 | outPutLine = "G1 F{} Z{:.3f}\n".format(speed_z_hop,Output_Z) 305 | lines_skin.append(outPutLine) 306 | In_G0 = False 307 | 308 | lines_skin.append(line) 309 | 310 | if idl == 1 : 311 | lines_not_skin.append(line) 312 | 313 | # Logger.log('d', "idl : {}".format(idl)) 314 | if idl>0 : 315 | result = ";BEGIN_OF_MODIFICATION" 316 | for line in lines_not_skin: 317 | result += "\n" 318 | result += line 319 | 320 | for line in lines_skin: 321 | result += "\n" 322 | result += line 323 | 324 | result += ";END_OF_MODIFICATION\n" 325 | else: 326 | result = "\n".join(lines) 327 | 328 | data[layer_index] = result 329 | 330 | return data 331 | -------------------------------------------------------------------------------- /InhibFan.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: 5axes 5 | # Date: July 13, 2022 6 | # 7 | # Description: postprocessing-script to supress the Fan on First layers 8 | # 9 | #------------------------------------------------------------------------------------------------------------------------------------ 10 | # 11 | # Version 1.1 13/07/2022 12 | # 13 | #------------------------------------------------------------------------------------------------------------------------------------ 14 | 15 | from ..Script import Script 16 | from UM.Application import Application # To get the current printer's settings. 17 | from cura.CuraVersion import CuraVersion # type: ignore 18 | from UM.Message import Message 19 | from UM.Logger import Logger 20 | from UM.i18n import i18nCatalog # Translation 21 | catalog = i18nCatalog("cura") 22 | 23 | __version__ = '1.1' 24 | 25 | class InhibFan(Script): 26 | def __init__(self): 27 | super().__init__() 28 | 29 | def getSettingDataString(self): 30 | return """{ 31 | "name": "InhibFan", 32 | "key": "InhibFan", 33 | "metadata": {}, 34 | "version": 2, 35 | "settings": 36 | { 37 | "inhiblayer": 38 | { 39 | "label": "Nb of Layers for Fan inhibition", 40 | "description": "Number of Layer where we want to turn off the print cooling fan", 41 | "type": "int", 42 | "default_value": 4, 43 | "minimum_value": 1, 44 | "maximum_value": 100, 45 | "minimum_value_warning": 1, 46 | "maximum_value_warning": 100 47 | }, 48 | "extruder_nb": 49 | { 50 | "label": "Extruder Id", 51 | "description": "Define extruder Id in case of multi extruders", 52 | "unit": "", 53 | "type": "int", 54 | "default_value": 1 55 | } 56 | } 57 | }""" 58 | 59 | def execute(self, data): 60 | 61 | inhiblayer = 0 62 | inhiblayer = int(self.getSettingValueByKey("inhiblayer")) 63 | 64 | extruder_id = self.getSettingValueByKey("extruder_nb") 65 | extruder_id = extruder_id -1 66 | 67 | # GEt extrud 68 | extrud = Application.getInstance().getGlobalContainerStack().extruderList 69 | # machine_extruder_count 70 | extruder_count=Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") 71 | extruder_count = extruder_count-1 72 | if extruder_id>extruder_count : 73 | extruder_id=extruder_count 74 | 75 | cool_fan_speed = extrud[extruder_id].getProperty("cool_fan_speed", "value") 76 | Logger.log('d', 'cool_fan_speed : {}'.format(cool_fan_speed)) 77 | setfan = int((int(cool_fan_speed)/100)*255) 78 | 79 | current_Layer = 0 80 | idl=0 81 | 82 | for layer in data: 83 | layer_index = data.index(layer) 84 | 85 | lines = layer.split("\n") 86 | for line in lines: 87 | 88 | if line.startswith(";LAYER:") and inhiblayer > 0 : 89 | current_Layer = int(line.split(":")[1]) 90 | current_Layer += 1 91 | # If the layer number is smaller than the Nb of Layers for Fan inhibition 92 | if current_Layer <= inhiblayer : 93 | idl=1 94 | # Special analyse for the following layer 95 | elif current_Layer == (inhiblayer + 1) : 96 | line_index = lines.index(line) 97 | next_line=lines[line_index+1] 98 | Logger.log('d', 'next_line : {}'.format(next_line)) 99 | # If we have a Fan Instruction leave It 100 | if next_line.startswith("M106 S") : 101 | Logger.log('d', 'Keep the S Value layer {}'.format(current_Layer)) 102 | # If we have a Fan turned off leave it like that 103 | elif next_line.startswith("M107") : 104 | Logger.log('d', 'Keep the Fan OFF layer {}'.format(current_Layer)) 105 | # If we have nothing then we need to set the fan value to the regular speed 106 | else : 107 | lines.insert(line_index + 1, "M106 S"+str(setfan)) 108 | idl=0 109 | 110 | else : 111 | idl=0 112 | 113 | if line.startswith("M106 S") and idl == 1 : 114 | line_index = lines.index(line) 115 | lines[line_index] = "M107" 116 | 117 | result = "\n".join(lines) 118 | data[layer_index] = result 119 | 120 | return data 121 | -------------------------------------------------------------------------------- /InsertAtLayerChange.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Ultimaker B.V. 2 | # Cura is released under the terms of the LGPLv3 or higher. 3 | # Created by Wayne Porter 4 | # Modification 5@xes 12/2020 Option increment_layer 5 | 6 | from ..Script import Script 7 | 8 | class InsertAtLayerChange(Script): 9 | def __init__(self): 10 | super().__init__() 11 | 12 | def getSettingDataString(self): 13 | return """{ 14 | "name": "Insert at layer change", 15 | "key": "InsertAtLayerChange", 16 | "metadata": {}, 17 | "version": 2, 18 | "settings": 19 | { 20 | "insert_location": 21 | { 22 | "label": "When to insert", 23 | "description": "Whether to insert code before or after layer change.", 24 | "type": "enum", 25 | "options": {"before": "Before", "after": "After"}, 26 | "default_value": "before" 27 | }, 28 | "increment_layer": 29 | { 30 | "label": "Layer increment", 31 | "description": "Number of layer increment to add this code", 32 | "type": "int", 33 | "value": "1", 34 | "minimum_value": "1" 35 | }, 36 | "gcode_to_add": 37 | { 38 | "label": "G-code to insert.", 39 | "description": "G-code to add before or after layer change.", 40 | "type": "str", 41 | "default_value": "" 42 | } 43 | } 44 | }""" 45 | 46 | def execute(self, data): 47 | increment = self.getSettingValueByKey("increment_layer") 48 | gcode_to_add = ";Code inserted by script : \n" 49 | tablines = self.getSettingValueByKey("gcode_to_add").split("
") 50 | for line in tablines: 51 | gcode_to_add = gcode_to_add + line + "\n" 52 | cur_inc=1 53 | 54 | for layer in data: 55 | # Check that a layer is being printed 56 | lines = layer.split("\n") 57 | for line in lines: 58 | if ";LAYER:" in line: 59 | index = data.index(layer) 60 | if increment == cur_inc : 61 | if self.getSettingValueByKey("insert_location") == "before": 62 | layer = gcode_to_add + layer 63 | else: 64 | layer = layer + gcode_to_add 65 | data[index] = layer 66 | cur_inc=1 67 | else : 68 | cur_inc+=1 69 | 70 | break 71 | return data 72 | -------------------------------------------------------------------------------- /InsertAtLayerNumber.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 5@xes 2 | # Insert At Layer Number Add comment 08/02/2023 3 | 4 | from ..Script import Script 5 | 6 | class InsertAtLayerNumber(Script): 7 | def __init__(self): 8 | super().__init__() 9 | 10 | def getSettingDataString(self): 11 | return """{ 12 | "name": "Insert at layer number", 13 | "key": "InsertAtLayerNumber", 14 | "metadata": {}, 15 | "version": 2, 16 | "settings": 17 | { 18 | "insert_location": 19 | { 20 | "label": "When to insert", 21 | "description": "Whether to insert code before or after layer change.", 22 | "type": "enum", 23 | "options": {"before": "Before", "after": "After"}, 24 | "default_value": "before" 25 | }, 26 | "position_layer": 27 | { 28 | "label": "Layer position", 29 | "description": "Layer Position where to add this code", 30 | "type": "int", 31 | "value": 1, 32 | "minimum_value": 1 33 | }, 34 | "gcode_to_add": 35 | { 36 | "label": "G-code to insert.", 37 | "description": "G-code to add before or after layer change.", 38 | "type": "str", 39 | "default_value": "" 40 | } 41 | } 42 | }""" 43 | 44 | def execute(self, data): 45 | position = self.getSettingValueByKey("position_layer") 46 | gcode_to_add = ";Code inserted by script : \n" 47 | tablines = self.getSettingValueByKey("gcode_to_add").split("
") 48 | for line in tablines: 49 | gcode_to_add = gcode_to_add + line + "\n" 50 | 51 | for layer in data: 52 | # Check that a layer is being printed 53 | lines = layer.split("\n") 54 | for line in lines: 55 | if ";LAYER:" in line: 56 | layer_number = int(line.split(":")[1])+1 57 | index = data.index(layer) 58 | if position == layer_number : 59 | if self.getSettingValueByKey("insert_location") == "before": 60 | layer = gcode_to_add + layer 61 | else: 62 | layer = layer + gcode_to_add 63 | data[index] = layer 64 | 65 | break 66 | return data 67 | -------------------------------------------------------------------------------- /KlipperPrintArea.py: -------------------------------------------------------------------------------- 1 | ''' Based on script by frankbags@https://gist.github.com/frankbags/c85d37d9faff7bce67b6d18ec4e716ff ''' 2 | import re # To perform the search and replace. 3 | from ..Script import Script 4 | 5 | 6 | class KlipperPrintArea(Script): 7 | def __init__(self): 8 | super().__init__() 9 | 10 | def getSettingDataString(self): 11 | return """{ 12 | "name": "Klipper print area mesh", 13 | "key": "KlipperPrintArea", 14 | "metadata": {}, 15 | "version": 2, 16 | "settings":{} 17 | }""" 18 | 19 | def execute(self, data): 20 | 21 | minMaxXY = {'MINX': 0, 'MINY': 0, 'MAXX': 0, 'MAXY': 0} 22 | startGcodeLineData = '' 23 | 24 | for layerNumber, layerData in enumerate(data): 25 | 26 | # search for print area boundary 27 | for k, v in minMaxXY.items(): 28 | result = re.search(str(k)+":(\d*\.?\d*)", layerData) 29 | if result is not None: 30 | minMaxXY[k] = result.group(1) 31 | # search for set print area macro 32 | areaStartGcode = re.search( 33 | ".*%(MINX|MAXX|MINY|MAXY)%.*", layerData) 34 | # replace print area template 35 | if areaStartGcode is not None: 36 | if not startGcodeLineData: 37 | startGcodeLineData = layerData 38 | for k, v in minMaxXY.items(): 39 | pattern3 = re.compile('%' + k + '%') 40 | startGcodeLineData = re.sub( 41 | pattern3, v, startGcodeLineData) 42 | data[layerNumber] = startGcodeLineData 43 | 44 | return data 45 | 46 | 47 | # start g-code format 48 | # START_PRINT EXTRUDER_TEMP={material_print_temperature_layer_0} BED_TEMP={material_bed_temperature_layer_0} AREA_START=%MINX%,%MINY% AREA_END=%MAXX%,%MAXY% -------------------------------------------------------------------------------- /LevelingMeshOptimizer.py: -------------------------------------------------------------------------------- 1 | # 2 | # Author : CCS86 3 | # Source initial : https://forum.duet3d.com/topic/14994/f-r-auto-define-m557-mesh-bounds-from-gcode/5?_=1637506151764 4 | # We have a single parameter (mesh spacing), and it parses the first layer gcode for min/max X and Y coordinates, and then replaces the M557 line in your start gcode. 5 | # You must have a static mesh leveling command in your start gcode, like: M557 X0:200 Y0:200 S20 6 | # This command wil be replace by the new M557 command based on the dimmension in the initial G-Code 7 | # 8 | # M557 : https://reprap.org/wiki/G-code#M557:_Set_Z_probe_point_or_define_probing_grid 9 | # 10 | 11 | import re 12 | 13 | from ..Script import Script 14 | 15 | class LevelingMeshOptimizer(Script): 16 | def getSettingDataString(self): 17 | return """{ 18 | "name": "Leveling Mesh Optimizer", 19 | "key": "LevelingMeshOptimizer", 20 | "metadata": {}, 21 | "version": 2, 22 | "settings": { 23 | "spacing": { 24 | "label": "Spacing", 25 | "description": "How far apart to space the probe points within the mesh", 26 | "unit": "mm", 27 | "type": "float", 28 | "default_value": 10 29 | } 30 | } 31 | }""" 32 | 33 | 34 | ## Calculates and fills in the bounds of the first layer. 35 | # \param data A list of lines of GCODE representing the entire print. 36 | # \return A similar list, with the bounds of the mesh filled in. 37 | def execute(self, data: [str]) -> [str]: 38 | _DATA_START_GCODE = 1 39 | _DATA_LAYER_0 = 2 40 | 41 | # Calculate bounds of first layer 42 | bounds = self.findBounds(data[_DATA_LAYER_0]) 43 | 44 | # Fill in bounds in start GCODE 45 | data[_DATA_START_GCODE] = self.fillBounds(data[_DATA_START_GCODE], bounds) 46 | 47 | return data 48 | 49 | 50 | ## Finds the minimum and maximum X and Y coordinates in a GCODE layer. 51 | # \param data A block of GCODE representing the layer. 52 | # \return A dict such that [X|Y][min|max] resolves to a float 53 | def findBounds(self, data: str) -> {str: {str: float}}: 54 | bounds = { 55 | "X": {"min": float("inf"), "max": float("-inf")}, 56 | "Y": {"min": float("inf"), "max": float("-inf")}, 57 | } 58 | 59 | for line in data.split("\n"): 60 | # Get coordinates on this line 61 | for match in re.findall(r"([XY])([\d.]+)\s", line): 62 | # Get axis letter 63 | axis = match[0] 64 | 65 | # Skip axes we don't care about 66 | if axis not in bounds: 67 | continue 68 | 69 | # Parse parameter value 70 | value = float(match[1]) 71 | 72 | # Update bounds 73 | bounds[axis]["min"] = round(min(bounds[axis]["min"], value),0) 74 | bounds[axis]["max"] = round(max(bounds[axis]["max"], value),0) 75 | 76 | return bounds 77 | 78 | 79 | ## Replaces the M557 command in the start GCODE so that the bounds are filled in. 80 | # \param data The entire start GCODE block. 81 | # \return The same GCODE but with the bounds of the mesh filled in. 82 | def fillBounds(self, data: str, bounds: {str: {str: float}}) -> str: 83 | # Fill in the level command template 84 | new_cmd = "M557 X%.1f:%.1f Y%.1f:%.1f S%.1f ; Leveling mesh defined by LevelingMeshOptimizer" % ( 85 | bounds["X"]["min"]-1, bounds["X"]["max"], 86 | bounds["Y"]["min"]-1, bounds["Y"]["max"], 87 | self.getSettingValueByKey("spacing"), 88 | ) 89 | 90 | # Replace M557 command in GCODE 91 | return re.sub(r"^M557 .*$", new_cmd, data, flags=re.MULTILINE) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cura-Postprocessing-Scripts 2 | Compilation of personal Ultimaker Cura postprocessing scripts 3 | 4 | 5 | Installation 6 | -- 7 | 8 | The files must be stored in the user script directory of the respective Cura version: **\AppData\Roaming\cura\ (current version of cura)\scripts** 9 | 10 | After the next start of Cura, the script can be added via Extension / Post-Processing / Modify G-Code Add a script. 11 | 12 | ![Adding script](./images/plugins.jpg) 13 | 14 | 15 | [DisplayPrintInfosOnLCD.py](DisplayPrintInfosOnLCD.py) 16 | ----- 17 | 18 | Description: This plugin shows custom messages about your print on the Printer Panel... 19 | Please look at the option 20 | - LayerId: Use the Layer ID coded in the Gcode and not an increment starting from 0 21 | 22 | ![DisplayPrintInfosOnLCD.py](./images/PrintInfos.jpg) 23 | 24 | [GCodeDocumentation.py](GCodeDocumentation.py) 25 | ----- 26 | Description: Add slicing parameter in the GCode Header 27 | 28 | ![GCodeDocumentation.py](./images/GcodeDocumentation.jpg) 29 | 30 | [SpeedTower.py](SpeedTower.py) 31 | ----- 32 | Description: postprocessing-script to easily define a Speed Tower. 33 | 34 | ![SpeedTower.py](./images/speedtower.jpg) 35 | 36 | This script offers the possibilities to modify : 37 | - Speed ( By using M220 instruction ) 38 | - Acceleration 39 | - Jerk 40 | - Junction Deviation 41 | - Marlin Linear Advance 42 | - RepRap Pressure Advance 43 | 44 | [TempFanTower.py](TempFanTower.py) 45 | ----- 46 | 47 | Description: postprocessing-script to easily use a temptower and not use 10 changeAtZ-scripts 48 | 49 | The default values are for this temptower PLA model [https://www.thingiverse.com/thing:2493504](https://www.thingiverse.com/thing:2493504) 50 | - Temp Tower PLA de 210 à 170 51 | - Possibility to define also a Fan Tower, Fan percentage speed indicate with a semi-colon as separator 52 | 53 | ![TempFanTower.py](./images/tempfan.jpg) 54 | 55 | 56 | [RetractTower.py](RetractTower.py) 57 | ----- 58 | 59 | Description: postprocessing-script to easily create a Retract Tower 60 | 61 | Two options : 62 | 63 | - Speed : Speed variation 64 | 65 | - Retract : Distance retract variation 66 | 67 | ![RetractTower.py](./images/retract-tower.jpg) 68 | 69 | 70 | [FanIroning.py](FanIroning.py) 71 | ----- 72 | 73 | Description: FlowIroning for 3D prints. Change the Fan value on ironing operation. 74 | 75 | ![FanIroning.py](./images/FanIroning.jpg) 76 | 77 | Note: Some ironing areas are not detected by the script. It is mainly the small areas located in contour areas. 78 | 79 | ![Sample not detected area](./images/NotDetectedFanIroning.jpg) 80 | 81 | [RepRapPrintInfos.py](RepRapPrintInfos.py) 82 | ----- 83 | 84 | Description: add header info and part thumbnail for RepRap machine 3DWOX 85 | 86 | ![part thumbnail](./images/benchy.jpg) 87 | 88 | **Not fully tested** 89 | 90 | [GradientInfill.py](GradientInfill.py) (original idea from [CNCKitchen](https://github.com/CNCKitchen/GradientInfill) ) 91 | ----- 92 | 93 | **Version: 1.5** 94 | 95 | GradientInfill.py Postprocessing Script for Cura Plugin. Save the file in the _C:\Program Files\Ultimaker Cura **X.X**\plugins\PostProcessingPlugin\scripts_ directory. 96 | 97 | Extrusion mode in Cura must be set in relative mode. If it's not the case an error message will be raised in Cura. 98 | 99 | ![Message](https://user-images.githubusercontent.com/11015345/72720216-c1662580-3b79-11ea-9583-60de8240eef2.jpg) 100 | 101 | No Gcode will be generated by Cura in this case. Same behavior if Cura settings are not suitable for Gradient Infill modification : 102 | 103 | - Infill pattern types ZigZag, Concentric, Cross, and Cross3D are not allowed 104 | - In Cura, the option "Connect Infill Lines" for the other patterns mustn't be used. 105 | 106 | The wall must be done before the Infill element. So in Cura, the Option infill_before_walls must be set to Off 107 | 108 | **Postprocessing Options** 109 | 110 | ![PostProcessing](./images/gradient.jpg) 111 | 112 | - Gradient Distance : Distance of the gradient (max to min) in mm 113 | - Gradient Discretization : Only applicable for linear infills; number of segments within the gradient(segmentLength=gradientThickness / gradientDiscretization) use sensible values to not overload 114 | - Max flow : Maximum extrusion flow 115 | - Min flow : Minimum extrusion flow 116 | - Short distance flow : Extrusion flow for short distance < 2x Gradient distance 117 | - Gradual speed : Activate also Gradual Speed linked to the gradual flow 118 | - Max over speed : Maximum over speed factor 119 | - Min over speed : Minimum over speed factor 120 | - Extruder Id : Define extruder Id in case of multi extruders 121 | - Test with outer wall : "Test the gradient with the outer wall segments 122 | 123 | 124 | A new Flow Value for a short distance (Linear move < 2 x Gradient distance) was added to the standard GradientInfill script. 125 | 126 | Add a gradual speed variation for a machine without a direct drive extruder. 127 | 128 | ![gradient infill](./images/gradient2.jpg) 129 | 130 | Sample part with a Gradient distance set to 8 mm : 131 | ![Gradient distance set to 8 mm](./images/gradient3.jpg) 132 | 133 | [CommentGCode.py](CommentGCode.py) 134 | ---- 135 | 136 | This Cura Postprocessing Script adds comments to the G-Code. The user can select or deselect comments for M-Commands and G-Commands separately. 137 | 138 | G0 and G1 commands are only commented if a retract is included. 139 | 140 | Command, description, and parameters are read from a CSV file. If a command is not contained, the required data is determined once via the website http://marlinfw.org/docs/gcode/ and added to the CSV file. 141 | 142 | ![CommentGCode.py](./images/commentGcode.jpg) 143 | 144 | 145 | [InsertAtLayerChange.py](InsertAtLayerChange.py) 146 | ---- 147 | 148 | Modification of the official script with Layer Increment. 149 | 150 | [InsertAtLayerNumber.py](InsertAtLayerNumber.py) 151 | ---- 152 | 153 | Insert a Gcode at Layer Number. Can be inserted before or after the Layer Number. 154 | 155 | ![InsertAtLayerNumber.py](./images/InsertAtLayerNumber.jpg) 156 | 157 | 158 | [FastFirstInfill.py](FastFirstInfill.py) 159 | ---- 160 | 161 | Script cura to set a higher filling speed for the first layer. 162 | 163 | ![FastFirstInfill.py](./images/fastfirstinfill.jpg) 164 | 165 | 166 | Original first layer speed before modification 167 | 168 | 169 | ![First layer speed](./images/origine.png) 170 | 171 | First layer speed after modification with the script. 172 | 173 | 174 | ![First layer speed after modification](./images/result.png) 175 | 176 | [CheckFirstSpeed.py](CheckFirstSpeed.py) 177 | ---- 178 | 179 | Script to modify the first layer infill and Check the first Wall Speed Bug Cura 5.6 180 | 181 | ![CheckFirstSpeed.py](./images/CheckFirstSpeed.jpg) 182 | 183 | Script used to fix a bug in Cura versions 5.5 and 5.6 [bug on github](https://github.com/Ultimaker/Cura/issues/17594) 184 | 185 | Original first layer speed before modification 186 | 187 | 188 | ![First layer speed](./images/CheckFirstSpeedorigine.png) 189 | 190 | First layer speed after modification with the script and a first infill speed of 30 mm/s. 191 | 192 | 193 | ![First layer speed after modification](./images/CheckFirstSpeedresult.png) 194 | 195 | 196 | [SlowZ.py](SlowZ.py) 197 | ---- 198 | 199 | Script cura to reduce the speed according to the print height. 200 | 201 | ![SlowZ.py](./images/slowz.jpg) 202 | 203 | - Slow Z percentage : Positive value to slow the print as the z value rises up to this percentage. 204 | 205 | - Slow Z height : Positive value to define the start height of the speed reduction. 206 | 207 | - Display details on LCD : This setting will insert M117 gcode instructions, to display the current modification in the G-Code being used. 208 | 209 | ![SlowZ settings](./images/slowzsettings.jpg) 210 | 211 | 212 | 213 | [MultiBrim.py](MultiBrim.py) 214 | ---- 215 | 216 | Script cura to duplicate the initial Brim/skirt to obtain a thick Brim. 217 | 218 | 219 | **Note :** Updated for Cura 5.0 & 5.1 220 | 221 | 222 | ![MultiBrim.py](./images/multibrim.jpg) 223 | 224 | - Brim addition : Number of brims to add to the existing one. 225 | 226 | - Brim speed : Speed for the subsequent brims. 227 | 228 | Multi-layer brim height request on Cura Github [#6929](https://github.com/Ultimaker/Cura/issues/6929) 229 | 230 | Different tests made with the current release (V1.6) 231 | 232 | ![MultiBrim test](./images/multilayerbrim.svg) 233 | 234 | 235 | [ChangeFanValue.py](ChangeFanValue.py) 236 | ---- 237 | 238 | Script Cura to change the FAN value when we are in the case of Minimum Layer Time. When we are in a minimum Layer Time situation the Script fixes the FAN values to the **Fan value in %** 239 | 240 | ![ChangeFanValue.py](./images/ChangeFanValue.jpg) 241 | 242 | Note : In GCode 100% = 255 for the Fan Value *M106S255*. 243 | 244 | 245 | [InhibFan.py](InhibFan.py) 246 | ---- 247 | 248 | Script Cura to turn off the print cooling fan on the first X Layers. 249 | 250 | ![InhibFan.py](./images/InhibFan.jpg) 251 | 252 | Note : In GCode turn off the print cooling fan. [M107](https://marlinfw.org/docs/gcode/M107.html). 253 | 254 | [ZMoveG0.py](ZMoveG0.py) 255 | ----- 256 | 257 | Description: Z hop for every G0 displacement even if Retraction is not defined ( Retraction must be deactivated). Use the speed_z_hop and the retraction_hop as retraction parameters. 258 | 259 | ![ZMoveG0.py](./images/ZmoveG0.jpg) 260 | 261 | Errors messages if Retraction is activated. 262 | 263 | ![Error ZMoveG0](./images/ErrorZmoveG0.jpg) 264 | 265 | 266 | [ZMoveIroning.py](ZMoveIroning.py) 267 | ----- 268 | 269 | Description: ZMoveIroning for 3D prints. Z hop for ironing 270 | 271 | ![ZMoveIroning.py](./images/ZmoveIroning.jpg) 272 | 273 | 274 | [SpoonOrder.py](SpoonOrder.py) 275 | ----- 276 | 277 | Description: SpoonOrder is a script used in the [spoon Anti-warping Plugin](https://github.com/5axes/SpoonAntiWarping), Print every spoon tabs first. 278 | 279 | ![SpoonOrder.py](./images/SpoonOrder.png) 280 | 281 | 282 | [InfillLast.py](InfillLast.py) 283 | ----- 284 | 285 | Description: InfillLast print for the first layer every Infill path at the end of the First Layer. ( Work in progress ) 286 | 287 | ![InfillLast.py](./images/InfillLast.png) 288 | 289 | 290 | 291 | [AlterZhops.py](AlterZhops.py) 292 | ----- 293 | 294 | Author : Greg Foresi [(GregValiant)](https://github.com/GregValiant) 295 | 296 | Description: This script changes the Z-Hop height from the beginning of the 'Start Layer' to the end of the 'End Layer'. If the new hop height is 0.0 it negates the z-hop movement. The Z-hop command lines are altered, not removed. 297 | 298 | - This script supports different hop heights for up to 4 extruders. 299 | - Z-Hops at tool change are not affected when 'Alter Z-Hops' runs BEFORE other post-processors that make code insertions just before Tool Change lines 300 | - Adaptive Layers is not compatible and there is an exit if it is enabled in Cura 301 | - Z-Hops must be enabled for at least one extruder in Cura or the plugin exits. 302 | 303 | ![AlterZhops.py](./images/AlterZhops.png) 304 | 305 | 306 | 307 | [DiagonalZHop.py](DiagonalZHop.py) 308 | ----- 309 | 310 | Modifiy existing Zhop to get Diagonal ZHop for 3D prints. 311 | -------------------------------------------------------------------------------- /RepRapPrintInfos.py: -------------------------------------------------------------------------------- 1 | # Cura PostProcessingPlugin 2 | # Author: 5axes 3 | # Date: Mars 06 2020 4 | # Modification : Mars 07 2020 5 | # Ajout de l'intégration du fichier RepRapPrintInfos.txt pour image impression 6 | # https://3dprinter.sindoh.com/fr/support/downloads/3dwox1 7 | # Modification : 25/05/2020 add thumbnail_gcode 8 | # Description: Ajout des infos pour machine RepRap machine 3DWOX 9 | 10 | from ..Script import Script 11 | 12 | 13 | from cura.CuraApplication import CuraApplication 14 | from cura.Snapshot import Snapshot 15 | from PyQt5.QtCore import Qt, QByteArray, QBuffer, QIODevice 16 | from PyQt5.QtGui import QImage 17 | from typing import List 18 | 19 | from UM.Logger import Logger 20 | from UM.Message import Message 21 | 22 | import string 23 | import os 24 | import textwrap 25 | 26 | class RepRapPrintInfos(Script): 27 | 28 | GCODE_LINE_PREFIX = "; " 29 | GCODE_LINE_WIDTH = 80 30 | 31 | def __init__(self): 32 | super().__init__() 33 | 34 | def _image_to_byte_array(self, image) -> QByteArray: 35 | byte_array = QByteArray() 36 | buffer = QBuffer(byte_array) 37 | buffer.open(QIODevice.WriteOnly) 38 | image.save(buffer, 'png') 39 | buffer.close() 40 | return byte_array 41 | 42 | def _image_to_base64(self, image) -> QByteArray: 43 | ba = self._image_to_byte_array(image) 44 | ba64 = ba.toBase64() 45 | return ba64 46 | 47 | def _txt_to_gcode(self, txt) -> str: 48 | wrapper = textwrap.TextWrapper(width=self.GCODE_LINE_WIDTH) 49 | Display_txt = wrapper.fill(txt) 50 | lines = Display_txt.split("\n") 51 | _Final_Txt="" 52 | _Counter = Display_txt.count('\n')+1 53 | for currentLine in lines: 54 | line_index = lines.index(currentLine)+1 55 | _Final_Txt += ";IMAGE[%d/%d] %s\n" % (line_index, _Counter, currentLine) 56 | 57 | return _Final_Txt 58 | 59 | def _create_snapshot(self, width, height): 60 | # must be called from the main thread because of OpenGL 61 | Logger.log("d", "Creating thumbnail image...") 62 | try: 63 | snapshot = Snapshot.snapshot(width = width, height = height) 64 | return snapshot 65 | except Exception: 66 | Logger.logException("w", "Failed to create snapshot image") 67 | return None 68 | 69 | def _create_thumbnail_gcode(self, width, height) -> str: 70 | min_size = min(width,height) 71 | tmp_snapshot = self._create_snapshot(min_size, min_size) 72 | # Scale it to the correct size 73 | if (width != height): 74 | snapshot = tmp_snapshot.copy(int((min_size-width)/2), int((min_size-height)/2), width, height) 75 | else: 76 | snapshot = tmp_snapshot 77 | 78 | ba64 = self._image_to_base64(snapshot) 79 | b64str = str(ba64, 'utf-8') 80 | b64gcode = self._txt_to_gcode(b64str) 81 | gcode = "\n" + self.GCODE_LINE_PREFIX + "\n" + \ 82 | self.GCODE_LINE_PREFIX + "thumbnail begin " + str(width) + "x" + str(height) + " " + str(len(b64str)) + "\n" + \ 83 | b64gcode + "\n" + \ 84 | self.GCODE_LINE_PREFIX + "thumbnail end\n" + self.GCODE_LINE_PREFIX + "\n" 85 | 86 | return gcode 87 | 88 | def getSettingDataString(self): 89 | return """{ 90 | "name": "Add 3DWOX Print Infos", 91 | "key": "RepRapPrintInfos", 92 | "metadata": {}, 93 | "version": 2, 94 | "settings": 95 | { 96 | "LayerId": 97 | { 98 | "label": "Utilisation Layer Id G-Code", 99 | "description": "Utilise le Layer Id codé dans le fichier G-Code. A utiliser pour impression pièce à pièce", 100 | "type": "bool", 101 | "default_value": false 102 | }, 103 | "thumbnail_width": 104 | { 105 | "label": "Thumbnail Width", 106 | "description": "Width of the thumbnail", 107 | "unit": "pixels", 108 | "type": "int", 109 | "default_value": 47, 110 | "minimum_value": "16", 111 | "minimum_value_warning": "16" 112 | }, 113 | "thumbnail_height": 114 | { 115 | "label": "Thumbnail Height", 116 | "description": "Height of the thumbnail", 117 | "unit": "pixels", 118 | "type": "int", 119 | "default_value": 47, 120 | "minimum_value": "16", 121 | "minimum_value_warning": "16" 122 | } 123 | } 124 | }""" 125 | 126 | def execute(self, data): 127 | max_layer = 0 128 | total_time = 0 129 | part = 0 130 | total_time_string = "" 131 | total_time_s=0 132 | current_time_s=0 133 | current_time_string = "" 134 | MCode = "M532" 135 | # Init variable pour éviter erreur si code non présent ou ordre différent 136 | min_x=0 137 | min_y=0 138 | min_z=0 139 | max_x=0 140 | max_y=0 141 | max_z=0 142 | percent=0 143 | 144 | thumbnail_width = self.getSettingValueByKey("thumbnail_width") 145 | thumbnail_height = self.getSettingValueByKey("thumbnail_height") 146 | 147 | 148 | 149 | Id = 1 150 | for layer in data: 151 | display_text = MCode + " L" + str(Id) 152 | layer_index = data.index(layer) 153 | lines = layer.split("\n") 154 | for line in lines: 155 | line_index = lines.index(line) 156 | 157 | if line.startswith(";FLAVOR:"): 158 | Logger.log("d", "Adding thumbnail image, resolution=" + str(thumbnail_width) + "x" + str(thumbnail_height)) 159 | thumbnail_gcode = self._create_thumbnail_gcode(thumbnail_width, thumbnail_height) 160 | tablines = thumbnail_gcode.split("\n") 161 | Ind=1 162 | for Cline in tablines: 163 | lines.insert(line_index + Ind, Cline) 164 | Ind += 1 165 | 166 | 167 | if line.startswith(";MINX:"): 168 | min_x = float(line.split(":")[1]) # Recuperation MINX 169 | if line.startswith(";MINY:"): 170 | min_y = float(line.split(":")[1]) # Recuperation MINY 171 | if line.startswith(";MINZ:"): 172 | min_z = float(line.split(":")[1]) # Recuperation MINZ 173 | if line.startswith(";MAXX:"): 174 | max_x = float(line.split(":")[1]) # Recuperation MAXX 175 | if line.startswith(";MAXY:"): 176 | max_y = float(line.split(":")[1]) # Recuperation MAXY 177 | if line.startswith(";MAXZ:"): 178 | max_z = float(line.split(":")[1]) # Recuperation MAXZ 179 | 180 | dimension_string = "{:05.1f}:{:05.1f}:{:05.1f}".format((max_x-min_x), (max_y-min_y), max_z) 181 | display_text = ";DIMENSION: [" + dimension_string + "]" 182 | lines.insert(line_index + 1, display_text) 183 | 184 | if line.startswith(";Filament used:"): 185 | Filament = line.split(":")[1] # Recuperation Filament used: 186 | Filament = Filament.split("m")[0] 187 | 188 | Filament_Used=float(Filament) 189 | Filament_MM=Filament_Used*1000 190 | 191 | display_text = ";ESTIMATION_FILAMENT: [" + str(int(Filament_MM)) + "]" 192 | lines.insert(line_index + 1, display_text) 193 | 194 | if line.startswith(";LAYER_COUNT:"): 195 | max_layer = line.split(":")[1] # Recuperation Nb Layer Maxi 196 | display_text = ";TOTAL_LAYER: ["+ str(max_layer) + "]" 197 | lines.insert(line_index + 1, display_text) 198 | 199 | # ECRITURE M532 200 | if line.startswith(";LAYER:"): 201 | # Logger.log('d', 'X Pourcentage : {}'.format(percent)) 202 | percent_string = " X{:d}".format(int(percent)) 203 | display_text = MCode + percent_string + " L" + str(Id) 204 | 205 | lines.insert(line_index + 1, display_text) # Insert du code M532 apres les layers 206 | 207 | if self.getSettingValueByKey("LayerId"): 208 | Id = int(line.split(":")[1]) # Utilise le Layer dans G-Code ;LAYER:1 209 | if Id == 0: 210 | part += 1 # Incrémente le numero de pièce 211 | Id += 1 212 | else: 213 | Id += 1 # Incrémente le numero de Layer (sans utiliser celui du Gcode) 214 | 215 | if line.startswith(";TIME:"): 216 | total_time = int(line.split(":")[1]) 217 | m, s = divmod(total_time, 60) # Decomposition en 218 | h, m = divmod(m, 60) # heures, minutes et secondes 219 | total_time_string = "{:04d}:{:02d}:{:02d}".format(int(h), int(m), int(s)) 220 | total_time_s= (h*3600)+(m*60)+s 221 | current_time_string = total_time_string 222 | display_text = ";ESTIMATION_TIME: [" + total_time_string + "]" 223 | lines.insert(line_index + 1, display_text) 224 | 225 | 226 | elif line.startswith(";TIME_ELAPSED:"): 227 | current_time = float(line.split(":")[1]) 228 | m1, s1 = divmod(current_time, 60) # Decomposition en 229 | h1, m1 = divmod(m1, 60) # heures, minutes et secondes 230 | current_time_s= (h1*3600)+(m1*60)+s1 231 | if total_time_s>0 : 232 | percent=(current_time_s/total_time_s)*100 233 | if percent>100 : 234 | percent=100 235 | current_time_string = "{:04d}:{:04d}:{:02d}".format(int(h1), int(m1), int(s1)) 236 | # Logger.log('d', 'Pourcentage : {}'.format(percent)) 237 | 238 | final_lines = "\n".join(lines) 239 | data[layer_index] = final_lines 240 | 241 | return data 242 | -------------------------------------------------------------------------------- /RetractContinue.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023 UltiMaker B.V. 2 | # The PostProcessingPlugin is released under the terms of the AGPLv3 or higher. 3 | # Altered 06-01-2023 by GregValiant (Greg Foresi) 4 | # Added regex to check for Zhop lines because they were interfering with the script during combing. 5 | # Changed the significant digits to: F=0, X=3, Y=3, Z=2 6 | 7 | from ..Script import Script 8 | import re 9 | from UM.Application import Application # To get current absolute/relative setting. 10 | from UM.Math.Vector import Vector 11 | 12 | from typing import List, Tuple 13 | 14 | 15 | class RetractContinue(Script): 16 | """Continues retracting during all travel moves.""" 17 | 18 | def getSettingDataString(self) -> str: 19 | return """{ 20 | "name": "Retract Continue", 21 | "key": "RetractContinue", 22 | "metadata": {}, 23 | "version": 2, 24 | "settings": 25 | { 26 | "extra_retraction_speed": 27 | { 28 | "label": "Extra Retraction Ratio", 29 | "description": "How much does it retract during the travel move, by ratio of the travel length.", 30 | "unit": "mmRet/mmDist", 31 | "type": "float", 32 | "default_value": 0.05 33 | } 34 | } 35 | }""" 36 | 37 | def _getTravelMove(self, travel_move: str, default_pos: Vector) -> Tuple[Vector, float]: 38 | travel = Vector( 39 | self.getValue(travel_move, "X", default_pos.x), 40 | self.getValue(travel_move, "Y", default_pos.y), 41 | self.getValue(travel_move, "Z", default_pos.z) 42 | ) 43 | f = self.getValue(travel_move, "F", -1.0) 44 | return travel, f 45 | 46 | def _travelMoveString(self, travel: Vector, f: float, e: float) -> str: 47 | # Note that only G1 moves are written, since extrusion is included. 48 | if f <= 0.0: 49 | return f"G1 X{travel.x:.3f} Y{travel.y:.3f} Z{travel.z:.2f} E{e:.5f}" 50 | else: 51 | return f"G1 F{f:.0f} X{travel.x:.3f} Y{travel.y:.3f} Z{travel.z:.2f} E{e:.5f}" 52 | 53 | def execute(self, data: List[str]) -> List[str]: 54 | current_e = 0.0 55 | to_compensate = 0 # Used when extrusion mode is relative. 56 | is_active = False # Whether retract-continue is in effect. 57 | 58 | current_pos = Vector(0.0, 0.0, 0.0) 59 | last_pos = Vector(0.0, 0.0, 0.0) 60 | 61 | extra_retraction_speed = self.getSettingValueByKey("extra_retraction_speed") 62 | relative_extrusion = Application.getInstance().getGlobalContainerStack().getProperty( 63 | "relative_extrusion", "value" 64 | ) 65 | 66 | for layer_number, layer in enumerate(data): 67 | lines = layer.split("\n") 68 | for line_number, line in enumerate(lines): 69 | 70 | # Focus on move-type lines. 71 | code_g = self.getValue(line, "G") 72 | if code_g not in [0, 1]: 73 | continue 74 | 75 | # Track X,Y,Z location. 76 | last_pos = last_pos.set(current_pos.x, current_pos.y, current_pos.z) 77 | current_pos = current_pos.set( 78 | self.getValue(line, "X", current_pos.x), 79 | self.getValue(line, "Y", current_pos.y), 80 | self.getValue(line, "Z", current_pos.z) 81 | ) 82 | 83 | # Track extrusion 'axis' position. 84 | last_e = current_e 85 | e_value = self.getValue(line, "E") 86 | if e_value: 87 | current_e = (current_e if relative_extrusion else 0) + e_value 88 | 89 | # Handle lines: Detect retractions and compensate relative if G1, potential retract-continue if G0. 90 | # and ignore Zhop lines 91 | if code_g == 1 and re.search("G1 F(\d*) Z(\d.*)", line) == None: 92 | if last_e > (current_e + 0.0001): # Account for floating point inaccuracies. 93 | 94 | # There is a retraction, each following G0 command needs to continue the retraction. 95 | is_active = True 96 | continue 97 | 98 | elif relative_extrusion and is_active: 99 | 100 | # If 'relative', the first G1 command after the total retraction will have to compensate more. 101 | travel, f = self._getTravelMove(lines[line_number], current_pos) 102 | lines[line_number] = self._travelMoveString(travel, f, to_compensate + e_value) 103 | to_compensate = 0.0 104 | 105 | # There is no retraction (see continue in the retract-clause) and everything else has been handled. 106 | is_active = False 107 | # If the line is G0 or a Zhop then maake changes---------------------------------------- 108 | elif code_g == 0 or re.search("G1 F(\d*) Z(\d.*)", line) != None: 109 | if not is_active: 110 | continue 111 | #Ignore G1 Z hop lines and act on the G0 lines---------------------------- 112 | if re.search("G1 F(\d*) Z(\d.*)", line) == None: 113 | # The retract-continue is active, so each G0 until the next extrusion needs to continue retraction. 114 | travel, f = self._getTravelMove(lines[line_number], current_pos) 115 | travel_length = (current_pos - last_pos).length() 116 | extra_retract = travel_length * extra_retraction_speed 117 | new_e = (0 if relative_extrusion else current_e) - extra_retract 118 | to_compensate += extra_retract 119 | current_e -= extra_retract 120 | lines[line_number] = self._travelMoveString(travel, f, new_e) 121 | 122 | new_layer = "\n".join(lines) 123 | data[layer_number] = new_layer 124 | return data 125 | -------------------------------------------------------------------------------- /SlowZ.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: 5axes 5 | # Date: November 07, 2021 6 | # 7 | # Description: postprocessing script to slowdown the speed according to the Z height 8 | # https://marlinfw.org/docs/gcode/M220.html 9 | # 10 | #------------------------------------------------------------------------------------------------------------------------------------ 11 | # 12 | # Version 1.0 07/11/2021 Add slow down using M220 S(SlowDown%) 13 | # 14 | #------------------------------------------------------------------------------------------------------------------------------------ 15 | 16 | from ..Script import Script 17 | from UM.Logger import Logger 18 | from UM.Application import Application 19 | import re #To perform the search 20 | from enum import Enum 21 | 22 | __version__ = '1.0' 23 | 24 | class Section(Enum): 25 | """Enum for section type.""" 26 | 27 | NOTHING = 0 28 | SKIRT = 1 29 | INNER_WALL = 2 30 | OUTER_WALL = 3 31 | INFILL = 4 32 | SKIN = 5 33 | SKIN2 = 6 34 | 35 | def is_begin_layer_line(line: str) -> bool: 36 | """Check if current line is the start of a layer section. 37 | 38 | Args: 39 | line (str): Gcode line 40 | 41 | Returns: 42 | bool: True if the line is the start of a layer section 43 | """ 44 | return line.startswith(";LAYER:") 45 | 46 | def is_z_line(line: str) -> bool: 47 | """Check if current line is a Z line 48 | 49 | Args: 50 | line (str): Gcode line 51 | 52 | Returns: 53 | bool: True if the line is a Z line segment 54 | """ 55 | return "G0" in line and "Z" in line and not "E" in line 56 | 57 | class SlowZ(Script): 58 | def __init__(self): 59 | super().__init__() 60 | 61 | def getSettingDataString(self): 62 | return """{ 63 | "name": "SlowZ", 64 | "key": "SlowZ", 65 | "metadata": {}, 66 | "version": 2, 67 | "settings": 68 | { 69 | "slowz_percentage": 70 | { 71 | "label": "Slow Z percentage", 72 | "description": "Positive value to slow the print as the z value rises up to this percentage.", 73 | "type": "float", 74 | "unit": "%", 75 | "default_value": 0, 76 | "minimum_value": "0", 77 | "maximum_value_warning": "50", 78 | "maximum_value": "90" 79 | }, 80 | "slowz_height": 81 | { 82 | "label": "Slow Z height", 83 | "description": "Positive value to define the start height of the speed reduction.", 84 | "type": "float", 85 | "unit": "mm", 86 | "default_value": 0, 87 | "minimum_value": "0" 88 | }, 89 | "lcdfeedback": 90 | { 91 | "label": "Display details on LCD", 92 | "description": "This setting will insert M117 gcode instructions, to display current modification in the G-Code is being used.", 93 | "type": "bool", 94 | "default_value": true 95 | } 96 | } 97 | }""" 98 | 99 | def execute(self, data): 100 | 101 | SlowZPercentage = float(self.getSettingValueByKey("slowz_percentage")) 102 | SlowZHeight = float(self.getSettingValueByKey("slowz_height")) 103 | UseLcd = self.getSettingValueByKey("lcdfeedback") 104 | # Logger.log('d', 'SlowZPercentage : {:f}'.format(SlowZPercentage)) 105 | # Logger.log('d', 'SlowZHeight : {:f}'.format(SlowZHeight)) 106 | 107 | idl=0 108 | currentz=0 109 | 110 | for layer in data: 111 | layer_index = data.index(layer) 112 | 113 | lines = layer.split("\n") 114 | for line in lines: 115 | 116 | if line.startswith(";LAYER_COUNT:"): 117 | # Logger.log("w", "found LAYER_COUNT %s", line[13:]) 118 | layercount=int(line[13:]) 119 | 120 | if is_begin_layer_line(line): 121 | line_index = lines.index(line) 122 | # Logger.log('d', 'layer_lines : {}'.format(line)) 123 | currentlayer=int(line[7:]) 124 | # Logger.log('d', 'currentlayer : {:d}'.format(currentlayer)) 125 | if line.startswith(";LAYER:0"): 126 | currentz=0 127 | idl=1 128 | 129 | if idl == 1 and currentz >= SlowZHeight: 130 | idl=2 131 | startlayer=currentlayer 132 | # Logger.log("w", "Z Height %f", currentz) 133 | 134 | #Logger.log("w", "LAYER %s", line[7:]) 135 | if idl >= 2 : 136 | speed_value = 100 - int(float(SlowZPercentage)*((currentlayer-startlayer)/(layercount-startlayer))) 137 | lines.insert(2,"M220 S" + str(speed_value)) 138 | if UseLcd == True : 139 | lcd_gcode = "M117 Speed {:d}%".format(int(speed_value)) 140 | lines.insert(3,lcd_gcode) 141 | 142 | 143 | if idl == 1 and is_z_line(line): 144 | searchZ = re.search(r"Z(\d*\.?\d*)", line) 145 | if searchZ: 146 | currentz=float(searchZ.group(1)) 147 | # Logger.log('d', 'Current Z : {:f}'.format(currentz)) 148 | 149 | result = "\n".join(lines) 150 | data[layer_index] = result 151 | 152 | return data 153 | -------------------------------------------------------------------------------- /SpeedTower.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: 5axes 5 | # Date: February 29, 2020 6 | # 7 | # Description: postprocessing script to easily define a Speed Tower 8 | # Option for Speed 9 | # Acceleration 10 | # Jerk 11 | # Junction Deviation 12 | # Marlin Linear Advance 13 | # RepRap Pressure Advance 14 | # 15 | #------------------------------------------------------------------------------------------------------------------------------------ 16 | # 17 | # Version 1.0 29/02/2020 18 | # Version 1.1 29/01/2021 19 | # Version 1.2 05/04/2021 by dotdash32(https://github.com/dotdash32) for Marlin Linear Advance & RepRap Pressure Advance 20 | # Version 1.3 18/04/2021 : ChangeLayerOffset += 2 21 | # Version 1.4 18/05/2021 : float 22 | # Version 1.5 14/02/2022 : Set Speed using M220 S 23 | # Version 1.6 15/02/2022 : Change Int for changelayeroffset & changelayer 24 | # Version 1.7 04/08/2022 : Restore and Save the Speed Factor in case of Speed option by using M220 B and M220 R 25 | # https://marlinfw.org/docs/gcode/M220.html 26 | # 27 | #------------------------------------------------------------------------------------------------------------------------------------ 28 | 29 | from ..Script import Script 30 | from UM.Application import Application 31 | from UM.Logger import Logger 32 | import re #To perform the search 33 | 34 | __version__ = '1.7' 35 | 36 | class SpeedTower(Script): 37 | def __init__(self): 38 | super().__init__() 39 | 40 | def getSettingDataString(self): 41 | return """{ 42 | "name": "SpeedTower", 43 | "key": "SpeedTower", 44 | "metadata": {}, 45 | "version": 2, 46 | "settings": 47 | { 48 | "command": { 49 | "label": "Command", 50 | "description": "GCode Commande", 51 | "type": "enum", 52 | "options": { 53 | "speed": "Speed", 54 | "acceleration": "Acceleration", 55 | "jerk": "Jerk", 56 | "junction": "Junction Deviation", 57 | "marlinadv": "Marlin Linear", 58 | "rrfpresure": "RepRap Pressure" 59 | }, 60 | "default_value": "acceleration" 61 | }, 62 | "startValue": 63 | { 64 | "label": "Starting value", 65 | "description": "The starting value of the Tower.", 66 | "type": "float", 67 | "default_value": 8.0 68 | }, 69 | "valueChange": 70 | { 71 | "label": "Value Increment", 72 | "description": "The value change of each block, can be positive or negative. I you want 50 and then 40, you need to set this to -10.", 73 | "type": "float", 74 | "default_value": 4.0 75 | }, 76 | "changelayer": 77 | { 78 | "label": "Change Layer", 79 | "description": "How many layers needs to be printed before the value should be changed.", 80 | "type": "int", 81 | "default_value": 30, 82 | "minimum_value": 1, 83 | "maximum_value": 1000, 84 | "maximum_value_warning": 100 85 | }, 86 | "changelayeroffset": 87 | { 88 | "label": "Change Layer Offset", 89 | "description": "If the print has a base, indicate the number of layers from which to start the changes.", 90 | "type": "int", 91 | "default_value": 4, 92 | "minimum_value": 0, 93 | "maximum_value": 1000, 94 | "maximum_value_warning": 100 95 | }, 96 | "lcdfeedback": 97 | { 98 | "label": "Display details on LCD", 99 | "description": "This setting will insert M117 gcode instructions, to display current modification in the G-Code is being used.", 100 | "type": "bool", 101 | "default_value": true 102 | } 103 | } 104 | }""" 105 | 106 | def execute(self, data): 107 | 108 | UseLcd = self.getSettingValueByKey("lcdfeedback") 109 | Instruction = self.getSettingValueByKey("command") 110 | StartValue = float(self.getSettingValueByKey("startValue")) 111 | ValueChange = float(self.getSettingValueByKey("valueChange")) 112 | ChangeLayer = int(self.getSettingValueByKey("changelayer")) 113 | ChangeLayerOffset = int(self.getSettingValueByKey("changelayeroffset")) 114 | ChangeLayerOffset += 2 # Modification to take into account the numbering offset in Gcode 115 | # layer_index = 0 for initial Block 1= Start Gcode normaly first layer = 0 116 | 117 | CurrentValue = StartValue 118 | Command="" 119 | max_layer=9999 120 | idl=0 121 | 122 | for layer in data: 123 | layer_index = data.index(layer) 124 | 125 | lines = layer.split("\n") 126 | for line in lines: 127 | if line.startswith(";LAYER_COUNT:"): 128 | max_layer = int(line.split(":")[1]) # Recuperation Nb Layer Maxi 129 | # Logger.log('d', 'Max_layer : {}'.format(max_layer)) 130 | 131 | if line.startswith(";LAYER:"): 132 | line_index = lines.index(line) 133 | # Logger.log('d', 'Instruction : {}'.format(Instruction)) 134 | # Logger.log('d', 'layer_index : {}'.format(layer_index)) 135 | # Logger.log('d', 'ChangeLayerOffset : {}'.format(ChangeLayerOffset)) 136 | 137 | if (layer_index==ChangeLayerOffset): 138 | if (Instruction=='speed'): 139 | Command = "M220 B\nM220 S{:d}".format(int(CurrentValue)) 140 | lcd_gcode = "M117 Speed S{:d}".format(int(CurrentValue)) 141 | if (Instruction=='acceleration'): 142 | Command = "M204 S{:d}".format(int(CurrentValue)) 143 | lcd_gcode = "M117 Acceleration S{:d}".format(int(CurrentValue)) 144 | if (Instruction=='jerk'): 145 | Command = "M205 X{:d} Y{:d}".format(int(CurrentValue), int(CurrentValue)) 146 | lcd_gcode = "M117 Jerk X{:d} Y{:d}".format(int(CurrentValue), int(CurrentValue)) 147 | if (Instruction=='junction'): 148 | Command = "M205 J{:.3f}".format(float(CurrentValue)) 149 | lcd_gcode = "M117 Junction J{:.3f}".format(float(CurrentValue)) 150 | if (Instruction=='marlinadv'): 151 | Command = "M900 K{:.3f}".format(float(CurrentValue)) 152 | lcd_gcode = "M117 Linear Advance K{:.3f}".format(float(CurrentValue)) 153 | if (Instruction=='rrfpresure'): 154 | Command = "M572 D0 S{:.3f}".format(float(CurrentValue)) 155 | lcd_gcode = "M117 Pressure Advance S{:.3f}".format(float(CurrentValue)) 156 | 157 | lines.insert(line_index + 1, ";TYPE:CUSTOM LAYER") 158 | lines.insert(line_index + 2, Command) 159 | if UseLcd == True : 160 | lines.insert(line_index + 3, lcd_gcode) 161 | 162 | if ((layer_index-ChangeLayerOffset) % ChangeLayer == 0) and ((layer_index-ChangeLayerOffset)>0): 163 | CurrentValue += ValueChange 164 | if (Instruction=='speed'): 165 | Command = "M220 S{:d}".format(int(CurrentValue)) 166 | lcd_gcode = "M117 Speed S{:d}".format(int(CurrentValue)) 167 | if (Instruction=='acceleration'): 168 | Command = "M204 S{:d}".format(int(CurrentValue)) 169 | lcd_gcode = "M117 Acceleration S{:d}".format(int(CurrentValue)) 170 | if (Instruction=='jerk'): 171 | Command = "M205 X{:d} Y{:d}".format(int(CurrentValue), int(CurrentValue)) 172 | lcd_gcode = "M117 Jerk X{:d} Y{:d}".format(int(CurrentValue), int(CurrentValue)) 173 | if (Instruction=='junction'): 174 | Command = "M205 J{:.3f}".format(float(CurrentValue)) 175 | lcd_gcode = "M117 Junction J{:.3f}".format(float(CurrentValue)) 176 | if (Instruction=='marlinadv'): 177 | Command = "M900 K{:.3f}".format(float(CurrentValue)) 178 | lcd_gcode = "M117 Linear Advance K{:.3f}".format(float(CurrentValue)) 179 | if (Instruction=='rrfpresure'): 180 | Command = "M572 D0 S{:.3f}".format(float(CurrentValue)) 181 | lcd_gcode = "M117 Pressure Advance S{:.3f}".format(float(CurrentValue)) 182 | 183 | lines.insert(line_index + 1, ";TYPE:CUSTOM VALUE") 184 | lines.insert(line_index + 2, Command) 185 | if UseLcd == True : 186 | lines.insert(line_index + 3, lcd_gcode) 187 | 188 | # Logger.log('d', 'layer_index : {}'.format(layer_index)) 189 | if (Instruction=='speed') and (layer_index==max_layer+1) : 190 | line_index = len(lines) 191 | # Logger.log('d', 'line_index : {}'.format(line_index)) 192 | lines.insert(line_index, "M220 R\n") 193 | 194 | result = "\n".join(lines) 195 | data[layer_index] = result 196 | 197 | return data 198 | -------------------------------------------------------------------------------- /SpoonOrder.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: 5axes 5 | # Date: March 13, 2023 6 | # 7 | # Description: SpoonOrder 8 | # 9 | #------------------------------------------------------------------------------------------------------------------------------------ 10 | # 11 | # Version 1.0 13/03/2023 first prototype right now must be use with the relative extrusion activated 12 | # 13 | #------------------------------------------------------------------------------------------------------------------------------------ 14 | 15 | from ..Script import Script 16 | from UM.Logger import Logger 17 | from UM.Application import Application 18 | from UM.Message import Message 19 | import re #To perform the search 20 | from UM.i18n import i18nCatalog # Translation 21 | catalog = i18nCatalog("cura") 22 | 23 | __version__ = '1.0' 24 | 25 | def is_begin_layer_line(line: str) -> bool: 26 | """Check if current line is the start of a layer section. 27 | 28 | Args: 29 | line (str): Gcode line 30 | 31 | Returns: 32 | bool: True if the line is the start of a layer section 33 | """ 34 | return line.startswith(";LAYER:") 35 | 36 | def is_begin_skirt_line(line: str) -> bool: 37 | """Check if current line is the start of a SKIRT section. 38 | 39 | Args: 40 | line (str): Gcode line 41 | 42 | Returns: 43 | bool: True if the line is the start of a SKIRT section 44 | """ 45 | return line.startswith(";TYPE:SKIRT") 46 | 47 | def is_begin_type_line(line: str) -> bool: 48 | """Check if current line is the start of a type section. 49 | 50 | Args: 51 | line (str): Gcode line 52 | 53 | Returns: 54 | bool: True if the line is the start of a type section 55 | """ 56 | return line.startswith(";TYPE") 57 | 58 | def is_begin_mesh_line(line: str) -> bool: 59 | """Check if current line is the start of a new MESH. 60 | 61 | Args: 62 | line (str): Gcode line 63 | 64 | Returns: 65 | bool: True if the line is the start of a new MESH 66 | """ 67 | return line.startswith(";MESH:") 68 | 69 | def is_e_line(line: str) -> bool: 70 | """Check if current line is a an Extruder line 71 | 72 | Args: 73 | line (str): Gcode line 74 | 75 | Returns: 76 | bool: True if the line is an Extruder line segment 77 | """ 78 | return "G1" in line and "E" in line 79 | 80 | def is_relative_extrusion_line(line: str) -> bool: 81 | """Check if current line is a relative extrusion line 82 | 83 | Args: 84 | line (str): Gcode line 85 | 86 | Returns: 87 | bool: True if the line is a relative extrusion line 88 | """ 89 | return "M83" in line 90 | 91 | def is_absolute_extrusion_line(line: str) -> bool: 92 | """Check if current line is an absolute extrusion line 93 | 94 | Args: 95 | line (str): Gcode line 96 | 97 | Returns: 98 | bool: True if the line is an absolute extrusion line 99 | """ 100 | return "M82" in line 101 | 102 | def is_relative_instruction_line(line: str) -> bool: 103 | """Check if current line contain a M83 / G91 instruction 104 | 105 | Args: 106 | line (str): Gcode line 107 | 108 | Returns: 109 | bool: True contain a M83 / G91 instruction 110 | """ 111 | return "G91" in line or "M83" in line 112 | 113 | def is_not_relative_instruction_line(line: str) -> bool: 114 | """Check if current line contain a M82 / G90 instruction 115 | 116 | Args: 117 | line (str): Gcode line 118 | 119 | Returns: 120 | bool: True contain a M82 / G90 instruction 121 | """ 122 | return "G90" in line or "M82" in line 123 | 124 | def is_reset_extruder_line(line: str) -> bool: 125 | """Check if current line contain a G92 E0 126 | 127 | Args: 128 | line (str): Gcode line 129 | 130 | Returns: 131 | bool: True contain a G92 E0 instruction 132 | """ 133 | return "G92" in line and "E0" in line 134 | 135 | 136 | class SpoonOrder(Script): 137 | def __init__(self): 138 | super().__init__() 139 | 140 | def getSettingDataString(self): 141 | return """{ 142 | "name": "SpoonOrder", 143 | "key": "SpoonOrder", 144 | "metadata": {}, 145 | "version": 2, 146 | "settings": 147 | { 148 | "layer": 149 | { 150 | "label": "Layer Analyse", 151 | "description": "Number of layer to analyse", 152 | "type": "int", 153 | "default_value": 1, 154 | "minimum_value": 0 155 | }, 156 | "marker": 157 | { 158 | "label": "Spoon Identificator", 159 | "description": "Spoon Tab identificator", 160 | "type": "str", 161 | "default_value": "SpoonTab" 162 | } 163 | } 164 | }""" 165 | 166 | def execute(self, data): 167 | 168 | LayerAnalyse = int(self.getSettingValueByKey("layer")) 169 | LayerAnalyse -= 1 170 | Logger.log('d', "LayerAnalyse : {}".format(LayerAnalyse)) 171 | Marker = str(self.getSettingValueByKey("marker")) 172 | 173 | extrud = Application.getInstance().getGlobalContainerStack().extruderList 174 | relative_extrusion = bool(extrud[0].getProperty("relative_extrusion", "value")) 175 | 176 | if not relative_extrusion: 177 | Message("Must be in mode Relative extrusion", title = catalog.i18nc("@info:title", "Post Processing Spoon Order")).show() 178 | return data 179 | 180 | idl=0 181 | 182 | currentlayer=0 183 | CurrentE=0 184 | ResetE=0 185 | RelativeExtruder = False 186 | SaveE = -1 187 | 188 | for layer in data: 189 | layer_index = data.index(layer) 190 | # Logger.log('d', "Layer_index founded : {}".format(layer_index)) 191 | lines_spoon = [] 192 | lines_not_spoon = [] 193 | 194 | lines = layer.split("\n") 195 | for line in lines: 196 | # line_index = lines.index(line) 197 | # Check if the line start with the Comment Char 198 | if is_relative_instruction_line(line) and line[0] != ";" : 199 | relative_extrusion = True 200 | # Logger.log('d', "Relative_extrusion founded : {}".format(line)) 201 | 202 | # Check if the line start with the Comment Char 203 | if is_not_relative_instruction_line(line) and line[0] != ";" : 204 | relative_extrusion = False 205 | 206 | if is_reset_extruder_line(line) and line[0] != ";" : 207 | # Logger.log('d', "Reset_extruder :" + str(CurrentE)) 208 | CurrentE = 0 209 | SaveE = 0 210 | 211 | if is_relative_extrusion_line(line): 212 | RelativeExtruder = True 213 | 214 | if is_absolute_extrusion_line(line): 215 | RelativeExtruder = False 216 | 217 | if line.startswith(";LAYER_COUNT:"): 218 | # Logger.log("w", "found LAYER_COUNT %s", line[13:]) 219 | layercount=int(line[13:]) 220 | 221 | if idl >= 1 and line.startswith(";TIME_ELAPSED:"): 222 | idl = 1 223 | 224 | # ;LAYER:X 225 | if is_begin_layer_line(line): 226 | Logger.log('d', "layer_lines : {}".format(line)) 227 | currentlayer=int(line[7:]) 228 | if currentlayer <= LayerAnalyse : 229 | # Logger.log('d', "CurrentLayer : {} / {}".format(currentlayer,LayerAnalyse)) 230 | idl=2 231 | else: 232 | idl=0 233 | 234 | if idl >= 1 and is_begin_mesh_line(line) : 235 | if Marker in line : 236 | # Logger.log('d', "Marker mesh : {}".format(line)) 237 | idl = 2 238 | else: 239 | # Logger.log('d', "Not Marker mesh : {}".format(line)) 240 | idl = 1 241 | 242 | #--------------------------------------- 243 | # Add the Spoon line to the spoon path 244 | #--------------------------------------- 245 | if idl == 2 : 246 | lines_spoon.append(line) 247 | 248 | if idl == 1 : 249 | lines_not_spoon.append(line) 250 | 251 | # Logger.log('d', "idl : {}".format(idl)) 252 | if idl>0 : 253 | result = ";BEGIN_OF_MODIFICATION" 254 | for line in lines_spoon: 255 | result += "\n" 256 | result += line 257 | for line in lines_not_spoon: 258 | result += "\n" 259 | result += line 260 | result += ";END_OF_MODIFICATION\n" 261 | else: 262 | result = "\n".join(lines) 263 | 264 | data[layer_index] = result 265 | 266 | return data 267 | -------------------------------------------------------------------------------- /TempFanTower.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: 5axes 5 | # Date: January 13, 2020 6 | # 7 | # Description: postprocessing-script to easily define a TempTower and not use 10 changeAtZ-scripts 8 | # 9 | # 10 | #------------------------------------------------------------------------------------------------------------------------------------ 11 | # 12 | # Version 1.1 9/01/2020 13 | # Version 1.2 11/01/2020 Fan modification after Bridge 14 | # Version 1.3 18/04/2021 : ChangeLayerOffset += 2 15 | # Version 1.4 18/05/2021 : ChangeLayerOffset 16 | # 17 | # Version 1.5 15/02/2022 Change Int for Layeroffset & changelayer 18 | # Version 1.6 05/08/2022 Option maintainbridgevalue 19 | #------------------------------------------------------------------------------------------------------------------------------------ 20 | 21 | from ..Script import Script 22 | from UM.Application import Application 23 | from UM.Logger import Logger 24 | 25 | __version__ = '1.6' 26 | 27 | class TempFanTower(Script): 28 | def __init__(self): 29 | super().__init__() 30 | 31 | def getSettingDataString(self): 32 | return """{ 33 | "name": "TempFanTower", 34 | "key": "TempFanTower", 35 | "metadata": {}, 36 | "version": 2, 37 | "settings": 38 | { 39 | "startTemperature": 40 | { 41 | "label": "Starting Temperature", 42 | "description": "The starting temperature of the TempTower", 43 | "type": "int", 44 | "default_value": 220, 45 | "minimum_value": 100, 46 | "maximum_value": 300, 47 | "minimum_value_warning": 170, 48 | "maximum_value_warning": 280 49 | }, 50 | "temperaturechange": 51 | { 52 | "label": "Temperature Increment", 53 | "description": "The temperature change of each block, can be positive or negative. If you want 220 and then 210, you need to set this to -10", 54 | "type": "int", 55 | "default_value": -5, 56 | "minimum_value": -100, 57 | "maximum_value": 100, 58 | "minimum_value_warning": -20, 59 | "maximum_value_warning": 20 60 | }, 61 | "changelayer": 62 | { 63 | "label": "Change Layer", 64 | "description": "How many layers needs to be printed before the temperature should be changed", 65 | "type": "int", 66 | "default_value": 52, 67 | "minimum_value": 1, 68 | "maximum_value": 1000, 69 | "minimum_value_warning": 5, 70 | "maximum_value_warning": 100 71 | }, 72 | "changelayeroffset": 73 | { 74 | "label": "Change Layer Offset", 75 | "description": "If the print has a base, indicate the number of layers from which to start the changes.", 76 | "type": "int", 77 | "default_value": 5, 78 | "minimum_value": 0, 79 | "maximum_value": 1000, 80 | "maximum_value_warning": 100 81 | }, 82 | "usefanvalue": 83 | { 84 | "label": "Activate Fan Tower", 85 | "description": "Activate also a fan variation to create a Fan Tower", 86 | "type": "bool", 87 | "default_value": false 88 | }, 89 | "fanchange": 90 | { 91 | "label": "Fan values in %", 92 | "description": "The fan speed change of each block, list value separated by a comma ';' ", 93 | "type": "str", 94 | "default_value": "100;40;0", 95 | "enabled": "usefanvalue" 96 | }, 97 | "maintainbridgevalue": 98 | { 99 | "label": "Keep Bridge Fan Value", 100 | "description": "Don't Change the Bridge Fan value", 101 | "type": "bool", 102 | "default_value": false, 103 | "enabled": "usefanvalue" 104 | } 105 | } 106 | }""" 107 | 108 | def execute(self, data): 109 | 110 | startTemperature = self.getSettingValueByKey("startTemperature") 111 | temperaturechange = self.getSettingValueByKey("temperaturechange") 112 | changelayer = int(self.getSettingValueByKey("changelayer")) 113 | ChangeLayerOffset = int(self.getSettingValueByKey("changelayeroffset")) 114 | ChangeLayerOffset += 2 # Modification to take into account the numbering offset in Gcode 115 | # layer_index = 0 for initial Block 1= Start Gcode normaly first layer = 0 116 | 117 | fanvalues_str = self.getSettingValueByKey("fanchange") 118 | fanvalues = fanvalues_str.split(";") 119 | nbval = len(fanvalues) - 1 120 | usefan = False 121 | 122 | if (nbval>0): 123 | usefan = bool(self.getSettingValueByKey("usefanvalue")) 124 | bridgevalue = bool(self.getSettingValueByKey("maintainbridgevalue")) 125 | else: 126 | usefan = False 127 | bridgevalue = False 128 | 129 | 130 | currentTemperature = startTemperature 131 | currentfan = int((int(fanvalues[0])/100)*255) # 100% = 255 pour ventilateur 132 | 133 | idl=0 134 | afterbridge = False 135 | 136 | for layer in data: 137 | layer_index = data.index(layer) 138 | 139 | lines = layer.split("\n") 140 | for line in lines: 141 | if line.startswith("M106 S") and ((layer_index-ChangeLayerOffset)>0) and (usefan) : 142 | if afterbridge or not bridgevalue : 143 | line_index = lines.index(line) 144 | currentfan = int((int(fanvalues[idl])/100)*255) # 100% = 255 pour ventilateur 145 | lines[line_index] = "M106 S"+str(int(currentfan))+ " ; FAN MODI" 146 | afterbridge -= 1 147 | else : 148 | line_index = lines.index(line) 149 | afterbridge += 1 150 | 151 | if line.startswith("M107") and ((layer_index-ChangeLayerOffset)>0) and (usefan): 152 | afterbridge = 1 153 | line_index = lines.index(line) 154 | 155 | if line.startswith(";BRIDGE") : 156 | afterbridge = 0 157 | line_index = lines.index(line) 158 | 159 | if line.startswith(";LAYER:"): 160 | line_index = lines.index(line) 161 | 162 | if (layer_index==ChangeLayerOffset): 163 | lines.insert(line_index + 1, ";TYPE:CUSTOM LAYER") 164 | lines.insert(line_index + 2, "M104 S"+str(currentTemperature)) 165 | idl=0 166 | if (usefan): 167 | currentfan = int((int(fanvalues[idl])/100)*255) # 100% = 255 pour ventilateur 168 | lines.insert(line_index + 3, "M106 S"+str(currentfan)) 169 | 170 | if ((layer_index-ChangeLayerOffset) % changelayer == 0) and ((layer_index-ChangeLayerOffset)>0): 171 | if (usefan) and (idl < nbval): 172 | idl += 1 173 | currentfan = int((int(fanvalues[idl])/100)*255) # 100% = 255 pour ventilateur 174 | lines.insert(line_index + 1, ";TYPE:CUSTOM FAN") 175 | lines.insert(line_index + 2, "M106 S"+str(int(currentfan))) 176 | else: 177 | currentTemperature += temperaturechange 178 | lines.insert(line_index + 1, ";TYPE:CUSTOM TEMP") 179 | lines.insert(line_index + 2, "M104 S"+str(currentTemperature)) 180 | if (usefan): 181 | # On repart à la valeur de départ 182 | idl = 0 183 | currentfan = int((int(fanvalues[idl])/100)*255) # 100% = 255 pour ventilateur 184 | lines.insert(line_index + 3, "M106 S"+str(int(currentfan))) 185 | 186 | 187 | result = "\n".join(lines) 188 | data[layer_index] = result 189 | 190 | return data 191 | -------------------------------------------------------------------------------- /TemperatureTower.py: -------------------------------------------------------------------------------- 1 | # Created by Jakub Wietrzyk https://github.com/jaaaco 2 | # Based on https://github.com/asmaurya/Temperature-Tower-Plugin-for-Cura-4.- 3 | 4 | from typing import List 5 | 6 | from ..Script import Script 7 | from UM.Application import Application 8 | 9 | # Performs a search-and-replace on all g-code. 10 | class TemperatureTower(Script): 11 | def getSettingDataString(self): 12 | return """{ 13 | "name": "Temperature Tower", 14 | "key": "TemperatureTower", 15 | "metadata": {}, 16 | "version": 2, 17 | "settings": 18 | { 19 | "change_at": 20 | { 21 | "label": "Change Temp at", 22 | "description": "Whether to decrease temp at layer or height interval", 23 | "type": "enum", 24 | "options": {"height": "Height", "layer": "Layer No."}, 25 | "default_value": "layer" 26 | }, 27 | "height_inc": 28 | { 29 | "label": "Height Interval", 30 | "description": "At the increase of how many mm height does the temp decreases", 31 | "unit": "mm", 32 | "type": "float", 33 | "default_value": 5.0, 34 | "minimum_value": 0, 35 | "enabled": "change_at == 'height'" 36 | }, 37 | "layer_inc": 38 | { 39 | "label": "Layer Interval", 40 | "description": "At the increase of how many layers does the temp decreases", 41 | "type": "int", 42 | "value": 1, 43 | "minimum_value": 0, 44 | "minimum_value_warning": "1", 45 | "enabled": "change_at == 'layer'" 46 | }, 47 | "layer_start": 48 | { 49 | "label": "Start Layer", 50 | "description": "From which layer the temp decrease has to be started", 51 | "type": "int", 52 | "value": 1, 53 | "minimum_value": 0, 54 | "minimum_value_warning": "1", 55 | "enabled": "change_at == 'layer'" 56 | }, 57 | "height_start": 58 | { 59 | "label": "Start Height (mm)", 60 | "description": "From which height the temp decrease has to be started", 61 | "type": "float", 62 | "value": 0.2, 63 | "unit": "mm", 64 | "minimum_value": 0.2, 65 | "minimum_value_warning": "1", 66 | "enabled": "change_at == 'height'" 67 | }, 68 | "temperature_start": 69 | { 70 | "label": "Start Temperature", 71 | "description": "Initial temperature", 72 | "unit": "°C", 73 | "type": "int", 74 | "default_value": 200 75 | }, 76 | "temperature_dec": 77 | { 78 | "label": "Temperature Decrement Step", 79 | "description": "Decrease temperature by this much with each increment", 80 | "unit": "°C", 81 | "type": "int", 82 | "default_value": 5 83 | } 84 | } 85 | }""" 86 | 87 | def execute(self, data: List[str]): 88 | new_temperature = self.getSettingValueByKey("temperature_start") + self.getSettingValueByKey("temperature_dec") 89 | comment = " ; Added by Temperature Tower Post Processing Plugin \n" 90 | layer_height = Application.getInstance().getGlobalContainerStack().getProperty("layer_height", "value") 91 | if self.getSettingValueByKey("change_at") == "layer": 92 | for layer in range(self.getSettingValueByKey("layer_start"), len(data)-2, self.getSettingValueByKey("layer_inc")): 93 | new_temperature -= self.getSettingValueByKey("temperature_dec") 94 | if new_temperature < 0: 95 | new_temperature = 0 96 | data[layer] = "\nM104 S" + str(new_temperature) + comment + data[layer] 97 | else: 98 | for layer in range(int(self.getSettingValueByKey("height_start") / layer_height), len(data)-2, int(self.getSettingValueByKey("height_inc") / layer_height)): 99 | new_temperature -= self.getSettingValueByKey("temperature_dec") 100 | if new_temperature < 0: 101 | new_temperature = 0 102 | data[layer] = "\nM104 S" + str(new_temperature) + comment + data[layer] 103 | return data -------------------------------------------------------------------------------- /UPPostProcess.py: -------------------------------------------------------------------------------- 1 | from ..Script import Script 2 | import re 3 | 4 | # from cura.Settings.ExtruderManager import ExtruderManager 5 | # Issue History 6 | # Vesrion 1.0 - Inital Release 7 | # Version 2.0 - Fixed error with X and Y axis directions 8 | #. 9 | 10 | 11 | class UPPostProcess(Script): 12 | 13 | def __init__(self): 14 | super().__init__() 15 | self.Ypattern = re.compile("Y\d+") 16 | self.Zpattern = re.compile("Z\d+") 17 | self.Xpattern = re.compile("X\d+") 18 | 19 | 20 | 21 | 22 | def getSettingDataString(self): 23 | return """{ 24 | "name":"Up PostProcess", 25 | "key": "UpPostProcess", 26 | "metadata": {}, 27 | "version": 2, 28 | "settings": { 29 | "UP_profile": 30 | { 31 | "label": "Profile", 32 | "description": "Convert CURA to UP gcode", 33 | "type": "enum", 34 | "options": { 35 | "UP":"UP" 36 | }, 37 | "default_value": "UP" 38 | }, 39 | "UP_Zvalue": 40 | { 41 | "label": "Z offset in Up Studio", 42 | "description": " Z height when bed at nozzle", 43 | "type": "float", 44 | "default_value": 205 45 | } 46 | } 47 | }""" 48 | 49 | def execute(self, data: list): 50 | 51 | version = "0.1 C Jackson" 52 | 53 | self.UPProfile = self.getSettingValueByKey("UP_profile") 54 | self.UPZmax = self.getSettingValueByKey("UP_Zvalue") 55 | self.selectedTool = "" 56 | self.valveClosed = True 57 | 58 | index = 0 59 | for active_layer in data: 60 | 61 | self.output = "" 62 | #if index == 0: 63 | self.output += "; Selected profile: " + self.UPProfile + ", ZMax set at " + str(self.UPZmax) 64 | self.output += "; version " + version + "\n" 65 | 66 | lines = active_layer.split("\n") 67 | #self.output += "; Number of lines to be processed " + str(lines) +"\n" 68 | for line in lines: 69 | commentIndex = line.find(";") 70 | if commentIndex >= 0: 71 | comment = line[commentIndex + 1:] 72 | line = line[0:commentIndex] 73 | else: 74 | comment = "" 75 | 76 | if self.UPProfile == "UP": 77 | self.UPGcodeParse(line, comment) 78 | else: 79 | self.output += ";" + "\n" 80 | 81 | data[index] = self.output 82 | index += 1 83 | return data 84 | # 85 | # End of Main Loop 86 | # 87 | def UPGcodeParse(self, line, comment): 88 | 89 | hasY = re.search(self.Ypattern, line) 90 | hasZ = re.search(self.Zpattern, line) 91 | hasX = re.search(self.Xpattern, line) 92 | # There is 'Sxxx' in the line and second tool is selected and line doesn't contain both 93 | if hasX: 94 | line = line.replace("X","A") # Replace "Sxxx" with "Txxx " 95 | if hasY: 96 | line = line.replace("Y","B") # Replace "Sxxx" with "Txxx " 97 | line = line.replace("A","Y-") # Replace "Sxxx" with "Txxx " 98 | line = line.replace("B","X") # Replace "Sxxx" with "Txxx " 99 | if hasZ: 100 | #CBJ Pseudo Code needs to find the Zvlaue on the line i.e. Z0.3 101 | #CBJ then subtract this value from the UPZvalue provide by the user e.g. 205 102 | #CBJ replace theZvalue in the line with UPZvalue - Zvalue e.g. 204.7 103 | LayerZ = line.rpartition('Z')[-1] 104 | LayerZ = str(LayerZ) 105 | A = float(self.UPZmax) 106 | B = float(LayerZ) 107 | UPLayerZ = A-B 108 | UPLayerZ = str(UPLayerZ) 109 | line = line.partition('Z')[0] + "Z-" +UPLayerZ 110 | 111 | # Write the modifed line to file. 112 | if comment != "": 113 | self.output += line + ";" + comment + "\n" 114 | else: 115 | self.output += line + "\n" 116 | 117 | -------------------------------------------------------------------------------- /ZMoveG0.py: -------------------------------------------------------------------------------- 1 | # ZMoveG0 2 | """ 3 | ZMoveG0 for 3D prints. 4 | 5 | Z hop for every G0 6 | 7 | Author: 5axes 8 | Version: 1.0 9 | Version: 1.1 Remove some useless part of the Code. 10 | 11 | """ 12 | 13 | import re #To perform the search 14 | 15 | from ..Script import Script 16 | 17 | from UM.Application import Application 18 | from UM.Logger import Logger 19 | from UM.Message import Message 20 | from UM.i18n import i18nCatalog 21 | 22 | catalog = i18nCatalog("cura") 23 | 24 | __version__ = '1.1' 25 | 26 | class ZMoveG0(Script): 27 | def getSettingDataString(self): 28 | return """{ 29 | "name": "Z Move G0", 30 | "key": "ZMoveG0", 31 | "metadata": {}, 32 | "version": 2, 33 | "settings": 34 | { 35 | "extruder_nb": 36 | { 37 | "label": "Extruder Id", 38 | "description": "Define extruder Id in case of multi extruders", 39 | "unit": "", 40 | "type": "int", 41 | "default_value": 1 42 | } 43 | } 44 | }""" 45 | 46 | ## ----------------------------------------------------------------------------- 47 | # 48 | # Main Prog 49 | # 50 | ## ----------------------------------------------------------------------------- 51 | def execute(self, data): 52 | 53 | current_z = 0 54 | Zr = "Z0" 55 | Zc = "Z0" 56 | 57 | extruder_id = self.getSettingValueByKey("extruder_nb") 58 | extruder_id = extruder_id -1 59 | 60 | # machine_extruder_count 61 | extruder_count=Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") 62 | extruder_count = extruder_count-1 63 | if extruder_id>extruder_count : 64 | extruder_id=extruder_count 65 | 66 | extrud = Application.getInstance().getGlobalContainerStack().extruderList 67 | 68 | # Get the Cura retraction_hop and speed_z_hop as Zhop parameter 69 | retraction_hop = float(extrud[extruder_id].getProperty("retraction_hop", "value")) 70 | speed_z_hop = int(extrud[extruder_id].getProperty("speed_z_hop", "value")) 71 | speed_z_hop = speed_z_hop * 60 72 | 73 | # Check if Z hop is desactivated 74 | retraction_hop_enabled= extrud[extruder_id].getProperty("retraction_hop_enabled", "value") 75 | if retraction_hop_enabled == True: 76 | # 77 | Logger.log('d', 'Mode Z Hop must not be activated') 78 | Message(catalog.i18nc("@message", "Mode Z Hop must not be activated"), title = catalog.i18nc("@info:title", "Post Processing")).show() 79 | return data 80 | 81 | In_G0 = False 82 | for layer_index, layer in enumerate(data): 83 | lines = layer.split("\n") 84 | 85 | for line_index, currentLine in enumerate(lines): 86 | 87 | if currentLine.startswith("G0") and "Z" in currentLine : 88 | searchZ = re.search(r"Z(\d*\.?\d*)", currentLine) 89 | if searchZ: 90 | current_z=float(searchZ.group(1)) 91 | Zc = "Z"+searchZ.group(1) 92 | 93 | if currentLine.startswith("G0") and not In_G0 : 94 | Output_Z=current_z+retraction_hop 95 | outPutLine1 = "G1 F{} Z{:.3f}\n".format(speed_z_hop,Output_Z) 96 | # Logger.log('d', "Zc Zr : {} {}".format(Zc,Zr)) 97 | Zr = "Z{:.3f}".format(Output_Z) 98 | currentLine=currentLine.replace(Zc, Zr) 99 | outPutLine = outPutLine1 + currentLine 100 | lines[line_index] = outPutLine 101 | In_G0 = True 102 | 103 | if currentLine.startswith("G1") and In_G0 : 104 | outPutLine2 = "G1 F{} Z{:.3f}\n".format(speed_z_hop,current_z) 105 | outPutLine = outPutLine2 + currentLine 106 | lines[line_index] = outPutLine 107 | In_G0 = False 108 | # 109 | # end of analyse 110 | # 111 | 112 | final_lines = "\n".join(lines) 113 | data[layer_index] = final_lines 114 | return data 115 | -------------------------------------------------------------------------------- /ZMoveIroning.py: -------------------------------------------------------------------------------- 1 | # ZMoveIroning 2 | """ 3 | ZMoveIroning for 3D prints. 4 | 5 | Z hop for ironing 6 | 7 | Author: 5axes 8 | Version: 1.0 9 | Version: 1.1 Remove some useless part of the Code. 10 | 11 | """ 12 | 13 | import re #To perform the search 14 | from enum import Enum 15 | from ..Script import Script 16 | 17 | from UM.Application import Application 18 | from UM.Logger import Logger 19 | from UM.Message import Message 20 | from UM.i18n import i18nCatalog 21 | 22 | catalog = i18nCatalog("cura") 23 | 24 | __version__ = '1.1' 25 | 26 | class Section(Enum): 27 | """Enum for section type.""" 28 | 29 | NOTHING = 0 30 | SKIRT = 1 31 | INNER_WALL = 2 32 | OUTER_WALL = 3 33 | INFILL = 4 34 | SKIN = 5 35 | SKIN2 = 6 36 | 37 | 38 | def is_extrusion_line(line: str) -> bool: 39 | """Check if current line is a standard printing segment. 40 | 41 | Args: 42 | line (str): Gcode line 43 | 44 | Returns: 45 | bool: True if the line is a standard printing segment 46 | """ 47 | return "G1" in line and "X" in line and "Y" in line and "E" in line 48 | 49 | def is_not_extrusion_line(line: str) -> bool: 50 | """Check if current line is a rapid movement segment. 51 | 52 | Args: 53 | line (str): Gcode line 54 | 55 | Returns: 56 | bool: True if the line is a standard printing segment 57 | """ 58 | return "G0" in line and "X" in line and "Y" in line and not "E" in line 59 | 60 | def is_begin_skin_segment_line(line: str) -> bool: 61 | """Check if current line is the start of an skin. 62 | 63 | Args: 64 | line (str): Gcode line 65 | 66 | Returns: 67 | bool: True if the line is the start of an skin section 68 | """ 69 | return line.startswith(";TYPE:SKIN") 70 | 71 | 72 | class ZMoveIroning(Script): 73 | def getSettingDataString(self): 74 | return """{ 75 | "name": "Z Move Ironing", 76 | "key": "ZMoveIroning", 77 | "metadata": {}, 78 | "version": 2, 79 | "settings": 80 | { 81 | "extruder_nb": 82 | { 83 | "label": "Extruder Id", 84 | "description": "Define extruder Id in case of multi extruders", 85 | "unit": "", 86 | "type": "int", 87 | "default_value": 1 88 | } 89 | } 90 | }""" 91 | 92 | ## ----------------------------------------------------------------------------- 93 | # 94 | # Main Prog 95 | # 96 | ## ----------------------------------------------------------------------------- 97 | 98 | def execute(self, data): 99 | 100 | extruder_id = self.getSettingValueByKey("extruder_nb") 101 | extruder_id = extruder_id -1 102 | 103 | # machine_extruder_count 104 | extruder_count=Application.getInstance().getGlobalContainerStack().getProperty("machine_extruder_count", "value") 105 | extruder_count = extruder_count-1 106 | if extruder_id>extruder_count : 107 | extruder_id=extruder_count 108 | 109 | extrud = Application.getInstance().getGlobalContainerStack().extruderList 110 | 111 | retraction_hop = float(extrud[extruder_id].getProperty("retraction_hop", "value")) 112 | speed_z_hop = int(extrud[extruder_id].getProperty("speed_z_hop", "value")) 113 | speed_z_hop = speed_z_hop * 60 114 | 115 | ironingenabled = extrud[extruder_id].getProperty("ironing_enabled", "value") 116 | if ironingenabled == False: 117 | # 118 | Logger.log('d', 'Gcode must be generate with ironing mode') 119 | Message(catalog.i18nc("@message","Gcode must be generate with ironing mode"), title = catalog.i18nc("@info:title", "Post Processing")).show() 120 | return None 121 | 122 | retraction_hop_enabled= extrud[extruder_id].getProperty("retraction_hop_enabled", "value") 123 | if retraction_hop_enabled == True: 124 | # 125 | Logger.log('d', 'Mode Z Hop must not be activated') 126 | Message(catalog.i18nc("@message","Mode Z Hop must not be activated"), title = catalog.i18nc("@info:title", "Post Processing")).show() 127 | return None 128 | 129 | """Parse Gcode and modify infill portions with an extrusion width gradient.""" 130 | currentSection = Section.NOTHING 131 | in_z_hop = False 132 | 133 | 134 | for layer_index, layer in enumerate(data): 135 | lines = layer.split("\n") 136 | for line_index, currentLine in enumerate(lines): 137 | 138 | if currentLine.startswith("G0") and "Z" in currentLine : 139 | searchZ = re.search(r"Z(\d*\.?\d*)", currentLine) 140 | if searchZ: 141 | current_z=float(searchZ.group(1)) 142 | 143 | if is_begin_skin_segment_line(currentLine) and not (currentSection == Section.SKIN): 144 | currentSection = Section.SKIN 145 | continue 146 | 147 | # SKIN After SKIN = Ironing operation 148 | if currentSection == Section.SKIN: 149 | if is_begin_skin_segment_line(currentLine): 150 | currentSection = Section.SKIN2 151 | continue 152 | elif ";" in currentLine: 153 | currentSection = Section.NOTHING 154 | 155 | if currentSection == Section.SKIN2: 156 | if is_not_extrusion_line(currentLine): 157 | 158 | if not in_z_hop: 159 | in_z_hop = True 160 | Output_Z=current_z+retraction_hop 161 | outPutLine = "G1 F{} Z{:.3f}\n".format(speed_z_hop,Output_Z) 162 | outPutLine = outPutLine + currentLine 163 | lines[line_index] = outPutLine 164 | else: 165 | if in_z_hop: 166 | in_z_hop = False 167 | outPutLine = currentLine + "\nG1 F{} Z{:.3f}".format(speed_z_hop,current_z) 168 | lines[line_index] = outPutLine 169 | 170 | # 171 | # comment like ;MESH:NONMESH 172 | # 173 | if currentLine.startswith(";") : 174 | currentSection = Section.NOTHING 175 | # 176 | # end of analyse 177 | # 178 | 179 | final_lines = "\n".join(lines) 180 | data[layer_index] = final_lines 181 | return data 182 | -------------------------------------------------------------------------------- /ZOffsetBrim.py: -------------------------------------------------------------------------------- 1 | # ZOffsetBrim 2 | """ 3 | ZOffsetBrim for 3D prints. 4 | 5 | Z Offset just on Brim / Skirt 6 | 7 | Author: 5axes 8 | Version: 1.0 9 | 10 | """ 11 | 12 | from ..Script import Script 13 | from UM.Logger import Logger 14 | import re #To perform the search 15 | from enum import Enum 16 | from UM.Message import Message 17 | from UM.i18n import i18nCatalog 18 | catalog = i18nCatalog("cura") 19 | 20 | __version__ = '1.0' 21 | 22 | class Section(Enum): 23 | """Enum for section type.""" 24 | 25 | NOTHING = 0 26 | SKIRT = 1 27 | INNER_WALL = 2 28 | OUTER_WALL = 3 29 | INFILL = 4 30 | SKIN = 5 31 | SKIN2 = 6 32 | 33 | 34 | 35 | def is_begin_layer_line(line: str) -> bool: 36 | """Check if current line is the start of a layer section. 37 | 38 | Args: 39 | line (str): Gcode line 40 | 41 | Returns: 42 | bool: True if the line is the start of a layer section 43 | """ 44 | return line.startswith(";LAYER:") 45 | 46 | 47 | def is_extrusion_line(line: str) -> bool: 48 | """Check if current line is a standard printing segment. 49 | 50 | Args: 51 | line (str): Gcode line 52 | 53 | Returns: 54 | bool: True if the line is a standard printing segment 55 | """ 56 | return "G1" in line and "X" in line and "Y" in line and "E" in line 57 | 58 | def is_not_extrusion_line(line: str) -> bool: 59 | """Check if current line is a rapid movement with a Z component segment. 60 | 61 | Args: 62 | line (str): Gcode line 63 | 64 | Returns: 65 | bool: True if the line is a standard printing segment 66 | """ 67 | return "G0" in line and "Z" in line and not "E" in line 68 | 69 | def is_retract_line(line: str) -> bool: 70 | """Check if current line is a speed movement with a Z component segment. 71 | 72 | Args: 73 | line (str): Gcode line 74 | 75 | Returns: 76 | bool: True if the line is a standard printing segment 77 | """ 78 | return "G1" in line and "Z" in line and not "E" in line 79 | 80 | def is_begin_skirt_segment_line(line: str) -> bool: 81 | """Check if current line is the start of an skirt. 82 | 83 | Args: 84 | line (str): Gcode line 85 | 86 | Returns: 87 | bool: True if the line is the start of an skirt section 88 | """ 89 | return line.startswith(";TYPE:SKIRT") 90 | 91 | def is_begin_segment_line(line: str) -> bool: 92 | """Check if current line is the start of a new Type section. 93 | 94 | Args: 95 | line (str): Gcode line 96 | 97 | Returns: 98 | bool: True if the line is the start of a new Type section 99 | """ 100 | return line.startswith(";TYPE:") 101 | 102 | 103 | class ZOffsetBrim(Script): 104 | def getSettingDataString(self): 105 | return """{ 106 | "name": "Z Offset Brim", 107 | "key": "ZOffsetBrim", 108 | "metadata": {}, 109 | "version": 2, 110 | "settings": 111 | { 112 | "offset": 113 | { 114 | "label": "Offset Value", 115 | "description": "Define the Offset Value to the brim", 116 | "unit": "mm", 117 | "type": "float", 118 | "default_value": -0.03 119 | } 120 | } 121 | }""" 122 | 123 | 124 | ## ----------------------------------------------------------------------------- 125 | # 126 | # Main Prog 127 | # 128 | ## ----------------------------------------------------------------------------- 129 | 130 | def execute(self, data): 131 | 132 | v_offset = self.getSettingValueByKey("offset") 133 | 134 | currentSection = Section.NOTHING 135 | in_Z_offset = False 136 | current_z = 0 137 | current_Layer = 0 138 | 139 | for layer in data: 140 | layer_index = data.index(layer) 141 | lines = layer.split("\n") 142 | line_index = -1 143 | 144 | for currentLine in lines: 145 | #line_index = lines.index(currentLine) 146 | line_index += 1 147 | 148 | if is_begin_layer_line(currentLine) : 149 | current_Layer = int(currentLine.split(":")[1]) 150 | current_Layer += 1 151 | continue 152 | 153 | if current_Layer == 1 : 154 | if is_not_extrusion_line(currentLine) or is_retract_line(currentLine) : 155 | # Logger.log('d', 'currentLine with Z : {}'.format(currentLine)) 156 | # Logger.log('d', 'line_index : {}'.format(line_index)) 157 | searchZ = re.search(r"Z(\d*\.?\d*)", currentLine) 158 | if searchZ : 159 | if not in_Z_offset : 160 | current_z=float(searchZ.group(1)) 161 | else : 162 | save_Z=float(searchZ.group(1)) 163 | Output_Z=save_Z+v_offset 164 | instructionZ="Z"+str(searchZ.group(1)) 165 | outPutZ = "Z{:.3f}".format(Output_Z) 166 | # Logger.log('d', 'save_Z : {:.3f}'.format(save_Z)) 167 | # Logger.log('d', 'line : {}'.format(currentLine)) 168 | # Logger.log('d', 'line replace : {}'.format(currentLine.replace(instructionZ,outPutZ))) 169 | lines[line_index]=currentLine.replace(instructionZ,outPutZ) 170 | 171 | if is_begin_segment_line(currentLine) and currentSection == Section.SKIRT: 172 | if in_Z_offset: 173 | cLine = lines[line_index+1] 174 | # Logger.log('d', 'cLine : {}'.format(cLine)) 175 | searchZ = re.search(r"Z(\d*\.?\d*)", cLine) 176 | if not searchZ : 177 | lines.insert(line_index + 1, "G0 Z{:.3f}".format(current_z)) 178 | in_Z_offset = False 179 | currentSection = Section.NOTHING 180 | continue 181 | 182 | if is_begin_skirt_segment_line(currentLine) and currentSection != Section.SKIRT : 183 | currentSection = Section.SKIRT 184 | if not in_Z_offset: 185 | # cas avec Z Hop 186 | cLine = lines[line_index+1] 187 | searchZ = re.search(r"Z(\d*\.?\d*)", cLine) 188 | if searchZ : 189 | current_z=float(searchZ.group(1)) 190 | else : 191 | Output_Z=current_z+v_offset 192 | lines.insert(line_index + 1, "G0 Z{:.3f}".format(Output_Z)) 193 | in_Z_offset = True 194 | # 195 | # end of analyse 196 | # 197 | 198 | final_lines = "\n".join(lines) 199 | data[layer_index] = final_lines 200 | return data 201 | -------------------------------------------------------------------------------- /images/AlterZhops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/AlterZhops.png -------------------------------------------------------------------------------- /images/ChangeFanValue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/ChangeFanValue.jpg -------------------------------------------------------------------------------- /images/CheckFirstSpeed.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/CheckFirstSpeed.JPG -------------------------------------------------------------------------------- /images/CheckFirstSpeedorigine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/CheckFirstSpeedorigine.png -------------------------------------------------------------------------------- /images/CheckFirstSpeedresult.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/CheckFirstSpeedresult.png -------------------------------------------------------------------------------- /images/ErrorZmoveG0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/ErrorZmoveG0.jpg -------------------------------------------------------------------------------- /images/FanIroning.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/FanIroning.jpg -------------------------------------------------------------------------------- /images/FanWithBridgeValue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/FanWithBridgeValue.jpg -------------------------------------------------------------------------------- /images/FanWithoutBridgeValue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/FanWithoutBridgeValue.jpg -------------------------------------------------------------------------------- /images/GcodeDocumentation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/GcodeDocumentation.jpg -------------------------------------------------------------------------------- /images/InfillLast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/InfillLast.png -------------------------------------------------------------------------------- /images/InhibFan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/InhibFan.jpg -------------------------------------------------------------------------------- /images/InsertAtLayerNumber.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/InsertAtLayerNumber.jpg -------------------------------------------------------------------------------- /images/NotDetectedFanIroning.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/NotDetectedFanIroning.jpg -------------------------------------------------------------------------------- /images/PrintInfos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/PrintInfos.jpg -------------------------------------------------------------------------------- /images/SpoonOrder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/SpoonOrder.png -------------------------------------------------------------------------------- /images/ZmoveG0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/ZmoveG0.jpg -------------------------------------------------------------------------------- /images/ZmoveIroning.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/ZmoveIroning.jpg -------------------------------------------------------------------------------- /images/benchy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/benchy.jpg -------------------------------------------------------------------------------- /images/commentGcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/commentGcode.jpg -------------------------------------------------------------------------------- /images/fastfirstinfill.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/fastfirstinfill.jpg -------------------------------------------------------------------------------- /images/gcode_temptower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/gcode_temptower.jpg -------------------------------------------------------------------------------- /images/gradient.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/gradient.jpg -------------------------------------------------------------------------------- /images/gradient2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/gradient2.jpg -------------------------------------------------------------------------------- /images/gradient3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/gradient3.jpg -------------------------------------------------------------------------------- /images/multibrim.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/multibrim.jpg -------------------------------------------------------------------------------- /images/origine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/origine.png -------------------------------------------------------------------------------- /images/plugins.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/plugins.jpg -------------------------------------------------------------------------------- /images/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/result.png -------------------------------------------------------------------------------- /images/retract-tower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/retract-tower.jpg -------------------------------------------------------------------------------- /images/slowz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/slowz.jpg -------------------------------------------------------------------------------- /images/slowzsettings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/slowzsettings.jpg -------------------------------------------------------------------------------- /images/speedtower.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/speedtower.jpg -------------------------------------------------------------------------------- /images/tempfan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5axes/Cura-Postprocessing-Scripts/7298def59db28e7779affe80d6440c0d6cdb4d90/images/tempfan.jpg -------------------------------------------------------------------------------- /startHeatingAtPercentage.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------------------------------ 2 | # 3 | # Cura PostProcessing Script 4 | # Author: Ricardo Ortega 5 | # Date: March 01, 2024 6 | # 7 | # Description: Modify M190 line 8 | # 9 | # 10 | #------------------------------------------------------------------------------------------------------------------------------------ 11 | # 12 | # Version 1.0 01/03/2024 Change the M190 temperature to a percentage to start heating the nozzle 13 | # 14 | #------------------------------------------------------------------------------------------------------------------------------------ 15 | 16 | from ..Script import Script 17 | from UM.Logger import Logger 18 | from UM.Application import Application 19 | 20 | from enum import Enum 21 | 22 | __version__ = '1.0' 23 | 24 | class Section(Enum): 25 | """Enum for section type.""" 26 | 27 | NOTHING = 0 28 | SKIRT = 1 29 | INNER_WALL = 2 30 | OUTER_WALL = 3 31 | INFILL = 4 32 | SKIN = 5 33 | SKIN2 = 6 34 | 35 | 36 | class startHeatingAtPercentage(Script): 37 | def __init__(self): 38 | super().__init__() 39 | 40 | def getSettingDataString(self): 41 | return """{ 42 | "name": "startHeatingAtPercentage", 43 | "key": "startHeatingAtPercentage", 44 | "metadata": {}, 45 | "version": 2, 46 | "settings": 47 | { 48 | "bedTempPercentage": 49 | { 50 | "label": "Bed temperature percentage to start heating the nozzle", 51 | "description": "What is the percentage of bed heating to start heating the nozzle.", 52 | "type": "float", 53 | "unit": "%", 54 | "default_value": 100, 55 | "minimum_value": "50", 56 | "maximum_value": "100" 57 | } 58 | } 59 | }""" 60 | 61 | def execute(self, data): 62 | 63 | bedTempPercentage = float(self.getSettingValueByKey("bedTempPercentage")) 64 | OnlyFirst=True 65 | for layer in data: 66 | layer_index = data.index(layer) 67 | 68 | lines = layer.split("\n") 69 | resLines=[] 70 | for line in lines: 71 | 72 | if ("M190" in line) and OnlyFirst: 73 | temp=line.split("S") 74 | if(len(temp)==2): 75 | percTemp=int((float(temp[1])*bedTempPercentage)/100) 76 | resLines.append(f"M190 S{percTemp}") 77 | OnlyFirst=False 78 | else: 79 | resLines.append(line) 80 | 81 | 82 | 83 | 84 | 85 | result = "\n".join(resLines) 86 | data[layer_index] = result 87 | 88 | return data --------------------------------------------------------------------------------