-- Sovol SV04 profile (IDEX)
-- Bedell Pierre 30/11/2022

current_extruder = 0
last_extruder_selected = 0 -- counter to track the selected / prepared extruders

current_frate = 0
current_fan_speed = -1

extruder_e = {} -- table of extrusion values for each extruder
extruder_e_reset = {} -- table of extrusion values for each extruder for e reset (to comply with G92 E0)
extruder_e_swap = {} -- table of extrusion values for each extruder to keep track of e at swap

for i = 0, extruder_count -1 do
  extruder_e[i] = 0.0
  extruder_e_reset[i] = 0.0
  extruder_e_swap[i] = 0.0
end

processing = false

path_type = {
--{ 'default',    'Craftware'}
  { ';perimeter',  ';segType:Perimeter' },
  { ';shell',      ';segType:HShell' },
  { ';infill',     ';segType:Infill' },
  { ';raft',       ';segType:Raft' },
  { ';brim',       ';segType:Skirt' },
  { ';shield',     ';segType:Pillar' },
  { ';support',    ';segType:Support' },
  { ';tower',      ';segType:Pillar'}
}

craftware = true -- allow the use of Craftware paths naming convention

--##################################################

function comment(text)
  output('; ' .. text)
end

function round(number, decimals)
  local power = 10^decimals
  return math.floor(number * power) / power
end

function e_to_mm_cube(filament_diameter, e)
  local r = filament_diameter / 2
  return (math.pi * r^2 ) * e
end

function vol_to_mass(volume, density)
  return density * volume
end

function e_from_dep(dep_length, dep_width, dep_height, extruder) -- get the E value (for G1 move) from a specified deposition move
  local r1 = dep_width / 2
  local r2 = filament_diameter_mm[extruder] / 2
  local extruded_vol = dep_length * math.pi * r1 * dep_height
  return extruded_vol / (math.pi * r2^2)
end

--##################################################

function header()
  output('G21 ; set units to millimeters')
  output('G90 ; use absolute coordinates')
  output('M82 ; use absolute distances for extrusion')
  
  output('\n; preheating the extruder(s)')
  if filament_tot_length_mm[0] > 0 then 
    output('M104 T0 S' .. extruder_temp_degree_c[0])
  end
  if filament_tot_length_mm[1] > 0 then 
    output('M104 T1 S' .. extruder_temp_degree_c[1])
  end

  output('M190 S' .. bed_temp_degree_c ..' ; set and wait for bed temperature')
  
  output('\nG28 ; home all axes')

  if auto_bed_leveling == true and reload_bed_mesh == false then
    output('G29 ; auto bed leveling\nG0 F6200 X0 Y0 ; back to the origin to begin the purge')
  elseif reload_bed_mesh == true then
    output('M420 S1 ; enable bed leveling (was disabled y G28)\nM420 L ; load previous mesh / bed level')
  else
    output('G0 F6200 X0 Y0')
  end

  output('\n; waiting for extruder(s) to reach target temparature')
  if filament_tot_length_mm[0] > 0 then 
    output('M109 T0 S' .. extruder_temp_degree_c[0])
  end
  if filament_tot_length_mm[1] > 0 then 
    output('M109 T1 S' .. extruder_temp_degree_c[1])
  end

  -- additionnal informations for Klipper web API (Moonraker)
  -- if feedback from Moonraker is implemented in the choosen web UI (Mainsail, Fluidd, Octoprint), this info will be used for gcode previewing 
  output("\n; Additionnal informations for Mooraker API")
  output("; Generated by <" .. slicer_name .. " " .. slicer_version .. ">")
  output("; print_height_mm :\t" .. f(extent_z))
  output("; layer_count :\t" .. f(extent_z/z_layer_height_mm))
  output("; filament_type : \t" .. name_en)
  output("; filament_name : \t" .. name_en)
  output("; filament_used_mm : \t" .. f(filament_tot_length_mm[0]) )
  -- caution! density is in g/cm3, convertion to g/mm3 needed!
  output("; filament_used_g : \t" .. f(vol_to_mass(e_to_mm_cube(filament_diameter_mm[0], filament_tot_length_mm[0]), filament_density/1000)) )
  output("; estimated_print_time_s : \t" .. time_sec)
  output("")

  current_frate = travel_speed_mm_per_sec * 60
