EyeTrackVR/EyeTrackApp/settings/modules/VRCFTSettingsModule.py
Lorow 7e41a65aac
Feature/etvr module support (#106)
* initial changes

* Mostly clean up, refactor registering listeners to make sense, backport tests

* Add initial implementation of VRCFTModuleSender

* Add basic GUI for the modules settings

* Fix tooltip descriptions

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* Fix type validation bugs, fix typos

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* Add checkbox to switch to ETVR Module

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* Black stuff

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* Remove coverage by default

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* Fix timeout in tests

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* HEAVY WIP: Refactor native output,

NOTE:

I brought back the entire old OSC implementation as a live reference, this will be removed once I'm done.

This also lays ground for other modes as they're pretty similar

# TODO:
# - there's ghosts in the machine - vrc osc is not working properly
# - min/maxing will require field combinators in the modules lmao

* HEAVY WIP: Refactor v1 params output,

# TODO:
# - min/maxing will require field combinators in the modules lmao

* HEAVY WIP: Refactor v2 params output

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Finish refactoring v2 and v1, fixup tests, refactor native

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Add tests for v1 params

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Add tests for native params

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Fix OSC not getting up after config reset. Remove reset command, config sends everything changed anyway, sunset the idea of using single client and thus simplify the code a bit

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Rename gui_PortNumber to gui_VRCFTModulePort for readability

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Cleanup EyeID usage

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Cleanup osc after rebase

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Make VRChatOSCSender a bit more readable

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Remove unsued VRChatOSCReceiver, this is taken care of by generic OSCReceiver

# TODO:
# - min/maxing will require field combinators in the modules lmao

* Commit crimes with try_convert_to_float to make osc, pysimplegui and pydantic happy

* Cleanup after merge

* Disable emulation by default

* Fix OSCReceiver crashing on unknown addresses

* Adjust VRCFT Module settings to look better in game

* Fix recalibrate and recenter for OSC only working for the right eye

* Fix save and restart button not restarting the tracking

* Fix broken tracking on v1 params for eye_x, clean up implementation

* Fix regular value being passed to OSC listeners instead of OSCMessage

* Add a TODO, probably to be ignored

* Add support for custom ETVR VRCFT Module listening address
2024-07-04 20:51:19 +02:00

226 lines
10 KiB
Python

from typing import Iterable
import PySimpleGUI as sg
from pydantic import AfterValidator
from typing_extensions import Annotated
from settings.modules.BaseModule import BaseSettingsModule, BaseValidationModel
from settings.modules.CommonFieldValidators import check_is_ip_address, try_convert_to_float
class VRCFTSettingsModuleValidationModel(BaseValidationModel):
gui_VRCFTModulePort: int
gui_VRCFTModuleIPAddress: Annotated[str, AfterValidator(check_is_ip_address)]
gui_ShouldEmulateEyeWiden: bool
gui_ShouldEmulateEyeSquint: bool
gui_ShouldEmulateEyebrows: bool
gui_WidenThresholdV1_min: float
gui_WidenThresholdV1_max: float
gui_WidenThresholdV2_min: float
gui_WidenThresholdV2_max: float
gui_SqueezeThresholdV1_min: float
gui_SqueezeThresholdV1_max: float
gui_SqueezeThresholdV2_min: float
gui_SqueezeThresholdV2_max: float
gui_EyebrowThresholdRising: float
gui_EyebrowThresholdLowering: float
# this is a hack. I don't like it, but that's what I gotta do to make both, Pydantic and PySimpleGUI happy
gui_OutputMultiplier: Annotated[float, AfterValidator(try_convert_to_float)]
class VRCFTSettingsModule(BaseSettingsModule):
def __init__(self, config, widget_id, **kwargs):
super().__init__(config=config, widget_id=widget_id, **kwargs)
self.validation_model = VRCFTSettingsModuleValidationModel
self.gui_VRCFTModulePort = f"-VRCFTSETTINGSPORTNUMBER{widget_id}"
self.gui_VRCFTModuleIPAddress = f"-VRCFTSETTINGSIPNUMBER{widget_id}"
self.gui_ShouldEmulateEyeWiden = f"-VRCFTSETTINGSEMULATEWIDEN{widget_id}"
self.gui_ShouldEmulateEyeSquint = f"-VRCFTSETTINGSEMULATEEYEWIDEN{widget_id}"
self.gui_ShouldEmulateEyebrows = f"-VRCFTSETTINGSEMULATEEYEBROWS{widget_id}"
self.gui_WidenThresholdV1_min = f"-VRCFTSETTINGSWIDENTHRESHOLDV1MIN{widget_id}"
self.gui_WidenThresholdV1_max = f"-VRCFTSETTINGSWIDENTHRESHOLDV1MAX{widget_id}"
self.gui_WidenThresholdV2_min = f"-VRCFTSETTINGSWIDENTHRESHOLDV2MIN{widget_id}"
self.gui_WidenThresholdV2_max = f"-VRCFTSETTINGSWIDENTHRESHOLDV2MAX{widget_id}"
self.gui_SqueezeThresholdV1_min = f"-VRCFTSETTINGSSQUEEZETHRESHOLDV1MIN{widget_id}"
self.gui_SqueezeThresholdV1_max = f"-VRCFTSETTINGSSQUEEZETHRESHOLDV1MAX{widget_id}"
self.gui_SqueezeThresholdV2_min = f"-VRCFTSETTINGSSQUEEZETHRESHOLDV2MIN{widget_id}"
self.gui_SqueezeThresholdV2_max = f"-VRCFTSETTINGSSQUEEZETHRESHOLDV2MAX{widget_id}"
self.gui_EyebrowThresholdRising = f"-VRCFTSETTINGSEYEBROWTHRESHOLDRISING{widget_id}"
self.gui_EyebrowThresholdLowering = f"-VRCFTSETTINGSEYEBROWTHRESHOLDLOWERING{widget_id}"
self.gui_OutputMultiplier = f"-VRCFTSETTINGSOUTPUTMULTIPLIER{widget_id}"
def get_layout(self) -> Iterable:
return [
[
sg.Text("Emulation selection:", background_color="#242224"),
],
[
sg.Checkbox(
"Emulate Eye Widen",
default=self.config.gui_ShouldEmulateEyeWiden,
key=self.gui_ShouldEmulateEyeWiden,
background_color="#424042",
),
sg.Checkbox(
"Emulate Eye Squint",
default=self.config.gui_ShouldEmulateEyeSquint,
key=self.gui_ShouldEmulateEyeSquint,
background_color="#424042",
),
sg.Checkbox(
"Emulate Eyebrows",
default=self.config.gui_ShouldEmulateEyebrows,
key=self.gui_ShouldEmulateEyebrows,
background_color="#424042",
),
],
[
sg.Text("General Module Settings:", background_color="#242224"),
],
[
sg.Text("VRCFT Module listening IP", background_color="#242224"),
sg.InputText(
self.config.gui_VRCFTModuleIPAddress,
key=self.gui_VRCFTModuleIPAddress,
size=(0, 10),
tooltip="Ip on which the module should listen.",
),
sg.Text("port", background_color="#242224"),
sg.InputText(
self.config.gui_VRCFTModulePort,
key=self.gui_VRCFTModulePort,
size=(0, 10),
tooltip="UDP port on which the module should listen.",
),
],
[
sg.Text("VRCFT Module output multiplier", background_color="#242224"),
sg.InputText(
self.config.gui_OutputMultiplier,
key=self.gui_OutputMultiplier,
size=(0, 10),
tooltip="Output multiplier adjusts the output by the given amount",
),
],
[
sg.Text("Eye Widen thresholds:", background_color="#424042"),
],
[
sg.Text("V1 Min:", background_color="#424042"),
sg.Slider(
range=(0, 1),
resolution=0.01,
default_value=self.config.gui_WidenThresholdV1_min,
orientation="h",
key=self.gui_WidenThresholdV1_min,
background_color="#424042",
tooltip="Controls the point at which the emulation should start for v1 params, reacts to openness",
),
sg.Text("V1 Max:", background_color="#424042"),
sg.Slider(
range=(0, 2),
resolution=0.01,
default_value=self.config.gui_WidenThresholdV1_max,
orientation="h",
key=self.gui_WidenThresholdV1_max,
background_color="#424042",
tooltip="Controls the maximum range of widen emulation",
),
],
[
sg.Text("V2 Min:", background_color="#424042"),
sg.Slider(
range=(0, 2),
resolution=0.01,
default_value=self.config.gui_WidenThresholdV2_min,
orientation="h",
key=self.gui_WidenThresholdV2_min,
background_color="#424042",
tooltip="Controls the point at which the emulation should start for v2 params, reacts to openness",
),
sg.Text("V2 Max:", background_color="#424042"),
sg.Slider(
range=(0, 2),
resolution=0.01,
default_value=self.config.gui_WidenThresholdV2_max,
orientation="h",
key=self.gui_WidenThresholdV2_max,
background_color="#424042",
tooltip="Controls the maximum range of widen emulation",
),
],
[
sg.Text("Eye Squeeze thresholds:", background_color="#424042"),
],
[
sg.Text("V1 Min:", background_color="#424042"),
sg.Slider(
range=(0, 1),
resolution=0.01,
default_value=self.config.gui_SqueezeThresholdV1_min,
orientation="h",
key=self.gui_SqueezeThresholdV1_min,
background_color="#424042",
tooltip="Controls the point at which the emulation should start for v1 params, reacts to openness",
),
sg.Text("V1 Max:", background_color="#424042"),
sg.Slider(
range=(0, 2),
resolution=0.01,
default_value=self.config.gui_SqueezeThresholdV1_max,
orientation="h",
key=self.gui_SqueezeThresholdV1_max,
background_color="#424042",
tooltip="Controls the maximum range of squeeze emulation",
),
],
[
sg.Text("V2 Min:", background_color="#424042"),
sg.Slider(
range=(0, 1),
resolution=0.01,
default_value=self.config.gui_SqueezeThresholdV2_min,
orientation="h",
key=self.gui_SqueezeThresholdV2_min,
background_color="#424042",
tooltip="Controls the point at which the emulation should start for v2 params, reacts to openness",
),
sg.Text("V2 Max:", background_color="#424042"),
sg.Slider(
range=(-2, 0),
resolution=0.01,
default_value=self.config.gui_SqueezeThresholdV2_max,
orientation="h",
key=self.gui_SqueezeThresholdV2_max,
background_color="#424042",
tooltip="Controls the maximum range of squeeze emulation",
),
],
[
sg.Text("Eyebrow emulation Thresholds:", background_color="#424042"),
],
[
sg.Text("Rising:", background_color="#424042"),
sg.Slider(
range=(0, 1),
resolution=0.01,
default_value=self.config.gui_EyebrowThresholdRising,
orientation="h",
key=self.gui_EyebrowThresholdRising,
background_color="#424042",
tooltip="Controls the point at which the emulation should start, reacts to openness",
),
sg.Text("Lowering:", background_color="#424042"),
sg.Slider(
range=(0, 2),
resolution=0.01,
default_value=self.config.gui_EyebrowThresholdLowering,
orientation="h",
key=self.gui_EyebrowThresholdLowering,
background_color="#424042",
tooltip="Controls the maximum range of eyebrows emulation",
),
],
]