# ColorMix script - 2-1 extruder color mix and blending
# This script is specific for the Geeetech A10M dual extruder but should work with other Marlin printers. 
# It runs with the PostProcessingPlugin which is released under the terms of the LGPLv3 or higher.
# This script is licensed under the Creative Commons - Attribution - Share Alike (CC BY-SA) terms

#Authors of the 2-1 ColorMix plug-in / script:
# Written by John Hryb - john.hryb.4@gmail.com

#history / change-log:
#V1.0.0 - Initial
#V1.1.0 - 
    # additions:
        #Object number - To select individual models or all when using "one at a time" print sequence
#V1.2.0
    # fixed layer heights Cura starts at 1 while G-code starts at 0
    # removed notes
    # changed Units of measurement to Units
#V1.2.1
    # Fixed mm bug when not in multiples of layer height
# Uses -
# M163 - Set Mix Factor 
# M164 - Save Mix - saves to T2 as a unique mix

import re # To perform the search and replace.
from ..Script import Script

class ColorMix(Script):
    def __init__(self):
        super().__init__()

    def getSettingDataString(self):
        return """{
            "name":"ColorMix 2-1 V1.2.1",
            "key":"ColorMix 2-1",
            "metadata": {},
            "version": 2,
            "settings":
            {
                "units_of_measurement":
                {
                    "label": "Units",
                    "description": "Input value as mm or layer number.",
                    "type": "enum",
                    "options": {"mm":"mm","layer":"Layer"},
                    "default_value": "layer"
                },
                "object_number":
                {
                    "label": "Object Number",
                    "description": "Select model to apply to for print one at a time print sequence. 0 = everything",
                    "type": "int",
                    "default_value": 0,
                    "minimum_value": "0"
                },
                "start_height":
                {
                    "label": "Start Height",
                    "description": "Value to start at (mm or layer)",
                    "type": "float",
                    "default_value": 0,
                    "minimum_value": "0"
                },
                "behavior":
                {
                    "label": "Fixed or blend",
                    "description": "Select Fixed (set new mixture) or Blend mode (dynamic mix)",
                    "type": "enum",
                    "options": {"fixed_value":"Fixed","blend_value":"Blend"},
                    "default_value": "fixed_value"
                },
                "finish_height":
                {
                    "label": "Finish Height",
                    "description": "Value to stop at (mm or layer)",
                    "type": "float",
                    "default_value": 0,
                    "minimum_value": "0",
                    "minimum_value_warning": "start_height",
                    "enabled": "behavior == 'blend_value'" 
                },
                "mix_start":
                {
                    "label": "Start mix ratio",
                    "description": "First extruder percentage 0-100",
                    "type": "float",
                    "default_value": 100,
                    "minimum_value": "0",
                    "minimum_value_warning": "0",
                    "maximum_value_warning": "100"
                },
                "mix_finish":
                {
                    "label": "End mix ratio",
                    "description": "First extruder percentage 0-100 to finish blend",
                    "type": "float",
                    "default_value": 0,
                    "minimum_value": "0",
                    "minimum_value_warning": "0",
                    "maximum_value_warning": "100",
                    "enabled": "behavior == 'blend_value'"
                }
            }
        }"""
    def getValue(self, line, key, default = None): #replace default getvalue due to comment-reading feature
        if not key in line or (";" in line and line.find(key) > line.find(";") and
                                   not ";ChangeAtZ" in key and not ";LAYER:" in key):
            return default
        subPart = line[line.find(key) + len(key):] #allows for string lengths larger than 1
        if ";ChangeAtZ" in key:
            m = re.search("^[0-4]", subPart)
        elif ";LAYER:" in key:
            m = re.search("^[+-]?[0-9]*", subPart)
        else:
            #the minus at the beginning allows for negative values, e.g. for delta printers
            m = re.search("^[-]?[0-9]*\.?[0-9]*", subPart)
        if m == None:
            return default
        try:
            return float(m.group(0))
        except:
            return default

    def execute(self, data):

        firstHeight = self.getSettingValueByKey("start_height")
        secondHeight = self.getSettingValueByKey("finish_height")
        firstMix = self.getSettingValueByKey("mix_start")
        secondMix = self.getSettingValueByKey("mix_finish")
        modelOfInterest = self.getSettingValueByKey("object_number")

        #get layer height
        layerHeight = 0
        for active_layer in data:
            lines = active_layer.split("\n")
            for line in lines:
                if ";Layer height: " in line:
                    layerHeight = self.getValue(line, ";Layer height: ", layerHeight)
                    break
            if layerHeight != 0:
                break

        #default layerHeight if not found
        if layerHeight == 0:
            layerHeight = .2

        #get layers to use
        startLayer = 0
        endLayer = 0
        if self.getSettingValueByKey("units_of_measurement") == "mm":
            startLayer = round(firstHeight / layerHeight)
            endLayer = round(secondHeight / layerHeight)
        else:  #layer height shifts down by one for g-code
            if firstHeight <= 0:
                firstHeight = 1
            if secondHeight <= 0:
                secondHeight = 1   
            startLayer = firstHeight - 1
            endLayer = secondHeight - 1
        #see if one-shot
        if self.getSettingValueByKey("behavior") == "fixed_value":
            endLayer = startLayer
            firstExtruderIncrements = 0
        else:  #blend
            firstExtruderIncrements = (secondMix - firstMix) / (endLayer - startLayer)
        firstExtruderValue = 0
        index = 0

        #start scanning
        layer = -1
        modelNumber = 0
        for active_layer in data:
            modified_gcode = ""
            lineIndex = 0
            lines = active_layer.split("\n")
            for line in lines:
                #dont leave blanks 
                if line != "":
                    modified_gcode += line + "\n"
                # find current layer
                if ";LAYER:" in line:
                    layer = self.getValue(line, ";LAYER:", layer)
                    #get model number by layer 0 repeats
                    if layer == 0:
                        modelNumber = modelNumber + 1
                    #search for layers to manipulate
                    if (layer >= startLayer) and (layer <= endLayer):
                        #make sure correct model is selected
                        if (modelOfInterest == 0) or (modelOfInterest == modelNumber):
                            #Delete old data if required
                            if lines[lineIndex + 4] == "T2":  
                                del lines[(lineIndex + 1):(lineIndex + 5)]
                            #add mixing commands
                            firstExtruderValue = int(((layer - startLayer) * firstExtruderIncrements) + firstMix)
                            if firstExtruderValue == 100:
                                modified_gcode += "M163 S0 P1\n"
                                modified_gcode += "M163 S1 P0\n"
                            elif firstExtruderValue == 0:
                                modified_gcode += "M163 S0 P0\n"
                                modified_gcode += "M163 S1 P1\n"
                            else:
                                modified_gcode += "M163 S0 P0.{:02d}\n".format(firstExtruderValue)
                                modified_gcode += "M163 S1 P0.{:02d}\n".format(100 - firstExtruderValue)
                            modified_gcode += "M164 S2\n"
                            modified_gcode += "T2\n"
                lineIndex += 1  #for deleting index
            data[index] = modified_gcode
            index += 1
        return data