end

function footer()
  output('\n; end of print\n')

  output('G92 E0')
  output('M107 ; fan off')

  output('; turning off extruders(s) heater(s)')
  if filament_tot_length_mm[0] > 0 then 
    output('M104 T0 S0')
  end
  if filament_tot_length_mm[1] > 0 then 
    output('M104 T1 S0')
  end

  output('M140 S0 ; turning off bed heater')

  output('G28 X Y ; move back X and Y to origin')
  output('G0 F6200 Y300 ; move bed for part removal')
  output('M84 ; disable motors')
end

function layer_start(zheight)
  local frate = 600
  comment('<layer ' .. layer_id ..'>')
  if not layer_spiralized then
    output('G0 F' .. f(frate) .. ' Z' .. f(zheight))
  end
  current_frate = frate
end

function layer_stop()
  extruder_e_reset[current_extruder] = extruder_e[current_extruder]
  output('G92 E0')
  comment('</layer ' .. layer_id ..'>')
end

function retract(extruder,e)
  extruder_e[extruder] = e - extruder_e_swap[extruder]
  local len = filament_priming_mm[extruder]
  local speed = priming_mm_per_sec[extruder] * 60
  local e_value = extruder_e[extruder] - extruder_e_reset[extruder] - len
  output(';retract')
  output('G1 F' .. f(speed) .. ' E' .. ff(e_value))
  extruder_e[extruder] = extruder_e[extruder] - len
  current_frate = speed
  return e - len
end

function prime(extruder,e)
  extruder_e[extruder] = e - extruder_e_swap[extruder]
  local len = filament_priming_mm[extruder]
  local speed = priming_mm_per_sec[extruder] * 60
  local e_value = extruder_e[extruder] - extruder_e_reset[extruder] + len
  output(';prime')
  output('G1 F' .. f(speed) .. ' E' .. ff(e_value))
  extruder_e[extruder] = extruder_e[extruder] + len
  current_frate = speed
  return e + len
end

function select_extruder(extruder)
  local n = nozzle_diameter_mm -- should be changed to nozzle_diameter_mm[extruder] when available
  -- hack to work around not beeing a lua global
  if extruder == 0 then
    n = nozzle_diameter_mm_0 
  elseif extruder == 1 then 
    n = nozzle_diameter_mm_1
  end

  local x_pos = 10
  local y_pos = 0
  local z_pos = 0.3

  local purge_length = 180
  local purge_width = n * 1.2
  local purge_height = z_pos
  
  local e_value = 0.0

  output('\n; purging extruder ' .. extruder)
  output('T' .. extruder)

  y_pos = y_pos + ((purge_width*4)*extruder)
  output('G0 F6000 X' .. f(x_pos) .. ' Y' .. f(y_pos) ..' Z' .. f(z_pos))
  output('G92 E0')

  -- purge line along x axis
  e_value = round(e_from_dep(purge_length, purge_width, purge_height, extruder),2)
  
  -- 1st line
  x_pos = x_pos + purge_length
  output('G1 F1000 X' .. f(x_pos) .. ' E' .. ff(e_value) .. '   ; purge line start')

  -- 2nd line
  x_pos = x_pos - purge_length
  y_pos = y_pos + purge_width
  e_value = e_value + e_value
  output('G1 F6000 Y' .. f(y_pos) ..' ; move to side a little')
  output('G1 F1000 X' .. f(x_pos) .. ' E' .. ff(e_value) .. '  ; 2nd purge line')
  
  output('G92 E0')
  output('; done purging extruder ' .. extruder)

  current_extruder = extruder
  current_frate = travel_speed_mm_per_sec * 60

  last_extruder_selected = last_extruder_selected + 1

  -- number_of_extruders is an IceSL internal Lua global variable which is used to know how many extruders will be used for a print job
  if last_extruder_selected == number_of_extruders then
    output('\n; start of print\n')
  end
