mirror of
https://github.com/EyeTrackVR/EyeTrackVR.git
synced 2025-09-26 23:09:28 +08:00

* 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
363 lines
14 KiB
Python
363 lines
14 KiB
Python
"""
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
,@@@@@@
|
|
@@@@@@@@@@@ @@@
|
|
@@@@@@@@@@@@ @@@@@@@@@@@
|
|
@@@@@@@@@@@@@ @@@@@@@@@@@@@@
|
|
@@@@@@@/ ,@@@@@@@@@@@@@
|
|
/@@@@@@@@@@@@@@@ @@@@@@@@
|
|
@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@
|
|
@@@@@@@@ @@@@@
|
|
,@@@ @@@@&
|
|
@@@@@@. @@@@
|
|
@@@ @@@@@@@@@/ @@@@@
|
|
,@@@. @@@@@@((@ @@@@(
|
|
//@@@ ,, @@@@ @@@@@
|
|
@@@( @@@@@@@
|
|
@@@ @ @@@@@@@@#
|
|
@@@@@@@@@@@@@@@@@
|
|
@@@@@@@@@@@@@(
|
|
|
|
Copyright (c) 2023 EyeTrackVR <3
|
|
LICENSE: GNU GPLv3
|
|
------------------------------------------------------------------------------------------------------
|
|
"""
|
|
|
|
import os
|
|
import PySimpleGUI as sg
|
|
import queue
|
|
import requests
|
|
import threading
|
|
from camera_widget import CameraWidget
|
|
from config import EyeTrackConfig
|
|
from eye import EyeId
|
|
from settings.VRCFTModuleSettings import VRCFTSettingsWidget
|
|
from settings.general_settings_widget import SettingsWidget
|
|
from settings.algo_settings_widget import AlgoSettingsWidget
|
|
from osc.osc import OSCManager
|
|
from osc.OSCMessage import OSCMessage
|
|
from utils.misc_utils import is_nt, resource_path
|
|
|
|
if is_nt:
|
|
from winotify import Notification
|
|
os.system("color") # init ANSI color
|
|
|
|
# Random environment variable to speed up webcam opening on the MSMF backend.
|
|
# https://github.com/opencv/opencv/issues/17687
|
|
os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0"
|
|
WINDOW_NAME = "EyeTrackApp"
|
|
RIGHT_EYE_NAME = "-RIGHTEYEWIDGET-"
|
|
LEFT_EYE_NAME = "-LEFTEYEWIDGET-"
|
|
SETTINGS_NAME = "-SETTINGSWIDGET-"
|
|
ALGO_SETTINGS_NAME = "-ALGOSETTINGSWIDGET-"
|
|
VRCFT_MODULE_SETTINGS_NAME = "-VRCFTSETTINGSWIDGET-"
|
|
LEFT_EYE_RADIO_NAME = "-LEFTEYERADIO-"
|
|
RIGHT_EYE_RADIO_NAME = "-RIGHTEYERADIO-"
|
|
BOTH_EYE_RADIO_NAME = "-BOTHEYERADIO-"
|
|
SETTINGS_RADIO_NAME = "-SETTINGSRADIO-"
|
|
ALGO_SETTINGS_RADIO_NAME = "-ALGOSETTINGSRADIO-"
|
|
VRCFT_MODULE_SETTINGS_RADIO_NAME = "-VRCFTSETTINGSRADIO-"
|
|
|
|
page_url = "https://github.com/RedHawk989/EyeTrackVR/releases/latest"
|
|
appversion = "EyeTrackApp 0.2.0 BETA 11"
|
|
|
|
|
|
def main():
|
|
# Get Configuration
|
|
config: EyeTrackConfig = EyeTrackConfig.load()
|
|
config.save()
|
|
|
|
cancellation_event = threading.Event()
|
|
# Check to see if we can connect to our video source first. If not, bring up camera finding
|
|
# dialog.
|
|
|
|
try:
|
|
if config.settings.gui_update_check:
|
|
response = requests.get("https://api.github.com/repos/EyeTrackVR/EyeTrackVR/releases/latest")
|
|
latestversion = response.json()["name"]
|
|
if (
|
|
appversion == latestversion
|
|
): # If what we scraped and hardcoded versions are same, assume we are up to date.
|
|
print(f"\033[92m[INFO] App is the latest version! [{latestversion}]\033[0m")
|
|
else:
|
|
print(
|
|
f"\033[93m[INFO] You have app version [{appversion}] installed. Please update to [{latestversion}] for the newest features.\033[0m"
|
|
)
|
|
try:
|
|
if is_nt:
|
|
cwd = os.getcwd()
|
|
# icon = cwd + "\Images\logo.ico"
|
|
icon = resource_path("Images/logo.ico")
|
|
toast = Notification(
|
|
app_id="EyeTrackApp",
|
|
title="New Update Available!",
|
|
msg=f"Please update to {latestversion}",
|
|
icon=r"{}".format(icon),
|
|
)
|
|
toast.add_actions(
|
|
label="Download Page",
|
|
launch="https://github.com/RedHawk989/EyeTrackVR/releases/latest",
|
|
)
|
|
toast.show()
|
|
except Exception as e:
|
|
print("[INFO] Toast notifications not supported")
|
|
except:
|
|
print("\033[91m[INFO] Could not check for updates. Please try again later.\033[0m")
|
|
|
|
osc_queue: queue.Queue[OSCMessage] = queue.Queue()
|
|
|
|
eyes = [
|
|
CameraWidget(EyeId.RIGHT, config, osc_queue),
|
|
CameraWidget(EyeId.LEFT, config, osc_queue),
|
|
]
|
|
|
|
settings = [
|
|
SettingsWidget(EyeId.SETTINGS, config),
|
|
AlgoSettingsWidget(EyeId.ALGOSETTINGS, config),
|
|
VRCFTSettingsWidget(EyeId.VRCFTMODULESETTINGS, config, osc_queue),
|
|
]
|
|
|
|
osc_manager = OSCManager(
|
|
osc_message_in_queue=osc_queue,
|
|
config=config,
|
|
)
|
|
config.register_listener_callback(osc_manager.update)
|
|
config.register_listener_callback(eyes[0].on_config_update)
|
|
config.register_listener_callback(eyes[1].on_config_update)
|
|
|
|
osc_manager.register_listeners(
|
|
config.settings.gui_osc_recenter_address,
|
|
[
|
|
eyes[0].recenter_eyes,
|
|
eyes[1].recenter_eyes,
|
|
],
|
|
)
|
|
osc_manager.register_listeners(
|
|
config.settings.gui_osc_recalibrate_address,
|
|
[
|
|
eyes[0].recalibrate_eyes,
|
|
eyes[1].recalibrate_eyes,
|
|
],
|
|
)
|
|
|
|
osc_manager.start()
|
|
|
|
layout = [
|
|
[
|
|
sg.Radio(
|
|
"Left Eye",
|
|
"EYESELECTRADIO",
|
|
background_color="#292929",
|
|
default=(config.eye_display_id == EyeId.LEFT),
|
|
key=LEFT_EYE_RADIO_NAME,
|
|
),
|
|
sg.Radio(
|
|
"Right Eye",
|
|
"EYESELECTRADIO",
|
|
background_color="#292929",
|
|
default=(config.eye_display_id == EyeId.RIGHT),
|
|
key=RIGHT_EYE_RADIO_NAME,
|
|
),
|
|
sg.Radio(
|
|
"Both Eyes",
|
|
"EYESELECTRADIO",
|
|
background_color="#292929",
|
|
default=(config.eye_display_id == EyeId.BOTH),
|
|
key=BOTH_EYE_RADIO_NAME,
|
|
),
|
|
sg.Radio(
|
|
"Settings",
|
|
"EYESELECTRADIO",
|
|
background_color="#292929",
|
|
default=(config.eye_display_id == EyeId.SETTINGS),
|
|
key=SETTINGS_RADIO_NAME,
|
|
),
|
|
sg.Radio(
|
|
"Algo Settings",
|
|
"EYESELECTRADIO",
|
|
background_color="#292929",
|
|
default=(config.eye_display_id == EyeId.ALGOSETTINGS),
|
|
key=ALGO_SETTINGS_RADIO_NAME,
|
|
),
|
|
sg.Radio(
|
|
"VRCFT Module Settings",
|
|
"EYESELECTRADIO",
|
|
background_color="#292929",
|
|
default=(config.eye_display_id == EyeId.VRCFTMODULESETTINGS),
|
|
key=VRCFT_MODULE_SETTINGS_RADIO_NAME,
|
|
),
|
|
],
|
|
[
|
|
sg.Column(
|
|
eyes[1].widget_layout,
|
|
vertical_alignment="top",
|
|
key=LEFT_EYE_NAME,
|
|
visible=(config.eye_display_id in [EyeId.LEFT, EyeId.BOTH]),
|
|
background_color="#424042",
|
|
),
|
|
sg.Column(
|
|
eyes[0].widget_layout,
|
|
vertical_alignment="top",
|
|
key=RIGHT_EYE_NAME,
|
|
visible=(config.eye_display_id in [EyeId.RIGHT, EyeId.BOTH]),
|
|
background_color="#424042",
|
|
),
|
|
sg.Column(
|
|
settings[0].get_layout(),
|
|
vertical_alignment="top",
|
|
key=SETTINGS_NAME,
|
|
visible=(config.eye_display_id in [EyeId.SETTINGS]),
|
|
background_color="#424042",
|
|
),
|
|
sg.Column(
|
|
settings[1].get_layout(),
|
|
vertical_alignment="top",
|
|
key=ALGO_SETTINGS_NAME,
|
|
visible=(config.eye_display_id in [EyeId.ALGOSETTINGS]),
|
|
background_color="#424042",
|
|
),
|
|
sg.Column(
|
|
settings[2].get_layout(),
|
|
vertical_alignment="top",
|
|
key=VRCFT_MODULE_SETTINGS_NAME,
|
|
visible=(config.eye_display_id in [EyeId.VRCFTMODULESETTINGS]),
|
|
background_color="#424042",
|
|
),
|
|
],
|
|
]
|
|
|
|
if config.eye_display_id in [EyeId.LEFT, EyeId.BOTH]:
|
|
eyes[1].start()
|
|
if config.eye_display_id in [EyeId.RIGHT, EyeId.BOTH]:
|
|
eyes[0].start()
|
|
if config.eye_display_id in [EyeId.SETTINGS]:
|
|
settings[0].start()
|
|
if config.eye_display_id in [EyeId.ALGOSETTINGS]:
|
|
settings[1].start()
|
|
if config.eye_display_id in [EyeId.VRCFTMODULESETTINGS]:
|
|
settings[2].start()
|
|
|
|
# the eye's needs to be running before it is passed to the OSC
|
|
# Create the window
|
|
window = sg.Window(
|
|
f"{appversion}",
|
|
layout,
|
|
icon=resource_path("Images/logo.ico"),
|
|
background_color="#292929",
|
|
)
|
|
|
|
# GUI Render loop
|
|
while True:
|
|
# First off, check for any events from the GUI
|
|
event, values = window.read(timeout=1)
|
|
|
|
# If we're in either mode and someone hits q, quit immediately
|
|
if event == "Exit" or event == sg.WIN_CLOSED:
|
|
for eye in eyes:
|
|
eye.stop()
|
|
cancellation_event.set()
|
|
osc_manager.shutdown()
|
|
print("\033[94m[INFO] Exiting EyeTrackApp\033[0m")
|
|
return
|
|
|
|
if values[RIGHT_EYE_RADIO_NAME] and config.eye_display_id != EyeId.RIGHT:
|
|
eyes[0].start()
|
|
eyes[1].stop()
|
|
settings[0].stop()
|
|
settings[1].stop()
|
|
settings[2].stop()
|
|
window[RIGHT_EYE_NAME].update(visible=True)
|
|
window[LEFT_EYE_NAME].update(visible=False)
|
|
window[SETTINGS_NAME].update(visible=False)
|
|
window[VRCFT_MODULE_SETTINGS_NAME].update(visible=False)
|
|
window[ALGO_SETTINGS_NAME].update(visible=False)
|
|
config.eye_display_id = EyeId.RIGHT
|
|
config.settings.tracker_single_eye = 2
|
|
config.save()
|
|
|
|
elif values[LEFT_EYE_RADIO_NAME] and config.eye_display_id != EyeId.LEFT:
|
|
settings[0].stop()
|
|
settings[1].stop()
|
|
settings[2].stop()
|
|
eyes[0].stop()
|
|
eyes[1].start()
|
|
window[RIGHT_EYE_NAME].update(visible=False)
|
|
window[LEFT_EYE_NAME].update(visible=True)
|
|
window[SETTINGS_NAME].update(visible=False)
|
|
window[VRCFT_MODULE_SETTINGS_NAME].update(visible=False)
|
|
window[ALGO_SETTINGS_NAME].update(visible=False)
|
|
config.eye_display_id = EyeId.LEFT
|
|
config.settings.tracker_single_eye = 1
|
|
config.save()
|
|
|
|
elif values[BOTH_EYE_RADIO_NAME] and config.eye_display_id != EyeId.BOTH:
|
|
settings[0].stop()
|
|
settings[1].stop()
|
|
settings[2].stop()
|
|
eyes[1].start()
|
|
eyes[0].start()
|
|
window[LEFT_EYE_NAME].update(visible=True)
|
|
window[RIGHT_EYE_NAME].update(visible=True)
|
|
window[SETTINGS_NAME].update(visible=False)
|
|
window[VRCFT_MODULE_SETTINGS_NAME].update(visible=False)
|
|
window[ALGO_SETTINGS_NAME].update(visible=False)
|
|
config.eye_display_id = EyeId.BOTH
|
|
config.settings.tracker_single_eye = 0
|
|
config.save()
|
|
|
|
elif values[SETTINGS_RADIO_NAME] and config.eye_display_id != EyeId.SETTINGS:
|
|
eyes[0].stop()
|
|
eyes[1].stop()
|
|
settings[1].stop()
|
|
settings[0].start()
|
|
settings[2].stop()
|
|
window[RIGHT_EYE_NAME].update(visible=False)
|
|
window[LEFT_EYE_NAME].update(visible=False)
|
|
window[SETTINGS_NAME].update(visible=True)
|
|
window[VRCFT_MODULE_SETTINGS_NAME].update(visible=False)
|
|
window[ALGO_SETTINGS_NAME].update(visible=False)
|
|
config.eye_display_id = EyeId.SETTINGS
|
|
config.save()
|
|
|
|
elif values[ALGO_SETTINGS_RADIO_NAME] and config.eye_display_id != EyeId.ALGOSETTINGS:
|
|
eyes[0].stop()
|
|
eyes[1].stop()
|
|
settings[0].stop()
|
|
settings[1].start()
|
|
settings[2].stop()
|
|
window[RIGHT_EYE_NAME].update(visible=False)
|
|
window[LEFT_EYE_NAME].update(visible=False)
|
|
window[SETTINGS_NAME].update(visible=False)
|
|
window[VRCFT_MODULE_SETTINGS_NAME].update(visible=False)
|
|
window[ALGO_SETTINGS_NAME].update(visible=True)
|
|
config.eye_display_id = EyeId.ALGOSETTINGS
|
|
config.save()
|
|
|
|
elif values[VRCFT_MODULE_SETTINGS_RADIO_NAME] and config.eye_display_id != EyeId.VRCFTMODULESETTINGS:
|
|
eyes[0].stop()
|
|
eyes[1].stop()
|
|
settings[0].stop()
|
|
settings[1].stop()
|
|
settings[2].start()
|
|
window[RIGHT_EYE_NAME].update(visible=False)
|
|
window[LEFT_EYE_NAME].update(visible=False)
|
|
window[SETTINGS_NAME].update(visible=False)
|
|
window[VRCFT_MODULE_SETTINGS_NAME].update(visible=True)
|
|
window[ALGO_SETTINGS_NAME].update(visible=False)
|
|
config.eye_display_id = EyeId.VRCFTMODULESETTINGS
|
|
config.save()
|
|
|
|
else:
|
|
# Otherwise, render all
|
|
for eye in eyes:
|
|
if eye.started():
|
|
eye.render(window, event, values)
|
|
for setting in settings:
|
|
if setting.started():
|
|
setting.render(window, event, values)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|