1# ColorMix script - 2-1 extruder color mix and blending 2# This script is specific for the Geeetech A10M dual extruder but should work with other Marlin printers. 3# It runs with the PostProcessingPlugin which is released under the terms of the AGPLv3 or higher. 4# This script is licensed under the Creative Commons - Attribution - Share Alike (CC BY-SA) terms 5 6#Authors of the 2-1 ColorMix plug-in / script: 7# Written by John Hryb - john.hryb.4@gmail.com 8 9#history / change-log: 10#V1.0.0 - Initial 11#V1.1.0 - 12 # additions: 13 #Object number - To select individual models or all when using "one at a time" print sequence 14#V1.2.0 15 # fixed layer heights Cura starts at 1 while G-code starts at 0 16 # removed notes 17 # changed Units of measurement to Units 18#V1.2.1 19 # Fixed mm bug when not in multiples of layer height 20# Uses - 21# M163 - Set Mix Factor 22# M164 - Save Mix - saves to T2 as a unique mix 23 24import re #To perform the search and replace. 25from ..Script import Script 26 27class ColorMix(Script): 28 def __init__(self): 29 super().__init__() 30 31 def getSettingDataString(self): 32 return """{ 33 "name":"ColorMix 2-1 V1.2.1", 34 "key":"ColorMix 2-1", 35 "metadata": {}, 36 "version": 2, 37 "settings": 38 { 39 "units_of_measurement": 40 { 41 "label": "Units", 42 "description": "Input value as mm or layer number.", 43 "type": "enum", 44 "options": {"mm":"mm","layer":"Layer"}, 45 "default_value": "layer" 46 }, 47 "object_number": 48 { 49 "label": "Object Number", 50 "description": "Select model to apply to for print one at a time print sequence. 0 = everything", 51 "type": "int", 52 "default_value": 0, 53 "minimum_value": "0" 54 }, 55 "start_height": 56 { 57 "label": "Start Height", 58 "description": "Value to start at (mm or layer)", 59 "type": "float", 60 "default_value": 0, 61 "minimum_value": "0" 62 }, 63 "behavior": 64 { 65 "label": "Fixed or blend", 66 "description": "Select Fixed (set new mixture) or Blend mode (dynamic mix)", 67 "type": "enum", 68 "options": {"fixed_value":"Fixed","blend_value":"Blend"}, 69 "default_value": "fixed_value" 70 }, 71 "finish_height": 72 { 73 "label": "Finish Height", 74 "description": "Value to stop at (mm or layer)", 75 "type": "float", 76 "default_value": 0, 77 "minimum_value": "0", 78 "minimum_value_warning": "start_height", 79 "enabled": "behavior == 'blend_value'" 80 }, 81 "mix_start": 82 { 83 "label": "Start mix ratio", 84 "description": "First extruder percentage 0-100", 85 "type": "float", 86 "default_value": 100, 87 "minimum_value": "0", 88 "minimum_value_warning": "0", 89 "maximum_value_warning": "100" 90 }, 91 "mix_finish": 92 { 93 "label": "End mix ratio", 94 "description": "First extruder percentage 0-100 to finish blend", 95 "type": "float", 96 "default_value": 0, 97 "minimum_value": "0", 98 "minimum_value_warning": "0", 99 "maximum_value_warning": "100", 100 "enabled": "behavior == 'blend_value'" 101 } 102 } 103 }""" 104 def getValue(self, line, key, default = None): #replace default getvalue due to comment-reading feature 105 if not key in line or (";" in line and line.find(key) > line.find(";") and 106 not ";ChangeAtZ" in key and not ";LAYER:" in key): 107 return default 108 subPart = line[line.find(key) + len(key):] #allows for string lengths larger than 1 109 if ";ChangeAtZ" in key: 110 m = re.search("^[0-4]", subPart) 111 elif ";LAYER:" in key: 112 m = re.search("^[+-]?[0-9]*", subPart) 113 else: 114 #the minus at the beginning allows for negative values, e.g. for delta printers 115 m = re.search("^[-]?[0-9]*\.?[0-9]*", subPart) 116 if m == None: 117 return default 118 try: 119 return float(m.group(0)) 120 except: 121 return default 122 123 def execute(self, data): 124 125 firstHeight = self.getSettingValueByKey("start_height") 126 secondHeight = self.getSettingValueByKey("finish_height") 127 firstMix = self.getSettingValueByKey("mix_start") 128 secondMix = self.getSettingValueByKey("mix_finish") 129 modelOfInterest = self.getSettingValueByKey("object_number") 130 131 #get layer height 132 layerHeight = 0 133 for active_layer in data: 134 lines = active_layer.split("\n") 135 for line in lines: 136 if ";Layer height: " in line: 137 layerHeight = self.getValue(line, ";Layer height: ", layerHeight) 138 break 139 if layerHeight != 0: 140 break 141 142 #default layerHeight if not found 143 if layerHeight == 0: 144 layerHeight = .2 145 146 #get layers to use 147 startLayer = 0 148 endLayer = 0 149 if self.getSettingValueByKey("units_of_measurement") == "mm": 150 startLayer = round(firstHeight / layerHeight) 151 endLayer = round(secondHeight / layerHeight) 152 else: #layer height shifts down by one for g-code 153 if firstHeight <= 0: 154 firstHeight = 1 155 if secondHeight <= 0: 156 secondHeight = 1 157 startLayer = firstHeight - 1 158 endLayer = secondHeight - 1 159 #see if one-shot 160 if self.getSettingValueByKey("behavior") == "fixed_value": 161 endLayer = startLayer 162 firstExtruderIncrements = 0 163 else: #blend 164 firstExtruderIncrements = (secondMix - firstMix) / (endLayer - startLayer) 165 firstExtruderValue = 0 166 index = 0 167 168 #start scanning 169 layer = -1 170 modelNumber = 0 171 for active_layer in data: 172 modified_gcode = "" 173 lineIndex = 0 174 lines = active_layer.split("\n") 175 for line in lines: 176 #dont leave blanks 177 if line != "": 178 modified_gcode += line + "\n" 179 # find current layer 180 if ";LAYER:" in line: 181 layer = self.getValue(line, ";LAYER:", layer) 182 #get model number by layer 0 repeats 183 if layer == 0: 184 modelNumber = modelNumber + 1 185 #search for layers to manipulate 186 if (layer >= startLayer) and (layer <= endLayer): 187 #make sure correct model is selected 188 if (modelOfInterest == 0) or (modelOfInterest == modelNumber): 189 #Delete old data if required 190 if lines[lineIndex + 4] == "T2": 191 del lines[(lineIndex + 1):(lineIndex + 5)] 192 #add mixing commands 193 firstExtruderValue = int(((layer - startLayer) * firstExtruderIncrements) + firstMix) 194 if firstExtruderValue == 100: 195 modified_gcode += "M163 S0 P1\n" 196 modified_gcode += "M163 S1 P0\n" 197 elif firstExtruderValue == 0: 198 modified_gcode += "M163 S0 P0\n" 199 modified_gcode += "M163 S1 P1\n" 200 else: 201 modified_gcode += "M163 S0 P0.{:02d}\n".format(firstExtruderValue) 202 modified_gcode += "M163 S1 P0.{:02d}\n".format(100 - firstExtruderValue) 203 modified_gcode += "M164 S2\n" 204 modified_gcode += "T2\n" 205 lineIndex += 1 #for deleting index 206 data[index] = modified_gcode 207 index += 1 208 return data