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