Created
August 6, 2017 11:31
Star
You must be signed in to star a gist
Auto Landscape Bracketing Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
Auto Bracketing Script | |
Auto focus bracketing with exposure bracketing | |
Plus the script does both time and frame bracketing, eg for ND/LE simulation | |
Release August 2017 | |
This version has some error detection, but the user must be aware of certain things: | |
* Long FL lenses may fail and this script is best used with MEDIUM & WIDE angle lenses. | |
* Select FP2HFD option to estimate number of focus brackets to HFD. Add 1 bracket to this for FP2INF use case. | |
* The QHFD option takes a two bracket set, one either side of the HFD. One at HFD/2 and one at either 2xHFD or 3xHFD. | |
* Under settings you can select the 2x or 3x HFD: the default is 2xHFD. | |
* This script runs independant of the ML DoFs and always calculated DoFs based on diffraction aware | |
* Must use a lens that reports focus distance and DoF can be calculated. | |
* Must be in manual mode. | |
* Switch stabalisation off as you will not be hand holding ;-) | |
* Must be in LV mode: script won't run unless in LV. | |
* ETTR ON: settings recommended are - no link to dual, SNRs to OFF, highlights very low or zero (your choice, and not Always On or Auto Snap as this may 'confuse' the script. | |
* You can switch focus bracketing on/off in the menu, ie use LE and/or exposure mode alone. The script handles the logic, ie if LE mode is being used, | |
* then you can't use focus or exposure bracketing. | |
* You can select Dual-ISO when focus bracketing. But you need to ensure Dual-ISO settings are 'correct'. | |
* Image review must not be set to HOLD in Canon menu. | |
* Assumes lens is focused at correct FP to start a focus stack, ie script moves to infinity or HFD according to mode. | |
* When exposure bracketing the script will calculate the ETTR (and ETTL if required) of the scene and # brackets. | |
* For non-auto exposure backeting (1 or 2 Ev) set base exposure to capture shadows. For Auto exposure backeting (1 or 2 Ev) the base exposure (shutter and aperture) can be anywhere. | |
* If ETTR fails, then try adjusting ETTR set time in menu to give your camera more time (or experiment with less) | |
* Final thought: photography should be relaxing, hence I personally don't mind that this script takes time to run. As it's | |
* running, look around you and enjoy nature and the moment :-) | |
******************************************************************************************************* | |
* * | |
* If you reference this script, please consider acknowledging me at http://photography.grayheron.net/ * | |
* * | |
******************************************************************************************************* | |
--]] | |
-- Declare a few variables for the script which load when the camera is switced on | |
delay = 0 | |
factor = 0 | |
num_brackets = 0 | |
ETTR_tv = 0 | |
base_tv = 0 | |
user_tv = 0 | |
LE_brackets = 0 | |
running = false | |
no_error = true | |
brackets_requested = false | |
infinity = 655000 -- based on ML infinity (same for all lenses) | |
function my_lens_dof_near() | |
--************************************************************ | |
-- This replaces the ML calculated DoF near | |
local focus_distance = lens.focus_distance | |
local focal_length = lens.focal_length | |
local aperture_value = camera.aperture.value | |
local unity_blur = menu.get("DOF Settings","Circle of Confusion") -- ML set total blur criterion | |
local ml_blur = unity_blur | |
local b_diff = 2.44*0.550*aperture_value -- visible diffraction blur at infinity, ie magnification = 0 | |
local b_diff_fp = b_diff*focus_distance/(focus_distance-focal_length) -- diffraction at fp, ie with magnification (really only kicks in for close work) | |
ml_blur = math.sqrt(unity_blur*unity_blur - b_diff_fp*b_diff_fp) -- calculate defocus blur | |
local d = focal_length/aperture_value -- diameter of aperture | |
local dof_near = d*focal_length*focus_distance | |
dof_near = dof_near/(ml_blur*(focus_distance-focal_length)/1000+d*focal_length) | |
return dof_near | |
--************************************************************ | |
end | |
function my_lens_dof_far() | |
--************************************************************ | |
-- This replaces the ML calculated DoF far | |
local focus_distance = lens.focus_distance | |
local focal_length = lens.focal_length | |
local aperture_value = camera.aperture.value | |
local unity_blur = menu.get("DOF Settings","Circle of Confusion") -- ML set total blur criterion | |
local ml_blur = unity_blur | |
local b_diff = 2.44*0.550*aperture_value -- visible diffraction blur at infinity, ie magnification = 0 | |
local b_diff_fp = b_diff*focus_distance/(focus_distance-focal_length) -- diffraction at fp, ie with magnification (really only kicks in for close work) | |
ml_blur = math.sqrt(unity_blur*unity_blur - b_diff_fp*b_diff_fp) -- calculate defocus blur | |
local d = focal_length/aperture_value -- diameter of aperture | |
local dof_far = -1*d*focal_length*focus_distance | |
dof_far = dof_far/(ml_blur*(focus_distance-focal_length)/1000-d*focal_length) | |
return dof_far | |
--************************************************************ | |
end | |
function my_lens_hyperfocal() | |
--************************************************************ | |
-- This replaces the ML calculated HFD | |
local focus_distance = lens.focus_distance | |
local focal_length = lens.focal_length | |
local aperture_value = camera.aperture.value | |
local unity_blur = menu.get("DOF Settings","Circle of Confusion") -- ML set total blur criterion | |
local ml_blur = unity_blur | |
local b_diff = 2.44*0.550*aperture_value -- visible diffraction blur at infinity, ie magnification = 0 | |
local b_diff_fp = b_diff*focus_distance/(focus_distance-focal_length) -- diffraction at fp, ie with magnification (really only kicks in for close work) | |
ml_blur = 0.001*math.sqrt(unity_blur*unity_blur - b_diff_fp*b_diff_fp) -- calculate defocus blur | |
local hyperfocal = focal_length*(1+focal_length/(aperture_value*ml_blur)) | |
return hyperfocal | |
--************************************************************ | |
end | |
function show_message(message,onoroff) | |
if onoroff then | |
display.print(message, 10, 45, FONT.LARGE ,COLOR.RED,COLOR.WHITE) | |
else | |
display.print(message, 10, 45, FONT.LARGE ,COLOR.TRANSPARENT,COLOR.TRANSPARENT) | |
end | |
end | |
function reset() -- tidy up at the end | |
if no_error == true then | |
display.notify_box("***FINISHED***",4000) | |
beep (3, 400 , 1000) -- notify end of script | |
-- Before completly finishing the script, check if any LE extras asked for | |
if scriptmenu.submenu["LE mode?"].submenu["select"].value ~= 0 and scriptmenu.submenu["LE mode?"].submenu["Plus"].value == "Dual-ISO" then | |
menu.set("Expo","Dual ISO",1) | |
msleep(500) | |
my_shoot() | |
menu.set("Expo","Dual ISO",0) | |
elseif scriptmenu.submenu["LE mode?"].submenu["select"].value ~= 0 and scriptmenu.submenu["LE mode?"].submenu["Plus"].value == "Auto Bracketing" then | |
menu.set("Shoot","Advanced Bracket",1) | |
msleep(500) | |
my_shoot() | |
menu.set("Shoot","Advanced Bracket",0) | |
end | |
else | |
beep (3, 200 , 1000) -- notify end of script | |
display.notify_box("***COULDN'T FINISH***",4000) | |
end | |
brackets_requested = false | |
LE_brackets = 0 | |
running = true | |
no_error = true | |
end | |
function my_shoot() | |
camera.shoot(false) | |
repeat | |
msleep(200) | |
until lv.running | |
end | |
function check_bookend() | |
if scriptmenu.submenu["Bookends?"].value == "yes" | |
then | |
local tv = camera.shutter.ms | |
local av = camera.aperture.value | |
camera.shutter.ms = 1 | |
camera.aperture.apex = 9 | |
my_shoot() | |
camera.shutter.ms = tv | |
camera.aperture.value = av | |
end | |
end | |
function find_ETTR() -- Find the 'ETTR' shutter value: make sure you have set ML ETTR values correctly | |
show_message("Getting exposure for the highlights",true) | |
local m = menu.get("Auto ETTR","Trigger mode") | |
menu.set("Auto ETTR","Trigger mode",0) -- set ETTR menu to get an ETTR exposure | |
local check_tv = 0 | |
repeat -- hang around until ETTR value has stabilised | |
check_tv = camera.shutter.apex | |
msleep (scriptmenu.submenu["Settings"].submenu["ETTR Set Time?"].value*1000) -- adjust this for your camera. The time delay allows the ML ETTR process to settle down | |
until check_tv == camera.shutter.apex | |
ETTR_tv = camera.shutter.apex | |
menu.set("Auto ETTR","Trigger mode",m) -- reset to user ML ETTR menu setting | |
show_message("Getting exposure for the highlights",false) | |
if ETTR_tv < base_tv | |
then -- base TV needs resetting | |
display.notify_box("Warning Shadow Tv < ETTR Tv",4000) | |
beep (3, 200 , 500) -- warning message | |
no_error = false | |
end | |
end | |
function find_ETTL() -- Find the 'ETTL' shutter value | |
show_message("Getting exposure for the shadows",true) | |
local m = menu.get("Auto ETTR","Shadow SNR limit") | |
local n = menu.get("Auto ETTR","Trigger mode") | |
menu.set("Auto ETTR","Trigger mode",0) -- set ETTR menu to get an ETTR exposure | |
menu.set("Auto ETTR","Shadow SNR limit", scriptmenu.submenu["Settings"].submenu["Shadow SNR"].value) | |
local check_tv = 0 | |
repeat -- hang around until ETTL value has stabilised | |
check_tv = camera.shutter.apex | |
msleep (scriptmenu.submenu["Settings"].submenu["ETTR Set Time?"].value*1000) -- adjust this for your camera. The time delay allows the ML ETTR process to settle down | |
until check_tv == camera.shutter.apex | |
base_tv = camera.shutter.apex | |
menu.set("Auto ETTR","Trigger mode",n) -- reset to user ML ETTR menu setting | |
menu.set("Auto ETTR","Shadow SNR limit",m) | |
show_message("Getting exposure for the shadows",false) | |
end | |
function calc_brackets() | |
if no_error then | |
num_brackets = (ETTR_tv - base_tv)/factor | |
num_brackets = math.ceil(math.abs(num_brackets)) | |
if num_brackets <= 1 then num_brackets = 1 end | |
end | |
end | |
function check_LE() | |
-- Check to see if user has requested LE bracketing of some kind | |
if scriptmenu.submenu["LE mode?"].submenu["select"].value ~= 0 | |
then | |
if scriptmenu.submenu["LE mode?"].submenu["Mode?"].value == "time" | |
then | |
LE_brackets = math.ceil(scriptmenu.submenu["LE mode?"].submenu["select"].value/camera.shutter.value) | |
else | |
LE_brackets = scriptmenu.submenu["LE mode?"].submenu["select"].value | |
end | |
brackets_requested = true | |
end | |
end | |
function take_brackets() | |
local i = 0 | |
if LE_brackets == 0 then | |
if no_error and brackets_requested then -- take exposure brackets | |
camera.shutter.apex = ETTR_tv + factor | |
for i = 1, num_brackets , 1 | |
do | |
camera.shutter.apex = camera.shutter.apex - factor | |
my_shoot() | |
end | |
if camera.shutter.apex > base_tv | |
then | |
camera.shutter.apex = base_tv | |
my_shoot() | |
end | |
-- check_lens_ready() | |
else -- take a single image | |
camera.shutter.apex = base_tv | |
my_shoot() | |
end | |
else -- take some LE bracketing images | |
for i = 1, LE_brackets , 1 | |
do | |
my_shoot() | |
end | |
end | |
end | |
function move(direction) | |
-- Note direction not used in this version | |
local lens_ok = true | |
local a1 = 0 | |
local b1 = 0 | |
local a2 = my_lens_dof_near() | |
local b2 = my_lens_dof_far() | |
local HFD_reached = false | |
local infinity_reached = false | |
local fp = lens.focus_distance | |
local shift_right = my_lens_hyperfocal()*scriptmenu.submenu["Settings"].submenu["Far QHFD shift"].value | |
local shift_left = my_lens_hyperfocal()/2 | |
repeat -- wait just in case LV not running | |
msleep(200) | |
until lv.running | |
if scriptmenu.submenu["Focus bracketing?"].value == "QHFD" then -- take a two focus brackets either side of the HFD. First go to HFD/2: | |
if lens.focus_distance < shift_left then | |
repeat | |
lens_ok = lens.focus(-1,1,true) | |
until lens.focus_distance > shift_left | |
elseif lens.focus_distance > shift_left then | |
repeat | |
lens_ok = lens.focus(1,1,true) | |
until lens.focus_distance < shift_left or not lens_ok | |
end -- as lens positioned at start of QHFD focus bracket set | |
take_brackets() -- then move to the other side of the HFD position | |
repeat | |
lens_ok = lens.focus(-1,1,true) | |
until lens.focus_distance > shift_right | |
take_brackets() -- to complete the QHFD two image bracket set | |
else -- do one of the other focus bracket requests | |
repeat | |
a1=a2 | |
b1=b2 | |
repeat | |
if scriptmenu.submenu["Focus bracketing?"].value == "FP2HFD" and fp > shift_right then | |
HFD_reached = true | |
break | |
end | |
if fp > infinity then | |
infinity_reached = true | |
break | |
end | |
lens_ok = lens.focus(-1,2,true) -- move large steps forward | |
b2 = my_lens_dof_far() | |
a2 = my_lens_dof_near() | |
fp = lens.focus_distance | |
until (a2 >= b1) | |
repeat | |
if (a2 < b1) then break end | |
lens.focus(1,1,true) -- move small steps back | |
b2 = my_lens_dof_far() | |
a2 = my_lens_dof_near() | |
fp = lens.focus_distance | |
until (a2 < b1) | |
if scriptmenu.submenu["Focus bracketing?"].value == "FP2HFD" and fp > shift_right then -- uses 2x or 3x HFD for best results | |
HFD_reached = true | |
repeat lens.focus(1,1,true) until (lens.focus_distance < shift_right ) | |
repeat lens.focus(-1,1,true) until lens.focus_distance >= shift_right | |
else | |
HFD_reached = false | |
end | |
if fp > infinity then | |
infinity_reached = true | |
repeat lens.focus(1,1,true) until (lens.focus_distance < infinity ) | |
repeat lens.focus(-1,1,true) until lens.focus_distance >= infinity -- move one step to infinity | |
else | |
infinity_reached = false | |
end | |
take_brackets() | |
until (HFD_reached or infinity_reached) | |
end | |
end | |
function albs() | |
if brackets_requested and scriptmenu.submenu["LE mode?"].submenu["select"].value == 0 then | |
if factor == 3 or factor == 4 then -- calculate the shadow end of the bracket set | |
find_ETTL() | |
factor = factor - 2 | |
end | |
find_ETTR() -- calculate the highlight end of the bracket set | |
calc_brackets() -- number of exposure brackets | |
else | |
msleep(scriptmenu.submenu["Script delay"].value*1000) -- delay in ms | |
end | |
if no_error then | |
check_bookend() | |
if scriptmenu.submenu["Settings"].submenu["Dual?"].value == "On" then menu.set("Expo","Dual ISO",1) end | |
-- Get DoF info & take first (only) exposure(s) | |
if scriptmenu.submenu["Focus bracketing?"].value ~= "QHFD" then | |
take_brackets() | |
end | |
-- Take additional focus brackets if requested | |
if scriptmenu.submenu["Focus bracketing?"].value ~= "no" then | |
move (0) | |
end | |
if scriptmenu.submenu["Settings"].submenu["Dual?"].value == "On" then menu.set("Expo","Dual ISO",0) end | |
check_bookend() | |
end | |
reset() -- do some tiding up beforing exiting | |
end | |
function setup() | |
-- Start the main script running and do some error checking | |
menu.close() | |
factor = scriptmenu.submenu["Exp Ev option"].value -- exposure bracketing options | |
if not lv.enabled then lv.start() end -- just in case | |
-- Do some checking | |
if camera.model_short == "EOSM" then | |
no_error = false | |
beep (3, 200 , 1000) | |
display.notify_box("Sorry doesn't (yet) work with the EOSM", 3000) | |
msleep(3000) | |
reset() | |
elseif scriptmenu.submenu["Focus bracketing?"].value == "FP2HFD" and lens.focus_distance > my_lens_hyperfocal() then | |
no_error = false | |
beep (3, 200 , 1000) | |
display.notify_box("Warning: FP beyond HFD", 3000) | |
msleep(3000) | |
reset() | |
elseif scriptmenu.submenu["Focus bracketing?"].value == "FP2INF" and lens.focus_distance > infinity then | |
no_error = false | |
beep (3, 200 , 1000) | |
display.notify_box("Warning: FP at or beyond infinity", 3000) | |
msleep(3000) | |
reset() | |
elseif my_lens_dof_far() == my_lens_dof_near() then | |
no_error = false | |
beep (3, 200 , 1000) | |
display.notify_box("Check ML setting for CoC", 3000) | |
msleep(3000) | |
reset() | |
elseif math.abs(my_lens_dof_near()-my_lens_dof_far()) < 10 then -- adjust this test to your taste (default is 10mm) | |
-- the above test is there to handle long FL cases, as the DoF.near and Dof.far may be too close together | |
-- which can throw out the script. | |
-- So don't use a long lens ;-) | |
display.notify_box("Warning: Change lens FL or focus point", 3000) | |
reset() | |
else -- Good to go :-) | |
check_LE() | |
if factor ~= 0 then brackets_requested = true else brackets_requested = false end | |
if running -- then script has already been used since camera switched on | |
then -- reset some stuff...just to be sure :-) | |
no_error = true | |
end | |
if scriptmenu.submenu["Focus bracketing?"].value ~= "no" then -- focus stacking requested | |
if lens.focus_distance ~= 0 and lens.af -- lens is OK | |
then -- fit to run the main function | |
base_tv = camera.shutter.apex | |
albs() | |
else -- lens not OK! | |
beep (3, 200 , 500) -- warning message | |
display.notify_box("Check your lens", 5000) | |
end | |
else -- 'just' use in non-focus stacking mode | |
base_tv = camera.shutter.apex | |
albs() | |
end | |
end | |
end | |
function hfd_count() -- estimates number of focus brackets to HFD | |
local s = lens.focus_distance | |
local H = my_lens_hyperfocal() | |
local dof_f = my_lens_dof_far() | |
local count = 1 | |
local df = 0 | |
local s2 = 0 | |
if s > H then s = 0.999*H end | |
repeat | |
count = count + 1 | |
df = (H*s)/(H-s) | |
s2 = (df*H)/(H-df) | |
s = s2 | |
until s > H or s < 0 | |
return count | |
end | |
scriptmenu = menu.new | |
{ | |
parent = "Shoot", -- change this to position the script into other ML menu locations | |
name = "Auto Bracketing", | |
help = "Remember: set LV, DoF & AF on", | |
depends_on = DEPENDS_ON.LIVEVIEW, | |
warning = function(this) | |
if menu.get("Expo","Auto ETTR") == 0 then return "ETTR switched off" end | |
end, | |
submenu = | |
{ | |
{ | |
name = "Script Control", | |
help = "Does what it says", | |
warning = function(this) | |
if menu.get("Expo","Auto ETTR") == 0 and not lv.enabled then return "ETTR and LV switched off" end | |
if not lv.enabled then return "LV not enabled" end | |
if menu.get("Expo","Auto ETTR") == 0 then return "ETTR switched off" end | |
end, | |
submenu = | |
{ | |
{ | |
name = "Run Script", | |
select = function(this) | |
if menu.get("Expo","Auto ETTR") ~= 0 and lv.enabled and key.last == KEY.SET then task.create(setup) end | |
end, | |
help = "Runs the main script", | |
}, | |
{ | |
name = "Turn Script Off", | |
select = function(this) | |
event.keypress = nil | |
menu.close() | |
end, | |
help = "Simply ensures key presses are returned to normal", | |
}, | |
}, | |
}, | |
{ | |
name = "Focus bracketing?", | |
help = "Select FP2HFD option to estimate number of focus brackets to HFD", | |
choices = {"no","FP2INF","FP2HFD","QHFD"}, | |
icon_type = ICON_TYPE.BOOL, | |
warning = function(this) | |
if scriptmenu.submenu["LE mode?"].submenu["select"].value ~= 0 then | |
this.value = "no" | |
return "LE mode on" | |
end | |
end, | |
rinfo = function(this) | |
if scriptmenu.submenu["Focus bracketing?"].value == "FP2HFD" and lens.focus_distance < my_lens_hyperfocal() | |
then | |
return hfd_count() end | |
end, | |
}, | |
{ | |
name = "Exp Ev option", | |
help = "Auto exposure bracketing. 1Ev & 2Ev are from user set shadow exposure", | |
help2 = "If Auto 1Ev & Auto 2Ev selected bracketing is fully auto", | |
min = 0, | |
max = 4, | |
icon_type = ICON_TYPE.BOOL, | |
warning = function(this) | |
if scriptmenu.submenu["LE mode?"].submenu["select"].value ~= 0 then | |
this.value = 0 | |
return "LE mode on" | |
end | |
end, | |
rinfo = function(this) | |
if this.value == 0 then return "Off" | |
elseif this.value == 1 then return "1Ev" | |
elseif this.value == 2 then return "2Ev" | |
elseif this.value == 3 then return "Auto 1Ev" | |
elseif this.value == 4 then return "Auto 2Ev" | |
end | |
end, | |
}, | |
{ | |
name = "LE mode?", | |
rinfo = function(this) | |
if scriptmenu.submenu["LE mode?"].submenu["select"].value == 0 | |
then return "Off" | |
else return "On" | |
end | |
end, | |
help = "In seconds or frames. Zero = no LE bracketing", | |
help2 = "LE or frame bracketing trumps all other bracketing modes", | |
submenu = | |
{ | |
{ | |
name = "select", | |
min = 0, | |
max = 300, | |
icon_type = ICON_TYPE.BOOL, | |
rinfo = function(this) | |
if scriptmenu.submenu["LE mode?"].submenu["Mode?"].value == "time" then return "secs" else return "frames" end | |
end, | |
select = function(this,delta) | |
this.value = this.value + delta | |
if this.value > this.max then | |
this.value = this.min | |
elseif this.value < this.min then | |
this.value = this.max | |
end | |
end, | |
warning = function(this) | |
if scriptmenu.submenu["LE mode?"].submenu["Mode?"].value == "time" then | |
LE_brackets = math.ceil(scriptmenu.submenu["LE mode?"].submenu["select"].value/camera.shutter.value) | |
if LE_brackets == 0 then | |
return "LE bracket taking switched off" | |
else | |
return "Note: "..tostring(LE_brackets).." brackets will be taken" | |
end | |
end | |
end | |
}, | |
{ | |
name = "Mode?", | |
choices = {"time","# frames"}, | |
help = "In seconds or #frames. Zero = no LE bracketing" | |
}, | |
{ | |
name = "Plus", | |
icon_type = ICON_TYPE.BOOL, | |
choices = {"Nothing","Dual-ISO","Auto Bracketing"}, | |
help = "Takes an addition image or images, on top of the LE sequence" | |
} | |
} | |
}, | |
{ | |
name = "Bookends?", | |
help = "Places a dark frame at start and end of focus bracket set", | |
choices = {"no","yes"}, | |
icon_type = ICON_TYPE.BOOL, | |
}, | |
{ | |
name = "Script delay", | |
help = "Delays script start by stated number of secs", | |
min = 0, | |
max = 5, | |
icon_type = ICON_TYPE.BOOL, | |
select = function(this,delta) | |
if this.value == 0 and delta == 1 then this.value = 2 | |
elseif this.value == 0 and delta == -1 then this.value = 5 | |
elseif this.value == 2 and delta == 1 then this.value = 5 | |
elseif this.value == 2 and delta == -1 then this.value = 0 | |
elseif this.value == 5 and delta == 1 then this.value = 0 | |
elseif this.value == 5 and delta == -1 then this.value = 2 | |
end | |
end | |
}, | |
{ | |
name = "Settings", | |
help = "Various settings for the script", | |
submenu = | |
{ | |
{ | |
name = "ETTR Set Time?", | |
help = "A camera specific variable in seconds", | |
help2 = "Change if ETTR fails to find a solution", | |
min = 2, | |
max = 8, | |
value = 4 -- this works on a 5D3 | |
}, | |
{ | |
name = "Shadow SNR", | |
help = "Signal-2-Noise ratio in Ev: 4-6 is the sweet spot", | |
min = 2, | |
max = 6, | |
value = 6 | |
}, | |
{ | |
name = "Dual?", | |
help = "Dual-ISO On or Off", | |
choices = {"Off","On"}, | |
icon_type = ICON_TYPE.BOOL, | |
}, | |
{ | |
name = "Far QHFD shift", | |
help = "2 or 3 x HFD", | |
min = 2, | |
max = 3, | |
value = 2 | |
}, | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment