ColorMix.py 8.4 KB

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