diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e21e0c6..f7fa384 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: pip install -r requirements.txt - name: Build with pyinstaller - run: pyinstaller --hidden-import=clr --add-binary "./owo/OWO.dll;owo" --onefile --distpath ./build --name=vrc-owo-suit main.py + run: pyinstaller --hidden-import=clr --add-data "./img/logo.png;img" --add-binary "./owo/OWO.dll;owo" --onefile --distpath ./build --name=vrc-owo-suit main.py - name: Deploy EXE uses: actions/upload-artifact@v3 diff --git a/config.py b/config.py index 8b1432a..6356c95 100644 --- a/config.py +++ b/config.py @@ -1,36 +1,63 @@ import json +import params +import os +import json class Config: - def __init__(self, server_port: int, owo_ip: str, intensity: int, frequency: int): - self.server_port = server_port - self.owo_ip = owo_ip - self.intensity = intensity - self.frequency = frequency + def __init__(self): + self.APP_NAME = 'VRChatOWOSuit' + self.default_config = { + "server_port": 9001, + "owo_ip": "", + "should_detect_ip": True, + "should_connect_on_startup": False, + "frequency": 100, + "intensities": { + params.owo_suit_Pectoral_L: 60, + params.owo_suit_Pectoral_R: 60, + params.owo_suit_Abdominal_L: 60, + params.owo_suit_Abdominal_R: 60, + params.owo_suit_Arm_L: 60, + params.owo_suit_Arm_R: 60, + params.owo_suit_Dorsal_L: 60, + params.owo_suit_Dorsal_R: 60, + params.owo_suit_Lumbar_L: 60, + params.owo_suit_Lumbar_R: 60, + } + } + self.current_config = None + + def get_by_key(self, key: str): + return self.current_config.get(key) + + def update(self, key: str, nextValue): + if (key in self.current_config): + self.current_config[key] = nextValue + + def read_config_from_disk(self): + appdata_path = os.environ.get('LOCALAPPDATA') + app_directory = os.path.join(appdata_path, self.APP_NAME) + os.makedirs(app_directory, exist_ok=True) + config_path = os.path.join(app_directory, 'config.json') + if os.path.exists(config_path): + with open(config_path, 'r') as file: + data = json.load(file) + return data + else: + with open(config_path, 'w') as file: + json.dump(self.default_config, file, indent=4) + return self.default_config + def write_config_to_disk(self): + if self.current_config == None: + return + appdata_path = os.environ.get('LOCALAPPDATA') + app_directory = os.path.join(appdata_path, self.APP_NAME) + os.makedirs(app_directory, exist_ok=True) + config_path = os.path.join(app_directory, 'config.json') + with open(config_path, 'w') as file: + json.dump(self.current_config, file, indent=4) -def get() -> Config: - server_port = 9001 - owo_ip = "" - intensity = 10 - frequency = 100 - try: - f = open('./vrc-owo-suit.config.json') - data = json.load(f) - f.close() - if ("server_port" in data and type(data['server_port']) is int): - print(f"Using server_port {data['server_port']}.") - server_port = data['server_port'] - if ("owo_ip" in data and type(data['owo_ip']) is str): - print(f"Using owo_ip {data['owo_ip']}.") - owo_ip = data['owo_ip'] - if ("intensity" in data and type(data['intensity']) is int): - print(f"Using intensity {data['intensity']}.") - intensity = data['intensity'] - if ("frequency" in data and type(data['frequency']) is int): - print(f"Using frequency {data['frequency']}.") - frequency = data['frequency'] - except: - print("Config file not found, using default settings") - finally: - return Config(server_port, owo_ip, intensity, frequency) + def init(self): + self.current_config = self.read_config_from_disk() diff --git a/event.py b/event.py new file mode 100644 index 0000000..07b24d4 --- /dev/null +++ b/event.py @@ -0,0 +1,19 @@ +import atexit +from concurrent.futures import ThreadPoolExecutor + + +class Event: + def __init__(self, max_workers=3): + self.listeners = [] + self.executor = ThreadPoolExecutor(max_workers=max_workers) + atexit.register(self.executor.shutdown) + + def add_listener(self, func): + self.listeners.append(func) + + def remove_listener(self, func): + self.listeners.remove(func) + + def dispatch(self, *args, **kwargs): + for listener in self.listeners: + self.executor.submit(listener, *args, **kwargs) diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..bf80fdd --- /dev/null +++ b/gui.py @@ -0,0 +1,298 @@ +import params +import config +import webbrowser +from event import Event +import dearpygui.dearpygui as dpg +from enum import Enum, auto + + +class Element(Enum): + IP_ADDRESS_INPUT = auto() + DETECT_IP_ADDRESS_CHECKBOX = auto() + SERVER_PORT_NUMBER_INPUT = auto() + FREQUENCY_SETTING_SLIDER = auto() + LEFT_PECTORAL_SETTING_SLIDER = auto() + RIGHT_PECTORAL_SETTING_SLIDER = auto() + LEFT_ABDOMINAL_SETTING_SLIDER = auto() + RIGHT_ABDOMINAL_SETTING_SLIDER = auto() + LEFT_ARM_SETTING_SLIDER = auto() + RIGHT_ARM_SETTING_SLIDER = auto() + LEFT_DORSAL_SETTING_SLIDER = auto() + RIGHT_DORSAL_SETTING_SLIDER = auto() + LEFT_LUMBAR_SETTING_SLIDER = auto() + RIGHT_LUMBAR_SETTING_SLIDER = auto() + TERMINAL_WINDOW_INPUT = auto() + CONNECT_BUTTON = auto() + SAVE_SETTINGS_BUTTON = auto() + CLEAR_CONSOLE_BUTTON = auto() + TOGGLES_INTERACTIONS_BUTTON = auto() + CONNECT_ON_STARTUP_CHECKBOX = auto() + CONTRIBUTE_BUTTON = auto() + + +class Gui: + def __init__(self, config: config.Config, window_width: int, window_height: int, logo_path: str): + self.config = config + self.sliders = [] + self.window_width = window_width + self.window_height = window_height + self.logo_path = logo_path + self.on_connect_clicked = Event() + self.on_save_settings_clicked = Event() + self.on_clear_console_clicked = Event() + self.on_toggle_interaction_clicked = Event() + self.elements = { + Element.IP_ADDRESS_INPUT: None, + Element.DETECT_IP_ADDRESS_CHECKBOX: None, + Element.SERVER_PORT_NUMBER_INPUT: None, + Element.FREQUENCY_SETTING_SLIDER: None, + Element.LEFT_PECTORAL_SETTING_SLIDER: None, + Element.RIGHT_PECTORAL_SETTING_SLIDER: None, + Element.LEFT_ABDOMINAL_SETTING_SLIDER: None, + Element.RIGHT_ABDOMINAL_SETTING_SLIDER: None, + Element.LEFT_ARM_SETTING_SLIDER: None, + Element.RIGHT_ARM_SETTING_SLIDER: None, + Element.LEFT_DORSAL_SETTING_SLIDER: None, + Element.RIGHT_DORSAL_SETTING_SLIDER: None, + Element.LEFT_LUMBAR_SETTING_SLIDER: None, + Element.RIGHT_LUMBAR_SETTING_SLIDER: None, + Element.TERMINAL_WINDOW_INPUT: None, + Element.CONNECT_BUTTON: None, + Element.SAVE_SETTINGS_BUTTON: None, + Element.CLEAR_CONSOLE_BUTTON: None, + Element.TOGGLES_INTERACTIONS_BUTTON: None, + Element.CONNECT_ON_STARTUP_CHECKBOX: None, + Element.CONTRIBUTE_BUTTON: None, + } + self.element_to_config_key = { + Element.SERVER_PORT_NUMBER_INPUT: "server_port", + Element.IP_ADDRESS_INPUT: "owo_ip", + Element.DETECT_IP_ADDRESS_CHECKBOX: "should_detect_ip", + Element.CONNECT_ON_STARTUP_CHECKBOX: "should_connect_on_startup", + Element.FREQUENCY_SETTING_SLIDER: "frequency", + "intensities": { + Element.LEFT_PECTORAL_SETTING_SLIDER: params.owo_suit_Pectoral_L, + Element.RIGHT_PECTORAL_SETTING_SLIDER: params.owo_suit_Pectoral_R, + Element.LEFT_ABDOMINAL_SETTING_SLIDER: params.owo_suit_Abdominal_L, + Element.RIGHT_ABDOMINAL_SETTING_SLIDER: params.owo_suit_Abdominal_R, + Element.LEFT_ARM_SETTING_SLIDER: params.owo_suit_Arm_L, + Element.RIGHT_ARM_SETTING_SLIDER: params.owo_suit_Arm_R, + Element.LEFT_DORSAL_SETTING_SLIDER: params.owo_suit_Dorsal_L, + Element.RIGHT_DORSAL_SETTING_SLIDER: params.owo_suit_Dorsal_R, + Element.LEFT_LUMBAR_SETTING_SLIDER: params.owo_suit_Lumbar_L, + Element.RIGHT_LUMBAR_SETTING_SLIDER: params.owo_suit_Lumbar_R, + } + + } + self.ids_to_elements = None + + def handle_connect_callback(self, sender, app_data): + self.on_connect_clicked.dispatch(sender, app_data) + + def handle_save_settings_callback(self): + self.config.write_config_to_disk() + self.print_terminal("Settings Saved!") + + def handle_clear_console_callback(self, sender, app_data): + self.on_clear_console_clicked.dispatch(sender, app_data) + + def handle_toggle_interactions_callback(self, sender, app_data): + self.on_toggle_interaction_clicked.dispatch() + + def handle_input_change(self, sender, app_data): + element = self.ids_to_elements.get(sender) + config_key = self.element_to_config_key.get(element) + # this implies its an intensity + if config_key is None: + intensities_map = self.element_to_config_key.get("intensities") + config_key = intensities_map.get(element) + intensities = self.config.get_by_key("intensities") + intensities[config_key] = app_data + self.config.update("intensities", intensities) + return + self.config.update(config_key, app_data) + + def handle_contribute_callback(self, sender, app_data): + webbrowser.open("https://github.com/uzair-ashraf/vrc-owo-suit") + + def handle_connecting_state_change(self, next_state): + if next_state == "CONNECTING": + dpg.configure_item( + self.elements[Element.CONNECT_BUTTON], label="Connecting...", enabled=False) + return + elif next_state == "CONNECTED": + dpg.configure_item( + self.elements[Element.CONNECT_BUTTON], label="Connected!", enabled=False) + return + elif next_state == "DISCONNECTED": + dpg.configure_item( + self.elements[Element.CONNECT_BUTTON], label="Connect", enabled=True) + return + + def create_centered_image(self, tag: str, path: str): + image_width, image_height, _, data = dpg.load_image(path) + + with dpg.texture_registry(): + dpg.add_static_texture( + width=image_width, height=image_height, default_value=data, tag=tag) + + spacer_width = (self.window_width - image_width) / 2 + with dpg.group(horizontal=True): + width_spacer_id = dpg.add_spacer(width=int(spacer_width) - 25) + dpg.add_image(tag) + width_spacer_id_2 = dpg.add_spacer(width=int(spacer_width)) + + def resize_callback(): + current_window_width = dpg.get_viewport_width() + spacer_width = (current_window_width - image_width) / 2 + dpg.configure_item(width_spacer_id, width=int(spacer_width) - 25) + dpg.configure_item(width_spacer_id_2, width=int(spacer_width)) + + return resize_callback + + def print_terminal(self, text: str) -> None: + value = dpg.get_value(self.elements[Element.TERMINAL_WINDOW_INPUT]) + dpg.set_value( + self.elements[Element.TERMINAL_WINDOW_INPUT], text + '\n' + value) + + def on_clear_console(self, *args) -> None: + dpg.set_value( + self.elements[Element.TERMINAL_WINDOW_INPUT], "Cleared.") + + def add_listeners(self) -> None: + self.on_clear_console_clicked.add_listener(self.on_clear_console) + + def create_owo_suit_ip_address_input(self): + owo_ip = self.config.get_by_key("owo_ip") or "" + dpg.add_text("OWO Suit IP Address") + self.elements[Element.IP_ADDRESS_INPUT] = dpg.add_input_text(default_value=owo_ip, + width=-1, callback=self.handle_input_change) + + def create_detect_address_checkbox(self): + should_detect_ip = self.config.get_by_key("should_detect_ip") + self.elements[Element.DETECT_IP_ADDRESS_CHECKBOX] = dpg.add_checkbox( + label="Automatically Detect IP Address", default_value=should_detect_ip, callback=self.handle_input_change) + + def create_server_port_input(self): + server_port = self.config.get_by_key("server_port") or 9001 + dpg.add_text("Server Port Number") + self.elements[Element.SERVER_PORT_NUMBER_INPUT] = dpg.add_input_int(default_value=server_port, + width=-1, callback=self.handle_input_change) + + def create_frequency_slider(self): + frequency = self.config.get_by_key("frequency") + dpg.add_text("Frequency Settings") + self.elements[Element.FREQUENCY_SETTING_SLIDER] = dpg.add_slider_int( + min_value=0, + max_value=100, + width=-1, + default_value=frequency, + callback=self.handle_input_change + ) + + def create_intensity_settings(self): + dpg.add_text("Intensity Settings") + element_labels = { + Element.LEFT_PECTORAL_SETTING_SLIDER: "Left Pectoral", + Element.RIGHT_PECTORAL_SETTING_SLIDER: "Right Pectoral", + Element.LEFT_ABDOMINAL_SETTING_SLIDER: "Left Abdominal", + Element.RIGHT_ABDOMINAL_SETTING_SLIDER: "Right Abdominal", + Element.LEFT_ARM_SETTING_SLIDER: "Left Arm", + Element.RIGHT_ARM_SETTING_SLIDER: "Right Arm", + Element.LEFT_DORSAL_SETTING_SLIDER: "Left Dorsal", + Element.RIGHT_DORSAL_SETTING_SLIDER: "Right Dorsal", + Element.LEFT_LUMBAR_SETTING_SLIDER: "Left Lumbar", + Element.RIGHT_LUMBAR_SETTING_SLIDER: "Right Lumbar", + } + for element, label in element_labels.items(): + self.create_intensity_slider(element, label) + + def create_intensity_slider(self, element: Element, label: str): + intensities_map = self.element_to_config_key.get("intensities") + config_key = intensities_map.get(element) + intensities = self.config.get_by_key("intensities") + default_value = intensities.get(config_key) or 0 + self.elements[element] = dpg.add_slider_int(default_value=default_value, min_value=0, width=-120, + max_value=100, label=label, callback=self.handle_input_change) + + def create_logs_output(self): + dpg.add_text("Logs") + self.elements[Element.TERMINAL_WINDOW_INPUT] = dpg.add_input_text( + multiline=True, readonly=True, height=90, width=-1) + + def create_button_group(self): + with dpg.group(horizontal=True): + self.elements[Element.CONNECT_BUTTON] = dpg.add_button(label="Connect", + callback=self.handle_connect_callback) + self.elements[Element.SAVE_SETTINGS_BUTTON] = dpg.add_button(label="Save Settings", + callback=self.handle_save_settings_callback) + self.elements[Element.CLEAR_CONSOLE_BUTTON] = dpg.add_button(label="Clear Console", + callback=self.handle_clear_console_callback) + self.elements[Element.TOGGLES_INTERACTIONS_BUTTON] = dpg.add_button(label="Toggle Interactions", + callback=self.handle_toggle_interactions_callback) + + def create_connect_startup_checkbox(self): + should_connect_on_startup = self.config.get_by_key( + "should_connect_on_startup" + ) + self.elements[Element.CONNECT_ON_STARTUP_CHECKBOX] = dpg.add_checkbox( + default_value=should_connect_on_startup, + label="Automatically Connect on Startup", + callback=self.handle_input_change + ) + + def create_footer(self): + with dpg.group(width=-1): + self.elements[Element.CONTRIBUTE_BUTTON] = dpg.add_button( + label="\t\t\t\t Created by Shadoki.\nThis application is not affiliated with VRChat or OWO.\n\t\t\t\t Want to contribute?", + width=-1, + callback=self.handle_contribute_callback + ) + + def validate_connect_on_startup(self): + should_connect_on_startup = self.config.get_by_key( + "should_connect_on_startup" + ) + if should_connect_on_startup: + self.handle_connect_callback(Element.CONNECT_BUTTON, None) + + def init(self): + dpg.create_context() + with dpg.window(tag="MAIN_WINDOW"): + dpg.add_spacer(height=20) + handle_centered_image = self.create_centered_image( + "logo", self.logo_path) + dpg.add_spacer(height=20) + self.create_owo_suit_ip_address_input() + self.create_detect_address_checkbox() + dpg.add_spacer(height=20) + self.create_server_port_input() + dpg.add_spacer(height=20) + self.create_frequency_slider() + dpg.add_spacer(height=20) + self.create_intensity_settings() + dpg.add_spacer(height=20) + self.create_logs_output() + dpg.add_spacer(height=20) + self.create_button_group() + dpg.add_spacer(height=20) + self.create_connect_startup_checkbox() + dpg.add_spacer(height=20) + self.create_footer() + + self.add_listeners() + dpg.create_viewport(title='VRChat OWO Suit', + width=self.window_width, height=self.window_height) + dpg.set_viewport_resize_callback(handle_centered_image) + self.ids_to_elements = { + value: key for key, value in self.elements.items()} + dpg.setup_dearpygui() + dpg.show_viewport() + dpg.set_primary_window("MAIN_WINDOW", True) + + def run(self): + self.validate_connect_on_startup() + dpg.start_dearpygui() + + def cleanup(self): + dpg.destroy_context() diff --git a/img/logo.png b/img/logo.png new file mode 100644 index 0000000..b7ad23b Binary files /dev/null and b/img/logo.png differ diff --git a/main.py b/main.py index e6e9368..151bdd7 100644 --- a/main.py +++ b/main.py @@ -3,23 +3,37 @@ from pythonosc.dispatcher import Dispatcher from pythonosc.osc_server import ThreadingOSCUDPServer from owo_suit import OWOSuit -import config +from config import Config +from gui import Gui +import os + +gui = None try: - print("Starting OWO Suit...") - c = config.get() - owo_suit = OWOSuit(c.owo_ip, c.frequency, c.intensity) + cfg = Config() + cfg.init() + logo_path = os.path.abspath(os.path.join(os.path.dirname(__file__), './img/logo.png')) + gui = Gui(config=cfg, window_width=550, + window_height=1000, logo_path=logo_path) + gui.init() + owo_suit = OWOSuit(config=cfg, gui=gui) owo_suit.init() dispatcher = Dispatcher() owo_suit.map_parameters(dispatcher) + + server_port = cfg.get_by_key("server_port") + osc_server = ThreadingOSCUDPServer( - ("127.0.0.1", c.server_port), dispatcher, asyncio.new_event_loop()) + ("127.0.0.1", server_port), dispatcher, asyncio.new_event_loop()) threading.Thread(target=lambda: osc_server.serve_forever(2), daemon=True).start() threading.Thread(target=owo_suit.watch, daemon=True).start() - input("Press any key to exit\n") + gui.run() except KeyboardInterrupt: print("Shutting Down...\n") except OSError: pass +finally: + if gui is not None: + gui.cleanup() diff --git a/owo_suit.py b/owo_suit.py index a744069..d11de5b 100644 --- a/owo_suit.py +++ b/owo_suit.py @@ -1,62 +1,68 @@ # pyright: reportMissingImports=false from pythonosc import dispatcher +from event import Event +from config import Config +from gui import Gui +import params import time import clr -from System.Reflection import Assembly -Assembly.UnsafeLoadFrom('./owo/OWO.dll') -from OWOGame import OWO, Sensation, SensationsFactory, Muscle, MicroSensation, ConnectionState - -# Sensations that are predefined -# 'Ball', 'GunRecoil', 'Bleed', 'Insvects', 'Wind', 'Dart', 'MachineGunRecoil', 'Punch', 'DaggerEntry', 'DaggerMovement', 'FastDriving', 'IdleSpeed', 'InsectBites', 'ShotEntry', 'ShotExit', 'Shot', 'Dagger', 'Hug', 'HeartBeat' +import os -# Muscle Properties -# 'Pectoral_R', 'Pectoral_L', 'Abdominal_R', 'Abdominal_L', 'Arm_R', 'Arm_L', 'Dorsal_R', 'Dorsal_L', 'Lumbar_R', 'Lumbar_L', 'AllMuscles', 'BackMuscles', 'FrontMuscles', 'FrontMusclesWithoutArms', 'Arms', 'Dorsals', 'Pectorals', 'Abdominals' +dll_path = os.path.abspath(os.path.join(os.path.dirname(__file__), './owo/OWO.dll')) +from System.Reflection import Assembly +Assembly.UnsafeLoadFrom(dll_path) +from OWOGame import OWO, SensationsFactory, Muscle, ConnectionState class OWOSuit: - def __init__(self, owo_ip: str, frequency: int, intensity: int): - self.owo_ip: str = owo_ip - self.intensity: int = intensity - self.frequency: int = frequency + def __init__(self, config: Config, gui: Gui): + self.config = config + self.gui = gui self.active_muscles: set = set() - self.touch_sensation: MicroSensation = SensationsFactory.Create( - self.frequency, 10, self.intensity, 0, 0, 0) self.osc_parameters: dict[str, Muscle] = { - "/avatar/parameters/owo_suit_Pectoral_R": Muscle.Pectoral_R, - "/avatar/parameters/owo_suit_Pectoral_L": Muscle.Pectoral_L, - "/avatar/parameters/owo_suit_Abdominal_R": Muscle.Abdominal_R, - "/avatar/parameters/owo_suit_Abdominal_L": Muscle.Abdominal_L, - "/avatar/parameters/owo_suit_Arm_R": Muscle.Arm_R, - "/avatar/parameters/owo_suit_Arm_L": Muscle.Arm_L, - "/avatar/parameters/owo_suit_Dorsal_R": Muscle.Dorsal_R, - "/avatar/parameters/owo_suit_Dorsal_L": Muscle.Dorsal_L, - "/avatar/parameters/owo_suit_Lumbar_R": Muscle.Lumbar_R, - "/avatar/parameters/owo_suit_Lumbar_L": Muscle.Lumbar_L, + params.owo_suit_Pectoral_R: Muscle.Pectoral_R, + params.owo_suit_Pectoral_L: Muscle.Pectoral_L, + params.owo_suit_Abdominal_R: Muscle.Abdominal_R, + params.owo_suit_Abdominal_L: Muscle.Abdominal_L, + params.owo_suit_Arm_R: Muscle.Arm_R, + params.owo_suit_Arm_L: Muscle.Arm_L, + params.owo_suit_Dorsal_R: Muscle.Dorsal_R, + params.owo_suit_Dorsal_L: Muscle.Dorsal_L, + params.owo_suit_Lumbar_R: Muscle.Lumbar_R, + params.owo_suit_Lumbar_L: Muscle.Lumbar_L, } + self.muscles_to_parameters: dict[Muscle, str] = { + value: key for key, value in self.osc_parameters.items()} + self.is_connecting = False + self.is_paused = False + self.on_connection_state_change = Event() - def ping_muscles(self) -> None: - for address, muscle in self.osc_parameters.items(): - print(f'Pinging {address}') - self.send_sensation(muscle) - time.sleep(.1) + def toggle_interactions(self): + self.is_paused = not self.is_paused + if self.is_paused: + self.gui.print_terminal( + "Interactions Paused.") + else: + self.gui.print_terminal( + "Interactions Continued.") + + def create_sensation(self, parameter: str): + frequency = self.config.get_by_key("frequency") or 50 + intensities = self.config.get_by_key("intensities") + intensity = getattr(intensities, parameter, 0) + return SensationsFactory.Create( + frequency, .1, intensity, 0, 0, 0) def watch(self) -> None: while True: - if not self.is_connected(): - self.retry_connect() - if len(self.active_muscles) > 0: - OWO.Send(self.touch_sensation, list(self.active_muscles)) - print("\033[SSending sensation to: ", self.active_muscles) - else: - OWO.Stop() - time.sleep(.3) + if len(self.active_muscles) > 0 and not self.is_paused: + for muscle in self.active_muscles: + parameter = self.muscles_to_parameters.get(muscle) + sensation = self.create_sensation(parameter) + OWO.Send(sensation, muscle) + time.sleep(.1) def on_collission_enter(self, address: str, *args) -> None: - if address == "/avatar/parameters/owo_intensity": - self.intensity = int(args[0]*100) - print("Set intensity to: "+str(self.intensity)) - self.touch_sensation = SensationsFactory.Create( - self.frequency, 10, self.intensity, 0, 0, 0) if not address in self.osc_parameters: return if len(args) != 1: @@ -74,8 +80,9 @@ def map_parameters(self, dispatcher: dispatcher.Dispatcher) -> None: dispatcher.set_default_handler(self.on_collission_enter) def connect(self) -> bool: - if self.owo_ip != "": - OWO.Connect(self.owo_ip) + owo_ip = self.config.get_by_key("owo_ip") + if type(owo_ip) is str and owo_ip != "": + OWO.Connect(owo_ip) if self.is_connected(): return True OWO.AutoConnect() @@ -84,14 +91,32 @@ def connect(self) -> bool: def is_connected(self) -> bool: return OWO.ConnectionState == ConnectionState.Connected - def retry_connect(self) -> None: + def dispatch_connection_state_change(self) -> None: + if self.is_connecting: + self.on_connection_state_change.dispatch('CONNECTING') + return + if self.is_connected(): + self.on_connection_state_change.dispatch('CONNECTED') + return + self.on_connection_state_change.dispatch('DISCONNECTED') + + def retry_connect(self, *args) -> None: + if self.is_connecting: + return + self.is_connecting = True + self.dispatch_connection_state_change() ok = self.connect() while not ok: - print( - f'Failed to connect to suit, trying again... IP: {self.owo_ip or "N/A"}') + self.gui.print_terminal( + "Failed to connect to suit, trying again..." + ) ok = self.connect() time.sleep(1) + self.is_connecting = False + self.dispatch_connection_state_change() def init(self) -> None: - self.retry_connect() - print("Successfully connected to OWO suit!") + self.gui.on_connect_clicked.add_listener(self.retry_connect) + self.gui.on_toggle_interaction_clicked.add_listener(self.toggle_interactions) + self.on_connection_state_change.add_listener( + self.gui.handle_connecting_state_change) diff --git a/params.py b/params.py new file mode 100644 index 0000000..2bfa1d9 --- /dev/null +++ b/params.py @@ -0,0 +1,10 @@ +owo_suit_Pectoral_R = "/avatar/parameters/owo_suit_Pectoral_R" +owo_suit_Pectoral_L = "/avatar/parameters/owo_suit_Pectoral_L" +owo_suit_Abdominal_R = "/avatar/parameters/owo_suit_Abdominal_R" +owo_suit_Abdominal_L = "/avatar/parameters/owo_suit_Abdominal_L" +owo_suit_Arm_R = "/avatar/parameters/owo_suit_Arm_R" +owo_suit_Arm_L = "/avatar/parameters/owo_suit_Arm_L" +owo_suit_Dorsal_R = "/avatar/parameters/owo_suit_Dorsal_R" +owo_suit_Dorsal_L = "/avatar/parameters/owo_suit_Dorsal_L" +owo_suit_Lumbar_R = "/avatar/parameters/owo_suit_Lumbar_R" +owo_suit_Lumbar_L = "/avatar/parameters/owo_suit_Lumbar_L" diff --git a/requirements.txt b/requirements.txt index 12cf357..3b536b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ python-osc==1.8.0 -pythonnet==3.0.1 \ No newline at end of file +pythonnet==3.0.1 +dearpygui==1.10.0 \ No newline at end of file