├── _config.yml ├── README.md ├── CoreLibrary.py ├── ColorShift.py └── Melt.py /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-time-machine -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MELT Multi-Extruder Layering Tool 2 | 3 | A Cura plugin that adds support for the [M3D ProMega 3D Printer](https://store.printm3d.com/pages/promega) Compound Extruder and QuadFusion Extruder. 4 | 5 | Please visit the [melt Wiki](https://github.com/gargansa/MELT/wiki) for more information! 6 | 7 | ## Cura 8 | [Cura](https://ultimaker.com/en/products/ultimaker-cura-software) is a 3rd party slicing software created and maintained by the folks over at [Ultimaker](https://ultimaker.com/). This software is provided for free and can be used to generate [.gcode](https://en.wikipedia.org/wiki/G-code) files for use with your [M3D ProMega 3D Printer](https://store.printm3d.com/pages/promega). 9 | 10 | ## M3D 11 | [M3D](http://printm3d.com/) is one of the leading manufacturers of consumer 3D printers and filaments in the world. M3D is the company that produces the [M3D ProMega 3D Printer](https://store.printm3d.com/pages/promega). 12 | 13 | ## MELT 14 | [melt](https://github.com/gargansa/MELT) is a plugin that adds support for some of the new / advanced features of the [M3D ProMega 3D Printer](https://store.printm3d.com/pages/promega). 15 | 16 | ## Version History 17 | 18 | 19 | ## Current Features List 20 | 1. Support for 2-4 extruders 21 | 2. allows the shift to start at any layer or percentage of the print and stop at any layer or percentage. 22 | 3. Initial Flow setting for raft or anything before affected layers 23 | 4. Shift Modifiers - Normal, Wood Texture, Repeating Pattern, Random, Lerp, Slope, Ellipse 24 | 5. Rate Modifiers - Normal, Random 25 | 6. Direction Modifier (incase you loaded the filament into opposite extruders) 26 | 7. Final Flow setting for anything after the affected layers 27 | 8. Debug reporting to gcode file for troubleshooting user problems that may arise 28 | 9. Ability to only set the initial extruder rate and not shift through the print by setting change rate to 0 29 | 10. Multiple runs of script will allow you to shift from 1:0 to 0:1 for the first % of the print and then 0:1 to 1:0 for the next % of the print 30 | 11. Option to wrap the shift back to the beginning nozzle with user input circular or linear to just end at the last extruder 31 | 12. Allows a gradient shift through any number of objects in the same direction. 32 | 33 | ## Possible Next Features 34 | 1. Ability to change at a specific layer once 35 | 2. Ability to Insert a pause at end of shift so that filaments can be removed and changed to create rainbow effect 36 | 3. Having the ability to apply both "fixed mix ratios" and "gradients" to different parts of a multi file print. 37 | 4. Dynamically display extruders to account for both the Dual Compound and QuadFusion extruders 38 | 5. Increasing the number of modifiers as long as they are unique, artistic or useful. 39 | 6. Ability to set start and end range values to each extruder 40 | 7. Ability to select which extruders to preform the interpolation with when using three or four extruders 41 | 8. Ability to extrude with both extruders on compound, and all four on QuadFusion at once to achieve faster print time. 42 | 9. Ability to define starting and stopping layers for mixes. Example: First 50% of print, shift between 0.5,0.0,0.0,0.5 to 0.0,0.5,0.5,0.0 and last 50% of print shift from 0.5,0.5,0.0,0.0 to 0.0,0.0,0.5,0.5 43 | 44 | ## Longer term goals (complex goals) 45 | 1. Ability to gradient from side to side, or bottom corner to opposing top corner. 46 | 2. Ability to paint the surface(even if low quality) and predict and split the gcode where needed to execute the change early enough to hit the mark. 47 | 48 | ## Known Bugs 49 | 1. Its possible to enter non numeral values for initial and final extruder values but non numerals would break the code 50 | 2. Possibly need to be able to edit P0 part of line on each extruder adjustment 51 | 3. Lerp, Slope, Ellipse Modifiers may need touchups 52 | 4. 53 | 54 | 55 | -------------------------------------------------------------------------------- /CoreLibrary.py: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # 3 | # MELT Core Library 4 | # 5 | # Methods to facilitate processing gcode. 6 | # 7 | # Usage: 8 | # Creating a mix: 9 | # mix = Miso.Mix([0.5,0.5,0.5,0.5]) 10 | # 11 | # Configuring a tool: 12 | # tool = Miso.Tool([mix]) 13 | # Miso.setToolConfig(0, tool) # sets this mix for tool 0 14 | # 15 | # Configuring a gradient: 16 | # gradientStart = Miso.Mix([0.1,0.1,0.1,0.1], 0.25) 17 | # gradientStop = Miso.Mix([0.9,0,0,0], 0.75) 18 | # tool = Miso.Tool([gradientStart, gradientStop]) 19 | # Miso.setToolConfig(2, tool) # sets this gradient for tool 2 20 | # 21 | # Converting gcode: 22 | # maxZHeight = maxHeightOfPrint 23 | # newcode = Miso.fromGcode(gcode, maxZHeight) 24 | # 25 | ################################################################################ 26 | 27 | import re 28 | 29 | class Miso: 30 | # Hash of ToolConfigurations 31 | # Allows extruder mixes to be assigned to different tools 32 | _toolConfigs = {} 33 | 34 | @staticmethod 35 | def setToolConfig(toolId, toolConfig): 36 | Miso._toolConfigs[toolId] = toolConfig 37 | 38 | @staticmethod 39 | def getToolConfig(toolId): 40 | if toolId in Miso._toolConfigs: 41 | return Miso._toolConfigs[toolId] 42 | return Miso.Tool() #default 43 | 44 | # Forward-reading modification of gcode here 45 | # tracks tool changes, z changes, and relative / absolute changes 46 | # When an extrusion command is found and any of this info has changed 47 | # then a new mix command is written 48 | @staticmethod 49 | def fromGcode(gcode, zmax): 50 | tool = 0 51 | toolHistory = tool 52 | zpos = 0 53 | zposHistory = zpos 54 | relative = False 55 | relativeHistory = relative 56 | newcode = '' 57 | for line in gcode: 58 | tool = Miso.Gcode.updateTool(line, tool) 59 | zpos = Miso.Gcode.updateZ(line, zpos, relative) 60 | relative = Miso.Gcode.updateRelative(line, relative) 61 | isDirty = tool != toolHistory or zpos != zposHistory or relative != relativeHistory 62 | if isDirty and Miso.Gcode.isExtrude(line): 63 | toolHistory = tool 64 | zposHistory = zpos 65 | relativeHistory = relative 66 | newcode += Miso.Gcode.formatMix(tool, zpos, zmax) + "\n" 67 | newcode += line + '\n' 68 | return newcode 69 | 70 | # Miso.Tool 71 | # Mix and gradient information for a specific tool 72 | # Example: 73 | # toolConfig = Miso.Tool([mix1, mix2, ...]) 74 | class Tool: 75 | def __init__(self, stops=None): 76 | stops = stops or [Miso.Mix()] 77 | self.stops = {} 78 | for stop in stops: 79 | self.stops[stop.zstop] = stop.mix 80 | 81 | # Miso.Mix 82 | # Mix information for a single stop (layer) 83 | # Z is expressed in percentage (0 to 1) 84 | # extruders is an array of percentages (0 to 1) 85 | class Mix: 86 | def __init__(self, mix=[1], zstop=0): 87 | self.mix = mix 88 | self.zstop = zstop 89 | 90 | # Miso.Gcode 91 | # Methods that help read and generate gcode 92 | class Gcode: 93 | _movecodes = re.compile('^\\s*(G0|G1).+Z(?P\\d+)\\b') 94 | _extrudecodes = re.compile('^\\s*(G0|G1|G2|G3).+(E|F)\\d+\\b') 95 | _toolcodes = re.compile('^\\s*T(?P\\d+)\\b') 96 | _absolutecodes = re.compile('^\\s*G91\\b') 97 | _relativecodes = re.compile('^\\s*G90\\b') 98 | 99 | @staticmethod 100 | def updateRelative(line, current): 101 | if Miso.Gcode._relativecodes.match(line): 102 | return True 103 | if Miso.Gcode._absolutecodes.match(line): 104 | return False 105 | return current 106 | 107 | @staticmethod 108 | def updateTool(line, current): 109 | match = Miso.Gcode._toolcodes.search(line) 110 | if match: 111 | return int(match.group('toolid')) 112 | return current 113 | 114 | @staticmethod 115 | def updateZ(line, current, relative): 116 | match = Miso.Gcode._movecodes.search(line) 117 | if match and relative: 118 | change = float(match.group('distance')) 119 | return current + change 120 | if match: 121 | return float(match.group('distance')) 122 | return current 123 | 124 | @staticmethod 125 | def isExtrude(line): 126 | return Miso.Gcode._extrudecodes.match(line) 127 | 128 | @staticmethod 129 | def formatMix(tool, zpos, zmax): 130 | index = zpos / zmax 131 | mix = Miso.Gcode._calcMix(index, tool) 132 | for i in range(len(mix)): 133 | mix[i] = Miso.Gcode._formatNumber(mix[i]) 134 | return 'M567 P' + str(tool) + ' E' + ':'.join(mix) 135 | 136 | @staticmethod 137 | def _calcMix(index, tool): 138 | segment = Miso.Gcode._calcSegment(index, tool) 139 | srange = segment.keys() 140 | if len(srange) == 0: 141 | return [1] 142 | if len(srange) == 1: 143 | return segment[srange[0]] 144 | index = (index - min(srange[0], srange[1])) / (max(srange[0], srange[1])-min(srange[0], srange[1])) 145 | mix = [] 146 | start = segment[min(srange[0], srange[1])] 147 | end = segment[max(srange[0], srange[1])] 148 | for extruder in range(max(len(start), len(end))): 149 | svalue = len(start) < extruder and start[extruder] or 0 150 | evalue = len(end) < extruder and end[extruder] or 0 151 | mix.append((evalue - svalue) * index + svalue) 152 | return mix 153 | 154 | @staticmethod 155 | def _calcSegment(index, tool): # NOTE: this will allow mixes that total more than 1 156 | stops = Miso.getToolConfig(tool).stops 157 | start = None 158 | end = None 159 | for stop in stops.keys(): # TODO: If stop is 0 there will be a bug 160 | start = stop <= index and (start != None and max(start, stop) or stop) or start 161 | end = stop >= index and (end != None and max(end, stop) or stop) or end 162 | segment = {} 163 | if start: 164 | segment[start] = stops[start] 165 | if end: 166 | segment[end] = stops[end] 167 | return segment 168 | 169 | @staticmethod 170 | def _formatNumber(value): 171 | value = str(value).strip() 172 | if re.match('^\\.', value): 173 | value = '0' + value 174 | filter = re.search('\\d+(\\.\\d{1,2})?', value) 175 | if not filter: 176 | return '0' 177 | return filter.string[filter.start():filter.end()] 178 | -------------------------------------------------------------------------------- /ColorShift.py: -------------------------------------------------------------------------------- 1 | from ..Script import Script 2 | # current problems... 3 | # running two sets of post processing fails the second post process 4 | # possibly need an option for shift every 4 layers and shift about 100 times per print to be more clear with choices 5 | # need description of project 6 | 7 | # Recent fixed problems 8 | # Reduced size of clamp function 9 | # Reduced size of adjust_extruder_rate function 10 | # simplified verifying input isn't backwards for layer_no and percentage 11 | # simplified initiate_extruder function 12 | # Math is correct between starting layer shift and ending layer shift so that ratio starts at 0:1 and ends at 1:0 instead of ending at 0.994:0.006 for example 13 | 14 | # potential updates 15 | # possible initial option choices 16 | # easy mode (shift approx 100 times from top to bottom) 17 | # Clamp Percentage (shift every n layers between start percentage and end percentage) 18 | # clamp layer (shift every n layers between defined start and stop layers) 19 | 20 | # possible secondary option (type of shifts) 21 | # normal shift 22 | # reverse shift 23 | # logarithmic shift 24 | # exponential shift 25 | # fibonacci shift (1+1+2+3+5+8) 26 | # heartbeat shift (pulse to set degree and frequency) 27 | # wood shift (randomize up to degree of change and length of ring) 28 | 29 | # Credits 30 | # gargansa, bass4aj 31 | 32 | # convenience function for coding familiarity 33 | def clamp(value, minimum, maximum): 34 | return max(minimum, min(value, maximum)) 35 | 36 | # function to compile the initial setup into a string 37 | def initiate_extruder(existing_gcode,*ext): 38 | qty_ext = len(ext) 39 | setup_line = "" 40 | setup_line += ";Modified by ColorShift" + "\n" 41 | setup_line += existing_gcode + " Modified:\n" 42 | #define tool 43 | for i in range(0, qty_ext): 44 | if i == 0: 45 | setup_line += "M563 P0 D" + str(i) 46 | else: 47 | setup_line += ":" + str(i) 48 | setup_line += " H1 ;Define tool 0" + "\n" 49 | #position tool 50 | for i in range(0, qty_ext): 51 | setup_line += "M563 P2 D" + str(i) + " H" + str(i+1) 52 | setup_line += " ;Position " + str(i+1) + " tool" + "\n" 53 | #set axis offsets 54 | setup_line += "G10 P0 X0 Y0 Z0 ;Set tool 0 axis offsets" + "\n" 55 | #activate tool 56 | setup_line += "G10 P2 R120 S220 ;Set initial tool 0 active" + "\n" 57 | #set initial extrusion rate 58 | setup_line += adjust_extruder_rate(";Initial Extruder Rate", *ext) 59 | #initiate the tool 60 | setup_line += "M568 P0 S1 ;Turn on tool mixing for the extruder" + "\n" 61 | return setup_line 62 | 63 | # function to compile extruder info into a string 64 | def adjust_extruder_rate(existing_gcode, *ext): 65 | i = 0 66 | for item in ext: 67 | if i == 0: 68 | setup_line = existing_gcode + " ;Modified: \nM567 P0 E" + str(item) 69 | else: 70 | setup_line += ":" + str(item) + "\n" 71 | i += 1 72 | return setup_line 73 | 74 | 75 | class ColorShift(Script): 76 | version = "1.0.0" 77 | def __init__(self): 78 | super().__init__() 79 | 80 | def getSettingDataString(self): 81 | return """{ 82 | "name":"ColorShift", 83 | "key":"ColorShift", 84 | "metadata": {}, 85 | "version": 2, 86 | "settings": 87 | { 88 | "a_trigger": 89 | { 90 | "label": "Shifting Clamp Type", 91 | "description": "Begin and end shifting at percentage or layer.", 92 | "type": "enum", 93 | "options": {"percent":"Percentage","layer_no":"Layer No."}, 94 | "default_value": "percent" 95 | }, 96 | "percent_change_start": 97 | { 98 | "label": "Extrusion % Start", 99 | "description": "Percentage of layer height to start shifting extruder percentage.", 100 | "unit": "%", 101 | "type": "float", 102 | "default_value": 0, 103 | "minimum_value": "0", 104 | "maximum_value": "100", 105 | "minimum_value_warning": "0", 106 | "maximum_value_warning": "99", 107 | "enabled": "a_trigger == 'percent'" 108 | }, 109 | "percent_change_end": 110 | { 111 | "label": "Extrusion % End", 112 | "description": "Percentage of layer height to stop shifting extruder percentage.", 113 | "unit": "%", 114 | "type": "float", 115 | "default_value": 100, 116 | "minimum_value": "0", 117 | "maximum_value": "100", 118 | "minimum_value_warning": "1", 119 | "maximum_value_warning": "100", 120 | "enabled": "a_trigger == 'percent'" 121 | }, 122 | "layer_change_start": 123 | { 124 | "label": "Extrusion # Start", 125 | "description": "Layer to start shifting extruder percentage.", 126 | "unit": "#", 127 | "type": "int", 128 | "default_value": 0, 129 | "minimum_value": "0", 130 | "enabled": "a_trigger == 'layer_no'" 131 | }, 132 | "layer_change_end": 133 | { 134 | "label": "Extrusion # End", 135 | "description": "Layer to stop changing extruder percentage. If layer is more then total layers the max layer will be chosen.", 136 | "unit": "#", 137 | "type": "int", 138 | "default_value": 100000, 139 | "minimum_value": "0", 140 | "enabled": "a_trigger == 'layer_no'" 141 | }, 142 | "b_trigger": 143 | { 144 | "label": "Direction of Shift", 145 | "description": "Allows the shift to run the opposite direction without swapping filaments.", 146 | "type": "enum", 147 | "options": {"normal":"Normal","reversed":"Reversed"}, 148 | "default_value": "normal" 149 | }, 150 | "e1_trigger": 151 | { 152 | "label": "Expert Controls", 153 | "description": "Enable more controls. Some of which are for debugging purposes and may change or be removed later", 154 | "type": "bool", 155 | "default_value": false 156 | }, 157 | "adjustments": 158 | { 159 | "label": "Adjustments", 160 | "description": "To grant an even shift adjustments are calculated based on this formula affected_layers/adjustments = change rate. The affected_layers are the total layers within the clamp set by percentages or layer numbers chosen. Adjustments are set here. The resulting change_rate is rounded down and the print will shift every count of change_rate.", 161 | "unit": "# of changes", 162 | "type": "int", 163 | "default_value": 100, 164 | "minimum_value": "1", 165 | "maximum_value": "400", 166 | "minimum_value_warning": "1", 167 | "maximum_value_warning": "200", 168 | "enabled": "e1_trigger" 169 | }, 170 | "flow_one_adjust": 171 | { 172 | "label": "Ext One Flow Adjust", 173 | "description": "This compensates for under or over extrusion due to variances in filament", 174 | "unit": "% + -", 175 | "type": "float", 176 | "default_value": 0, 177 | "minimum_value": "-20", 178 | "maximum_value": "20", 179 | "minimum_value_warning": "-5", 180 | "maximum_value_warning": "5", 181 | "enabled": "e1_trigger" 182 | }, 183 | "flow_two_adjust": 184 | { 185 | "label": "Ext Two Flow Adjust", 186 | "description": "This compensates for under or over extrusion due to variances in filament", 187 | "unit": "% + -", 188 | "type": "float", 189 | "default_value": 0, 190 | "minimum_value": "-20", 191 | "maximum_value": "20", 192 | "minimum_value_warning": "-5", 193 | "maximum_value_warning": "5", 194 | "enabled": "e1_trigger" 195 | }, 196 | "flow_min": 197 | { 198 | "label": "Minimum Flow Allowed", 199 | "description": "Clamp to keep both extruders flowing a small amount to prevent clogs", 200 | "unit": "%", 201 | "type": "float", 202 | "default_value": 1, 203 | "minimum_value": "0", 204 | "maximum_value": "10", 205 | "minimum_value_warning": "1", 206 | "maximum_value_warning": "5", 207 | "enabled": "e1_trigger" 208 | } 209 | } 210 | }""" 211 | 212 | def execute(self, data: list): # used to be data: list 213 | # Set user settings from cura 214 | choice = self.getSettingValueByKey("a_trigger") 215 | direction = self.getSettingValueByKey("b_trigger") 216 | adjustments = int(clamp(self.getSettingValueByKey("adjustments"), 0, 999)) # clamp within understandable range 217 | flow_one_adjust = float((self.getSettingValueByKey("flow_one_adjust"))/100)+1 # convert user input into multi 218 | flow_two_adjust = float((self.getSettingValueByKey("flow_two_adjust"))/100)+1 # convert user input into multi 219 | flow_min = float(self.getSettingValueByKey("flow_min")/100) 220 | flow_clamp_adjust = float(1-(flow_min*2)) 221 | 222 | # Make sure user settings are in order after loading 223 | percent_start = float(self.getSettingValueByKey("percent_change_start")/100) 224 | percent_end = float(self.getSettingValueByKey("percent_change_end")/100) 225 | if percent_end < percent_start: 226 | percent_start, percent_end = percent_end, percent_start 227 | 228 | # Make sure user settings are in order after loading 229 | layer_start = int(self.getSettingValueByKey("layer_change_start")) 230 | layer_end = int(self.getSettingValueByKey("layer_change_end")) 231 | if layer_end < layer_start: 232 | layer_start, layer_end = layer_end, layer_start 233 | 234 | # Initialize things that affect decisions 235 | current_position = 0 236 | index = 0 237 | 238 | # Iterate through the layers 239 | for active_layer in data: 240 | 241 | # Remove the whitespace and split the gcode into lines 242 | lines = active_layer.strip().split("\n") 243 | 244 | modified_gcode = "" 245 | for line in lines: 246 | 247 | # Find where to add the initial setup info 248 | if ";LAYER_COUNT:" in line: 249 | 250 | # Find the actual total layers in the gcode 251 | total_layers = float(line[(line.index(':') + 1): len(line)]) 252 | 253 | # Calculate positions based on total_layers 254 | if choice == 'percent': 255 | start_position = int(int(total_layers) * float(percent_start)) # find where to start 256 | current_position = start_position 257 | end_position = int(int(total_layers) * float(percent_end)) # find where to end 258 | if choice == 'layer_no': 259 | start_position = int(clamp(layer_start, 0, total_layers)) 260 | current_position = start_position 261 | end_position = int(clamp(layer_end, 0, total_layers)) 262 | 263 | # Find the layers that are actually affected or the ones within the clamp set by user 264 | adjusted_layers = end_position - current_position 265 | 266 | # Make sure that the set adjustments are less then the actual affected layers since you can only adjust once per layer 267 | adjustments = clamp(int(adjustments), 0, adjusted_layers) 268 | 269 | # Find how often to adjust the rate 270 | change_rate = int(int(adjusted_layers) / int(adjustments)) 271 | 272 | # Math to determine extruder percentage based on layer location without flow clamp and adjustments 273 | location = (current_position-start_position)/(adjusted_layers-change_rate) 274 | 275 | # Adjust extruder percentages by user set flow and clamp adjustments 276 | extruder_one = format(location * flow_one_adjust * flow_clamp_adjust + flow_min, '.3f') 277 | extruder_two = format((1-location) * flow_two_adjust * flow_clamp_adjust + flow_min, '.3f') 278 | 279 | # Send extruder percentages to be compiled into a string based on direction set by user 280 | if direction == 'normal': 281 | modified_gcode = initiate_extruder(line, extruder_one, extruder_two) 282 | else: 283 | modified_gcode = initiate_extruder(line, extruder_two, extruder_one) 284 | 285 | # Find where to add for affected layers 286 | elif ";LAYER:" + str(current_position) in line and int(current_position) < int(end_position): 287 | 288 | # Same thing we did above 289 | location = (current_position-start_position)/(adjusted_layers-change_rate) 290 | extruder_one = format(location*flow_one_adjust * flow_clamp_adjust + flow_min, '.3f') 291 | extruder_two = format((1-location)*flow_two_adjust * flow_clamp_adjust + flow_min, '.3f') 292 | if direction == 'normal': 293 | modified_gcode = adjust_extruder_rate(line, extruder_one, extruder_two) 294 | else: 295 | modified_gcode = adjust_extruder_rate(line, extruder_two, extruder_one) 296 | 297 | # Increase the position for the next line to find it on the next loop 298 | current_position += int(change_rate) 299 | 300 | # If no conditions apply leave the code alone 301 | else: 302 | modified_gcode += line + "\n" 303 | 304 | # Replace the data 305 | data[index] = modified_gcode 306 | index += 1 307 | return data 308 | -------------------------------------------------------------------------------- /Melt.py: -------------------------------------------------------------------------------- 1 | # Current Version contains 2 | # Support for 2-4 extruders 3 | # Initial Flow for raft or anything before affected layers 4 | # First part of modifiers so future options can be added easier (needs work) 5 | # Modifiers Part 1. base_input - allowing it to be easier to add a wood texture modifier and heart beat modifier 6 | # Modifiers Part 2. change_rate - allowing random distances between changes for wood texture 7 | # Fixed duplicate 0:1:0, 0:1:0:0, and 0:0:1:0 showing up 8 | # Abandoned Overflow until able to prove its useful 9 | # Re-enabled the initiate_extruder function 10 | # Ability to choose what color the print ends with after adjusted layers 11 | # Added debug reporting to gcode file for troubleshooting user problems that may arise 12 | # Ability to set the initial extruder rate and not shift through the print by setting change rate to 0 13 | # Pattern Modifier added (Heartbeat) 14 | # Split Rate Modifiers from Modifiers to add versatility 15 | # Allowed for multiple runs of the script (this allows you to shift 1:0 > 0:1 for the first 20% of the print and then shift 0:1 > 1:0 for the last 20%) 16 | # Enabled ability to wrap the shift back to the beginning nozzle with user input circular or linear 17 | # Added Slope Modifier 18 | # Added Ellipse Modifier 19 | # Added Lerp Modifier 20 | # Changed Initial extruder code to allow user to make adjustments 21 | 22 | # Current Bugs 23 | # duplicate values when running multiple scripts that overlap affected areas 24 | # Its possible to enter non numeral values for initial and final extruder values but non numerals would break the code 25 | 26 | # Future Updates 27 | # More Modifiers (exponential, logarithmic) 28 | 29 | # Credits for contributions 30 | # gargansa, bass4aj, kenix, laraeb, datadink, keyreaper 31 | 32 | from ..Script import Script 33 | import random 34 | 35 | 36 | # Convenience function for gargansa's coding familiarity 37 | def clamp(value, minimum, maximum): 38 | return max(minimum, min(value, maximum)) 39 | 40 | 41 | # Function to compile the initial setup into a string 42 | def initiate_extruder(existing_gcode, *gcode): 43 | setup_line = "" 44 | setup_line += existing_gcode 45 | for line in gcode: 46 | if line != str(""): 47 | setup_line += "\n" + str(line) #+ "\n" 48 | 49 | return setup_line 50 | 51 | 52 | # Function to compile extruder info into a string 53 | def adjust_extruder_rate(existing_gcode, *ext): 54 | i = 0 55 | for item in ext: 56 | if i == 0: 57 | setup_line = existing_gcode + "\nM567 P0 E" + str(item) 58 | else: 59 | setup_line += ":" + str(item) 60 | i += 1 61 | setup_line += "\n" 62 | return setup_line 63 | 64 | 65 | # Just used to output info to text file to help debug 66 | def print_debug(*report_data): 67 | setup_line = ";Debug " 68 | for item in report_data: 69 | setup_line += str(item) 70 | setup_line += "\n" 71 | return setup_line 72 | 73 | 74 | class Melt(Script): 75 | version = "3.4.0" 76 | 77 | def __init__(self): 78 | super().__init__() 79 | 80 | def getSettingDataString(self): 81 | return """{ 82 | "name":"Multi-Extruder Layering Tool """ + self.version + """ (MELT)", 83 | "key":"Melt", 84 | "metadata": {}, 85 | "version": 2, 86 | "settings": 87 | { 88 | "firmware_type": 89 | { 90 | "label": "Firmware Type", 91 | "description": "Type of Firmware Supported.", 92 | "type": "enum", 93 | "options": {"duet":"Duet"}, 94 | "default_value": "duet" 95 | }, 96 | "qty_extruders": 97 | { 98 | "label": "Number of extruders", 99 | "description": "How many extruders in mixing nozzle.", 100 | "type": "enum", 101 | "options": {"2":"Two","3":"Three","4":"Four"}, 102 | "default_value": "2" 103 | }, 104 | "a_trigger": 105 | { 106 | "label": "Shifting Clamp Type", 107 | "description": "Begin and end shifting at percentage or layer.", 108 | "type": "enum", 109 | "options": {"percent":"Percentage","layer_no":"Layer No."}, 110 | "default_value": "percent" 111 | }, 112 | "percent_change_start": 113 | { 114 | "label": "Extrusion % Start", 115 | "description": "Percentage of layer height to start shifting extruder percentage.", 116 | "unit": "%", 117 | "type": "float", 118 | "default_value": 0, 119 | "minimum_value": "0", 120 | "maximum_value": "100", 121 | "minimum_value_warning": "0", 122 | "maximum_value_warning": "99", 123 | "enabled": "a_trigger == 'percent'" 124 | }, 125 | "percent_change_end": 126 | { 127 | "label": "Extrusion % End", 128 | "description": "Percentage of layer height to stop shifting extruder percentage.", 129 | "unit": "%", 130 | "type": "float", 131 | "default_value": 100, 132 | "minimum_value": "0", 133 | "maximum_value": "100", 134 | "minimum_value_warning": "1", 135 | "maximum_value_warning": "100", 136 | "enabled": "a_trigger == 'percent'" 137 | }, 138 | "layer_change_start": 139 | { 140 | "label": "Extrusion # Start", 141 | "description": "Layer to start shifting extruder percentage.", 142 | "unit": "#", 143 | "type": "int", 144 | "default_value": 0, 145 | "minimum_value": "0", 146 | "enabled": "a_trigger == 'layer_no'" 147 | }, 148 | "layer_change_end": 149 | { 150 | "label": "Extrusion # End", 151 | "description": "Layer to stop changing extruder percentage. If layer is more then total layers the max layer will be chosen.", 152 | "unit": "#", 153 | "type": "int", 154 | "default_value": 100000, 155 | "minimum_value": "0", 156 | "enabled": "a_trigger == 'layer_no'" 157 | }, 158 | "b_trigger": 159 | { 160 | "label": "Extruder Rotation", 161 | "description": "The order in which the shift moves through the extruders.", 162 | "type": "enum", 163 | "options": {"normal":"Normal","reversed":"Reversed"}, 164 | "default_value": "normal" 165 | }, 166 | "change_rate": 167 | { 168 | "label": "Shift frequency", 169 | "description": "How many layers until the color is shifted each time. Setting to 0 Allows the initial rate to be set only.", 170 | "unit": "#", 171 | "type": "int", 172 | "default_value": 4, 173 | "minimum_value": "0", 174 | "maximum_value": "1000", 175 | "minimum_value_warning": "1", 176 | "maximum_value_warning": "100" 177 | }, 178 | "c_trigger": 179 | { 180 | "label": "Shift Loop Type", 181 | "description": "::Linear: Start with primary extruder finish with last extruder. ::Circular: Start with primary extruder shift through to last extruder and then shift back to primary extruder", 182 | "type": "enum", 183 | "options": {"1":"Linear","0":"Circular"}, 184 | "default_value": "1" 185 | }, 186 | "e_trigger": 187 | { 188 | "label": "Modifier Type", 189 | "description": "::Normal: Sets the shift to change gradually throughout the length of the layers within the clamp. ::Wood Texture: Sets one extruder at a random small percentage and adjusts change frequency by a random amount, simulating wood grain. ::Repeating Pattern: Repeats a set extruder pattern throughout the print. ", 190 | "type": "enum", 191 | "options": {"normal":"Normal", "wood":"Wood Texture", "pattern":"Repeating Pattern", "random":"Random", "lerp":"Linear Interpolation", "slope":"Slope Formula", "ellipse":"Ellipse Formula"}, 192 | "default_value": "normal" 193 | }, 194 | "f_trigger": 195 | { 196 | "label": "Rate Modifier Type", 197 | "description": "How often the print shifts. ::Normal: Uses the change rate. ::Random: picks a number of layers between the set change_rate and change_rate*2", 198 | "type": "enum", 199 | "options": {"normal":"Normal", "random":"Random"}, 200 | "default_value": "normal" 201 | }, 202 | "lerp_i": 203 | { 204 | "label": "Linear Interpolation", 205 | "description": "Values shift up or down y axis.", 206 | "type": "float", 207 | "default_value": 0, 208 | "minimum_value": "-1", 209 | "maximum_value": "1", 210 | "minimum_value_warning": "-0.9", 211 | "maximum_value_warning": "0.9", 212 | "enabled": "e_trigger == 'lerp'" 213 | }, 214 | "slope_m": 215 | { 216 | "label": "Slope", 217 | "description": "Slope (M) of the formula Y=MX+B. Normal range between -2 to 0. -1 is a one to one even slope. Values higher than -1 will start the shift later in the print. Values lower than -1 will start the print with a lower value for the primary extruder.", 218 | "type": "float", 219 | "default_value": -1, 220 | "minimum_value": "-2", 221 | "maximum_value": "0", 222 | "minimum_value_warning": "-25", 223 | "maximum_value_warning": "0.1", 224 | "enabled": "e_trigger == 'slope'" 225 | }, 226 | "slope_i": 227 | { 228 | "label": "Y Intercept", 229 | "description": "Y Intercept (B) of the formula Y=MX+B. Normal range between 2 to 0. 1 is normal. Values lower than 1 will cause the primary extruder to retain 1 longer. Values higher than 1 will start out with primary extruder at a lower value, which will also run out sooner.", 230 | "type": "float", 231 | "default_value": 1, 232 | "minimum_value": "0", 233 | "maximum_value": "2", 234 | "minimum_value_warning": "0.1", 235 | "maximum_value_warning": "1.9", 236 | "enabled": "e_trigger == 'slope'" 237 | }, 238 | "pattern": 239 | { 240 | "label": "Pattern", 241 | "description": "Set a repeating pattern of extruder values between 0 and 1.", 242 | "type": "str", 243 | "default_value": "0.5,1,0.25,0.75,0.5,0", 244 | "enabled": "e_trigger == 'pattern'" 245 | }, 246 | "e1_trigger": 247 | { 248 | "label": "Expert Controls", 249 | "description": "Enable more controls. Some of which are for debugging purposes and may change or be removed later", 250 | "type": "bool", 251 | "default_value": false 252 | }, 253 | "enable_initial": 254 | { 255 | "label": "Enable Initial Setup", 256 | "description": "If your extruder setup isn't already set in the duet’s sd card settings.", 257 | "type": "bool", 258 | "default_value": false, 259 | "enabled": "e1_trigger" 260 | }, 261 | "initial_a": 262 | { 263 | "label": "Define Tool", 264 | "description": "Define Tool", 265 | "type": "str", 266 | "default_value": "M563 P0 D0:1 H1", 267 | "enabled": "enable_initial" 268 | }, 269 | "initial_b": 270 | { 271 | "label": "Set Tool Axis Offsets", 272 | "description": "Set Tool Axis Offsets", 273 | "type": "str", 274 | "default_value": "G10 P0 X0 Y0 Z0", 275 | "enabled": "enable_initial" 276 | }, 277 | "initial_c": 278 | { 279 | "label": "Set Initial Tool Active", 280 | "description": "Set Initial Tool Active", 281 | "type": "str", 282 | "default_value": "G10 P0 R120 S220", 283 | "enabled": "enable_initial" 284 | }, 285 | "initial_d": 286 | { 287 | "label": "Turn On Tool Mixing For The Extruder", 288 | "description": "Turn On Tool Mixing For The Extruder", 289 | "type": "str", 290 | "default_value": "M568 P0 S1", 291 | "enabled": "enable_initial" 292 | }, 293 | "initial_e": 294 | { 295 | "label": "Extra GCode", 296 | "description": "Any Extra Gcode To Run At Start Of Print.", 297 | "type": "str", 298 | "default_value": "", 299 | "enabled": "enable_initial" 300 | }, 301 | "initial_flow": 302 | { 303 | "label": "Initial Main Flow", 304 | "description": "Flow to initially set extruders must total up to 1.000 ::This allows the extruder to be set for any portion of the print before the actual shift begins.", 305 | "unit": "0-1", 306 | "type": "str", 307 | "default_value": "1,0,0,0", 308 | "enabled": "e1_trigger" 309 | }, 310 | "final_flow": 311 | { 312 | "label": "Final Main Flow", 313 | "description": "Flow to initially set extruders must total up to 1.000 ::This allows the extruder to be set for any portion of the print after the affected layers.", 314 | "unit": "0-1", 315 | "type": "str", 316 | "default_value": "0,0,0,1", 317 | "enabled": "e1_trigger" 318 | }, 319 | "flow_adjust": 320 | { 321 | "label": "Ext Flow Adjust", 322 | "description": "This compensates for under or over extrusion due to variances in filament", 323 | "unit": "% + -", 324 | "type": "float", 325 | "default_value": 0, 326 | "minimum_value": "-20", 327 | "maximum_value": "20", 328 | "minimum_value_warning": "-5", 329 | "maximum_value_warning": "5", 330 | "enabled": "e1_trigger" 331 | }, 332 | "flow_min": 333 | { 334 | "label": "Minimum Flow Allowed", 335 | "description": "Clamp to keep both extruders flowing a small amount to prevent clogs. 0% to 5% Normally", 336 | "unit": "%", 337 | "type": "float", 338 | "default_value": 0, 339 | "minimum_value": "0", 340 | "maximum_value": "10", 341 | "minimum_value_warning": "0", 342 | "maximum_value_warning": "5", 343 | "enabled": "e1_trigger" 344 | } 345 | } 346 | }""" 347 | 348 | def execute(self, data: list): # used to be data: list 349 | # Set user settings from cura 350 | clamp_choice = self.getSettingValueByKey("a_trigger") 351 | direction = self.getSettingValueByKey("b_trigger") 352 | 353 | # EVENTUALLY USED TO MAKE THE EXTRUDER LOOP BACK TO THE FIRST EXTRUDER 354 | loop = self.getSettingValueByKey("c_trigger") # linear or circular (not currently enabled) 355 | 356 | modifier = self.getSettingValueByKey("e_trigger") # normal, wood, pattern 357 | rate_modifier = self.getSettingValueByKey("f_trigger") # normal, random 358 | change_rate = int(self.getSettingValueByKey("change_rate")) 359 | initial_flows = [float(initial_flow) for initial_flow in self.getSettingValueByKey("initial_flow").strip().split(',')] 360 | final_flows = [float(final_flow) for final_flow in self.getSettingValueByKey("final_flow").strip().split(',')] 361 | flow_adjust = float((self.getSettingValueByKey("flow_adjust")) / 100) + 1 # convert user input into multi 362 | qty_extruders = int(self.getSettingValueByKey("qty_extruders")) 363 | flow_min = float(self.getSettingValueByKey("flow_min") / 100) * qty_extruders 364 | flow_clamp_adjust = float(1 - (flow_min * qty_extruders)) 365 | pattern = [float(pattern) for pattern in self.getSettingValueByKey("pattern").strip().split(',')] 366 | lerp_i = self.getSettingValueByKey("lerp_i") 367 | slope_m = self.getSettingValueByKey("slope_m") 368 | slope_i = self.getSettingValueByKey("slope_i") 369 | 370 | enable_initial = self.getSettingValueByKey("enable_initial") 371 | initial_a = self.getSettingValueByKey("initial_a") 372 | initial_b = self.getSettingValueByKey("initial_b") 373 | initial_c = self.getSettingValueByKey("initial_c") 374 | initial_d = self.getSettingValueByKey("initial_d") 375 | initial_e = self.getSettingValueByKey("initial_e") 376 | 377 | # INITIATE EXTRUDERS AS ZERO EXCEPT FIRST ONE 378 | base_input = [0] * qty_extruders 379 | base_input[0] = 1 380 | 381 | # CHECK ORDER OF VALUES ENTERED BY USER 382 | percent_start = float(self.getSettingValueByKey("percent_change_start") / 100) 383 | percent_end = float(self.getSettingValueByKey("percent_change_end") / 100) 384 | layer_start = int(self.getSettingValueByKey("layer_change_start")) 385 | layer_end = int(self.getSettingValueByKey("layer_change_end")) 386 | # REORDER IF BACKWARDS 387 | if percent_end < percent_start: 388 | percent_start, percent_end = percent_end, percent_start 389 | if layer_end < layer_start: 390 | layer_start, layer_end = layer_end, layer_start 391 | 392 | current_position = 0 393 | end_position = 0 394 | index = 0 395 | has_been_run = 0 396 | 397 | # Iterate through the layers 398 | for active_layer in data: 399 | 400 | # Remove the whitespace and split the gcode into lines 401 | lines = active_layer.strip().split("\n") 402 | 403 | modified_gcode = "" 404 | for line in lines: 405 | if ";Modified:" in line: 406 | has_been_run = 1 407 | elif ";LAYER_COUNT:" in line: 408 | # FINDING THE ACTUAL AFFECTED LAYERS 409 | total_layers = float(line[(line.index(':') + 1): len(line)]) 410 | 411 | # Calculate positions based on total_layers 412 | if clamp_choice == 'percent': 413 | start_position = int(int(total_layers) * float(percent_start)) 414 | current_position = start_position 415 | end_position = int(int(total_layers) * float(percent_end)) 416 | if clamp_choice == 'layer_no': 417 | start_position = int(clamp(layer_start, 0, total_layers)) 418 | current_position = start_position 419 | end_position = int(clamp(layer_end, 0, total_layers)) 420 | 421 | # how many layers are affected 422 | adjusted_layers = end_position - start_position 423 | 424 | # Make sure the change_rate doesnt sit outside of allowed values 425 | change_rate = int(clamp(change_rate, 0, adjusted_layers)) 426 | 427 | # SETTING THE FLOWS SET BY USER IN EXPERT CONTROLS 428 | while len(initial_flows) < len(base_input): # Ensure at least as many are set as needed 429 | initial_flows.append(float(0)) 430 | total_value = 0 431 | i = 0 432 | for ext in base_input: # Disregard extras 433 | initial_flows[i] = clamp(initial_flows[i], 0, 1) # Clamp to value range 434 | if i == len(base_input)-1 and total_value < 1: # Make the last one add up to 1 435 | base_input[i] = 1 - total_value 436 | elif initial_flows[i] + total_value < 1: # Keep within total of 1 437 | base_input[i] = initial_flows[i] 438 | else: 439 | base_input[i] = 1 - total_value 440 | total_value += base_input[i] 441 | i += 1 442 | 443 | # ASSIGN THE INITIAL VALUES TO A SINGLE GCODE LINE 444 | ext_gcode_list = [''] * qty_extruders 445 | gcode_index = 0 446 | for code in ext_gcode_list: 447 | ext_gcode_list[ext_gcode_list.index(code)] = format(base_input[gcode_index] * flow_adjust * flow_clamp_adjust + flow_min, '.3f') 448 | gcode_index += 1 449 | 450 | modified_gcode += line # list the initial line info 451 | 452 | if has_been_run == 0: 453 | if enable_initial: 454 | modified_gcode += initiate_extruder("", initial_a, initial_b, initial_c, initial_d, initial_e) 455 | if direction == 'normal': 456 | modified_gcode += adjust_extruder_rate("", *ext_gcode_list) 457 | else: 458 | modified_gcode += adjust_extruder_rate("", *ext_gcode_list[::-1]) 459 | 460 | # DEBUG FOR USER REPORTING 461 | modified_gcode += print_debug("Version:", self.version) 462 | modified_gcode += print_debug("Clamp_choice:", clamp_choice, " Direction:", direction) 463 | modified_gcode += print_debug("Modifier:", modifier, " Rate Modifier:", rate_modifier) 464 | modified_gcode += print_debug("Pattern:", pattern) 465 | modified_gcode += print_debug("Change_rate:", change_rate, " Initial_flows:", initial_flows, " Final_flows", final_flows) 466 | modified_gcode += print_debug("Qty_extruders:", qty_extruders, " Flow_min:", flow_min) 467 | modified_gcode += print_debug("Percent_start:", percent_start, " Percent_end:", percent_end) 468 | modified_gcode += print_debug("Layer_start:", layer_start, " Layer_end:", layer_end) 469 | 470 | 471 | # INITIATE VALUES USED THROUGH THE AFFECTED LAYERS 472 | if change_rate != 0: 473 | changes_total = int(adjusted_layers/change_rate) # how many times are we allowed to adjust 474 | else: 475 | changes_total = 0 476 | 477 | #changes_per_extruder = int(changes_total/(qty_extruders-1)) 478 | changes_per_extruder = int(changes_total/(qty_extruders-int(loop))) 479 | current_extruder = 0 480 | next_extruder = 1 481 | ext_fraction = changes_per_extruder 482 | 483 | # CHANGES MADE TO LAYERS THROUGH THE AFFECTED LAYERS 484 | elif ";LAYER:" + str(current_position) in line and int(current_position) < int(end_position): 485 | 486 | # ADJUST EXTRUDER RATES FOR NEXT AFFECTED LAYER 2 PARTS (should be simplified) 487 | # Part 1 adjust rate by fraction to avoid rounding errors from addition 488 | if modifier == 'normal': 489 | base_input[current_extruder], base_input[next_extruder] = standard_shift(ext_fraction, changes_per_extruder) 490 | elif modifier == 'wood': 491 | base_input[current_extruder], base_input[next_extruder] = wood_shift(0.05, 0.25) 492 | elif modifier == 'pattern': 493 | base_input[current_extruder], base_input[next_extruder] = pattern_shift(pattern) 494 | elif modifier == 'random': 495 | base_input[current_extruder], base_input[next_extruder] = random_shift() 496 | elif modifier == 'lerp': 497 | base_input[current_extruder], base_input[next_extruder] = lerp_shift(0, changes_per_extruder, ext_fraction/changes_per_extruder, lerp_i) 498 | elif modifier == 'slope': 499 | base_input[current_extruder], base_input[next_extruder] = lerp_shift(ext_fraction, changes_per_extruder, slope_m, slope_i) 500 | elif modifier == 'ellipse': 501 | base_input[current_extruder], base_input[next_extruder] = ellipse_shift(ext_fraction/changes_per_extruder) 502 | 503 | # Part 2 initialize what percentage to reach wrap extruders back to start if out of range 504 | ext_fraction -= 1 505 | if ext_fraction < 0: 506 | current_extruder += 1 507 | next_extruder += 1 508 | ext_fraction = changes_per_extruder-1 # -1 to avoid duplicate 0:1:0 509 | if next_extruder == qty_extruders: 510 | next_extruder = 0 511 | 512 | # lAST TWEAK ADJUST THE EXTRUDER VALUES BY FLOW ADJUSTMENTS AND LIMITS 513 | ext_gcode_list = [''] * qty_extruders 514 | gcode_index = 0 515 | for ext in ext_gcode_list: 516 | ext_gcode_list[ext_gcode_list.index(ext)] = format(base_input[gcode_index] * flow_adjust * flow_clamp_adjust + flow_min, '.3f') 517 | gcode_index += 1 518 | 519 | # TURN THE EXTRUDER VALUES INTO A SINGLE GCODE LINE 520 | if direction == 'normal': 521 | modified_gcode += adjust_extruder_rate(line, *ext_gcode_list) 522 | else: 523 | modified_gcode += adjust_extruder_rate(line, *ext_gcode_list[::-1]) 524 | 525 | # CHANGE THE POSITION FOR NEXT RUN 526 | if rate_modifier == 'normal': 527 | current_position += standard_rate(change_rate) 528 | elif rate_modifier == 'random': 529 | current_position += random_rate(change_rate, change_rate*2) 530 | 531 | # CHANGES MADE AFTER THE LAST AFFECTED LAYER TO COMPLETE THE PRINT WITH 532 | elif ";LAYER:" + str(end_position) in line: # Runs last and only runs once 533 | # SETTING THE FLOWS SET BY USER IN EXPERT CONTROLS 534 | while len(final_flows) < len(base_input): # Ensure at least as many are set as needed 535 | final_flows.append(float(0)) 536 | total_value = 0 537 | i = 0 538 | for ext in base_input: # Disregard extras 539 | final_flows[i] = clamp(final_flows[i], 0, 1) # Clamp to value range 540 | if i == len(base_input) - 1 and total_value < 1: # Make the last one add up to 1 541 | base_input[i] = 1 - total_value 542 | elif final_flows[i] + total_value < 1: # Keep within total of 1 543 | base_input[i] = final_flows[i] 544 | else: 545 | base_input[i] = 1 - total_value 546 | total_value += base_input[i] 547 | i += 1 548 | 549 | # ASSIGN THE INITIAL VALUES TO A SINGLE GCODE LINE 550 | ext_gcode_list = [''] * qty_extruders 551 | gcode_index = 0 552 | for code in ext_gcode_list: 553 | ext_gcode_list[ext_gcode_list.index(code)] = format(base_input[gcode_index] * flow_adjust * flow_clamp_adjust + flow_min, '.3f') 554 | gcode_index += 1 555 | 556 | # change direction of shift if set by user 557 | if direction == 'normal': 558 | modified_gcode += adjust_extruder_rate(line, *ext_gcode_list) 559 | else: 560 | modified_gcode += adjust_extruder_rate(line, *ext_gcode_list[::-1]) 561 | 562 | # LEAVE ALL OTHER LINES ALONE SINCE THEY ARE NOT NEW LAYERS 563 | else: 564 | modified_gcode += line + "\n" 565 | 566 | # REPLACE THE DATA 567 | data[index] = modified_gcode 568 | index += 1 569 | return data 570 | 571 | 572 | # MODIFIERS FOR DIFFERENT EFFECTS ON EXTRUDERS 573 | # SHIFTS AFFECT EXTRUDER RATIOS AND RETURN BOTH VALUES TOGETHER (X AND 1-X) 574 | def standard_shift(numerator, denominator): 575 | return numerator/denominator, (denominator-numerator)/denominator 576 | 577 | 578 | def wood_shift(min_percentage, max_percentage): 579 | random_value = random.uniform(min_percentage, max_percentage) 580 | return random_value, 1-random_value 581 | 582 | 583 | def pattern_shift(list): 584 | value = list.pop() 585 | list.insert(0, value) 586 | return value, 1-value 587 | 588 | 589 | def random_shift(): 590 | random_value = random.uniform(0, 1) 591 | return random_value, 1-random_value 592 | 593 | 594 | def slope_shift(x_numerator, x_denomerator, m_slope, y_intercept): 595 | y = (m_slope*x_numerator/x_denomerator)+y_intercept 596 | y = clamp(y, 0, 1) 597 | return 1-y, y 598 | 599 | 600 | def lerp_shift(v0, v1, t, i): 601 | #answer = (1 - t) * v0 + t * (v1*(1+i)) 602 | answer = 1 603 | return 1-answer, answer 604 | 605 | 606 | 607 | def ellipse_shift(x): 608 | y = 4 - ((0.12*x*x) + (1.12 * x) + 2.78) 609 | y = clamp(y, 0, 1) 610 | y = pow(y, 0.5) 611 | y = clamp(y, 0, 1) # This is unnecessary but here just as an in case. 612 | return 1-y, y 613 | 614 | 615 | # RATES AFFECT FREQUENCY OF SHIFTS AND RETURN ONE VALUE 616 | def standard_rate(rate): 617 | return rate 618 | 619 | 620 | def random_rate(min_percentage, max_percentage): 621 | random_value = int(random.uniform(min_percentage, max_percentage)) 622 | return random_value 623 | 624 | 625 | 626 | 627 | --------------------------------------------------------------------------------