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
213 lines
6.8 KiB
Python
213 lines
6.8 KiB
Python
"""
|
|
------------------------------------------------------------------------------------------------------
|
|
|
|
,@@@@@@
|
|
@@@@@@@@@@@ @@@
|
|
@@@@@@@@@@@@ @@@@@@@@@@@
|
|
@@@@@@@@@@@@@ @@@@@@@@@@@@@@
|
|
@@@@@@@/ ,@@@@@@@@@@@@@
|
|
/@@@@@@@@@@@@@@@ @@@@@@@@
|
|
@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@
|
|
@@@@@@@@ @@@@@
|
|
,@@@ @@@@&
|
|
@@@@@@. @@@@
|
|
@@@ @@@@@@@@@/ @@@@@
|
|
,@@@. @@@@@@((@ @@@@(
|
|
//@@@ ,, @@@@ @@@@@
|
|
@@@( @@@@@@@
|
|
@@@ @ @@@@@@@@#
|
|
@@@@@@@@@@@@@@@@@
|
|
@@@@@@@@@@@@@(
|
|
|
|
Copyright (c) 2023 EyeTrackVR <3
|
|
LICENSE: GNU GPLv3
|
|
------------------------------------------------------------------------------------------------------
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import types
|
|
from collections import namedtuple
|
|
from typing import (
|
|
Any,
|
|
ClassVar,
|
|
Dict,
|
|
List,
|
|
TYPE_CHECKING,
|
|
Tuple,
|
|
Type,
|
|
TypeVar,
|
|
Iterator,
|
|
Mapping,
|
|
)
|
|
|
|
__all__ = (
|
|
"Enum",
|
|
"EyeLR",
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from typing_extensions import Self
|
|
|
|
|
|
def _create_value_cls(name: str, comparable: bool):
|
|
# All the type ignores here are due to the type checker being unable to recognise
|
|
# Runtime type creation without exploding.
|
|
cls = namedtuple("_EnumValue_" + name, "name value")
|
|
cls.__repr__ = lambda self: f"<{name}.{self.name}: {self.value!r}>" # type: ignore
|
|
cls.__str__ = lambda self: f"{name}.{self.name}" # type: ignore
|
|
if comparable:
|
|
cls.__le__ = lambda self, other: isinstance(other, self.__class__) and self.value <= other.value # type: ignore
|
|
cls.__ge__ = lambda self, other: isinstance(other, self.__class__) and self.value >= other.value # type: ignore
|
|
cls.__lt__ = lambda self, other: isinstance(other, self.__class__) and self.value < other.value # type: ignore
|
|
cls.__gt__ = lambda self, other: isinstance(other, self.__class__) and self.value > other.value # type: ignore
|
|
return cls
|
|
|
|
|
|
def _is_descriptor(obj):
|
|
return (
|
|
hasattr(obj, "__get__") or hasattr(obj, "__set__") or hasattr(obj, "__delete__")
|
|
)
|
|
|
|
|
|
class EnumMeta(type):
|
|
if TYPE_CHECKING:
|
|
__name__: ClassVar[str]
|
|
_enum_member_names_: ClassVar[List[str]]
|
|
_enum_member_map_: ClassVar[Dict[str, Any]]
|
|
_enum_value_map_: ClassVar[Dict[Any, Any]]
|
|
|
|
def __new__(
|
|
cls,
|
|
name: str,
|
|
bases: Tuple[type, ...],
|
|
attrs: Dict[str, Any],
|
|
*,
|
|
comparable: bool = False,
|
|
):
|
|
value_mapping = {}
|
|
member_mapping = {}
|
|
member_names = []
|
|
|
|
value_cls = _create_value_cls(name, comparable)
|
|
for key, value in list(attrs.items()):
|
|
is_descriptor = _is_descriptor(value)
|
|
if key[0] == "_" and not is_descriptor:
|
|
continue
|
|
|
|
# Special case classmethod to just pass through
|
|
if isinstance(value, classmethod):
|
|
continue
|
|
|
|
if is_descriptor:
|
|
setattr(value_cls, key, value)
|
|
del attrs[key]
|
|
continue
|
|
|
|
try:
|
|
new_value = value_mapping[value]
|
|
except KeyError:
|
|
new_value = value_cls(name=key, value=value)
|
|
value_mapping[value] = new_value
|
|
member_names.append(key)
|
|
|
|
member_mapping[key] = new_value
|
|
attrs[key] = new_value
|
|
|
|
attrs["_enum_value_map_"] = value_mapping
|
|
attrs["_enum_member_map_"] = member_mapping
|
|
attrs["_enum_member_names_"] = member_names
|
|
attrs["_enum_value_cls_"] = value_cls
|
|
actual_cls = super().__new__(cls, name, bases, attrs)
|
|
value_cls._actual_enum_cls_ = actual_cls # type: ignore # Runtime attribute isn't understood
|
|
return actual_cls
|
|
|
|
def __iter__(cls) -> Iterator[Any]:
|
|
return (cls._enum_member_map_[name] for name in cls._enum_member_names_)
|
|
|
|
def __reversed__(cls) -> Iterator[Any]:
|
|
return (
|
|
cls._enum_member_map_[name] for name in reversed(cls._enum_member_names_)
|
|
)
|
|
|
|
def __len__(cls) -> int:
|
|
return len(cls._enum_member_names_)
|
|
|
|
def __repr__(cls) -> str:
|
|
return f"<enum {cls.__name__}>"
|
|
|
|
@property
|
|
def __members__(cls) -> Mapping[str, Any]:
|
|
return types.MappingProxyType(cls._enum_member_map_)
|
|
|
|
def __call__(cls, value: str) -> Any:
|
|
try:
|
|
return cls._enum_value_map_[value]
|
|
except (KeyError, TypeError):
|
|
raise ValueError(f"{value!r} is not a valid {cls.__name__}")
|
|
|
|
def __getitem__(cls, key: str) -> Any:
|
|
return cls._enum_member_map_[key]
|
|
|
|
def __setattr__(cls, name: str, value: Any) -> None:
|
|
raise TypeError("Enums are immutable.")
|
|
|
|
def __delattr__(cls, attr: str) -> None:
|
|
raise TypeError("Enums are immutable")
|
|
|
|
def __instancecheck__(self, instance: Any) -> bool:
|
|
# isinstance(x, Y)
|
|
# -> __instancecheck__(Y, x)
|
|
try:
|
|
return instance._actual_enum_cls_ is self
|
|
except AttributeError:
|
|
return False
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from enum import Enum, IntEnum
|
|
else:
|
|
|
|
class Enum(metaclass=EnumMeta):
|
|
@classmethod
|
|
def try_value(cls, value):
|
|
try:
|
|
return cls._enum_value_map_[value]
|
|
except (KeyError, TypeError):
|
|
return value
|
|
|
|
|
|
E = TypeVar("E", bound="Enum")
|
|
|
|
|
|
def create_unknown_value(cls: Type[E], val: Any) -> E:
|
|
value_cls = cls._enum_value_cls_ # type: ignore # This is narrowed below
|
|
name = f"unknown_{val}"
|
|
return value_cls(name=name, value=val)
|
|
|
|
|
|
def try_enum(cls: Type[E], val: Any) -> E:
|
|
"""A function that tries to turn the value into enum ``cls``.
|
|
If it fails it returns a proxy invalid value instead.
|
|
"""
|
|
|
|
try:
|
|
return cls._enum_value_map_[val] # type: ignore # All errors are caught below
|
|
except (KeyError, TypeError, AttributeError):
|
|
return create_unknown_value(cls, val)
|
|
|
|
|
|
# The line above is based on the code in the following url
|
|
# https://github.com/Rapptz/discord.py/blob/f7e97954950ffb0e34238d70813454caa6f1a3ae/discord/enums.py
|
|
|
|
|
|
class EyeLR(Enum):
|
|
LEFT = 1
|
|
RIGHT = 2
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
def __int__(self) -> int:
|
|
return self.value
|