mirror of
https://github.com/EyeTrackVR/EyeTrackVR.git
synced 2025-11-04 14:39:42 +08:00
Initial settings rewrite into modules in preparation for resetting to defaults and for better settings handling
This commit is contained in:
parent
62fba485ba
commit
c93e279157
@ -1,6 +1,6 @@
|
|||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
from config import EyeTrackSettingsConfig
|
from config import EyeTrackConfig
|
||||||
from osc import EyeId
|
from osc import EyeId
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Event
|
from threading import Event
|
||||||
@ -8,7 +8,7 @@ from threading import Event
|
|||||||
|
|
||||||
class AlgoSettingsWidget:
|
class AlgoSettingsWidget:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, widget_id: EyeId, main_config: EyeTrackSettingsConfig, osc_queue: Queue
|
self, widget_id: EyeId, main_config: EyeTrackConfig, osc_queue: Queue
|
||||||
):
|
):
|
||||||
|
|
||||||
self.gui_flip_x_axis_left = f"-FLIPXAXISLEFT{widget_id}-"
|
self.gui_flip_x_axis_left = f"-FLIPXAXISLEFT{widget_id}-"
|
||||||
@ -522,5 +522,4 @@ class AlgoSettingsWidget:
|
|||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
self.main_config.save()
|
self.main_config.save()
|
||||||
# print(self.main_config)
|
|
||||||
self.osc_queue.put(EyeId.ALGOSETTINGS)
|
self.osc_queue.put(EyeId.ALGOSETTINGS)
|
||||||
|
|||||||
@ -88,8 +88,8 @@ class EyeTrackConfig(BaseModel):
|
|||||||
right_eye: EyeTrackCameraConfig = EyeTrackCameraConfig()
|
right_eye: EyeTrackCameraConfig = EyeTrackCameraConfig()
|
||||||
left_eye: EyeTrackCameraConfig = EyeTrackCameraConfig()
|
left_eye: EyeTrackCameraConfig = EyeTrackCameraConfig()
|
||||||
settings: EyeTrackSettingsConfig = EyeTrackSettingsConfig()
|
settings: EyeTrackSettingsConfig = EyeTrackSettingsConfig()
|
||||||
# algo_settings: EyeTrackSettingsConfig = EyeTrackSettingsConfig()
|
|
||||||
eye_display_id: EyeId = EyeId.RIGHT
|
eye_display_id: EyeId = EyeId.RIGHT
|
||||||
|
__listeners = []
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load():
|
def load():
|
||||||
@ -114,6 +114,13 @@ class EyeTrackConfig(BaseModel):
|
|||||||
load_config = EyeTrackConfig()
|
load_config = EyeTrackConfig()
|
||||||
return load_config
|
return load_config
|
||||||
|
|
||||||
|
def update(self, data, save=False):
|
||||||
|
for field, value in data.items():
|
||||||
|
setattr(self.settings, field, value)
|
||||||
|
self.__notify_listeners()
|
||||||
|
if save:
|
||||||
|
self.save()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
# make sure this is only called if there is a change
|
# make sure this is only called if there is a change
|
||||||
if os.path.exists(CONFIG_FILE_NAME):
|
if os.path.exists(CONFIG_FILE_NAME):
|
||||||
@ -129,3 +136,11 @@ class EyeTrackConfig(BaseModel):
|
|||||||
with open(CONFIG_FILE_NAME, "w") as settings_file:
|
with open(CONFIG_FILE_NAME, "w") as settings_file:
|
||||||
json.dump(obj=self.dict(), fp=settings_file)
|
json.dump(obj=self.dict(), fp=settings_file)
|
||||||
print(f"\033[92m[INFO] Config Saved Successfully\033[0m")
|
print(f"\033[92m[INFO] Config Saved Successfully\033[0m")
|
||||||
|
|
||||||
|
def register_listener_callback(self, callback):
|
||||||
|
print(f"[DEBUG] Registering listener {callback}")
|
||||||
|
self.__listeners.append(callback)
|
||||||
|
|
||||||
|
def __notify_listeners(self):
|
||||||
|
for listener in self.__listeners:
|
||||||
|
listener()
|
||||||
|
|||||||
@ -90,7 +90,7 @@ def main():
|
|||||||
]
|
]
|
||||||
|
|
||||||
settings = [
|
settings = [
|
||||||
SettingsWidget(EyeId.SETTINGS, config, osc_queue),
|
SettingsWidget(EyeId.SETTINGS, config),
|
||||||
AlgoSettingsWidget(EyeId.ALGOSETTINGS, config, osc_queue),
|
AlgoSettingsWidget(EyeId.ALGOSETTINGS, config, osc_queue),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -282,6 +282,5 @@ def main():
|
|||||||
setting.render(window, event, values)
|
setting.render(window, event, values)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@ -1,201 +1,54 @@
|
|||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
from config import EyeTrackSettingsConfig
|
from config import EyeTrackConfig, EyeTrackSettingsConfig
|
||||||
from osc import EyeId
|
from osc import EyeId
|
||||||
from queue import Queue
|
|
||||||
from threading import Event
|
from threading import Event
|
||||||
|
|
||||||
class SettingsWidget:
|
from settings.modules.GeneralSettingsModule import GeneralSettingsModule
|
||||||
def __init__(self, widget_id: EyeId, main_config: EyeTrackSettingsConfig, osc_queue: Queue):
|
from settings.modules.OneEuroSettingsModule import OneEuroSettingsModule
|
||||||
|
from settings.modules.OSCSettingsModule import OSCSettingsModule
|
||||||
|
|
||||||
self.gui_flip_x_axis_left = f"-FLIPXAXISLEFT{widget_id}-"
|
|
||||||
self.gui_flip_x_axis_right = f"-FLIPXAXISRIGHT{widget_id}-"
|
class SettingsWidget:
|
||||||
self.gui_flip_y_axis = f"-FLIPYAXIS{widget_id}-"
|
def __init__(self, widget_id: EyeId, main_config: EyeTrackConfig):
|
||||||
self.gui_general_settings_layout = f"-GENERALSETTINGSLAYOUT{widget_id}-"
|
self.is_saving = False
|
||||||
self.gui_osc_address = f"-OSCADDRESS{widget_id}-"
|
|
||||||
self.gui_osc_port = f"-OSCPORT{widget_id}-"
|
|
||||||
self.gui_osc_receiver_port = f"OSCRECEIVERPORT{widget_id}-"
|
|
||||||
self.gui_osc_recenter_address = f"OSCRECENTERADDRESS{widget_id}-"
|
|
||||||
self.gui_osc_recalibrate_address = f"OSCRECALIBRATEADDRESS{widget_id}-"
|
|
||||||
self.gui_BLOB = f"-BLOBFALLBACK{widget_id}-"
|
|
||||||
self.gui_HSF = f"-HSF{widget_id}-"
|
|
||||||
self.gui_DADDY = f"-DADDY{widget_id}-"
|
|
||||||
self.gui_DADDYP = f"-DADDYP{widget_id}-"
|
|
||||||
self.gui_RANSAC3D = f"-RANSAC3D{widget_id}-"
|
|
||||||
self.gui_BLINK = f"-BLINK{widget_id}-"
|
|
||||||
self.gui_IBO = f"-IBO{widget_id}-"
|
|
||||||
self.gui_HSRAC = f"-HSRAC{widget_id}-"
|
|
||||||
self.gui_HSF_radius = f"-HSFRADIUS{widget_id}-"
|
|
||||||
self.gui_blob_maxsize = f"-BLOBMAXSIZE{widget_id}-"
|
|
||||||
self.gui_blob_minsize = f"-BLOBMINSIZE{widget_id}-"
|
|
||||||
self.gui_speed_coefficient = f"-SPEEDCOEFFICIENT{widget_id}-"
|
|
||||||
self.gui_min_cutoff = f"-MINCUTOFF{widget_id}-"
|
|
||||||
self.gui_eye_falloff = f"-EYEFALLOFF{widget_id}-"
|
|
||||||
self.gui_skip_autoradius = f"-SKIPAUTORADIUS{widget_id}-"
|
|
||||||
self.gui_HSRACP = f"-HSRACP{widget_id}-"
|
|
||||||
self.gui_RANSAC3DP = f"-RANSAC3DP{widget_id}-"
|
|
||||||
self.gui_HSFP = f"-HSFP{widget_id}-"
|
|
||||||
self.gui_BLOBP = f"-BLOBP{widget_id}-"
|
|
||||||
self.gui_thresh_add = f"-THRESHADD{widget_id}-"
|
|
||||||
self.gui_ROSC = f"-ROSC{widget_id}-"
|
|
||||||
self.gui_vrc_native = f"-VRCNATIVE{widget_id}-"
|
|
||||||
self.gui_circular_crop_left = f"-CIRCLECROPLEFT{widget_id}-"
|
|
||||||
self.gui_circular_crop_right = f"-CIRCLECROPRIGHT{widget_id}-"
|
|
||||||
self.gui_update_check = f"-UPDATECHECK{widget_id}-"
|
|
||||||
self.gui_threshold_slider = f"-BLOBTHRESHOLD{widget_id}-"
|
|
||||||
self.main_config = main_config
|
self.main_config = main_config
|
||||||
self.config = main_config.settings
|
self.config = main_config.settings
|
||||||
self.osc_queue = osc_queue
|
|
||||||
|
settings_modules = [
|
||||||
|
GeneralSettingsModule,
|
||||||
|
OneEuroSettingsModule,
|
||||||
|
OSCSettingsModule,
|
||||||
|
]
|
||||||
|
|
||||||
|
self.initialized_modules = self._initialize_modules(
|
||||||
|
settings_modules=settings_modules, widget_id=widget_id
|
||||||
|
)
|
||||||
|
self.gui_general_settings_layout = f"-GENERALSETTINGSLAYOUT{widget_id}-"
|
||||||
|
|
||||||
# Define the window's contents
|
# Define the window's contents
|
||||||
self.general_settings_layout = [
|
self.general_settings_layout = []
|
||||||
|
|
||||||
[
|
|
||||||
sg.Checkbox(
|
|
||||||
"Flip Left Eye X Axis",
|
|
||||||
default=self.config.gui_flip_x_axis_left,
|
|
||||||
key=self.gui_flip_x_axis_left,
|
|
||||||
background_color='#424042',
|
|
||||||
tooltip = "Flips the left eye's X axis.",
|
|
||||||
),
|
|
||||||
sg.Checkbox(
|
|
||||||
"Flip Right Eye X Axis",
|
|
||||||
default=self.config.gui_flip_x_axis_right,
|
|
||||||
key=self.gui_flip_x_axis_right,
|
|
||||||
background_color='#424042',
|
|
||||||
tooltip = "Flips the right eye's X axis.",
|
|
||||||
),
|
|
||||||
|
|
||||||
# ],
|
|
||||||
#[
|
|
||||||
sg.Checkbox(
|
|
||||||
"Flip Y Axis",
|
|
||||||
default=self.config.gui_flip_y_axis,
|
|
||||||
key=self.gui_flip_y_axis,
|
|
||||||
background_color='#424042',
|
|
||||||
tooltip = "Flips the eye's Y axis.",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[sg.Checkbox(
|
|
||||||
"VRC Native Eyetracking",
|
|
||||||
default=self.config.gui_vrc_native,
|
|
||||||
key=self.gui_vrc_native,
|
|
||||||
background_color='#424042',
|
|
||||||
tooltip = "Toggle VRCFT output or VRC native",
|
|
||||||
),
|
|
||||||
sg.Checkbox(
|
|
||||||
"Dual Eye Falloff",
|
|
||||||
default=self.config.gui_eye_falloff,
|
|
||||||
key=self.gui_eye_falloff,
|
|
||||||
background_color='#424042',
|
|
||||||
tooltip = "If one eye stops tracking, we send tracking data from your other eye.",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[sg.Checkbox(
|
|
||||||
"Check For Updates",
|
|
||||||
default=self.config.gui_update_check,
|
|
||||||
key=self.gui_update_check,
|
|
||||||
background_color='#424042',
|
|
||||||
tooltip = "Toggle update check on launch.",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
sg.Text("One Euro Filter Paramaters:", background_color='#242224'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
|
|
||||||
sg.Text("Min Frequency Cutoff", background_color='#424042'),
|
|
||||||
sg.InputText(
|
|
||||||
self.config.gui_min_cutoff,
|
|
||||||
key=self.gui_min_cutoff,
|
|
||||||
size=(0,10),
|
|
||||||
),
|
|
||||||
#],
|
|
||||||
#[
|
|
||||||
sg.Text("Speed Coefficient", background_color='#424042'),
|
|
||||||
sg.InputText(
|
|
||||||
self.config.gui_speed_coefficient,
|
|
||||||
key=self.gui_speed_coefficient,
|
|
||||||
size=(0,10),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
sg.Text("OSC Settings:", background_color='#242224'),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
sg.Text("Address:", background_color='#424042'),
|
|
||||||
sg.InputText(
|
|
||||||
self.config.gui_osc_address,
|
|
||||||
key=self.gui_osc_address,
|
|
||||||
size=(0,20),
|
|
||||||
tooltip = "IP address we send OSC data to.",
|
|
||||||
),
|
|
||||||
|
|
||||||
# ],
|
|
||||||
# [
|
|
||||||
sg.Text("Port:", background_color='#424042'),
|
|
||||||
sg.InputText(
|
|
||||||
self.config.gui_osc_port,
|
|
||||||
key=self.gui_osc_port,
|
|
||||||
size=(0,10),
|
|
||||||
tooltip = "OSC port we send data to.",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
sg.Text("Receive functions", background_color='#424042'),
|
|
||||||
sg.Checkbox(
|
|
||||||
"",
|
|
||||||
default=self.config.gui_ROSC,
|
|
||||||
key=self.gui_ROSC,
|
|
||||||
background_color='#424042',
|
|
||||||
size=(0,10),
|
|
||||||
tooltip = "Toggle OSC receive functions.",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
sg.Text("Receiver Port:", background_color='#424042'),
|
|
||||||
sg.InputText(
|
|
||||||
self.config.gui_osc_receiver_port,
|
|
||||||
key=self.gui_osc_receiver_port,
|
|
||||||
size=(0,10),
|
|
||||||
tooltip = "Port we receive OSC data from (used to recalibrate or recenter app from within VRChat.",
|
|
||||||
),
|
|
||||||
#],
|
|
||||||
# [
|
|
||||||
sg.Text("Recenter Address:", background_color='#424042'),
|
|
||||||
sg.InputText(
|
|
||||||
self.config.gui_osc_recenter_address,
|
|
||||||
key=self.gui_osc_recenter_address,
|
|
||||||
size=(0,10),
|
|
||||||
tooltip = "OSC Address used for recentering your eye.",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
sg.Text("Recalibrate Address:", background_color='#424042'),
|
|
||||||
sg.InputText(
|
|
||||||
self.config.gui_osc_recalibrate_address,
|
|
||||||
key=self.gui_osc_recalibrate_address,
|
|
||||||
size=(0,10),
|
|
||||||
tooltip = "OSC address we use for recalibrating your eye",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
|
for module in self.initialized_modules:
|
||||||
|
self.general_settings_layout.extend(module.get_layout())
|
||||||
|
|
||||||
self.widget_layout = [
|
self.widget_layout = [
|
||||||
[
|
[
|
||||||
sg.Text("General Settings:", background_color='#242224'),
|
sg.Text("General Settings:", background_color="#242224"),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
sg.Column(self.general_settings_layout, key=self.gui_general_settings_layout, background_color='#424042' ),
|
sg.Column(
|
||||||
|
self.general_settings_layout,
|
||||||
|
key=self.gui_general_settings_layout,
|
||||||
|
background_color="#424042",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
self.cancellation_event = Event() # Set the event until start is called, otherwise we can block if shutdown is called.
|
self.cancellation_event = (
|
||||||
|
Event()
|
||||||
|
) # Set the event until start is called, otherwise we can block if shutdown is called.
|
||||||
self.cancellation_event.set()
|
self.cancellation_event.set()
|
||||||
self.image_queue = Queue()
|
|
||||||
|
|
||||||
|
|
||||||
def started(self):
|
def started(self):
|
||||||
return not self.cancellation_event.is_set()
|
return not self.cancellation_event.is_set()
|
||||||
@ -212,81 +65,38 @@ class SettingsWidget:
|
|||||||
return
|
return
|
||||||
self.cancellation_event.set()
|
self.cancellation_event.set()
|
||||||
|
|
||||||
|
def _update_and_save_config(self, validated_data: dict):
|
||||||
|
self.main_config.update(validated_data, save=True)
|
||||||
|
self.is_saving = False
|
||||||
|
|
||||||
|
def _handle_errors(self, errors):
|
||||||
|
print(errors)
|
||||||
|
|
||||||
def render(self, window, event, values):
|
def render(self, window, event, values):
|
||||||
# If anything has changed in our configuration settings, change/update those.
|
validated_data, errors = {}, []
|
||||||
changed = False
|
# we might want to think about event driven architecture here eventually, validate only
|
||||||
|
# if anything changes instead of checking for changes
|
||||||
|
for module in self.initialized_modules:
|
||||||
|
module_validated_data = module.validate(values)
|
||||||
|
if module_validated_data.changes:
|
||||||
|
validated_data.update(module_validated_data.changes)
|
||||||
|
if module_validated_data.errors:
|
||||||
|
errors.append(module_validated_data.errors)
|
||||||
|
|
||||||
if self.config.gui_osc_port != int(values[self.gui_osc_port]):
|
if not errors and validated_data and not self.is_saving:
|
||||||
print(self.config.gui_osc_port, values[self.gui_osc_port])
|
self.is_saving = True
|
||||||
try:
|
self._update_and_save_config(validated_data)
|
||||||
int(values[self.gui_osc_port])
|
|
||||||
if len(values[self.gui_osc_port]) <= 5:
|
|
||||||
self.config.gui_osc_port = int(values[self.gui_osc_port])
|
|
||||||
changed = True
|
|
||||||
else:
|
|
||||||
print("\033[91m[ERROR] OSC port value must be an integer 0-65535\033[0m")
|
|
||||||
except:
|
|
||||||
print("\033[91m[ERROR] OSC port value must be an integer 0-65535\033[0m")
|
|
||||||
|
|
||||||
if self.config.gui_osc_receiver_port != int(values[self.gui_osc_receiver_port]):
|
if errors:
|
||||||
try:
|
self._handle_errors(errors)
|
||||||
int(values[self.gui_osc_receiver_port])
|
|
||||||
if len(values[self.gui_osc_receiver_port]) <= 5:
|
|
||||||
self.config.gui_osc_receiver_port = int(values[self.gui_osc_receiver_port])
|
|
||||||
changed = True
|
|
||||||
else:
|
|
||||||
print("\033[91m[ERROR] OSC receive port value must be an integer 0-65535\033[0m")
|
|
||||||
except:
|
|
||||||
print("\033[91m[ERROR] OSC receive port value must be an integer 0-65535\033[0m")
|
|
||||||
|
|
||||||
if self.config.gui_osc_address != values[self.gui_osc_address]:
|
def _initialize_modules(self, settings_modules, widget_id):
|
||||||
self.config.gui_osc_address = values[self.gui_osc_address]
|
return [
|
||||||
changed = True
|
module(
|
||||||
|
config=self.config,
|
||||||
if self.config.gui_osc_recenter_address != values[self.gui_osc_recenter_address]:
|
settings=self.main_config,
|
||||||
self.config.gui_osc_recenter_address = values[self.gui_osc_recenter_address]
|
settings_base_class=EyeTrackSettingsConfig,
|
||||||
changed = True
|
widget_id=widget_id,
|
||||||
|
)
|
||||||
if self.config.gui_osc_recalibrate_address != values[self.gui_osc_recalibrate_address]:
|
for module in settings_modules
|
||||||
self.config.gui_osc_recalibrate_address = values[self.gui_osc_recalibrate_address]
|
]
|
||||||
changed = True
|
|
||||||
|
|
||||||
if self.config.gui_min_cutoff != values[self.gui_min_cutoff]:
|
|
||||||
self.config.gui_min_cutoff = values[self.gui_min_cutoff]
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if self.config.gui_speed_coefficient != values[self.gui_speed_coefficient]:
|
|
||||||
self.config.gui_speed_coefficient = values[self.gui_speed_coefficient]
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if self.config.gui_flip_x_axis_right != values[self.gui_flip_x_axis_right]:
|
|
||||||
self.config.gui_flip_x_axis_right = values[self.gui_flip_x_axis_right]
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if self.config.gui_flip_x_axis_left != values[self.gui_flip_x_axis_left]:
|
|
||||||
self.config.gui_flip_x_axis_left = values[self.gui_flip_x_axis_left]
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if self.config.gui_vrc_native != values[self.gui_vrc_native]:
|
|
||||||
self.config.gui_vrc_native = values[self.gui_vrc_native]
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if self.config.gui_update_check != values[self.gui_update_check]:
|
|
||||||
self.config.gui_update_check = values[self.gui_update_check]
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if self.config.gui_flip_y_axis != values[self.gui_flip_y_axis]:
|
|
||||||
self.config.gui_flip_y_axis = values[self.gui_flip_y_axis]
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if self.config.gui_eye_falloff != values[self.gui_eye_falloff]:
|
|
||||||
self.config.gui_eye_falloff = values[self.gui_eye_falloff]
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if self.config.gui_ROSC != values[self.gui_ROSC]:
|
|
||||||
self.config.gui_ROSC = values[self.gui_ROSC]
|
|
||||||
changed = True
|
|
||||||
|
|
||||||
if changed:
|
|
||||||
self.main_config.save()
|
|
||||||
self.osc_queue.put((EyeId.SETTINGS))
|
|
||||||
|
|||||||
1
EyeTrackApp/settings/constants.py
Normal file
1
EyeTrackApp/settings/constants.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
BACKGROUND_COLOR = "#424042"
|
||||||
73
EyeTrackApp/settings/modules/BaseModule.py
Normal file
73
EyeTrackApp/settings/modules/BaseModule.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
from typing import Iterable, Optional, NamedTuple, Any
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from config import EyeTrackSettingsConfig
|
||||||
|
|
||||||
|
|
||||||
|
class MissingValidationModelException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MissingValidationConfigException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BaseValidationModel(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationResult(NamedTuple):
|
||||||
|
changes: Optional[dict[str, str]]
|
||||||
|
errors: list[Any] # ErrorDict but we cannot import it, it's not exposed
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSettingsModule:
|
||||||
|
def __init__(self, config: EyeTrackSettingsConfig, settings_base_class, widget_id, **kwargs):
|
||||||
|
self.validation_model: BaseValidationModel = BaseValidationModel # noqa
|
||||||
|
self.config = config
|
||||||
|
self.settings_base_class = settings_base_class
|
||||||
|
self.widget_id = widget_id
|
||||||
|
|
||||||
|
def get_validation_model(self):
|
||||||
|
"""Return validation model, can be overridden for custom behaviour"""
|
||||||
|
return self.validation_model
|
||||||
|
|
||||||
|
def initialize_validation_model(self, values):
|
||||||
|
validation_model = self.get_validation_model()
|
||||||
|
if not validation_model:
|
||||||
|
raise MissingValidationModelException()
|
||||||
|
|
||||||
|
field_mapping = {}
|
||||||
|
for field in self.validation_model.schema().get("properties"):
|
||||||
|
field_mapping[field] = values[getattr(self, field)]
|
||||||
|
|
||||||
|
return validation_model(**field_mapping)
|
||||||
|
|
||||||
|
def validate(self, values, raise_exception=False) -> Optional[ValidationResult]:
|
||||||
|
"""Return """
|
||||||
|
if not self.config:
|
||||||
|
raise MissingValidationConfigException()
|
||||||
|
try:
|
||||||
|
changes = {}
|
||||||
|
validated_model = self.initialize_validation_model(values)
|
||||||
|
# this is empty
|
||||||
|
for field, value in validated_model.dict().items():
|
||||||
|
if getattr(self.config, field) != value:
|
||||||
|
changes[field] = value
|
||||||
|
|
||||||
|
return ValidationResult(changes, [])
|
||||||
|
except pydantic.ValidationError as e:
|
||||||
|
if not raise_exception:
|
||||||
|
return ValidationResult(None, e.errors())
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_layout(self) -> Iterable:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_defaults(self) -> dict:
|
||||||
|
defaults = {}
|
||||||
|
for field in self.validation_model.schema().keys():
|
||||||
|
defaults[field] = getattr(self.settings_base_class, field)
|
||||||
|
return defaults
|
||||||
76
EyeTrackApp/settings/modules/GeneralSettingsModule.py
Normal file
76
EyeTrackApp/settings/modules/GeneralSettingsModule.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
from config import EyeTrackSettingsConfig
|
||||||
|
from settings.modules.BaseModule import BaseSettingsModule, BaseValidationModel
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
|
||||||
|
class GeneralSettingsValidationModel(BaseValidationModel):
|
||||||
|
gui_flip_x_axis_left: bool
|
||||||
|
gui_flip_x_axis_right: bool
|
||||||
|
gui_flip_y_axis: bool
|
||||||
|
gui_vrc_native: bool
|
||||||
|
gui_eye_falloff: bool
|
||||||
|
gui_update_check: bool
|
||||||
|
|
||||||
|
|
||||||
|
class GeneralSettingsModule(BaseSettingsModule):
|
||||||
|
def __init__(self, config, widget_id, settings_base_class, **kwargs):
|
||||||
|
super().__init__(config, widget_id, settings_base_class, **kwargs)
|
||||||
|
self.validation_model = GeneralSettingsValidationModel
|
||||||
|
self.gui_flip_x_axis_left = f"-FLIPXAXISLEFT{widget_id}-"
|
||||||
|
self.gui_flip_x_axis_right = f"-FLIPXAXISRIGHT{widget_id}-"
|
||||||
|
self.gui_flip_y_axis = f"-FLIPYAXIS{widget_id}-"
|
||||||
|
self.gui_eye_falloff = f"-EYEFALLOFF{widget_id}-"
|
||||||
|
self.gui_vrc_native = f"-VRCNATIVE{widget_id}-"
|
||||||
|
self.gui_update_check = f"-UPDATECHECK{widget_id}-"
|
||||||
|
|
||||||
|
def get_layout(self):
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
sg.Checkbox(
|
||||||
|
"Flip Left Eye X Axis",
|
||||||
|
default=self.config.gui_flip_x_axis_left,
|
||||||
|
key=self.gui_flip_x_axis_left,
|
||||||
|
background_color="#424042",
|
||||||
|
tooltip="Flips the left eye's X axis.",
|
||||||
|
),
|
||||||
|
sg.Checkbox(
|
||||||
|
"Flip Right Eye X Axis",
|
||||||
|
default=self.config.gui_flip_x_axis_right,
|
||||||
|
key=self.gui_flip_x_axis_right,
|
||||||
|
background_color="#424042",
|
||||||
|
tooltip="Flips the right eye's X axis.",
|
||||||
|
),
|
||||||
|
sg.Checkbox(
|
||||||
|
"Flip Y Axis",
|
||||||
|
default=self.config.gui_flip_y_axis,
|
||||||
|
key=self.gui_flip_y_axis,
|
||||||
|
background_color="#424042",
|
||||||
|
tooltip="Flips the eye's Y axis.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Checkbox(
|
||||||
|
"VRC Native Eyetracking",
|
||||||
|
default=self.config.gui_vrc_native,
|
||||||
|
key=self.gui_vrc_native,
|
||||||
|
background_color="#424042",
|
||||||
|
tooltip="Toggle VRCFT output or VRC native",
|
||||||
|
),
|
||||||
|
sg.Checkbox(
|
||||||
|
"Dual Eye Falloff",
|
||||||
|
default=self.config.gui_eye_falloff,
|
||||||
|
key=self.gui_eye_falloff,
|
||||||
|
background_color="#424042",
|
||||||
|
tooltip="If one eye stops tracking, we send tracking data from your other eye.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Checkbox(
|
||||||
|
"Check For Updates",
|
||||||
|
default=self.config.gui_update_check,
|
||||||
|
key=self.gui_update_check,
|
||||||
|
background_color="#424042",
|
||||||
|
tooltip="Toggle update check on launch.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]
|
||||||
83
EyeTrackApp/settings/modules/OSCSettingsModule.py
Normal file
83
EyeTrackApp/settings/modules/OSCSettingsModule.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
from settings.modules.BaseModule import BaseSettingsModule, BaseValidationModel
|
||||||
|
from settings.constants import BACKGROUND_COLOR
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
|
||||||
|
class OSCValidationModel(BaseValidationModel):
|
||||||
|
gui_osc_port: int
|
||||||
|
gui_osc_address: str
|
||||||
|
gui_ROSC: bool
|
||||||
|
gui_osc_receiver_port: int
|
||||||
|
gui_osc_recenter_address: str
|
||||||
|
gui_osc_recalibrate_address: str
|
||||||
|
|
||||||
|
|
||||||
|
class OSCSettingsModule(BaseSettingsModule):
|
||||||
|
def __init__(self, config, widget_id, settings_base_class, **kwargs):
|
||||||
|
super().__init__(config, widget_id, settings_base_class, **kwargs)
|
||||||
|
self.validation_model = OSCValidationModel
|
||||||
|
self.gui_osc_address = f"-OSCADDRESS{widget_id}-"
|
||||||
|
self.gui_osc_port = f"-OSCPORT{widget_id}-"
|
||||||
|
self.gui_ROSC = f"-ROSC{widget_id}-"
|
||||||
|
self.gui_osc_receiver_port = f"OSCRECEIVERPORT{widget_id}-"
|
||||||
|
self.gui_osc_recenter_address = f"OSCRECENTERADDRESS{widget_id}-"
|
||||||
|
self.gui_osc_recalibrate_address = f"OSCRECALIBRATEADDRESS{widget_id}-"
|
||||||
|
|
||||||
|
def get_layout(self):
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
sg.Text("OSC Settings:", background_color="#242224"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Text("Address:", background_color=BACKGROUND_COLOR),
|
||||||
|
sg.InputText(
|
||||||
|
self.config.gui_osc_address,
|
||||||
|
key=self.gui_osc_address,
|
||||||
|
size=(0, 20),
|
||||||
|
tooltip="IP address we send OSC data to.",
|
||||||
|
),
|
||||||
|
sg.Text("Port:", background_color=BACKGROUND_COLOR),
|
||||||
|
sg.InputText(
|
||||||
|
self.config.gui_osc_port,
|
||||||
|
key=self.gui_osc_port,
|
||||||
|
size=(0, 10),
|
||||||
|
tooltip="OSC port we send data to.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Text("Receive functions", background_color=BACKGROUND_COLOR),
|
||||||
|
sg.Checkbox(
|
||||||
|
"",
|
||||||
|
default=self.config.gui_ROSC,
|
||||||
|
key=self.gui_ROSC,
|
||||||
|
background_color=BACKGROUND_COLOR,
|
||||||
|
size=(0, 10),
|
||||||
|
tooltip="Toggle OSC receive functions.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Text("Receiver Port:", background_color=BACKGROUND_COLOR),
|
||||||
|
sg.InputText(
|
||||||
|
self.config.gui_osc_receiver_port,
|
||||||
|
key=self.gui_osc_receiver_port,
|
||||||
|
size=(0, 10),
|
||||||
|
tooltip="Port we receive OSC data from (used to recalibrate or recenter app from within VRChat.",
|
||||||
|
),
|
||||||
|
sg.Text("Recenter Address:", background_color=BACKGROUND_COLOR),
|
||||||
|
sg.InputText(
|
||||||
|
self.config.gui_osc_recenter_address,
|
||||||
|
key=self.gui_osc_recenter_address,
|
||||||
|
size=(0, 10),
|
||||||
|
tooltip="OSC Address used for recentering your eye.",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Text("Recalibrate Address:", background_color=BACKGROUND_COLOR),
|
||||||
|
sg.InputText(
|
||||||
|
self.config.gui_osc_recalibrate_address,
|
||||||
|
key=self.gui_osc_recalibrate_address,
|
||||||
|
size=(0, 10),
|
||||||
|
tooltip="OSC address we use for recalibrating your eye",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]
|
||||||
37
EyeTrackApp/settings/modules/OneEuroSettingsModule.py
Normal file
37
EyeTrackApp/settings/modules/OneEuroSettingsModule.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from settings.modules.BaseModule import BaseSettingsModule, BaseValidationModel
|
||||||
|
from settings.constants import BACKGROUND_COLOR
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
|
||||||
|
class OneEuroFilterValidationModel(BaseValidationModel):
|
||||||
|
gui_speed_coefficient: str # GUI lib does not support doubles ;-;
|
||||||
|
gui_min_cutoff: str # or floats ;-;
|
||||||
|
|
||||||
|
|
||||||
|
class OneEuroSettingsModule(BaseSettingsModule):
|
||||||
|
def __init__(self, config, widget_id, settings_base_class, **kwargs):
|
||||||
|
super().__init__(config, widget_id, settings_base_class, **kwargs)
|
||||||
|
self.gui_speed_coefficient = f"-SPEEDCOEFFICIENT{widget_id}-"
|
||||||
|
self.gui_min_cutoff = f"-MINCUTOFF{widget_id}-"
|
||||||
|
self.validation_model = OneEuroFilterValidationModel
|
||||||
|
|
||||||
|
def get_layout(self):
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
sg.Text("One Euro Filter Paramaters:", background_color='#242224'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Text("Min Frequency Cutoff", background_color=BACKGROUND_COLOR),
|
||||||
|
sg.InputText(
|
||||||
|
self.config.gui_min_cutoff,
|
||||||
|
key=self.gui_min_cutoff,
|
||||||
|
size=(0, 10),
|
||||||
|
),
|
||||||
|
sg.Text("Speed Coefficient", background_color=BACKGROUND_COLOR),
|
||||||
|
sg.InputText(
|
||||||
|
self.config.gui_speed_coefficient,
|
||||||
|
key=self.gui_speed_coefficient,
|
||||||
|
size=(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]
|
||||||
Loading…
Reference in New Issue
Block a user