end

function swap_extruder(from,to,x,y,z)
  output('\n; swap_extruder')
  extruder_e_swap[from] = extruder_e_swap[from] + extruder_e[from] - extruder_e_reset[from]

  local e_value = 20

  -- swap extruder
  output('G0 F600 Z' .. f(z + z_lift_mm) .. '; z-lift for extruder swap')
  output('G92 E0')
  output('T' .. to)
  output('G92 E0')
  output('; purging extruder')
  output('G1 F600 E' .. e_value)
  e_value = e_value - filament_priming_mm[to]
  output('G1 F' .. f(retract_mm_per_sec[to]*60) .. ' E' .. ff(e_value ) .. ' ; retract before moving back to position')
  output('G92 E0')
  output('; travel back to position before resuming')
  output('G0 F' .. f(travel_speed_mm_per_sec*60) .. ' X' .. f(x) .. ' Y' .. f(y))
  output('')

  current_extruder = to
  current_frate = travel_speed_mm_per_sec * 60
end

function move_xyz(x,y,z)
  if processing == true then
    output(';travel')
    processing = false
  end
  output('G0 F' .. f(current_frate) .. ' X' .. f(x) .. ' Y' .. f(y) .. ' Z' .. f(z))
end

function move_xyze(x,y,z,e)
  extruder_e[current_extruder] = e - extruder_e_swap[current_extruder]
  local e_value = extruder_e[current_extruder] - extruder_e_reset[current_extruder]

  if processing == false then 
    processing = true
    p_type = craftware and 2 or 1 -- select path type
    if      path_is_perimeter then output(path_type[1][p_type])
    elseif  path_is_shell     then output(path_type[2][p_type])
    elseif  path_is_infill    then output(path_type[3][p_type])
    elseif  path_is_raft      then output(path_type[4][p_type])
    elseif  path_is_brim      then output(path_type[5][p_type])
    elseif  path_is_shield    then output(path_type[6][p_type])
    elseif  path_is_support   then output(path_type[7][p_type])
    elseif  path_is_tower     then output(path_type[8][p_type])
    end
  end

  output('G1 F' .. f(current_frate) .. ' X' .. f(x) .. ' Y' .. f(y) .. ' Z' .. f(z) .. ' E' .. ff(e_value))
end

function move_e(e)
  extruder_e[current_extruder] = e - extruder_e_swap[current_extruder]
  local e_value = extruder_e[current_extruder] - extruder_e_reset[current_extruder]
  output('G1 F' .. f(current_frate) .. ' E' .. ff(e_value))
end

function set_feedrate(feedrate)
  current_frate = feedrate
end

function extruder_start()
end

function extruder_stop()
end

function progress(percent)
end

function set_extruder_temperature(extruder, temperature)
  output('M104 T' .. extruder .. ' S' .. temperature)
end

function set_and_wait_extruder_temperature(extruder, temperature)
  output('M109 T' .. extruder .. ' S' .. temperature)
end

function set_fan_speed(speed)
  if speed ~= current_fan_speed then
    output('M106 S'.. math.floor(255 * speed/100))
    current_fan_speed = speed
  end
end

function wait(sec,x,y,z)
  output("; WAIT --" .. sec .. "s remaining" )
  output("G0 F" .. travel_speed_mm_per_sec .. " X10 Y10")
  output("G4 S" .. sec .. "; wait for " .. sec .. "s")
  output("G0 F" .. travel_speed_mm_per_sec .. " X" .. f(x) .. " Y" .. f(y) .. " Z" .. ff(z))
end
