From 2e2c237a269cf0c8ef3b033d5b77dba66a9e355b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 16 Apr 2024 10:21:55 +0900 Subject: [PATCH 01/63] =?UTF-8?q?=F0=9F=9A=A7[WIP/TEST]=20Model=20:=20VRCh?= =?UTF-8?q?at=E3=81=A7MUTE=E3=81=AB=E3=81=97=E3=81=9F=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AB=E3=83=9E=E3=82=A4=E3=82=AF=E6=96=87=E5=AD=97=E8=B5=B7?= =?UTF-8?q?=E3=81=93=E3=81=97=E3=82=92=E3=81=97=E3=81=AA=E3=81=84=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E5=AE=9F=E8=A3=85=EF=BC=88=E9=80=94=E4=B8=AD?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 21 +++++++++++++++++++ model.py | 57 +++++++++++++++++++++++++++------------------------ 2 files changed, 51 insertions(+), 27 deletions(-) diff --git a/controller.py b/controller.py index 054f0d6c..880d7708 100644 --- a/controller.py +++ b/controller.py @@ -910,6 +910,24 @@ def callbackSetEnableSendReceivedMessageToVrc(value): # ---------------------Speaker2Chatbox--------------------- +def createDictOSCReceiveParameters(): + osc_parameter_prefix = "/avatar/parameters/" + param_MuteSelf = "MuteSelf" + param_Voice = "Voice" + + def change_handler_muteself(address, osc_arguments): + config.VRCHAT_MUTESELF = osc_arguments + + def change_handler_voice(address, osc_arguments): + config.VRCHAT_MUTESELF = False + + dict_filter_and_target = { + osc_parameter_prefix + param_MuteSelf: change_handler_muteself, + osc_parameter_prefix + param_Voice: change_handler_voice, + } + return dict_filter_and_target + + # Advanced Settings Tab def callbackSetOscIpAddress(value): if value == "": @@ -975,6 +993,9 @@ def createMainWindow(splash): if config.ENABLE_LOGGER is True: model.startLogger() + # init OSC + model.startReceiveOSC(createDictOSCReceiveParameters()) + splash.toProgress(3) # Last one. # set UI and callback diff --git a/model.py b/model.py index 0f844182..9c6e2950 100644 --- a/model.py +++ b/model.py @@ -209,39 +209,42 @@ class Model: def oscSendMessage(message): sendMessage(message, config.OSC_IP_ADDRESS, config.OSC_PORT) - def checkOSCStarted(self, fnc): - self.is_valid_osc = False - def checkOscReceive(address, osc_arguments): - if self.is_valid_osc is False: - self.is_valid_osc = True + # def checkOSCStarted(self, fnc): + # self.is_valid_osc = False + # def checkOscReceive(address, osc_arguments): + # if self.is_valid_osc is False: + # self.is_valid_osc = True - self.listening_server = receiveOscParameters(checkOscReceive) - def oscListener(): - self.listening_server.serve_forever() + # self.listening_server = receiveOscParameters(checkOscReceive) + # def oscListener(): + # self.listening_server.serve_forever() - def sendTestActionLoop(): - for _ in range(10): - sendTestAction() - if self.is_valid_osc is True: - break - sleep(0.1) - self.listening_server.shutdown() + # def sendTestActionLoop(): + # for _ in range(10): + # sendTestAction() + # if self.is_valid_osc is True: + # break + # sleep(0.1) + # self.listening_server.shutdown() - # start receive osc - th_receive_osc_parameters = Thread(target=oscListener) - th_receive_osc_parameters.daemon = True - th_receive_osc_parameters.start() + # # start receive osc + # th_receive_osc_parameters = Thread(target=oscListener) + # th_receive_osc_parameters.daemon = True + # th_receive_osc_parameters.start() - # check osc started - th_send_osc_test_action = Thread(target=sendTestActionLoop) - th_send_osc_test_action.daemon = True - th_send_osc_test_action.start() + # # check osc started + # th_send_osc_test_action = Thread(target=sendTestActionLoop) + # th_send_osc_test_action.daemon = True + # th_send_osc_test_action.start() - th_receive_osc_parameters.join() - th_send_osc_test_action.join() + # th_receive_osc_parameters.join() + # th_send_osc_test_action.join() - if self.is_valid_osc is False: - fnc() + # if self.is_valid_osc is False: + # fnc() + + def startReceiveOSC(self, fnc): + self.listening_server = receiveOscParameters(fnc) @staticmethod def checkSoftwareUpdated(): From 1c19236d62970fb67ac5b0120f66a9e06b7ddc51 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 16 Apr 2024 21:44:26 +0900 Subject: [PATCH 02/63] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20Model=20:?= =?UTF-8?q?=20VRChat=E3=81=AEmute/voice=E3=82=92=E6=A4=9C=E5=87=BA?= =?UTF-8?q?=E3=81=97=E6=96=87=E5=AD=97=E8=B5=B7=E3=81=93=E3=81=97=E3=82=92?= =?UTF-8?q?=E5=81=9C=E6=AD=A2=E3=81=99=E3=82=8B=E6=A9=9F=E8=83=BD=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 12 +++++++ controller.py | 24 +++++++++---- model.py | 80 +++++++++++++++++++++++++++++++++++------ models/osc/osc_tools.py | 6 ++-- 4 files changed, 102 insertions(+), 20 deletions(-) diff --git a/config.py b/config.py index 9e17df87..87efb0d6 100644 --- a/config.py +++ b/config.py @@ -830,6 +830,17 @@ class Config: self._ENABLE_LOGGER = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + @json_serializable('ENABLE_MUTE_DETECT') + def ENABLE_MUTE_DETECT(self): + return self._ENABLE_MUTE_DETECT + + @ENABLE_MUTE_DETECT.setter + def ENABLE_MUTE_DETECT(self, value): + if isinstance(value, bool): + self._ENABLE_MUTE_DETECT = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property @json_serializable('IS_CONFIG_WINDOW_COMPACT_MODE') def IS_CONFIG_WINDOW_COMPACT_MODE(self): @@ -995,6 +1006,7 @@ class Config: self._ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = False # Speaker2Chatbox self._ENABLE_SPEAKER2CHATBOX_PASS = "000000000" self._ENABLE_LOGGER = False + self._ENABLE_MUTE_DETECT = False self._IS_CONFIG_WINDOW_COMPACT_MODE = False def load_config(self): diff --git a/controller.py b/controller.py index 880d7708..a5a4dbda 100644 --- a/controller.py +++ b/controller.py @@ -909,20 +909,30 @@ def callbackSetEnableSendReceivedMessageToVrc(value): config.ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = value # ---------------------Speaker2Chatbox--------------------- - def createDictOSCReceiveParameters(): osc_parameter_prefix = "/avatar/parameters/" param_MuteSelf = "MuteSelf" param_Voice = "Voice" - def change_handler_muteself(address, osc_arguments): - config.VRCHAT_MUTESELF = osc_arguments + def change_handler_mute(address, osc_arguments): + if config.ENABLE_MUTE_DETECT is True: + if osc_arguments is True and change_handler_mute.status_mute is False: + model.stopPutQueueMicAudio() + change_handler_mute.status_mute = True + elif osc_arguments is False and change_handler_mute.status_mute is True: + model.startPutQueueMicAudio() + change_handler_mute.status_mute = False def change_handler_voice(address, osc_arguments): - config.VRCHAT_MUTESELF = False + if config.ENABLE_MUTE_DETECT is True: + if change_handler_mute.status_mute is True: + model.startPutQueueMicAudio() + change_handler_mute.status_mute = False + + change_handler_mute.status_mute = False dict_filter_and_target = { - osc_parameter_prefix + param_MuteSelf: change_handler_muteself, + osc_parameter_prefix + param_MuteSelf: change_handler_mute, osc_parameter_prefix + param_Voice: change_handler_voice, } return dict_filter_and_target @@ -993,8 +1003,8 @@ def createMainWindow(splash): if config.ENABLE_LOGGER is True: model.startLogger() - # init OSC - model.startReceiveOSC(createDictOSCReceiveParameters()) + # init OSC receive + model.startReceiveOSC() splash.toProgress(3) # Last one. diff --git a/model.py b/model.py index 9c6e2950..39a3117d 100644 --- a/model.py +++ b/model.py @@ -17,7 +17,7 @@ from typing import Callable from flashtext import KeywordProcessor from models.translation.translation_translator import Translator from models.transcription.transcription_utils import getInputDevices, getOutputDevices -from models.osc.osc_tools import sendTyping, sendMessage, sendTestAction, receiveOscParameters +from models.osc.osc_tools import sendTyping, sendMessage, receiveOscParameters from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder, SelectedSpeakerEnergyAndAudioRecorder from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakerEnergyRecorder from models.transcription.transcription_transcriber import AudioTranscriber @@ -46,6 +46,18 @@ class threadFnc(Thread): return self.fnc(*self._args, **self._kwargs) +class ConditionalQueue(Queue): + def __init__(self, flag=True, *args, **kwargs): + super().__init__(*args, **kwargs) + self.flag = flag + + def put(self, item, block=True, timeout=None): + if self.flag is True: + super().put(item, block, timeout) + + def set_flag(self, value): + self.flag = value + class Model: _instance = None @@ -67,6 +79,9 @@ class Model: self.speaker_energy_plot_progressbar = None self.translator = Translator() self.keyword_processor = KeywordProcessor() + self.mic_audio_queue = ConditionalQueue() + # self.mic_energy_queue = ConditionalQueue() + self.mute_status = False def checkCTranslatorCTranslate2ModelWeight(self): return checkCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE) @@ -243,8 +258,39 @@ class Model: # if self.is_valid_osc is False: # fnc() - def startReceiveOSC(self, fnc): - self.listening_server = receiveOscParameters(fnc) + # def startReceiveOSC(self, fnc): + # th_osc_server = threadFnc(receiveOscParameters, args=(fnc,)) + # th_osc_server.daemon = True + # th_osc_server.start() + + def startReceiveOSC(self): + osc_parameter_prefix = "/avatar/parameters/" + param_MuteSelf = "MuteSelf" + param_Voice = "Voice" + + def change_handler_mute(address, osc_arguments): + if config.ENABLE_MUTE_DETECT is True: + if osc_arguments is True and self.mute_status is False: + self.stopPutQueueMicAudio() + self.mute_status = True + elif osc_arguments is False and self.mute_status is True: + self.startPutQueueMicAudio() + self.mute_status = False + + def change_handler_voice(address, osc_arguments): + if config.ENABLE_MUTE_DETECT is True: + if self.mute_status is True: + self.startPutQueueMicAudio() + self.mute_status = False + + dict_filter_and_target = { + osc_parameter_prefix + param_MuteSelf: change_handler_mute, + osc_parameter_prefix + param_Voice: change_handler_voice, + } + + th_osc_server = threadFnc(receiveOscParameters, args=(dict_filter_and_target,)) + th_osc_server.daemon = True + th_osc_server.start() @staticmethod def checkSoftwareUpdated(): @@ -321,8 +367,8 @@ class Model: pass return - mic_audio_queue = Queue() - # mic_energy_queue = Queue() + self.mic_audio_queue = ConditionalQueue() + # self.mic_energy_queue = ConditionalQueue() mic_device = choice_mic_device[0] record_timeout = config.INPUT_MIC_RECORD_TIMEOUT phase_timeout = config.INPUT_MIC_PHRASE_TIMEOUT @@ -335,8 +381,8 @@ class Model: dynamic_energy_threshold=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, record_timeout=record_timeout, ) - # self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, mic_energy_queue) - self.mic_audio_recorder.recordIntoQueue(mic_audio_queue, None) + # self.mic_audio_recorder.recordIntoQueue(self.mic_audio_queue, mic_energy_queue) + self.mic_audio_recorder.recordIntoQueue(self.mic_audio_queue, None) self.mic_transcriber = AudioTranscriber( speaker=False, source=self.mic_audio_recorder.source, @@ -348,15 +394,17 @@ class Model: ) def sendMicTranscript(): try: - self.mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) + self.mic_transcriber.transcribeAudioQueue(self.mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) message = self.mic_transcriber.getTranscript() fnc(message) except Exception: pass def endMicTranscript(): - mic_audio_queue.queue.clear() - # mic_energy_queue.queue.clear() + while not self.mic_audio_queue.empty(): + self.mic_audio_queue.get() + # while not self.mic_energy_queue.empty(): + # self.mic_energy_queue.get() del self.mic_transcriber gc.collect() @@ -378,6 +426,18 @@ class Model: # self.mic_get_energy.daemon = True # self.mic_get_energy.start() + def startPutQueueMicAudio(self): + while not self.mic_audio_queue.empty(): + self.mic_audio_queue.get() + self.mic_audio_queue.set_flag(True) + # self.mic_energy_queue.set_flag(True) + + def stopPutQueueMicAudio(self): + self.mic_audio_queue.set_flag(False) + while not self.mic_audio_queue.empty(): + self.mic_audio_queue.get() + # self.mic_energy_queue.set_flag(False) + def stopMicTranscript(self): if isinstance(self.mic_print_transcript, threadFnc): self.mic_print_transcript.stop() diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py index 51608f66..cf9804ac 100644 --- a/models/osc/osc_tools.py +++ b/models/osc/osc_tools.py @@ -1,5 +1,5 @@ from time import sleep -import threading +from threading import Thread from pythonosc import osc_message_builder from pythonosc import udp_client from pythonosc import dispatcher @@ -55,14 +55,14 @@ def receiveOscParameters(dict_filter_and_target, ip_address="127.0.0.1", title=" osc_dispatcher = dispatcher.Dispatcher() for filter, target in dict_filter_and_target.items(): osc_dispatcher.map(filter, target) - osc_udp_server = osc_server.ThreadingOSCUDPServer((ip_address, osc_port), osc_dispatcher) - threading.Thread(target=osc_udp_server.serve_forever, daemon = True).start() osc_client = OSCQueryService(title, http_port, osc_port) for filter, target in dict_filter_and_target.items(): osc_client.advertise_endpoint(filter) + osc_udp_server.serve_forever() + if __name__ == "__main__": osc_parameter_prefix = "/avatar/parameters/" osc_avatar_change_path = "/avatar/change" From 497de5bcff7bb9a92cad17b47453821484b3963b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 16 Apr 2024 21:46:27 +0900 Subject: [PATCH 03/63] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F[Remove]=20Model=20:?= =?UTF-8?q?=20OSC=20Receive=E3=81=AB=E3=81=A4=E3=81=84=E3=81=A6=E4=B8=8D?= =?UTF-8?q?=E8=A6=81=E3=81=AA=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/model.py b/model.py index 39a3117d..700e5f0e 100644 --- a/model.py +++ b/model.py @@ -224,45 +224,6 @@ class Model: def oscSendMessage(message): sendMessage(message, config.OSC_IP_ADDRESS, config.OSC_PORT) - # def checkOSCStarted(self, fnc): - # self.is_valid_osc = False - # def checkOscReceive(address, osc_arguments): - # if self.is_valid_osc is False: - # self.is_valid_osc = True - - # self.listening_server = receiveOscParameters(checkOscReceive) - # def oscListener(): - # self.listening_server.serve_forever() - - # def sendTestActionLoop(): - # for _ in range(10): - # sendTestAction() - # if self.is_valid_osc is True: - # break - # sleep(0.1) - # self.listening_server.shutdown() - - # # start receive osc - # th_receive_osc_parameters = Thread(target=oscListener) - # th_receive_osc_parameters.daemon = True - # th_receive_osc_parameters.start() - - # # check osc started - # th_send_osc_test_action = Thread(target=sendTestActionLoop) - # th_send_osc_test_action.daemon = True - # th_send_osc_test_action.start() - - # th_receive_osc_parameters.join() - # th_send_osc_test_action.join() - - # if self.is_valid_osc is False: - # fnc() - - # def startReceiveOSC(self, fnc): - # th_osc_server = threadFnc(receiveOscParameters, args=(fnc,)) - # th_osc_server.daemon = True - # th_osc_server.start() - def startReceiveOSC(self): osc_parameter_prefix = "/avatar/parameters/" param_MuteSelf = "MuteSelf" From 63dd15c10e7ca44b10789e4ac7a7acff95f658a2 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 29 Apr 2024 23:36:25 +0900 Subject: [PATCH 04/63] =?UTF-8?q?[Unmask]=20Overlay=E7=94=A8=E3=81=AEUI?= =?UTF-8?q?=E3=81=A8=E3=80=81config=E3=82=92=E6=88=BB=E3=81=97=E3=81=9F?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 116 +++++------ view.py | 180 +++++++++--------- .../createSideMenuAndSettingsBoxContainers.py | 30 +-- .../createSettingBox_Others.py | 12 -- .../setting_box_vr/createSettingBox_Vr.py | 16 +- .../main_window/createMainWindowWidgets.py | 84 ++++---- vrct_gui/vrct_gui.py | 13 +- 7 files changed, 226 insertions(+), 225 deletions(-) diff --git a/config.py b/config.py index 64b7c31c..393d723c 100644 --- a/config.py +++ b/config.py @@ -736,58 +736,58 @@ class Config: self._ENABLE_NOTICE_XSOVERLAY = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - # @property - # @json_serializable('OVERLAY_SETTINGS') - # def OVERLAY_SETTINGS(self): - # return self._OVERLAY_SETTINGS + @property + @json_serializable('OVERLAY_SETTINGS') + def OVERLAY_SETTINGS(self): + return self._OVERLAY_SETTINGS - # @OVERLAY_SETTINGS.setter - # def OVERLAY_SETTINGS(self, value): - # if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SETTINGS.keys()): - # for key, value in value.items(): - # if isinstance(value, float): - # self._OVERLAY_SETTINGS[key] = value - # saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.OVERLAY_SETTINGS) + @OVERLAY_SETTINGS.setter + def OVERLAY_SETTINGS(self, value): + if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SETTINGS.keys()): + for key, value in value.items(): + if isinstance(value, float): + self._OVERLAY_SETTINGS[key] = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.OVERLAY_SETTINGS) - # @property - # @json_serializable('ENABLE_OVERLAY_SMALL_LOG') - # def ENABLE_OVERLAY_SMALL_LOG(self): - # return self._ENABLE_OVERLAY_SMALL_LOG + @property + @json_serializable('ENABLE_OVERLAY_SMALL_LOG') + def ENABLE_OVERLAY_SMALL_LOG(self): + return self._ENABLE_OVERLAY_SMALL_LOG - # @ENABLE_OVERLAY_SMALL_LOG.setter - # def ENABLE_OVERLAY_SMALL_LOG(self, value): - # if isinstance(value, bool): - # self._ENABLE_OVERLAY_SMALL_LOG = value - # saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @ENABLE_OVERLAY_SMALL_LOG.setter + def ENABLE_OVERLAY_SMALL_LOG(self, value): + if isinstance(value, bool): + self._ENABLE_OVERLAY_SMALL_LOG = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - # @property - # @json_serializable('OVERLAY_SMALL_LOG_SETTINGS') - # def OVERLAY_SMALL_LOG_SETTINGS(self): - # return self._OVERLAY_SMALL_LOG_SETTINGS + @property + @json_serializable('OVERLAY_SMALL_LOG_SETTINGS') + def OVERLAY_SMALL_LOG_SETTINGS(self): + return self._OVERLAY_SMALL_LOG_SETTINGS - # @OVERLAY_SMALL_LOG_SETTINGS.setter - # def OVERLAY_SMALL_LOG_SETTINGS(self, value): - # if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SMALL_LOG_SETTINGS.keys()): - # for key, value in value.items(): - # match (key): - # case "x_pos" | "y_pos" | "depth": - # if isinstance(value, float): - # self._OVERLAY_SMALL_LOG_SETTINGS[key] = value - # case "display_duration" | "fadeout_duration": - # if isinstance(value, int): - # self._OVERLAY_SMALL_LOG_SETTINGS[key] = value - # saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.OVERLAY_SMALL_LOG_SETTINGS) + @OVERLAY_SMALL_LOG_SETTINGS.setter + def OVERLAY_SMALL_LOG_SETTINGS(self, value): + if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SMALL_LOG_SETTINGS.keys()): + for key, value in value.items(): + match (key): + case "x_pos" | "y_pos" | "depth": + if isinstance(value, float): + self._OVERLAY_SMALL_LOG_SETTINGS[key] = value + case "display_duration" | "fadeout_duration": + if isinstance(value, int): + self._OVERLAY_SMALL_LOG_SETTINGS[key] = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.OVERLAY_SMALL_LOG_SETTINGS) - # @property - # @json_serializable('OVERLAY_UI_TYPE') - # def OVERLAY_UI_TYPE(self): - # return self._OVERLAY_UI_TYPE + @property + @json_serializable('OVERLAY_UI_TYPE') + def OVERLAY_UI_TYPE(self): + return self._OVERLAY_UI_TYPE - # @OVERLAY_UI_TYPE.setter - # def OVERLAY_UI_TYPE(self, value): - # if isinstance(value, str): - # self._OVERLAY_UI_TYPE = value - # saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @OVERLAY_UI_TYPE.setter + def OVERLAY_UI_TYPE(self, value): + if isinstance(value, str): + self._OVERLAY_UI_TYPE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @json_serializable('ENABLE_SEND_MESSAGE_TO_VRC') @@ -1054,19 +1054,19 @@ class Config: self._ENABLE_SEND_ONLY_TRANSLATED_MESSAGES = False self._SEND_MESSAGE_BUTTON_TYPE = "show" self._ENABLE_NOTICE_XSOVERLAY = False - # self._OVERLAY_SETTINGS = { - # "opacity": 1.0, - # "ui_scaling": 1.0, - # } - # self._ENABLE_OVERLAY_SMALL_LOG = False - # self._OVERLAY_SMALL_LOG_SETTINGS = { - # "x_pos": 0.0, - # "y_pos": -0.41, - # "depth": 1.0, - # "display_duration": 5, - # "fadeout_duration": 2, - # } - # self._OVERLAY_UI_TYPE = "default" + self._OVERLAY_SETTINGS = { + "opacity": 1.0, + "ui_scaling": 1.0, + } + self._ENABLE_OVERLAY_SMALL_LOG = False + self._OVERLAY_SMALL_LOG_SETTINGS = { + "x_pos": 0.0, + "y_pos": -0.41, + "depth": 1.0, + "display_duration": 5, + "fadeout_duration": 2, + } + self._OVERLAY_UI_TYPE = "default" self._ENABLE_SEND_MESSAGE_TO_VRC = True self._ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = False # Speaker2Chatbox self._ENABLE_SPEAKER2CHATBOX_PASS = "000000000" diff --git a/view.py b/view.py index c76a90ae..e222da60 100644 --- a/view.py +++ b/view.py @@ -140,57 +140,57 @@ class View(): # Overlay Settings - # VAR_OVERLAY_SETTINGS=StringVar(value="Overlay Settings"), - # CALLBACK_SET_OPEN_OVERLAY_SETTINGS_WINDOW=self._openVrSettingsWindow, - # VAR_TO_DEFAULT_OVERLAY_SETTINGS=StringVar(value=i18n.t("overlay_settings.restore_default_settings")), - # CALLBACK_SET_TO_DEFAULT_OVERLAY_SETTINGS=self._toDefaultOverlaySettings, + VAR_OVERLAY_SETTINGS=StringVar(value="Overlay Settings"), + CALLBACK_SET_OPEN_OVERLAY_SETTINGS_WINDOW=self._openVrSettingsWindow, + VAR_TO_DEFAULT_OVERLAY_SETTINGS=StringVar(value=i18n.t("overlay_settings.restore_default_settings")), + CALLBACK_SET_TO_DEFAULT_OVERLAY_SETTINGS=self._toDefaultOverlaySettings, - # VAR_LABEL_OVERLAY_OPACITY=StringVar(value=i18n.t("overlay_settings.opacity")), - # SLIDER_RANGE_OVERLAY_OPACITY=(0.1, 1.0), - # NUMBER_OF_STEPS_OVERLAY_OPACITY=18, - # VAR_OVERLAY_OPACITY=DoubleVar(value=config.OVERLAY_SETTINGS["opacity"]), - # VAR_CURRENT_VALUE_OVERLAY_OPACITY=StringVar(value=floatToPctStr(config.OVERLAY_SETTINGS["opacity"])), + VAR_LABEL_OVERLAY_OPACITY=StringVar(value=i18n.t("overlay_settings.opacity")), + SLIDER_RANGE_OVERLAY_OPACITY=(0.1, 1.0), + NUMBER_OF_STEPS_OVERLAY_OPACITY=18, + VAR_OVERLAY_OPACITY=DoubleVar(value=config.OVERLAY_SETTINGS["opacity"]), + VAR_CURRENT_VALUE_OVERLAY_OPACITY=StringVar(value=floatToPctStr(config.OVERLAY_SETTINGS["opacity"])), - # VAR_LABEL_OVERLAY_UI_SCALING=StringVar(value=i18n.t("overlay_settings.ui_scaling")), - # SLIDER_RANGE_OVERLAY_UI_SCALING=(0.4, 2.0), - # NUMBER_OF_STEPS_OVERLAY_UI_SCALING=16, - # VAR_OVERLAY_UI_SCALING=DoubleVar(value=config.OVERLAY_SETTINGS["ui_scaling"]), - # VAR_CURRENT_VALUE_OVERLAY_UI_SCALING=StringVar(value=floatToPctStr(config.OVERLAY_SETTINGS["ui_scaling"])), + VAR_LABEL_OVERLAY_UI_SCALING=StringVar(value=i18n.t("overlay_settings.ui_scaling")), + SLIDER_RANGE_OVERLAY_UI_SCALING=(0.4, 2.0), + NUMBER_OF_STEPS_OVERLAY_UI_SCALING=16, + VAR_OVERLAY_UI_SCALING=DoubleVar(value=config.OVERLAY_SETTINGS["ui_scaling"]), + VAR_CURRENT_VALUE_OVERLAY_UI_SCALING=StringVar(value=floatToPctStr(config.OVERLAY_SETTINGS["ui_scaling"])), - # # CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS=None, + CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS=None, - # VAR_LABEL_OVERLAY_SMALL_LOG_X_POS=StringVar(value=i18n.t("overlay_settings.x_position")), - # SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-0.5, 0.5), - # NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=100, - # VAR_OVERLAY_SMALL_LOG_X_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), - # VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), + VAR_LABEL_OVERLAY_SMALL_LOG_X_POS=StringVar(value=i18n.t("overlay_settings.x_position")), + SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-0.5, 0.5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=100, + VAR_OVERLAY_SMALL_LOG_X_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), - # VAR_LABEL_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=i18n.t("overlay_settings.y_position")), - # SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-0.8, 0.8), - # NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=160, - # VAR_OVERLAY_SMALL_LOG_Y_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), - # VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), + VAR_LABEL_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=i18n.t("overlay_settings.y_position")), + SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-0.8, 0.8), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=160, + VAR_OVERLAY_SMALL_LOG_Y_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), - # VAR_LABEL_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=i18n.t("overlay_settings.depth")), - # SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH=(0.5, 1.5), - # NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH=100, - # VAR_OVERLAY_SMALL_LOG_DEPTH=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), - # VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), + VAR_LABEL_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=i18n.t("overlay_settings.depth")), + SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH=(0.5, 1.5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH=100, + VAR_OVERLAY_SMALL_LOG_DEPTH=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), - # VAR_LABEL_OVERLAY_SMALL_LOG_DISPLAY_DURATION=StringVar(value=i18n.t("overlay_settings.display_duration")), - # SLIDER_RANGE_OVERLAY_SMALL_LOG_DISPLAY_DURATION=(1, 60), - # NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DISPLAY_DURATION=59, - # VAR_OVERLAY_SMALL_LOG_DISPLAY_DURATION=IntVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"]), - # VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DISPLAY_DURATION=StringVar(value=f"{config.OVERLAY_SMALL_LOG_SETTINGS['display_duration']} second(s)"), + VAR_LABEL_OVERLAY_SMALL_LOG_DISPLAY_DURATION=StringVar(value=i18n.t("overlay_settings.display_duration")), + SLIDER_RANGE_OVERLAY_SMALL_LOG_DISPLAY_DURATION=(1, 60), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DISPLAY_DURATION=59, + VAR_OVERLAY_SMALL_LOG_DISPLAY_DURATION=IntVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DISPLAY_DURATION=StringVar(value=f"{config.OVERLAY_SMALL_LOG_SETTINGS['display_duration']} second(s)"), - # VAR_LABEL_OVERLAY_SMALL_LOG_FADEOUT_DURATION=StringVar(value=i18n.t("overlay_settings.fadeout_duration")), - # SLIDER_RANGE_OVERLAY_SMALL_LOG_FADEOUT_DURATION=(0, 5), - # NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_FADEOUT_DURATION=5, - # VAR_OVERLAY_SMALL_LOG_FADEOUT_DURATION=IntVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"]), - # VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_FADEOUT_DURATION=StringVar(value=f"{config.OVERLAY_SMALL_LOG_SETTINGS['fadeout_duration']} second(s)"), + VAR_LABEL_OVERLAY_SMALL_LOG_FADEOUT_DURATION=StringVar(value=i18n.t("overlay_settings.fadeout_duration")), + SLIDER_RANGE_OVERLAY_SMALL_LOG_FADEOUT_DURATION=(0, 5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_FADEOUT_DURATION=5, + VAR_OVERLAY_SMALL_LOG_FADEOUT_DURATION=IntVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_FADEOUT_DURATION=StringVar(value=f"{config.OVERLAY_SMALL_LOG_SETTINGS['fadeout_duration']} second(s)"), @@ -478,13 +478,13 @@ class View(): VAR_WHISPER_WEIGHT_TYPE=StringVar(value=self.getSelectableWhisperWeightTypeDict()[config.WHISPER_WEIGHT_TYPE]), - # # VR Tab - # VAR_LABEL_ENABLE_OVERLAY_SMALL_LOG=StringVar(value=i18n.t("config_window.enable_overlay_small_log.label")), - # VAR_DESC_ENABLE_OVERLAY_SMALL_LOG=None, - # # VAR_DESC_ENABLE_OVERLAY_SMALL_LOG=StringVar(value=i18n.t("config_window.enable_overlay_small_log.desc")), - # CALLBACK_SET_ENABLE_OVERLAY_SMALL_LOG=None, - # VAR_ENABLE_OVERLAY_SMALL_LOG=BooleanVar(value=config.ENABLE_OVERLAY_SMALL_LOG), - # VAR_OPEN_OVERLAY_SETTINGS_BUTTON=StringVar(value=i18n.t("config_window.enable_overlay_small_log.open_overlay_settings")), + # VR Tab + VAR_LABEL_ENABLE_OVERLAY_SMALL_LOG=StringVar(value=i18n.t("config_window.enable_overlay_small_log.label")), + VAR_DESC_ENABLE_OVERLAY_SMALL_LOG=None, + # VAR_DESC_ENABLE_OVERLAY_SMALL_LOG=StringVar(value=i18n.t("config_window.enable_overlay_small_log.desc")), + CALLBACK_SET_ENABLE_OVERLAY_SMALL_LOG=None, + VAR_ENABLE_OVERLAY_SMALL_LOG=BooleanVar(value=config.ENABLE_OVERLAY_SMALL_LOG), + VAR_OPEN_OVERLAY_SETTINGS_BUTTON=StringVar(value=i18n.t("config_window.enable_overlay_small_log.open_overlay_settings")), # Others Tab @@ -756,11 +756,11 @@ class View(): # VR Tab # VR Tab (Quick Settings) - # self.view_variable.CALLBACK_SET_OVERLAY_SETTINGS=config_window_registers.get("callback_set_overlay_settings", None) + self.view_variable.CALLBACK_SET_OVERLAY_SETTINGS=config_window_registers.get("callback_set_overlay_settings", None) - # self.view_variable.CALLBACK_SET_ENABLE_OVERLAY_SMALL_LOG=config_window_registers.get("callback_set_enable_overlay_small_log", None) - # # VR Tab (Quick Settings) - # self.view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS=config_window_registers.get("callback_set_overlay_small_log_settings", None) + self.view_variable.CALLBACK_SET_ENABLE_OVERLAY_SMALL_LOG=config_window_registers.get("callback_set_enable_overlay_small_log", None) + # VR Tab (Quick Settings) + self.view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS=config_window_registers.get("callback_set_overlay_small_log_settings", None) # Others Tab @@ -858,21 +858,21 @@ class View(): # Set Easter Egg - # self.count = 0 - # def clickedCounter(_e): - # if self.count < 2: - # self.count+=1 - # print("Easter egg count:", self.count) - # else: - # print("Easter egg count:", self.count, "Easter egg has enabled.") - # callFunctionIfCallable(self.view_variable.CALLBACK_ENABLE_EASTER_EGG) - # print(config.OVERLAY_UI_TYPE) + self.count = 0 + def clickedCounter(_e): + if self.count < 2: + self.count+=1 + print("Easter egg count:", self.count) + else: + print("Easter egg count:", self.count, "Easter egg has enabled.") + callFunctionIfCallable(self.view_variable.CALLBACK_ENABLE_EASTER_EGG) + print(config.OVERLAY_UI_TYPE) - # vrct_gui.sidebar_logo.bind( - # "", - # clickedCounter, - # "+" - # ) + vrct_gui.sidebar_logo.bind( + "", + clickedCounter, + "+" + ) # Insert sample conversation for testing. @@ -1114,32 +1114,32 @@ class View(): } - # def _toDefaultOverlaySettings(self): - # INIT_OVERLAY_SETTINGS = { - # "opacity": 1.0, - # "ui_scaling": 1.0, - # } - # INIT_OVERLAY_SMALL_LOG_SETTINGS = { - # "x_pos": 0.0, - # "y_pos": -0.41, - # "depth": 1.0, - # "display_duration": 5, - # "fadeout_duration": 2, - # } - # for key in INIT_OVERLAY_SETTINGS.keys(): - # callFunctionIfCallable(self.view_variable.CALLBACK_SET_OVERLAY_SETTINGS, INIT_OVERLAY_SETTINGS[key], key) + def _toDefaultOverlaySettings(self): + INIT_OVERLAY_SETTINGS = { + "opacity": 1.0, + "ui_scaling": 1.0, + } + INIT_OVERLAY_SMALL_LOG_SETTINGS = { + "x_pos": 0.0, + "y_pos": -0.41, + "depth": 1.0, + "display_duration": 5, + "fadeout_duration": 2, + } + for key in INIT_OVERLAY_SETTINGS.keys(): + callFunctionIfCallable(self.view_variable.CALLBACK_SET_OVERLAY_SETTINGS, INIT_OVERLAY_SETTINGS[key], key) - # for key in INIT_OVERLAY_SMALL_LOG_SETTINGS.keys(): - # callFunctionIfCallable(self.view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, INIT_OVERLAY_SMALL_LOG_SETTINGS[key], key) + for key in INIT_OVERLAY_SMALL_LOG_SETTINGS.keys(): + callFunctionIfCallable(self.view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, INIT_OVERLAY_SMALL_LOG_SETTINGS[key], key) - # self.setLatestConfigVariable("OverlayOpacity") - # self.setLatestConfigVariable("OverlayUiScaling") + self.setLatestConfigVariable("OverlayOpacity") + self.setLatestConfigVariable("OverlayUiScaling") - # self.setLatestConfigVariable("OverlaySmallLogXPos") - # self.setLatestConfigVariable("OverlaySmallLogYPos") - # self.setLatestConfigVariable("OverlaySmallLogDepth") - # self.setLatestConfigVariable("OverlaySmallLogDisplayDuration") - # self.setLatestConfigVariable("OverlaySmallLogFadeoutDuration") + self.setLatestConfigVariable("OverlaySmallLogXPos") + self.setLatestConfigVariable("OverlaySmallLogYPos") + self.setLatestConfigVariable("OverlaySmallLogDepth") + self.setLatestConfigVariable("OverlaySmallLogDisplayDuration") + self.setLatestConfigVariable("OverlaySmallLogFadeoutDuration") # Open Webpage Functions def openWebPage_Booth(self): @@ -1894,8 +1894,8 @@ class View(): # Print To Textbox. - # def printToTextbox_enableEasterEgg(self): - # self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.enabled_easter_egg")) + def printToTextbox_enableEasterEgg(self): + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.enabled_easter_egg")) def printToTextbox_enableTranslation(self): self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.enabled_translation")) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 52e72f01..d70a0eba 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -104,21 +104,21 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl ] }, }, - # { - # "side_menu_tab_attr_name": "side_menu_tab_vr", - # "label_attr_name": "label_vr", - # "selected_mark_attr_name": "selected_mark_vr", - # "textvariable": view_variable.VAR_SIDE_MENU_LABEL_VR, - # "setting_box_container_settings": { - # "setting_box_container_attr_name": "setting_box_container_vr", - # "setting_boxes": [ - # { - # "var_section_title": None, - # "setting_box": createSettingBox_Vr - # } - # ] - # }, - # }, + { + "side_menu_tab_attr_name": "side_menu_tab_vr", + "label_attr_name": "label_vr", + "selected_mark_attr_name": "selected_mark_vr", + "textvariable": view_variable.VAR_SIDE_MENU_LABEL_VR, + "setting_box_container_settings": { + "setting_box_container_attr_name": "setting_box_container_vr", + "setting_boxes": [ + { + "var_section_title": None, + "setting_box": createSettingBox_Vr + } + ] + }, + }, { "side_menu_tab_attr_name": "side_menu_tab_others", "label_attr_name": "label_others", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index 0d7d0ffb..93a249c7 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -18,9 +18,6 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v def checkboxSendMessageButtonTypeCallback(): callFunctionIfCallable(view_variable.CALLBACK_SET_SEND_MESSAGE_BUTTON_TYPE, view_variable.VAR_SEND_MESSAGE_BUTTON_TYPE.get()) - def checkboxNoticeXsoverlayCallback(checkbox_box_widget): - callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, checkbox_box_widget.get()) - def checkboxAutoExportMessageLogsCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, checkbox_box_widget.get()) @@ -64,15 +61,6 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v config_window.sb__send_message_button_type.grid(row=row) row+=1 - config_window.sb__notice_xsoverlay = createSettingBoxCheckbox( - for_var_label_text=view_variable.VAR_LABEL_ENABLE_NOTICE_XSOVERLAY, - for_var_desc_text=view_variable.VAR_DESC_ENABLE_NOTICE_XSOVERLAY, - checkbox_attr_name="sb__checkbox_notice_xsoverlay", - command=lambda: checkboxNoticeXsoverlayCallback(config_window.sb__checkbox_notice_xsoverlay), - variable=view_variable.VAR_ENABLE_NOTICE_XSOVERLAY, - ) - config_window.sb__notice_xsoverlay.grid(row=row) - row+=1 config_window.sb__auto_export_message_logs = createSettingBoxAutoExportMessageLogs( for_var_label_text=view_variable.VAR_LABEL_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_vr/createSettingBox_Vr.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_vr/createSettingBox_Vr.py index 2afa8748..8bb78264 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_vr/createSettingBox_Vr.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_vr/createSettingBox_Vr.py @@ -11,10 +11,13 @@ def createSettingBox_Vr(setting_box_wrapper, config_window, settings, view_varia callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_OVERLAY_SMALL_LOG, switch_widget.get()) def buttonOpenOverlaySettingsWindow(_e): - print(_e) callFunctionIfCallable(view_variable.CALLBACK_SET_OPEN_OVERLAY_SETTINGS_WINDOW) + def checkboxNoticeXsoverlayCallback(checkbox_box_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, checkbox_box_widget.get()) + + row=0 config_window.sb__enable_overlay_small_log = createSettingBox_Overlay( for_var_label_text=view_variable.VAR_LABEL_ENABLE_OVERLAY_SMALL_LOG, @@ -26,4 +29,15 @@ def createSettingBox_Vr(setting_box_wrapper, config_window, settings, view_varia label_button_clicked_command=buttonOpenOverlaySettingsWindow, ) config_window.sb__enable_overlay_small_log.grid(row=row) + row+=1 + + + config_window.sb__notice_xsoverlay = createSettingBoxCheckbox( + for_var_label_text=view_variable.VAR_LABEL_ENABLE_NOTICE_XSOVERLAY, + for_var_desc_text=view_variable.VAR_DESC_ENABLE_NOTICE_XSOVERLAY, + checkbox_attr_name="sb__checkbox_notice_xsoverlay", + command=lambda: checkboxNoticeXsoverlayCallback(config_window.sb__checkbox_notice_xsoverlay), + variable=view_variable.VAR_ENABLE_NOTICE_XSOVERLAY, + ) + config_window.sb__notice_xsoverlay.grid(row=row, pady=0) row+=1 \ No newline at end of file diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 06c24932..447dfb1d 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -48,59 +48,59 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): # start from 3 main_topbar_column=3 - # # Overlay Settings Button - # vrct_gui.overlay_settings_container = CTkFrame( - # vrct_gui.main_topbar_container, - # corner_radius=settings.uism.UPDATE_AVAILABLE_BUTTON_CORNER_RADIUS, - # fg_color=settings.ctm.MAIN_BG_COLOR, - # cursor="hand2", - # ) - # vrct_gui.overlay_settings_container.grid(row=0, column=main_topbar_column, padx=settings.uism.UPDATE_AVAILABLE_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="nsw") - # # vrct_gui.overlay_settings_container.grid_remove() + # Overlay Settings Button + vrct_gui.overlay_settings_container = CTkFrame( + vrct_gui.main_topbar_container, + corner_radius=settings.uism.UPDATE_AVAILABLE_BUTTON_CORNER_RADIUS, + fg_color=settings.ctm.MAIN_BG_COLOR, + cursor="hand2", + ) + vrct_gui.overlay_settings_container.grid(row=0, column=main_topbar_column, padx=settings.uism.UPDATE_AVAILABLE_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="nsw") + # vrct_gui.overlay_settings_container.grid_remove() - # vrct_gui.overlay_settings_container.grid_rowconfigure((0,2), weight=1) + vrct_gui.overlay_settings_container.grid_rowconfigure((0,2), weight=1) - # vrct_gui.overlay_settings_icon = CTkLabel( - # vrct_gui.overlay_settings_container, - # text=None, - # corner_radius=0, - # height=0, - # image=CTkImage(settings.image_file.CONFIGURATION_ICON_DISABLED, size=settings.uism.UPDATE_AVAILABLE_BUTTON_SIZE) - # ) - # vrct_gui.overlay_settings_icon.grid(row=1, column=0, padx=(settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX, settings.uism.UPDATE_AVAILABLE_PADX_BETWEEN_LABEL_AND_ICON), pady=0) + vrct_gui.overlay_settings_icon = CTkLabel( + vrct_gui.overlay_settings_container, + text=None, + corner_radius=0, + height=0, + image=CTkImage(settings.image_file.CONFIGURATION_ICON_DISABLED, size=settings.uism.UPDATE_AVAILABLE_BUTTON_SIZE) + ) + vrct_gui.overlay_settings_icon.grid(row=1, column=0, padx=(settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX, settings.uism.UPDATE_AVAILABLE_PADX_BETWEEN_LABEL_AND_ICON), pady=0) - # vrct_gui.overlay_settings_label = CTkLabel( - # vrct_gui.overlay_settings_container, - # textvariable=view_variable.VAR_OVERLAY_SETTINGS, - # height=0, - # corner_radius=0, - # font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.UPDATE_AVAILABLE_BUTTON_FONT_SIZE, weight="normal"), - # anchor="e", - # text_color=settings.ctm.TOP_BAR_BUTTON_TEXT_COLOR, - # # text_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_TEXT_COLOR, - # ) - # # This "right padx +1" is for fixing a bug that sticks out from the frame. I don't know why that happens... - # vrct_gui.overlay_settings_label.grid(row=1, column=1, padx=(0,settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX+1), pady=0) + vrct_gui.overlay_settings_label = CTkLabel( + vrct_gui.overlay_settings_container, + textvariable=view_variable.VAR_OVERLAY_SETTINGS, + height=0, + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.UPDATE_AVAILABLE_BUTTON_FONT_SIZE, weight="normal"), + anchor="e", + text_color=settings.ctm.TOP_BAR_BUTTON_TEXT_COLOR, + # text_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_TEXT_COLOR, + ) + # This "right padx +1" is for fixing a bug that sticks out from the frame. I don't know why that happens... + vrct_gui.overlay_settings_label.grid(row=1, column=1, padx=(0,settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX+1), pady=0) - # bindButtonFunctionAndColor( - # target_widgets=[ - # vrct_gui.overlay_settings_container, - # vrct_gui.overlay_settings_label, - # vrct_gui.overlay_settings_icon, - # ], - # enter_color=settings.ctm.TOP_BAR_BUTTON_HOVERED_BG_COLOR, - # leave_color=settings.ctm.TOP_BAR_BUTTON_BG_COLOR, - # clicked_color=settings.ctm.TOP_BAR_BUTTON_CLICKED_BG_COLOR, - # buttonReleasedFunction=lambda e: callFunctionIfCallable(view_variable.CALLBACK_SET_OPEN_OVERLAY_SETTINGS_WINDOW), - # ) + bindButtonFunctionAndColor( + target_widgets=[ + vrct_gui.overlay_settings_container, + vrct_gui.overlay_settings_label, + vrct_gui.overlay_settings_icon, + ], + enter_color=settings.ctm.TOP_BAR_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.TOP_BAR_BUTTON_BG_COLOR, + clicked_color=settings.ctm.TOP_BAR_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=lambda e: callFunctionIfCallable(view_variable.CALLBACK_SET_OPEN_OVERLAY_SETTINGS_WINDOW), + ) - # main_topbar_column+=1 + main_topbar_column+=1 # Update Available Button vrct_gui.update_available_container = CTkFrame( vrct_gui.main_topbar_container, diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index d234fd3e..cc0d2d97 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -14,7 +14,7 @@ from ._PrintToTextbox import _PrintToTextbox from .main_window import createMainWindowWidgets from .config_window import ConfigWindow -# from .quick_settings_window import QuickSettingsWindow +from .quick_settings_window import QuickSettingsWindow from .ui_utils import setDefaultActiveTab, setGeometryToCenterOfScreen, fadeInAnimation from utils import callFunctionIfCallable @@ -131,12 +131,11 @@ class VRCT_GUI(CTk): view_variable=self._view_variable ) - # self.quick_settings_window = QuickSettingsWindow( - # vrct_gui=self, - # settings=self.settings.config_window, - # view_variable=self._view_variable - # ) - # self.quick_settings_window.show() + self.quick_settings_window = QuickSettingsWindow( + vrct_gui=self, + settings=self.settings.config_window, + view_variable=self._view_variable + ) self.selectable_languages_window = _CreateSelectableLanguagesWindow( vrct_gui=self, From adf027203883df7c2baec755f7a0a0b57a1aeff3 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 30 Apr 2024 00:51:30 +0900 Subject: [PATCH 05/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20Transl?= =?UTF-8?q?ation=20DeepL=20Pro=E7=89=88=E3=81=A7=E8=AA=8D=E8=A8=BC?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller.py b/controller.py index dbc88560..90cd93f5 100644 --- a/controller.py +++ b/controller.py @@ -577,7 +577,7 @@ def callbackSetCtranslate2WeightType(value): def callbackSetDeeplAuthKey(value): print("callbackSetDeeplAuthKey", str(value)) view.clearNotificationMessage() - if len(value) == 39: + if len(value) == 36 or len(value) == 39: result = model.authenticationTranslatorDeepLAuthKey(auth_key=value) if result is True: key = value From e9afde0be4af8362398fd94dd8e4b1cfc77ffd70 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 1 May 2024 03:44:33 +0900 Subject: [PATCH 06/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model:=20mic/speak?= =?UTF-8?q?er=E3=81=AE=E9=9F=B3=E5=A3=B0=E5=85=A5=E5=8A=9B=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92OFF=E6=99=82=E3=81=AB=E5=BC=B7=E5=88=B6?= =?UTF-8?q?=E7=B5=82=E4=BA=86=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 8 ++++---- requirements.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/model.py b/model.py index f1d00f0c..ae0cc25d 100644 --- a/model.py +++ b/model.py @@ -427,7 +427,7 @@ class Model: self.mic_print_transcript.stop() self.mic_print_transcript = None if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): - self.mic_audio_recorder.stop(wait_for_stop=True) + self.mic_audio_recorder.stop(wait_for_stop=False, forced_stop=True) self.mic_audio_recorder = None # if isinstance(self.mic_get_energy, threadFnc): # self.mic_get_energy.stop() @@ -465,7 +465,7 @@ class Model: self.mic_energy_plot_progressbar.stop() self.mic_energy_plot_progressbar = None if isinstance(self.mic_energy_recorder, SelectedMicEnergyRecorder): - self.mic_energy_recorder.stop(wait_for_stop=True) + self.mic_energy_recorder.stop(wait_for_stop=False, forced_stop=True) self.mic_energy_recorder = None def startSpeakerTranscript(self, fnc, error_fnc=None): @@ -540,7 +540,7 @@ class Model: self.speaker_print_transcript.stop() self.speaker_print_transcript = None if isinstance(self.speaker_audio_recorder, SelectedSpeakerEnergyAndAudioRecorder): - self.speaker_audio_recorder.stop(wait_for_stop=True) + self.speaker_audio_recorder.stop(wait_for_stop=False, forced_stop=True) self.speaker_audio_recorder = None # if isinstance(self.speaker_get_energy, threadFnc): # self.speaker_get_energy.stop() @@ -578,7 +578,7 @@ class Model: self.speaker_energy_plot_progressbar.stop() self.speaker_energy_plot_progressbar = None if isinstance(self.speaker_energy_recorder, SelectedSpeakerEnergyRecorder): - self.speaker_energy_recorder.stop(wait_for_stop=True) + self.speaker_energy_recorder.stop(wait_for_stop=False, forced_stop=True) self.speaker_energy_recorder = None def notificationXSOverlay(self, message): diff --git a/requirements.txt b/requirements.txt index f92a7ddf..14958d63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,5 @@ ctranslate2==4.1.0 faster-whisper==1.0.1 openvr==1.26.701 translators @ git+https://github.com/misyaguziya/translators@5.8.9 -SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.2 +SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.3 tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.2 \ No newline at end of file From 3aa39bd1ee9d8a9d52858ff3e9e4b894b3455997 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 1 May 2024 15:02:37 +0900 Subject: [PATCH 07/63] =?UTF-8?q?[Unmask]=20overlay=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E6=88=BB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 104 +++++++++++++++++++++--------------------- model.py | 122 +++++++++++++++++++++++++------------------------- 2 files changed, 113 insertions(+), 113 deletions(-) diff --git a/controller.py b/controller.py index dbc88560..329f0fc3 100644 --- a/controller.py +++ b/controller.py @@ -27,10 +27,10 @@ def callbackFilepathConfigFile(): def callbackQuitVrct(): setMainWindowGeometry() -# def callbackEnableEasterEgg(): -# config.IS_EASTER_EGG_ENABLED = True -# config.OVERLAY_UI_TYPE = "sakura" -# view.printToTextbox_enableEasterEgg() +def callbackEnableEasterEgg(): + config.IS_EASTER_EGG_ENABLED = True + config.OVERLAY_UI_TYPE = "sakura" + view.printToTextbox_enableEasterEgg() def setMainWindowGeometry(): PRE_SCALING_INT = strPctToInt(view.getPreUiScaling()) @@ -161,14 +161,14 @@ def receiveSpeakerMessage(message): xsoverlay_message = messageFormatter("RECEIVED", translation, message) model.notificationXSOverlay(xsoverlay_message) - # if model.overlay.initialized is False: - # model.startOverlay() - # else: - # if config.ENABLE_OVERLAY_SMALL_LOG is True: - # overlay_image = model.createOverlayImageShort(message, translation) - # model.updateOverlay(overlay_image) - # # overlay_image = model.createOverlayImageLong("receive", message, translation) - # # model.updateOverlay(overlay_image) + if config.ENABLE_OVERLAY_SMALL_LOG is True: + if model.overlay.initialized is False: + model.startOverlay() + if model.overlay.initialized is True: + overlay_image = model.createOverlayImageShort(message, translation) + model.updateOverlay(overlay_image) + # overlay_image = model.createOverlayImageLong("receive", message, translation) + # model.updateOverlay(overlay_image) # ------------Speaker2Chatbox------------ if config.ENABLE_SPEAKER2CHATBOX is True: @@ -858,44 +858,44 @@ def callbackSetWhisperWeightType(value): config.SELECTED_TRANSCRIPTION_ENGINE = "Google" view.showRestartButtonIfRequired() -# # VR Tab -# def callbackSetOverlaySettings(value, set_type:str): -# print("callbackSetOverlaySettings", value, set_type) -# pre_settings = config.OVERLAY_SETTINGS -# pre_settings[set_type] = value -# config.OVERLAY_SETTINGS = pre_settings -# match (set_type): -# case "opacity": -# model.updateOverlayImageOpacity() -# case "ui_scaling": -# model.updateOverlayImageUiScaling() +# VR Tab +def callbackSetOverlaySettings(value, set_type:str): + print("callbackSetOverlaySettings", value, set_type) + pre_settings = config.OVERLAY_SETTINGS + pre_settings[set_type] = value + config.OVERLAY_SETTINGS = pre_settings + match (set_type): + case "opacity": + model.updateOverlayImageOpacity() + case "ui_scaling": + model.updateOverlayImageUiScaling() -# def callbackSetEnableOverlaySmallLog(value): -# print("callbackSetEnableOverlaySmallLog", value) -# config.ENABLE_OVERLAY_SMALL_LOG = value +def callbackSetEnableOverlaySmallLog(value): + print("callbackSetEnableOverlaySmallLog", value) + config.ENABLE_OVERLAY_SMALL_LOG = value -# if config.ENABLE_OVERLAY_SMALL_LOG is True: -# pass -# else: -# if model.overlay.initialized is True: -# model.clearOverlayImage() + if config.ENABLE_OVERLAY_SMALL_LOG is True: + pass + else: + if model.overlay.initialized is True: + model.clearOverlayImage() -# def callbackSetOverlaySmallLogSettings(value, set_type:str): -# print("callbackSetOverlaySmallLogSettings", value, set_type) -# pre_settings = config.OVERLAY_SMALL_LOG_SETTINGS -# pre_settings[set_type] = value -# config.OVERLAY_SMALL_LOG_SETTINGS = pre_settings -# match (set_type): -# case "x_pos": -# model.updateOverlayPosition() -# case "y_pos": -# model.updateOverlayPosition() -# case "depth": -# model.updateOverlayPosition() -# case "display_duration": -# model.updateOverlayTimes() -# case "fadeout_duration": -# model.updateOverlayTimes() +def callbackSetOverlaySmallLogSettings(value, set_type:str): + print("callbackSetOverlaySmallLogSettings", value, set_type) + pre_settings = config.OVERLAY_SMALL_LOG_SETTINGS + pre_settings[set_type] = value + config.OVERLAY_SMALL_LOG_SETTINGS = pre_settings + match (set_type): + case "x_pos": + model.updateOverlayPosition() + case "y_pos": + model.updateOverlayPosition() + case "depth": + model.updateOverlayPosition() + case "display_duration": + model.updateOverlayTimes() + case "fadeout_duration": + model.updateOverlayTimes() # Others Tab def callbackSetEnableAutoClearMessageBox(value): @@ -1049,7 +1049,7 @@ def createMainWindow(splash): # set UI and callback view.register( common_registers={ - # "callback_enable_easter_egg": callbackEnableEasterEgg, + "callback_enable_easter_egg": callbackEnableEasterEgg, "callback_update_software": callbackUpdateSoftware, "callback_restart_software": callbackRestartSoftware, @@ -1137,10 +1137,10 @@ def createMainWindow(splash): "callback_set_use_whisper_feature": callbackSetUserWhisperFeature, "callback_set_whisper_weight_type": callbackSetWhisperWeightType, - # # VR Tab - # "callback_set_overlay_settings": callbackSetOverlaySettings, - # "callback_set_enable_overlay_small_log": callbackSetEnableOverlaySmallLog, - # "callback_set_overlay_small_log_settings": callbackSetOverlaySmallLogSettings, + # VR Tab + "callback_set_overlay_settings": callbackSetOverlaySettings, + "callback_set_enable_overlay_small_log": callbackSetEnableOverlaySmallLog, + "callback_set_overlay_small_log_settings": callbackSetOverlaySmallLogSettings, # Others Tab "callback_set_enable_auto_clear_chatbox": callbackSetEnableAutoClearMessageBox, diff --git a/model.py b/model.py index ae0cc25d..8095bd94 100644 --- a/model.py +++ b/model.py @@ -26,8 +26,8 @@ from models.translation.translation_languages import translation_lang from models.transcription.transcription_languages import transcription_lang from models.translation.translation_utils import checkCTranslate2Weight from models.transcription.transcription_whisper import checkWhisperWeight -# from models.overlay.overlay import Overlay -# from models.overlay.overlay_image import OverlayImage +from models.overlay.overlay import Overlay +from models.overlay.overlay_image import OverlayImage from config import config @@ -72,18 +72,18 @@ class Model: self.previous_receive_message = "" self.translator = Translator() self.keyword_processor = KeywordProcessor() - # self.overlay = Overlay( - # config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], - # config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"], - # config.OVERLAY_SMALL_LOG_SETTINGS["depth"], - # config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"], - # config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"], - # config.OVERLAY_SETTINGS["opacity"], - # config.OVERLAY_SETTINGS["ui_scaling"], - # ) - # self.overlay_image = OverlayImage() - # self.pre_overlay_message = None - # self.th_overlay = None + self.overlay = Overlay( + config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["depth"], + config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"], + config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"], + config.OVERLAY_SETTINGS["opacity"], + config.OVERLAY_SETTINGS["ui_scaling"], + ) + self.overlay_image = OverlayImage() + self.pre_overlay_message = None + self.th_overlay = None def checkCTranslatorCTranslate2ModelWeight(self): return checkCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE) @@ -584,65 +584,65 @@ class Model: def notificationXSOverlay(self, message): xsoverlayForVRCT(content=f"{message}") - # def createOverlayImageShort(self, message, translation): - # your_language = config.TARGET_LANGUAGE - # target_language = config.SOURCE_LANGUAGE - # ui_type = config.OVERLAY_UI_TYPE - # self.pre_overlay_message = { - # "message" : message, - # "your_language" : your_language, - # "translation" : translation, - # "target_language" : target_language, - # "ui_type" : ui_type, - # } - # return self.overlay_image.createOverlayImageShort(message, your_language, translation, target_language, ui_type) + def createOverlayImageShort(self, message, translation): + your_language = config.TARGET_LANGUAGE + target_language = config.SOURCE_LANGUAGE + ui_type = config.OVERLAY_UI_TYPE + self.pre_overlay_message = { + "message" : message, + "your_language" : your_language, + "translation" : translation, + "target_language" : target_language, + "ui_type" : ui_type, + } + return self.overlay_image.createOverlayImageShort(message, your_language, translation, target_language, ui_type) # def createOverlayImageLong(self, message_type, message, translation): # your_language = config.TARGET_LANGUAGE if message_type == "receive" else config.SOURCE_LANGUAGE # target_language = config.SOURCE_LANGUAGE if message_type == "receive" else config.TARGET_LANGUAGE # return self.overlay_image.create_overlay_image_long(message_type, message, your_language, translation, target_language) - # def clearOverlayImage(self): - # if self.overlay.initialized is True: - # self.overlay.uiManager.uiClear() + def clearOverlayImage(self): + if self.overlay.initialized is True: + self.overlay.uiManager.uiClear() - # def updateOverlay(self, img): - # if self.overlay.initialized is True: - # self.overlay.uiManager.uiUpdate(img) + def updateOverlay(self, img): + if self.overlay.initialized is True: + self.overlay.uiManager.uiUpdate(img) - # def startOverlay(self): - # if self.overlay.initialized is False: - # self.overlay.init() + def startOverlay(self): + if self.overlay.initialized is False: + self.overlay.init() - # if self.overlay.initialized is True and self.th_overlay is None: - # self.th_overlay = Thread(target=self.overlay.startOverlay) - # self.th_overlay.daemon = True - # self.th_overlay.start() + if self.overlay.initialized is True and self.th_overlay is None: + self.th_overlay = Thread(target=self.overlay.startOverlay) + self.th_overlay.daemon = True + self.th_overlay.start() - # def updateOverlayPosition(self): - # if self.overlay.initialized is True: - # pos = (config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]) - # self.overlay.uiManager.setPosition(pos) - # depth = config.OVERLAY_SMALL_LOG_SETTINGS["depth"] - # self.overlay.uiManager.setDepth(depth) - # self.overlay.uiManager.posUpdate() + def updateOverlayPosition(self): + if self.overlay.initialized is True: + pos = (config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]) + self.overlay.uiManager.setPosition(pos) + depth = config.OVERLAY_SMALL_LOG_SETTINGS["depth"] + self.overlay.uiManager.setDepth(depth) + self.overlay.uiManager.posUpdate() - # def updateOverlayTimes(self): - # if self.overlay.initialized is True: - # display_duration = config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"] - # self.overlay.uiManager.setFadeTime(display_duration) - # fadeout_duration = config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"] - # self.overlay.uiManager.setFadeInterval(fadeout_duration) - # self.overlay.uiManager.update() + def updateOverlayTimes(self): + if self.overlay.initialized is True: + display_duration = config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"] + self.overlay.uiManager.setFadeTime(display_duration) + fadeout_duration = config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"] + self.overlay.uiManager.setFadeInterval(fadeout_duration) + self.overlay.uiManager.update() - # def updateOverlayImageOpacity(self): - # if self.overlay.initialized is True: - # opacity = config.OVERLAY_SETTINGS["opacity"] - # self.overlay.uiManager.setTransparency(opacity) + def updateOverlayImageOpacity(self): + if self.overlay.initialized is True: + opacity = config.OVERLAY_SETTINGS["opacity"] + self.overlay.uiManager.setTransparency(opacity) - # def updateOverlayImageUiScaling(self): - # if self.overlay.initialized is True: - # ui_scaling = config.OVERLAY_SETTINGS["ui_scaling"] - # self.overlay.uiManager.setUiScaling(ui_scaling) + def updateOverlayImageUiScaling(self): + if self.overlay.initialized is True: + ui_scaling = config.OVERLAY_SETTINGS["ui_scaling"] + self.overlay.uiManager.setUiScaling(ui_scaling) model = Model() \ No newline at end of file From cc7e21a6095751f6f828a4b7a1b73c599e6c5c38 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 1 May 2024 20:05:33 +0900 Subject: [PATCH 08/63] [Update] Config Window: Add switch function. VRChat Mic Mute Sync. --- controller.py | 5 +++++ locales/en.yml | 4 ++++ locales/ja.yml | 5 +++++ view.py | 7 +++++++ .../setting_box_others/createSettingBox_Others.py | 14 ++++++++++++++ 5 files changed, 35 insertions(+) diff --git a/controller.py b/controller.py index a5a4dbda..455cbfb7 100644 --- a/controller.py +++ b/controller.py @@ -855,6 +855,10 @@ def callbackSetEnableAutoExportMessageLogs(value): else: model.stopLogger() +def callbackSetEnableVrcMicMuteSync(value): + print("callbackSetEnableVrcMicMuteSync", value) + config.ENABLE_MUTE_DETECT = value + def callbackSetEnableSendMessageToVrc(value): print("callbackSetEnableSendMessageToVrc", value) config.ENABLE_SEND_MESSAGE_TO_VRC = value @@ -1103,6 +1107,7 @@ def createMainWindow(splash): "callback_set_send_message_button_type": callbackSetSendMessageButtonType, "callback_set_enable_notice_xsoverlay": callbackSetEnableNoticeXsoverlay, "callback_set_enable_auto_export_message_logs": callbackSetEnableAutoExportMessageLogs, + "callback_set_enable_vrc_mic_mute_sync": callbackSetEnableVrcMicMuteSync, "callback_set_enable_send_message_to_vrc": callbackSetEnableSendMessageToVrc, # Others(Message Formats(Send) "callback_set_send_message_format": callbackSetSendMessageFormat, diff --git a/locales/en.yml b/locales/en.yml index 8199c81d..48c861e1 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -224,6 +224,10 @@ config_window: label: Auto Export Message Logs desc: Automatically export the conversation messages as a text file. + vrc_mic_mute_sync: + label: VRChat Mic Mute Sync + desc: VRCT will not send the message to VRChat while VRChat's mic is muted. + send_message_to_vrc: label: Send Message To VRChat desc: There is a way to use it without sending messages to VRChat, but it is not supported. Enable this feature when you intend to send a message to VRChat. diff --git a/locales/ja.yml b/locales/ja.yml index 2c0fd2be..009d68f1 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -224,6 +224,11 @@ config_window: label: 会話ログを自動的に保存する desc: テキストファイルとしてログがlogsフォルダ内に保存されます。 + vrc_mic_mute_sync: + label: VRChatマイクミュート同期機能 + desc: VRChatのマイクがミュートされている間は、メッセージをVRChatに送信しません。 + + send_message_to_vrc: label: VRChatにメッセージを送信する desc: "サポート対象外ですが、VRChatにメッセージを送信せずに使う方法があります。送信したい場合、この機能を有効にする事を忘れないでください。" diff --git a/view.py b/view.py index a7466fa8..56858a4f 100644 --- a/view.py +++ b/view.py @@ -446,6 +446,12 @@ class View(): VAR_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=BooleanVar(value=config.ENABLE_LOGGER), + VAR_LABEL_ENABLE_VRC_MIC_MUTE_SYNC=StringVar(value=i18n.t("config_window.vrc_mic_mute_sync.label")), + VAR_DESC_ENABLE_VRC_MIC_MUTE_SYNC=StringVar(value=i18n.t("config_window.vrc_mic_mute_sync.desc")), + CALLBACK_SET_ENABLE_VRC_MIC_MUTE_SYNC=None, + VAR_ENABLE_VRC_MIC_MUTE_SYNC=BooleanVar(value=config.ENABLE_MUTE_DETECT), + + VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value=i18n.t("config_window.send_message_to_vrc.label")), VAR_DESC_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value=i18n.t("config_window.send_message_to_vrc.desc")), CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC=None, @@ -684,6 +690,7 @@ class View(): self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY=config_window_registers.get("callback_set_enable_notice_xsoverlay", None) self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=config_window_registers.get("callback_set_enable_auto_export_message_logs", None) + self.view_variable.CALLBACK_SET_ENABLE_VRC_MIC_MUTE_SYNC=config_window_registers.get("callback_set_enable_vrc_mic_mute_sync", None) self.view_variable.CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC=config_window_registers.get("callback_set_enable_send_message_to_vrc", None) self.view_variable.CALLBACK_SET_SEND_MESSAGE_FORMAT=config_window_registers.get("callback_set_send_message_format", None) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index b32116fe..4348c2db 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -27,6 +27,9 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v def buttonAutoExportMessageLogsCallback(): callFunctionIfCallable(view_variable.CALLBACK_OPEN_FILEPATH_LOGS) + def checkboxVrcMuteSyncCallback(checkbox_box_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_VRC_MIC_MUTE_SYNC, checkbox_box_widget.get()) + def checkboxEnableSendMessageToVrcCallback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC, checkbox_box_widget.get()) @@ -86,6 +89,17 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v row+=1 + config_window.sb__vrc_mic_mute_sync = createSettingBoxCheckbox( + for_var_label_text=view_variable.VAR_LABEL_ENABLE_VRC_MIC_MUTE_SYNC, + for_var_desc_text=view_variable.VAR_DESC_ENABLE_VRC_MIC_MUTE_SYNC, + checkbox_attr_name="sb__checkbox_vrc_mic_mute_sync", + command=lambda: checkboxVrcMuteSyncCallback(config_window.sb__checkbox_vrc_mic_mute_sync), + variable=view_variable.VAR_ENABLE_VRC_MIC_MUTE_SYNC, + ) + config_window.sb__vrc_mic_mute_sync.grid(row=row) + row+=1 + + config_window.sb__enable_send_message_to_vrc = createSettingBoxCheckbox( for_var_label_text=view_variable.VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC, for_var_desc_text=view_variable.VAR_DESC_ENABLE_SEND_MESSAGE_TO_VRC, From bd0a0e45921b841851ada910a0947a18c318d11f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 2 May 2024 14:25:21 +0900 Subject: [PATCH 09/63] =?UTF-8?q?[Chore]=20=E5=A4=89=E6=95=B0=E5=90=8D?= =?UTF-8?q?=E5=A4=89=E6=9B=B4:=20config.ENABLE=5FMUTE=5FDETECT=20->=20conf?= =?UTF-8?q?ig.ENABLE=5FVRC=5FMIC=5FMUTE=5FSYNC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 14 +++++++------- controller.py | 6 +++--- model.py | 4 ++-- view.py | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/config.py b/config.py index 87efb0d6..8fd1942e 100644 --- a/config.py +++ b/config.py @@ -831,14 +831,14 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - @json_serializable('ENABLE_MUTE_DETECT') - def ENABLE_MUTE_DETECT(self): - return self._ENABLE_MUTE_DETECT + @json_serializable('ENABLE_VRC_MIC_MUTE_SYNC') + def ENABLE_VRC_MIC_MUTE_SYNC(self): + return self._ENABLE_VRC_MIC_MUTE_SYNC - @ENABLE_MUTE_DETECT.setter - def ENABLE_MUTE_DETECT(self, value): + @ENABLE_VRC_MIC_MUTE_SYNC.setter + def ENABLE_VRC_MIC_MUTE_SYNC(self, value): if isinstance(value, bool): - self._ENABLE_MUTE_DETECT = value + self._ENABLE_VRC_MIC_MUTE_SYNC = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @@ -1006,7 +1006,7 @@ class Config: self._ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = False # Speaker2Chatbox self._ENABLE_SPEAKER2CHATBOX_PASS = "000000000" self._ENABLE_LOGGER = False - self._ENABLE_MUTE_DETECT = False + self._ENABLE_VRC_MIC_MUTE_SYNC = False self._IS_CONFIG_WINDOW_COMPACT_MODE = False def load_config(self): diff --git a/controller.py b/controller.py index 455cbfb7..6f4f586c 100644 --- a/controller.py +++ b/controller.py @@ -857,7 +857,7 @@ def callbackSetEnableAutoExportMessageLogs(value): def callbackSetEnableVrcMicMuteSync(value): print("callbackSetEnableVrcMicMuteSync", value) - config.ENABLE_MUTE_DETECT = value + config.ENABLE_VRC_MIC_MUTE_SYNC = value def callbackSetEnableSendMessageToVrc(value): print("callbackSetEnableSendMessageToVrc", value) @@ -919,7 +919,7 @@ def createDictOSCReceiveParameters(): param_Voice = "Voice" def change_handler_mute(address, osc_arguments): - if config.ENABLE_MUTE_DETECT is True: + if config.ENABLE_VRC_MIC_MUTE_SYNC is True: if osc_arguments is True and change_handler_mute.status_mute is False: model.stopPutQueueMicAudio() change_handler_mute.status_mute = True @@ -928,7 +928,7 @@ def createDictOSCReceiveParameters(): change_handler_mute.status_mute = False def change_handler_voice(address, osc_arguments): - if config.ENABLE_MUTE_DETECT is True: + if config.ENABLE_VRC_MIC_MUTE_SYNC is True: if change_handler_mute.status_mute is True: model.startPutQueueMicAudio() change_handler_mute.status_mute = False diff --git a/model.py b/model.py index 700e5f0e..8bf62554 100644 --- a/model.py +++ b/model.py @@ -230,7 +230,7 @@ class Model: param_Voice = "Voice" def change_handler_mute(address, osc_arguments): - if config.ENABLE_MUTE_DETECT is True: + if config.ENABLE_VRC_MIC_MUTE_SYNC is True: if osc_arguments is True and self.mute_status is False: self.stopPutQueueMicAudio() self.mute_status = True @@ -239,7 +239,7 @@ class Model: self.mute_status = False def change_handler_voice(address, osc_arguments): - if config.ENABLE_MUTE_DETECT is True: + if config.ENABLE_VRC_MIC_MUTE_SYNC is True: if self.mute_status is True: self.startPutQueueMicAudio() self.mute_status = False diff --git a/view.py b/view.py index 56858a4f..38cfaae2 100644 --- a/view.py +++ b/view.py @@ -449,7 +449,7 @@ class View(): VAR_LABEL_ENABLE_VRC_MIC_MUTE_SYNC=StringVar(value=i18n.t("config_window.vrc_mic_mute_sync.label")), VAR_DESC_ENABLE_VRC_MIC_MUTE_SYNC=StringVar(value=i18n.t("config_window.vrc_mic_mute_sync.desc")), CALLBACK_SET_ENABLE_VRC_MIC_MUTE_SYNC=None, - VAR_ENABLE_VRC_MIC_MUTE_SYNC=BooleanVar(value=config.ENABLE_MUTE_DETECT), + VAR_ENABLE_VRC_MIC_MUTE_SYNC=BooleanVar(value=config.ENABLE_VRC_MIC_MUTE_SYNC), VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value=i18n.t("config_window.send_message_to_vrc.label")), From ee8c25dea9b489bddd8461d148708688bc0f079d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 2 May 2024 14:57:45 +0900 Subject: [PATCH 10/63] =?UTF-8?q?=F0=9F=9A=A7=20[WIP/TEST]=20Model=20:=20O?= =?UTF-8?q?verlay=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E9=80=90=E6=AC=A1?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=81=8B=E3=82=89=E5=AE=9A=E6=9C=9F=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 17 ++- model.py | 49 +++----- models/overlay/overlay.py | 2 +- models/overlay/overlay_2.py | 232 ++++++++++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 41 deletions(-) create mode 100644 models/overlay/overlay_2.py diff --git a/controller.py b/controller.py index 329f0fc3..9447cadf 100644 --- a/controller.py +++ b/controller.py @@ -164,11 +164,10 @@ def receiveSpeakerMessage(message): if config.ENABLE_OVERLAY_SMALL_LOG is True: if model.overlay.initialized is False: model.startOverlay() - if model.overlay.initialized is True: - overlay_image = model.createOverlayImageShort(message, translation) - model.updateOverlay(overlay_image) - # overlay_image = model.createOverlayImageLong("receive", message, translation) - # model.updateOverlay(overlay_image) + overlay_image = model.createOverlayImageShort(message, translation) + model.updateOverlay(overlay_image) + # overlay_image = model.createOverlayImageLong("receive", message, translation) + # model.updateOverlay(overlay_image) # ------------Speaker2Chatbox------------ if config.ENABLE_SPEAKER2CHATBOX is True: @@ -191,6 +190,7 @@ def startTranscriptionReceiveMessage(): def stopTranscriptionReceiveMessage(): model.stopSpeakerTranscript() + model.shutdownOverlay() view.setMainWindowAllWidgetsStatusToNormal() def startThreadingTranscriptionReceiveMessage(): @@ -874,11 +874,8 @@ def callbackSetEnableOverlaySmallLog(value): print("callbackSetEnableOverlaySmallLog", value) config.ENABLE_OVERLAY_SMALL_LOG = value - if config.ENABLE_OVERLAY_SMALL_LOG is True: - pass - else: - if model.overlay.initialized is True: - model.clearOverlayImage() + if config.ENABLE_OVERLAY_SMALL_LOG is False: + model.clearOverlayImage() def callbackSetOverlaySmallLogSettings(value, set_type:str): print("callbackSetOverlaySmallLogSettings", value, set_type) diff --git a/model.py b/model.py index 8095bd94..e18a05eb 100644 --- a/model.py +++ b/model.py @@ -26,7 +26,7 @@ from models.translation.translation_languages import translation_lang from models.transcription.transcription_languages import transcription_lang from models.translation.translation_utils import checkCTranslate2Weight from models.transcription.transcription_whisper import checkWhisperWeight -from models.overlay.overlay import Overlay +from models.overlay.overlay_2 import Overlay from models.overlay.overlay_image import OverlayImage from config import config @@ -603,46 +603,35 @@ class Model: # return self.overlay_image.create_overlay_image_long(message_type, message, your_language, translation, target_language) def clearOverlayImage(self): - if self.overlay.initialized is True: - self.overlay.uiManager.uiClear() + self.overlay.clearImage() def updateOverlay(self, img): - if self.overlay.initialized is True: - self.overlay.uiManager.uiUpdate(img) + self.overlay.setImage(img) def startOverlay(self): - if self.overlay.initialized is False: - self.overlay.init() - - if self.overlay.initialized is True and self.th_overlay is None: - self.th_overlay = Thread(target=self.overlay.startOverlay) - self.th_overlay.daemon = True - self.th_overlay.start() + self.overlay.startOverlay() def updateOverlayPosition(self): - if self.overlay.initialized is True: - pos = (config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]) - self.overlay.uiManager.setPosition(pos) - depth = config.OVERLAY_SMALL_LOG_SETTINGS["depth"] - self.overlay.uiManager.setDepth(depth) - self.overlay.uiManager.posUpdate() + pos = (config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]) + self.overlay.setPosition(pos) + depth = config.OVERLAY_SMALL_LOG_SETTINGS["depth"] + self.overlay.setDepth(depth) def updateOverlayTimes(self): - if self.overlay.initialized is True: - display_duration = config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"] - self.overlay.uiManager.setFadeTime(display_duration) - fadeout_duration = config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"] - self.overlay.uiManager.setFadeInterval(fadeout_duration) - self.overlay.uiManager.update() + display_duration = config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"] + self.overlay.setFadeTime(display_duration) + fadeout_duration = config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"] + self.overlay.setFadeInterval(fadeout_duration) def updateOverlayImageOpacity(self): - if self.overlay.initialized is True: - opacity = config.OVERLAY_SETTINGS["opacity"] - self.overlay.uiManager.setTransparency(opacity) + opacity = config.OVERLAY_SETTINGS["opacity"] + self.overlay.setTransparency(opacity) def updateOverlayImageUiScaling(self): - if self.overlay.initialized is True: - ui_scaling = config.OVERLAY_SETTINGS["ui_scaling"] - self.overlay.uiManager.setUiScaling(ui_scaling) + ui_scaling = config.OVERLAY_SETTINGS["ui_scaling"] + self.overlay.setUiScaling(ui_scaling) + + def shutdownOverlay(self): + self.overlay.shutdown() model = Model() \ No newline at end of file diff --git a/models/overlay/overlay.py b/models/overlay/overlay.py index 87cd12b1..a2787151 100644 --- a/models/overlay/overlay.py +++ b/models/overlay/overlay.py @@ -229,7 +229,7 @@ if __name__ == '__main__': return self.fnc(*self._args, **self._kwargs) - overlay = Overlay() + overlay = Overlay(0, 0, 1, 1, 1, 1, 1) overlay_image = OverlayImage() if overlay.initialized is False: diff --git a/models/overlay/overlay_2.py b/models/overlay/overlay_2.py new file mode 100644 index 00000000..7462f365 --- /dev/null +++ b/models/overlay/overlay_2.py @@ -0,0 +1,232 @@ +import ctypes +import psutil +import asyncio +import ctypes +import time +import openvr +from PIL import Image +from queue import Queue +from threading import Thread + +def checkSteamvrRunning() -> bool: + for proc in psutil.process_iter(): + if "vrserver.exe" == proc.name().lower(): + return True + return False + +def mat34Id(): + arr = openvr.HmdMatrix34_t() + arr[0][0] = 1 + arr[1][1] = 1 + arr[2][2] = 1 + return arr + +class Overlay: + def __init__(self, x, y , depth, fade_time, fade_interval, transparency, ui_scaling): + self.initialized = False + settings = { + "Color": [1, 1, 1], + "Transparency": transparency, + "Normalized_icon_X_position": x, + "Normalized_icon_Y_position": y, + "Icon_plane_depth": depth, + "Fade_time": fade_time, + "Fade_interval": fade_interval, + "Ui_scaling": ui_scaling, + } + self.settings = settings + self.system = None + self.overlay = None + self.handle = None + self.image_queue = Queue() + self.lastUpdate = time.monotonic() + self.thread_overlay = None + + def init(self): + try: + if checkSteamvrRunning() is True: + self.system = openvr.init(openvr.VRApplication_Background) + self.overlay = openvr.IVROverlay() + self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "SOverlay_Speaker2log_UI") + + self.setImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) + self.updateImage() + self.setColor(self.settings['Color']) + self.updateColor() + self.setTransparency(self.settings['Transparency']) + self.updateTransparency() + self.setUiScaling(self.settings['Ui_scaling']) + self.updateUiScaling() + self.setPosition((self.settings["Normalized_icon_X_position"], self.settings["Normalized_icon_Y_position"])) + self.updatePosition() + self.overlay.showOverlay(self.handle) + self.initialized = True + except Exception as e: + print("Could not initialise OpenVR", e) + + def setImage(self, img): + self.image_queue.put(img) + + def updateImage(self): + img = self.image_queue.get() + width, height = img.size + img = img.tobytes() + img = (ctypes.c_char * len(img)).from_buffer_copy(img) + self.overlay.setOverlayRaw(self.handle, img, width, height, 4) + + def clearImage(self): + while self.image_queue.empty() is False: + self.image_queue.get() + self.setImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) + + def setColor(self, col): + """ + col is a 3-tuple representing (r, g, b) + """ + self.settings["Color"] = col + + def updateColor(self): + r, g, b = self.settings["Color"] + self.overlay.setOverlayColor(self.handle, r, g, b) + + def setTransparency(self, transparency): + self.settings['Transparency'] = transparency + + def updateTransparency(self): + self.overlay.setOverlayAlpha(self.handle, self.settings['Transparency']) + + def setUiScaling(self, ui_scaling): + self.settings['Ui_scaling'] = ui_scaling + + def updateUiScaling(self): + self.overlay.setOverlayWidthInMeters(self.handle, self.settings['Ui_scaling']) + + def setPosition(self, pos): + """ + pos is a 2-tuple representing normalized (x, y) + """ + self.settings["Normalized_icon_X_position"] = pos[0] + self.settings["Normalized_icon_Y_position"] = pos[1] + + def setDepth(self, depth): + self.settings["Icon_plane_depth"] = depth + + def updatePosition(self): + self.transform = mat34Id() # no rotation required for HMD attachment + + # assign position + self.transform[0][3] = self.settings["Normalized_icon_X_position"] * self.settings['Icon_plane_depth'] + self.transform[1][3] = self.settings["Normalized_icon_Y_position"] * self.settings['Icon_plane_depth'] + self.transform[2][3] = - self.settings['Icon_plane_depth'] + + self.overlay.setOverlayTransformTrackedDeviceRelative( + self.handle, + openvr.k_unTrackedDeviceIndex_Hmd, + self.transform + ) + + def setFadeTime(self, fade_time): + self.settings['Fade_time'] = fade_time + + def setFadeInterval(self, fade_interval): + self.settings['Fade_interval'] = fade_interval + + def checkActive(self): + try: + if self.system is not None and self.initialized is True: + new_event = openvr.VREvent_t() + while self.system.pollNextEvent(new_event): + if new_event.eventType == openvr.VREvent_Quit: + return False + return True + except Exception as e: + print("Could not check SteamVR running") + print(e) + return False + + def evaluateTransparencyFade(self, lastUpdate, currentTime): + if (currentTime - lastUpdate) > self.settings['Fade_time']: + timeThroughInterval = currentTime - lastUpdate - self.settings['Fade_time'] + self.fadeRatio = 1 - timeThroughInterval / self.settings['Fade_interval'] + if self.fadeRatio < 0: + self.fadeRatio = 0 + self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings['Transparency']) + + def update(self): + if self.image_queue.empty() is False: + self.updateImage() + self.updateTransparency() + self.lastUpdate = time.monotonic() + + self.updateUiScaling() + self.updatePosition() + + currTime = time.monotonic() + if self.settings['Fade_interval'] != 0: + self.evaluateTransparencyFade(self.lastUpdate, currTime) + else: + self.updateTransparency() + + async def mainloop(self): + while self.checkActive() is True: + startTime = time.monotonic() + self.update() + sleepTime = (1 / 60) - (time.monotonic() - startTime) + if sleepTime > 0: + await asyncio.sleep(sleepTime) + + async def initMain(self): + await self.mainloop() + + def main(self): + self.init() + if self.initialized is True: + asyncio.run(self.initMain()) + + def startOverlay(self): + self.thread_overlay = Thread(target=self.main) + self.thread_overlay.daemon = True + self.thread_overlay.start() + + def shutdown(self): + if self.thread_overlay is not None: + ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(self.thread_overlay.ident), ctypes.py_object(SystemExit)) + if self.overlay is not None: + self.overlay.destroyOverlay(self.handle) + if self.system is not None: + openvr.shutdown() + self.initialized = False + +if __name__ == '__main__': + import threading + from overlay_image import OverlayImage + + overlay_image = OverlayImage() + overlay = Overlay(0, 0, 1, 1, 1, 1, 1) + thread = threading.Thread(target=overlay.startOverlay) + thread.start() + + for i in range(10): + print(f"time sleep {i}s") + time.sleep(1) + + # Example usage + img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") + overlay.setImage(img) + for i in range(10): + overlay.setPosition((i/10, i/10)) + time.sleep(0.1) + + img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") + overlay.setImage(img) + + for i in range(10): + overlay.setPosition((i/10, i/10)) + time.sleep(0.1) + + time.sleep(10) + + overlay.shutdown() + for i in range(10): + print(f"time sleep {i}s") + time.sleep(1) From 0eba5443b40ce32073513b6e5555d7488308f8d8 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 2 May 2024 18:24:09 +0900 Subject: [PATCH 11/63] =?UTF-8?q?=F0=9F=9A=A7=20[WIP/TEST]=20Model=20:=20O?= =?UTF-8?q?verlay=E3=81=AE=E5=87=A6=E7=90=86=E3=81=8B=E3=82=89async?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/overlay/overlay_2.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/models/overlay/overlay_2.py b/models/overlay/overlay_2.py index 7462f365..5238bde1 100644 --- a/models/overlay/overlay_2.py +++ b/models/overlay/overlay_2.py @@ -167,21 +167,18 @@ class Overlay: else: self.updateTransparency() - async def mainloop(self): + def mainloop(self): while self.checkActive() is True: startTime = time.monotonic() self.update() sleepTime = (1 / 60) - (time.monotonic() - startTime) if sleepTime > 0: - await asyncio.sleep(sleepTime) - - async def initMain(self): - await self.mainloop() + time.sleep(sleepTime) def main(self): self.init() if self.initialized is True: - asyncio.run(self.initMain()) + self.mainloop() def startOverlay(self): self.thread_overlay = Thread(target=self.main) @@ -191,10 +188,13 @@ class Overlay: def shutdown(self): if self.thread_overlay is not None: ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(self.thread_overlay.ident), ctypes.py_object(SystemExit)) + self.thread_overlay = None if self.overlay is not None: self.overlay.destroyOverlay(self.handle) + self.overlay = None if self.system is not None: openvr.shutdown() + self.system = None self.initialized = False if __name__ == '__main__': From b97273eace5b7b2b57b30c707c076d27c4e90799 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 3 May 2024 01:04:39 +0900 Subject: [PATCH 12/63] =?UTF-8?q?=F0=9F=9A=A7=20[WIP/TEST]=20Model=20:=20O?= =?UTF-8?q?verlay=E3=81=AE=E5=87=A6=E7=90=86=E3=81=A7=E7=94=BB=E5=83=8F?= =?UTF-8?q?=E3=82=92=E5=9B=BA=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 2 ++ img/test_chatbox.png | Bin 0 -> 33053 bytes models/overlay/overlay_2.py | 46 +++++++++++++----------------------- 3 files changed, 19 insertions(+), 29 deletions(-) create mode 100644 img/test_chatbox.png diff --git a/controller.py b/controller.py index 9447cadf..dfaf5506 100644 --- a/controller.py +++ b/controller.py @@ -164,8 +164,10 @@ def receiveSpeakerMessage(message): if config.ENABLE_OVERLAY_SMALL_LOG is True: if model.overlay.initialized is False: model.startOverlay() + print("model.startOverlay()") overlay_image = model.createOverlayImageShort(message, translation) model.updateOverlay(overlay_image) + print("model.updateOverlay(overlay_image)") # overlay_image = model.createOverlayImageLong("receive", message, translation) # model.updateOverlay(overlay_image) diff --git a/img/test_chatbox.png b/img/test_chatbox.png new file mode 100644 index 0000000000000000000000000000000000000000..9d96629967f0fae35ef0392ab6eb3b7dfaf43e09 GIT binary patch literal 33053 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%0AfkfgHl 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 literal 0 HcmV?d00001 diff --git a/models/overlay/overlay_2.py b/models/overlay/overlay_2.py index 5238bde1..42aa5097 100644 --- a/models/overlay/overlay_2.py +++ b/models/overlay/overlay_2.py @@ -1,6 +1,6 @@ import ctypes import psutil -import asyncio +from os import path as os_path import ctypes import time import openvr @@ -68,7 +68,8 @@ class Overlay: self.image_queue.put(img) def updateImage(self): - img = self.image_queue.get() + _ = self.image_queue.get() + img = Image.open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", "test_chatbox.png")) width, height = img.size img = img.tobytes() img = (ctypes.c_char * len(img)).from_buffer_copy(img) @@ -171,7 +172,7 @@ class Overlay: while self.checkActive() is True: startTime = time.monotonic() self.update() - sleepTime = (1 / 60) - (time.monotonic() - startTime) + sleepTime = (1 / 16) - (time.monotonic() - startTime) if sleepTime > 0: time.sleep(sleepTime) @@ -198,35 +199,22 @@ class Overlay: self.initialized = False if __name__ == '__main__': - import threading from overlay_image import OverlayImage - overlay_image = OverlayImage() - overlay = Overlay(0, 0, 1, 1, 1, 1, 1) - thread = threading.Thread(target=overlay.startOverlay) - thread.start() - for i in range(10): - print(f"time sleep {i}s") - time.sleep(1) + for i in range(100): + print(i) + overlay = Overlay(0, 0, 1, 1, 1, 1, 1) + overlay.startOverlay() + # time.sleep(0.1) - # Example usage - img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") - overlay.setImage(img) - for i in range(10): - overlay.setPosition((i/10, i/10)) - time.sleep(0.1) + # Example usage + img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") + overlay.setImage(img) + time.sleep(0.5) - img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") - overlay.setImage(img) + img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") + overlay.setImage(img) + time.sleep(0.5) - for i in range(10): - overlay.setPosition((i/10, i/10)) - time.sleep(0.1) - - time.sleep(10) - - overlay.shutdown() - for i in range(10): - print(f"time sleep {i}s") - time.sleep(1) + overlay.shutdown() From 6f318a8b05edfaee0905c5b2705e23de380a0265 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 3 May 2024 17:14:17 +0900 Subject: [PATCH 13/63] =?UTF-8?q?=F0=9F=9A=A7=20[WIP/TEST]=20Model=20:=20O?= =?UTF-8?q?verlay=E3=81=AE=E5=87=A6=E7=90=86=E3=81=A7=E7=94=BB=E5=83=8F?= =?UTF-8?q?=E7=94=9F=E6=88=90=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/controller.py b/controller.py index dfaf5506..aa298552 100644 --- a/controller.py +++ b/controller.py @@ -165,8 +165,8 @@ def receiveSpeakerMessage(message): if model.overlay.initialized is False: model.startOverlay() print("model.startOverlay()") - overlay_image = model.createOverlayImageShort(message, translation) - model.updateOverlay(overlay_image) + # overlay_image = model.createOverlayImageShort(message, translation) + model.updateOverlay(1) print("model.updateOverlay(overlay_image)") # overlay_image = model.createOverlayImageLong("receive", message, translation) # model.updateOverlay(overlay_image) From 0577756dc10cbf87385a29279fb49cd1bece74bd Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 3 May 2024 23:05:42 +0900 Subject: [PATCH 14/63] =?UTF-8?q?=F0=9F=9A=A7=20[WIP/TEST]=20Model=20:=20O?= =?UTF-8?q?verlay=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E6=9C=80=E5=B0=8F?= =?UTF-8?q?=E9=99=90=E3=81=BE=E3=81=A7=E3=81=AB=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 25 +++++++++++++------- model.py | 2 +- models/overlay/overlay_2.py | 41 +++++++++++++++------------------ models/overlay/overlay_image.py | 6 ++--- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/controller.py b/controller.py index aa298552..bdd640d7 100644 --- a/controller.py +++ b/controller.py @@ -162,11 +162,8 @@ def receiveSpeakerMessage(message): model.notificationXSOverlay(xsoverlay_message) if config.ENABLE_OVERLAY_SMALL_LOG is True: - if model.overlay.initialized is False: - model.startOverlay() - print("model.startOverlay()") - # overlay_image = model.createOverlayImageShort(message, translation) - model.updateOverlay(1) + overlay_image = model.createOverlayImageShort(message, translation) + model.updateOverlay(overlay_image) print("model.updateOverlay(overlay_image)") # overlay_image = model.createOverlayImageLong("receive", message, translation) # model.updateOverlay(overlay_image) @@ -192,7 +189,6 @@ def startTranscriptionReceiveMessage(): def stopTranscriptionReceiveMessage(): model.stopSpeakerTranscript() - model.shutdownOverlay() view.setMainWindowAllWidgetsStatusToNormal() def startThreadingTranscriptionReceiveMessage(): @@ -429,6 +425,14 @@ def callbackToggleTranscriptionReceive(is_turned_on): stopThreadingTranscriptionReceiveMessage() view.changeTranscriptionDisplayStatus("SPEAKER_OFF") + if config.ENABLE_TRANSCRIPTION_RECEIVE is True and config.ENABLE_OVERLAY_SMALL_LOG is True: + if model.overlay.initialized is False: + model.startOverlay() + print("model.startOverlay()") + elif config.ENABLE_TRANSCRIPTION_RECEIVE is False: + model.shutdownOverlay() + print("model.shutdownOverlay()") + def callbackToggleForeground(is_turned_on): config.ENABLE_FOREGROUND = is_turned_on if config.ENABLE_FOREGROUND is True: @@ -876,8 +880,13 @@ def callbackSetEnableOverlaySmallLog(value): print("callbackSetEnableOverlaySmallLog", value) config.ENABLE_OVERLAY_SMALL_LOG = value - if config.ENABLE_OVERLAY_SMALL_LOG is False: - model.clearOverlayImage() + if config.ENABLE_OVERLAY_SMALL_LOG is True and config.ENABLE_TRANSCRIPTION_RECEIVE is True: + if model.overlay.initialized is False: + model.startOverlay() + print("model.startOverlay()") + elif config.ENABLE_OVERLAY_SMALL_LOG is False: + model.shutdownOverlay() + print("model.shutdownOverlay()") def callbackSetOverlaySmallLogSettings(value, set_type:str): print("callbackSetOverlaySmallLogSettings", value, set_type) diff --git a/model.py b/model.py index e18a05eb..6c220c41 100644 --- a/model.py +++ b/model.py @@ -606,7 +606,7 @@ class Model: self.overlay.clearImage() def updateOverlay(self, img): - self.overlay.setImage(img) + self.overlay.updateImage(img) def startOverlay(self): self.overlay.startOverlay() diff --git a/models/overlay/overlay_2.py b/models/overlay/overlay_2.py index 42aa5097..24956f14 100644 --- a/models/overlay/overlay_2.py +++ b/models/overlay/overlay_2.py @@ -1,11 +1,11 @@ import ctypes import psutil -from os import path as os_path +# from os import path as os_path import ctypes import time import openvr from PIL import Image -from queue import Queue +# from queue import Queue from threading import Thread def checkSteamvrRunning() -> bool: @@ -38,7 +38,7 @@ class Overlay: self.system = None self.overlay = None self.handle = None - self.image_queue = Queue() + # self.image_queue = Queue() self.lastUpdate = time.monotonic() self.thread_overlay = None @@ -49,8 +49,7 @@ class Overlay: self.overlay = openvr.IVROverlay() self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "SOverlay_Speaker2log_UI") - self.setImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) - self.updateImage() + self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) self.setColor(self.settings['Color']) self.updateColor() self.setTransparency(self.settings['Transparency']) @@ -64,21 +63,23 @@ class Overlay: except Exception as e: print("Could not initialise OpenVR", e) - def setImage(self, img): - self.image_queue.put(img) + # def setImage(self, img): + # self.image_queue.put(img) - def updateImage(self): - _ = self.image_queue.get() - img = Image.open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", "test_chatbox.png")) + def updateImage(self, img): + # _ = self.image_queue.get() + # img = Image.open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", "test_chatbox.png")) width, height = img.size img = img.tobytes() img = (ctypes.c_char * len(img)).from_buffer_copy(img) self.overlay.setOverlayRaw(self.handle, img, width, height, 4) + self.updateTransparency() + self.lastUpdate = time.monotonic() def clearImage(self): - while self.image_queue.empty() is False: - self.image_queue.get() - self.setImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) + # while self.image_queue.empty() is False: + # self.image_queue.get() + self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) def setColor(self, col): """ @@ -154,13 +155,8 @@ class Overlay: self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings['Transparency']) def update(self): - if self.image_queue.empty() is False: - self.updateImage() - self.updateTransparency() - self.lastUpdate = time.monotonic() - - self.updateUiScaling() - self.updatePosition() + # self.updateUiScaling() + # self.updatePosition() currTime = time.monotonic() if self.settings['Fade_interval'] != 0: @@ -175,6 +171,7 @@ class Overlay: sleepTime = (1 / 16) - (time.monotonic() - startTime) if sleepTime > 0: time.sleep(sleepTime) + self.shutdown() def main(self): self.init() @@ -210,11 +207,11 @@ if __name__ == '__main__': # Example usage img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") - overlay.setImage(img) + overlay.updateImage(img) time.sleep(0.5) img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") - overlay.setImage(img) + overlay.updateImage(img) time.sleep(0.5) overlay.shutdown() diff --git a/models/overlay/overlay_image.py b/models/overlay/overlay_image.py index d1607179..c52756bb 100644 --- a/models/overlay/overlay_image.py +++ b/models/overlay/overlay_image.py @@ -140,9 +140,9 @@ class OverlayImage: def getUiSize(self): return { - "width": int(960*4), - "height": int(23*4), - "font_size": int(23*4), + "width": int(960), + "height": int(23), + "font_size": int(23), } def getUiColors(self, ui_type): From a5962f699b70e58d356229705080725dca65f312 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 4 May 2024 16:31:36 +0900 Subject: [PATCH 15/63] =?UTF-8?q?=F0=9F=9A=A7=20[WIP/TEST]=20Model=20:=20O?= =?UTF-8?q?verlay=E3=81=AE=E5=87=A6=E7=90=86=E3=81=AB=E3=83=9E=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 7 ++++--- models/overlay/overlay_2.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/controller.py b/controller.py index bdd640d7..48eb42db 100644 --- a/controller.py +++ b/controller.py @@ -162,9 +162,10 @@ def receiveSpeakerMessage(message): model.notificationXSOverlay(xsoverlay_message) if config.ENABLE_OVERLAY_SMALL_LOG is True: - overlay_image = model.createOverlayImageShort(message, translation) - model.updateOverlay(overlay_image) - print("model.updateOverlay(overlay_image)") + if model.overlay.initialized is True: + overlay_image = model.createOverlayImageShort(message, translation) + model.updateOverlay(overlay_image) + print("model.updateOverlay(overlay_image)") # overlay_image = model.createOverlayImageLong("receive", message, translation) # model.updateOverlay(overlay_image) diff --git a/models/overlay/overlay_2.py b/models/overlay/overlay_2.py index 24956f14..9ea8e532 100644 --- a/models/overlay/overlay_2.py +++ b/models/overlay/overlay_2.py @@ -203,7 +203,7 @@ if __name__ == '__main__': print(i) overlay = Overlay(0, 0, 1, 1, 1, 1, 1) overlay.startOverlay() - # time.sleep(0.1) + time.sleep(1) # Example usage img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") From 313682cbbdf3e2a88e36ccc3922db7da8e8799d4 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 4 May 2024 17:25:39 +0900 Subject: [PATCH 16/63] =?UTF-8?q?=F0=9F=9A=A7=20[WIP/TEST]=20Model=20:=20O?= =?UTF-8?q?verlay=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E8=B5=B7=E5=8B=95?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=82=89=E3=81=97=E3=81=A3=E3=81=B1=E3=81=AA?= =?UTF-8?q?=E3=81=97=E3=81=AB=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 10 ++++----- models/overlay/overlay_2.py | 43 ++++++++++++++++++------------------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/controller.py b/controller.py index 48eb42db..fb505fdc 100644 --- a/controller.py +++ b/controller.py @@ -427,12 +427,11 @@ def callbackToggleTranscriptionReceive(is_turned_on): view.changeTranscriptionDisplayStatus("SPEAKER_OFF") if config.ENABLE_TRANSCRIPTION_RECEIVE is True and config.ENABLE_OVERLAY_SMALL_LOG is True: - if model.overlay.initialized is False: + if model.overlay.initialized is False and model.overlay.checkSteamvrRunning() is True: model.startOverlay() print("model.startOverlay()") elif config.ENABLE_TRANSCRIPTION_RECEIVE is False: - model.shutdownOverlay() - print("model.shutdownOverlay()") + pass def callbackToggleForeground(is_turned_on): config.ENABLE_FOREGROUND = is_turned_on @@ -882,12 +881,11 @@ def callbackSetEnableOverlaySmallLog(value): config.ENABLE_OVERLAY_SMALL_LOG = value if config.ENABLE_OVERLAY_SMALL_LOG is True and config.ENABLE_TRANSCRIPTION_RECEIVE is True: - if model.overlay.initialized is False: + if model.overlay.initialized is False and model.overlay.checkSteamvrRunning() is True: model.startOverlay() print("model.startOverlay()") elif config.ENABLE_OVERLAY_SMALL_LOG is False: - model.shutdownOverlay() - print("model.shutdownOverlay()") + pass def callbackSetOverlaySmallLogSettings(value, set_type:str): print("callbackSetOverlaySmallLogSettings", value, set_type) diff --git a/models/overlay/overlay_2.py b/models/overlay/overlay_2.py index 9ea8e532..929be276 100644 --- a/models/overlay/overlay_2.py +++ b/models/overlay/overlay_2.py @@ -1,5 +1,6 @@ +import os import ctypes -import psutil +from psutil import process_iter # from os import path as os_path import ctypes import time @@ -8,12 +9,6 @@ from PIL import Image # from queue import Queue from threading import Thread -def checkSteamvrRunning() -> bool: - for proc in psutil.process_iter(): - if "vrserver.exe" == proc.name().lower(): - return True - return False - def mat34Id(): arr = openvr.HmdMatrix34_t() arr[0][0] = 1 @@ -44,22 +39,21 @@ class Overlay: def init(self): try: - if checkSteamvrRunning() is True: - self.system = openvr.init(openvr.VRApplication_Background) - self.overlay = openvr.IVROverlay() - self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "SOverlay_Speaker2log_UI") + self.system = openvr.init(openvr.VRApplication_Background) + self.overlay = openvr.IVROverlay() + self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "SOverlay_Speaker2log_UI") - self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) - self.setColor(self.settings['Color']) - self.updateColor() - self.setTransparency(self.settings['Transparency']) - self.updateTransparency() - self.setUiScaling(self.settings['Ui_scaling']) - self.updateUiScaling() - self.setPosition((self.settings["Normalized_icon_X_position"], self.settings["Normalized_icon_Y_position"])) - self.updatePosition() - self.overlay.showOverlay(self.handle) - self.initialized = True + self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) + self.setColor(self.settings['Color']) + self.updateColor() + self.setTransparency(self.settings['Transparency']) + self.updateTransparency() + self.setUiScaling(self.settings['Ui_scaling']) + self.updateUiScaling() + self.setPosition((self.settings["Normalized_icon_X_position"], self.settings["Normalized_icon_Y_position"])) + self.updatePosition() + self.overlay.showOverlay(self.handle) + self.initialized = True except Exception as e: print("Could not initialise OpenVR", e) @@ -195,6 +189,11 @@ class Overlay: self.system = None self.initialized = False + @staticmethod + def checkSteamvrRunning() -> bool: + _proc_name = "vrmonitor.exe" if os.name == 'nt' else "vrmonitor" + return _proc_name in (p.name() for p in process_iter()) + if __name__ == '__main__': from overlay_image import OverlayImage overlay_image = OverlayImage() From 3ea40e1bd312f0f2741c2a78b93201d58f727be2 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 5 May 2024 02:46:15 +0900 Subject: [PATCH 17/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model:=20mic/speak?= =?UTF-8?q?er=E3=81=AE=E9=9F=B3=E5=A3=B0=E5=85=A5=E5=8A=9B=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92OFF=E6=99=82=E3=81=AB=E5=BC=B7=E5=88=B6?= =?UTF-8?q?=E7=B5=82=E4=BA=86=E3=81=99=E3=82=8B=E5=87=A6=E7=90=86=E3=81=8C?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=9F=E3=81=9F?= =?UTF-8?q?=E3=82=81=E3=80=81=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/model.py b/model.py index ae0cc25d..f30dd0bb 100644 --- a/model.py +++ b/model.py @@ -427,7 +427,7 @@ class Model: self.mic_print_transcript.stop() self.mic_print_transcript = None if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): - self.mic_audio_recorder.stop(wait_for_stop=False, forced_stop=True) + self.mic_audio_recorder.stop() self.mic_audio_recorder = None # if isinstance(self.mic_get_energy, threadFnc): # self.mic_get_energy.stop() @@ -465,7 +465,7 @@ class Model: self.mic_energy_plot_progressbar.stop() self.mic_energy_plot_progressbar = None if isinstance(self.mic_energy_recorder, SelectedMicEnergyRecorder): - self.mic_energy_recorder.stop(wait_for_stop=False, forced_stop=True) + self.mic_energy_recorder.stop() self.mic_energy_recorder = None def startSpeakerTranscript(self, fnc, error_fnc=None): @@ -540,7 +540,7 @@ class Model: self.speaker_print_transcript.stop() self.speaker_print_transcript = None if isinstance(self.speaker_audio_recorder, SelectedSpeakerEnergyAndAudioRecorder): - self.speaker_audio_recorder.stop(wait_for_stop=False, forced_stop=True) + self.speaker_audio_recorder.stop() self.speaker_audio_recorder = None # if isinstance(self.speaker_get_energy, threadFnc): # self.speaker_get_energy.stop() @@ -578,7 +578,7 @@ class Model: self.speaker_energy_plot_progressbar.stop() self.speaker_energy_plot_progressbar = None if isinstance(self.speaker_energy_recorder, SelectedSpeakerEnergyRecorder): - self.speaker_energy_recorder.stop(wait_for_stop=False, forced_stop=True) + self.speaker_energy_recorder.stop() self.speaker_energy_recorder = None def notificationXSOverlay(self, message): From 5566ebe401d8aa73fb4c39df631d6f68457fbed9 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 6 May 2024 01:29:44 +0900 Subject: [PATCH 18/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model:=20mic/speak?= =?UTF-8?q?er=E3=81=AE=E9=9F=B3=E5=A3=B0=E5=85=A5=E5=8A=9B=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=81=AEThread=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3(energy=E3=81=AF=E3=81=BE=E3=81=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 38 ++++++++++--------- .../transcription_transcriber.py | 5 +++ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/model.py b/model.py index f30dd0bb..71c3d92f 100644 --- a/model.py +++ b/model.py @@ -36,19 +36,17 @@ class threadFnc(Thread): super(threadFnc, self).__init__(daemon=daemon, *args, **kwargs) self.fnc = fnc self.end_fnc = end_fnc - self._stop = Event() - def stop(self): - self._stop.set() - def stopped(self): - return self._stop.is_set() - def run(self): - while True: - if self.stopped(): - if callable(self.end_fnc): - self.end_fnc() - return - self.fnc(*self._args, **self._kwargs) + self.loop = True + def stop(self): + self.loop = False + + def run(self): + while self.loop: + self.fnc(*self._args, **self._kwargs) + if callable(self.end_fnc): + self.end_fnc() + return class Model: _instance = None @@ -392,9 +390,10 @@ class Model: ) def sendMicTranscript(): try: - self.mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) - message = self.mic_transcriber.getTranscript() - fnc(message) + res = self.mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) + if res: + message = self.mic_transcriber.getTranscript() + fnc(message) except Exception: pass @@ -425,6 +424,7 @@ class Model: def stopMicTranscript(self): if isinstance(self.mic_print_transcript, threadFnc): self.mic_print_transcript.stop() + self.mic_print_transcript.join() self.mic_print_transcript = None if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): self.mic_audio_recorder.stop() @@ -505,9 +505,10 @@ class Model: ) def sendSpeakerTranscript(): try: - self.speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) - message = self.speaker_transcriber.getTranscript() - fnc(message) + res = self.speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) + if res: + message = self.speaker_transcriber.getTranscript() + fnc(message) except Exception: pass @@ -538,6 +539,7 @@ class Model: def stopSpeakerTranscript(self): if isinstance(self.speaker_print_transcript, threadFnc): self.speaker_print_transcript.stop() + self.speaker_print_transcript.join() self.speaker_print_transcript = None if isinstance(self.speaker_audio_recorder, SelectedSpeakerEnergyAndAudioRecorder): self.speaker_audio_recorder.stop() diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index c5a6cbff..aaaca210 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -1,3 +1,4 @@ +import time from io import BytesIO from threading import Event import wave @@ -38,6 +39,9 @@ class AudioTranscriber: self.transcription_engine = "Whisper" def transcribeAudioQueue(self, audio_queue, language, country): + if audio_queue.empty(): + time.sleep(0.1) + return False audio, time_spoken = audio_queue.get() self.updateLastSampleAndPhraseStatus(audio, time_spoken) @@ -75,6 +79,7 @@ class AudioTranscriber: if text != '': self.updateTranscript(text) + return True def updateLastSampleAndPhraseStatus(self, data, time_spoken): source_info = self.audio_sources From 6552dc4aa8c57682c6c19d5ccdba0938d02388cc Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 6 May 2024 02:16:45 +0900 Subject: [PATCH 19/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model:=20ThreadFnc?= =?UTF-8?q?=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 4 ++-- models/transcription/transcription_transcriber.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/model.py b/model.py index 71c3d92f..bc0b2706 100644 --- a/model.py +++ b/model.py @@ -9,7 +9,7 @@ from datetime import datetime from logging import getLogger, FileHandler, Formatter, INFO from time import sleep from queue import Queue -from threading import Thread, Event +from threading import Thread from requests import get as requests_get import webbrowser @@ -33,7 +33,7 @@ from config import config class threadFnc(Thread): def __init__(self, fnc, end_fnc=None, daemon=True, *args, **kwargs): - super(threadFnc, self).__init__(daemon=daemon, *args, **kwargs) + super(threadFnc, self).__init__(daemon=daemon, target=fnc, *args, **kwargs) self.fnc = fnc self.end_fnc = end_fnc self.loop = True diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index aaaca210..56d9d979 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -40,7 +40,7 @@ class AudioTranscriber: def transcribeAudioQueue(self, audio_queue, language, country): if audio_queue.empty(): - time.sleep(0.1) + time.sleep(0.01) return False audio, time_spoken = audio_queue.get() self.updateLastSampleAndPhraseStatus(audio, time_spoken) From 39faa8707fab6f68823388a51c38de299dfb0b0c Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 6 May 2024 03:21:02 +0900 Subject: [PATCH 20/63] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20Model=20:?= =?UTF-8?q?=20custom=5Fspeech=5Frecognition=E3=81=AEversion=E3=82=92?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 14958d63..3e4c175b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,5 @@ ctranslate2==4.1.0 faster-whisper==1.0.1 openvr==1.26.701 translators @ git+https://github.com/misyaguziya/translators@5.8.9 -SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.3 +SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4 tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.2 \ No newline at end of file From 047a3a35bfd4d09518dc969d59af162c130fe719 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 6 May 2024 16:57:15 +0900 Subject: [PATCH 21/63] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20Model=20:?= =?UTF-8?q?=20OSC=20Parameter=E3=82=92=E8=83=BD=E5=8B=95=E7=9A=84=E3=81=AB?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E3=81=99=E3=82=8B=E5=87=A6=E7=90=86=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0/mute=E5=90=8C=E6=9C=9F=E6=A9=9F=E8=83=BD?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 37 +++++++++++++++++-------------------- models/osc/osc_tools.py | 9 ++++++++- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/model.py b/model.py index 700e5f0e..d393759f 100644 --- a/model.py +++ b/model.py @@ -17,7 +17,7 @@ from typing import Callable from flashtext import KeywordProcessor from models.translation.translation_translator import Translator from models.transcription.transcription_utils import getInputDevices, getOutputDevices -from models.osc.osc_tools import sendTyping, sendMessage, receiveOscParameters +from models.osc.osc_tools import sendTyping, sendMessage, receiveOscParameters, getOSCParameterValue from models.transcription.transcription_recorder import SelectedMicEnergyAndAudioRecorder, SelectedSpeakerEnergyAndAudioRecorder from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakerEnergyRecorder from models.transcription.transcription_transcriber import AudioTranscriber @@ -79,8 +79,7 @@ class Model: self.speaker_energy_plot_progressbar = None self.translator = Translator() self.keyword_processor = KeywordProcessor() - self.mic_audio_queue = ConditionalQueue() - # self.mic_energy_queue = ConditionalQueue() + self.mic_audio_queue = None self.mute_status = False def checkCTranslatorCTranslate2ModelWeight(self): @@ -227,7 +226,7 @@ class Model: def startReceiveOSC(self): osc_parameter_prefix = "/avatar/parameters/" param_MuteSelf = "MuteSelf" - param_Voice = "Voice" + self.mute_status = getOSCParameterValue(address=osc_parameter_prefix + param_MuteSelf) def change_handler_mute(address, osc_arguments): if config.ENABLE_MUTE_DETECT is True: @@ -238,18 +237,11 @@ class Model: self.startPutQueueMicAudio() self.mute_status = False - def change_handler_voice(address, osc_arguments): - if config.ENABLE_MUTE_DETECT is True: - if self.mute_status is True: - self.startPutQueueMicAudio() - self.mute_status = False - dict_filter_and_target = { osc_parameter_prefix + param_MuteSelf: change_handler_mute, - osc_parameter_prefix + param_Voice: change_handler_voice, } - th_osc_server = threadFnc(receiveOscParameters, args=(dict_filter_and_target,)) + th_osc_server = Thread(target=receiveOscParameters, args=(dict_filter_and_target,)) th_osc_server.daemon = True th_osc_server.start() @@ -330,6 +322,9 @@ class Model: self.mic_audio_queue = ConditionalQueue() # self.mic_energy_queue = ConditionalQueue() + if config.ENABLE_MUTE_DETECT is True and self.mute_status is True: + model.stopPutQueueMicAudio() + mic_device = choice_mic_device[0] record_timeout = config.INPUT_MIC_RECORD_TIMEOUT phase_timeout = config.INPUT_MIC_PHRASE_TIMEOUT @@ -388,16 +383,18 @@ class Model: # self.mic_get_energy.start() def startPutQueueMicAudio(self): - while not self.mic_audio_queue.empty(): - self.mic_audio_queue.get() - self.mic_audio_queue.set_flag(True) - # self.mic_energy_queue.set_flag(True) + if isinstance(self.mic_audio_queue, ConditionalQueue): + while not self.mic_audio_queue.empty(): + self.mic_audio_queue.get() + self.mic_audio_queue.set_flag(True) + # self.mic_energy_queue.set_flag(True) def stopPutQueueMicAudio(self): - self.mic_audio_queue.set_flag(False) - while not self.mic_audio_queue.empty(): - self.mic_audio_queue.get() - # self.mic_energy_queue.set_flag(False) + if isinstance(self.mic_audio_queue, ConditionalQueue): + self.mic_audio_queue.set_flag(False) + while not self.mic_audio_queue.empty(): + self.mic_audio_queue.get() + # self.mic_energy_queue.set_flag(False) def stopMicTranscript(self): if isinstance(self.mic_print_transcript, threadFnc): diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py index cf9804ac..e7aa7d23 100644 --- a/models/osc/osc_tools.py +++ b/models/osc/osc_tools.py @@ -1,10 +1,10 @@ from time import sleep -from threading import Thread from pythonosc import osc_message_builder from pythonosc import udp_client from pythonosc import dispatcher from pythonosc import osc_server from tinyoscquery.queryservice import OSCQueryService +from tinyoscquery.query import OSCQueryBrowser, OSCQueryClient from tinyoscquery.utility import get_open_udp_port, get_open_tcp_port # send OSC message typing @@ -48,6 +48,13 @@ def sendChangeVoice(ip_address="127.0.0.1", port=9000): sendInputVoice(flag=0, ip_address=ip_address, port=port) sleep(0.05) +def getOSCParameterValue(address, server_name="VRChat-Client"): + browser = OSCQueryBrowser() + sleep(1) + service = browser.find_service_by_name(server_name) + oscq = OSCQueryClient(service) + mute_self_node = oscq.query_node(address) + return mute_self_node.value[0] def receiveOscParameters(dict_filter_and_target, ip_address="127.0.0.1", title="VRCT"): osc_port = get_open_udp_port() From 7f6c9a53ac0becf126cd5571bf228503ee226119 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 6 May 2024 17:30:09 +0900 Subject: [PATCH 22/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20OSC=20?= =?UTF-8?q?mute=E5=90=8C=E6=9C=9F=E6=A9=9F=E8=83=BD=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 1 + model.py | 35 +++++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/controller.py b/controller.py index 6f4f586c..599963e3 100644 --- a/controller.py +++ b/controller.py @@ -858,6 +858,7 @@ def callbackSetEnableAutoExportMessageLogs(value): def callbackSetEnableVrcMicMuteSync(value): print("callbackSetEnableVrcMicMuteSync", value) config.ENABLE_VRC_MIC_MUTE_SYNC = value + model.changePutQueueMicAudio() def callbackSetEnableSendMessageToVrc(value): print("callbackSetEnableSendMessageToVrc", value) diff --git a/model.py b/model.py index fe38fe02..2cdbd49b 100644 --- a/model.py +++ b/model.py @@ -223,25 +223,24 @@ class Model: def oscSendMessage(message): sendMessage(message, config.OSC_IP_ADDRESS, config.OSC_PORT) + @staticmethod + def getMuteSelfStatus(): + return getOSCParameterValue(address="/avatar/parameters/MuteSelf") + def startReceiveOSC(self): osc_parameter_prefix = "/avatar/parameters/" param_MuteSelf = "MuteSelf" - self.mute_status = getOSCParameterValue(address=osc_parameter_prefix + param_MuteSelf) + self.mute_status = self.getMuteSelfStatus() def change_handler_mute(address, osc_arguments): - if config.ENABLE_VRC_MIC_MUTE_SYNC is True: - if osc_arguments is True and self.mute_status is False: + if osc_arguments is True and self.mute_status is False: + self.mute_status = True + if config.ENABLE_VRC_MIC_MUTE_SYNC is True: self.stopPutQueueMicAudio() - self.mute_status = True - elif osc_arguments is False and self.mute_status is True: + elif osc_arguments is False and self.mute_status is True: + self.mute_status = False + if config.ENABLE_VRC_MIC_MUTE_SYNC is True: self.startPutQueueMicAudio() - self.mute_status = False - - def change_handler_voice(address, osc_arguments): - if config.ENABLE_VRC_MIC_MUTE_SYNC is True: - if self.mute_status is True: - self.startPutQueueMicAudio() - self.mute_status = False dict_filter_and_target = { osc_parameter_prefix + param_MuteSelf: change_handler_mute, @@ -328,8 +327,7 @@ class Model: self.mic_audio_queue = ConditionalQueue() # self.mic_energy_queue = ConditionalQueue() - if config.ENABLE_MUTE_DETECT is True and self.mute_status is True: - model.stopPutQueueMicAudio() + self.changePutQueueMicAudio() mic_device = choice_mic_device[0] record_timeout = config.INPUT_MIC_RECORD_TIMEOUT @@ -402,6 +400,15 @@ class Model: self.mic_audio_queue.get() # self.mic_energy_queue.set_flag(False) + def changePutQueueMicAudio(self): + if config.ENABLE_VRC_MIC_MUTE_SYNC is True: + if self.mute_status is True: + self.stopPutQueueMicAudio() + else: + self.startPutQueueMicAudio() + else: + self.startPutQueueMicAudio() + def stopMicTranscript(self): if isinstance(self.mic_print_transcript, threadFnc): self.mic_print_transcript.stop() From 9241a7cf1f97b324623e15eba2b55d25786672d4 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 6 May 2024 18:37:13 +0900 Subject: [PATCH 23/63] =?UTF-8?q?=F0=9F=9A=A7[WIP/TEST]=20Model=20:=20mute?= =?UTF-8?q?=E5=90=8C=E6=9C=9F=E3=81=A7OFF=E6=99=82=E3=81=AB=E3=82=AD?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E3=81=AB=E5=85=A5=E3=81=A3=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=82=8B=E3=82=82=E3=81=AE=E3=81=AF=E6=96=87=E5=AD=97=E8=B5=B7?= =?UTF-8?q?=E3=81=93=E3=81=97=E3=82=92=E5=87=BA=E5=8A=9B=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/model.py b/model.py index 2cdbd49b..1da98538 100644 --- a/model.py +++ b/model.py @@ -396,9 +396,11 @@ class Model: def stopPutQueueMicAudio(self): if isinstance(self.mic_audio_queue, ConditionalQueue): self.mic_audio_queue.set_flag(False) - while not self.mic_audio_queue.empty(): - self.mic_audio_queue.get() - # self.mic_energy_queue.set_flag(False) + # queueを空にする場合を考慮 + if False: + while not self.mic_audio_queue.empty(): + self.mic_audio_queue.get() + # self.mic_energy_queue.set_flag(False) def changePutQueueMicAudio(self): if config.ENABLE_VRC_MIC_MUTE_SYNC is True: From 1401562ddbda139c61b5db4dd19677984b16a46d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 7 May 2024 10:13:10 +0900 Subject: [PATCH 24/63] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F[Remove]=20Model=20:?= =?UTF-8?q?=20=E4=B8=8D=E8=A6=81=E3=81=AA=E3=82=B3=E3=83=BC=E3=83=89(creat?= =?UTF-8?q?eDictOSCReceiveParameters)=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/controller.py b/controller.py index 599963e3..43b11844 100644 --- a/controller.py +++ b/controller.py @@ -914,35 +914,6 @@ def callbackSetEnableSendReceivedMessageToVrc(value): config.ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = value # ---------------------Speaker2Chatbox--------------------- -def createDictOSCReceiveParameters(): - osc_parameter_prefix = "/avatar/parameters/" - param_MuteSelf = "MuteSelf" - param_Voice = "Voice" - - def change_handler_mute(address, osc_arguments): - if config.ENABLE_VRC_MIC_MUTE_SYNC is True: - if osc_arguments is True and change_handler_mute.status_mute is False: - model.stopPutQueueMicAudio() - change_handler_mute.status_mute = True - elif osc_arguments is False and change_handler_mute.status_mute is True: - model.startPutQueueMicAudio() - change_handler_mute.status_mute = False - - def change_handler_voice(address, osc_arguments): - if config.ENABLE_VRC_MIC_MUTE_SYNC is True: - if change_handler_mute.status_mute is True: - model.startPutQueueMicAudio() - change_handler_mute.status_mute = False - - change_handler_mute.status_mute = False - - dict_filter_and_target = { - osc_parameter_prefix + param_MuteSelf: change_handler_mute, - osc_parameter_prefix + param_Voice: change_handler_voice, - } - return dict_filter_and_target - - # Advanced Settings Tab def callbackSetOscIpAddress(value): if value == "": From 8b866e1577146fc490d2e329b71e6a3161783190 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 7 May 2024 10:15:38 +0900 Subject: [PATCH 25/63] =?UTF-8?q?=F0=9F=9A=A7[WIP/TEST]=20Model=20:=20mute?= =?UTF-8?q?=E5=90=8C=E6=9C=9F=E5=87=A6=E7=90=86=E3=81=A7=E6=96=87=E5=AD=97?= =?UTF-8?q?=E8=B5=B7=E3=81=93=E3=81=97=E3=81=AE=E5=90=8C=E6=9C=9F=E3=82=92?= =?UTF-8?q?Queue=E3=81=8B=E3=82=89pause/resume=E3=81=AE=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 72 ++++++++++--------- .../transcription/transcription_recorder.py | 4 +- requirements.txt | 2 +- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/model.py b/model.py index 1da98538..bd60c615 100644 --- a/model.py +++ b/model.py @@ -9,7 +9,7 @@ from datetime import datetime from logging import getLogger, FileHandler, Formatter, INFO from time import sleep from queue import Queue -from threading import Thread, Event +from threading import Thread from requests import get as requests_get import webbrowser @@ -30,33 +30,30 @@ from config import config class threadFnc(Thread): def __init__(self, fnc, end_fnc=None, daemon=True, *args, **kwargs): - super(threadFnc, self).__init__(daemon=daemon, *args, **kwargs) + super(threadFnc, self).__init__(daemon=daemon, target=fnc, *args, **kwargs) self.fnc = fnc self.end_fnc = end_fnc - self._stop = Event() + self.loop = True + self._pause = False + def stop(self): - self._stop.set() - def stopped(self): - return self._stop.isSet() + self.loop = False + + def pause(self): + self._pause = True + + def resume(self): + self._pause = False + def run(self): - while True: - if self.stopped(): - if callable(self.end_fnc): - self.end_fnc() - return + while self.loop: self.fnc(*self._args, **self._kwargs) + while self._pause: + sleep(0.1) -class ConditionalQueue(Queue): - def __init__(self, flag=True, *args, **kwargs): - super().__init__(*args, **kwargs) - self.flag = flag - - def put(self, item, block=True, timeout=None): - if self.flag is True: - super().put(item, block, timeout) - - def set_flag(self, value): - self.flag = value + if callable(self.end_fnc): + self.end_fnc() + return class Model: _instance = None @@ -325,8 +322,8 @@ class Model: pass return - self.mic_audio_queue = ConditionalQueue() - # self.mic_energy_queue = ConditionalQueue() + self.mic_audio_queue = Queue() + # self.mic_energy_queue = Queue() self.changePutQueueMicAudio() mic_device = choice_mic_device[0] @@ -387,20 +384,27 @@ class Model: # self.mic_get_energy.start() def startPutQueueMicAudio(self): - if isinstance(self.mic_audio_queue, ConditionalQueue): + # キューをクリア + if isinstance(self.mic_audio_queue, Queue): while not self.mic_audio_queue.empty(): self.mic_audio_queue.get() - self.mic_audio_queue.set_flag(True) - # self.mic_energy_queue.set_flag(True) + + # 文字起こしを再開 + # if isinstance(self.mic_print_transcript, threadFnc): + # self.mic_print_transcript.resume() + + # 音声のレコードを再開 + if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): + self.mic_audio_recorder.resume() def stopPutQueueMicAudio(self): - if isinstance(self.mic_audio_queue, ConditionalQueue): - self.mic_audio_queue.set_flag(False) - # queueを空にする場合を考慮 - if False: - while not self.mic_audio_queue.empty(): - self.mic_audio_queue.get() - # self.mic_energy_queue.set_flag(False) + # 文字起こしを一時停止 + # if isinstance(self.mic_print_transcript, threadFnc): + # self.mic_print_transcript.pause() + + # 音声のレコードを一時停止 + if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): + self.mic_audio_recorder.pause() def changePutQueueMicAudio(self): if config.ENABLE_VRC_MIC_MUTE_SYNC is True: diff --git a/models/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index 0128a37b..c5f6af56 100644 --- a/models/transcription/transcription_recorder.py +++ b/models/transcription/transcription_recorder.py @@ -115,9 +115,9 @@ class BaseEnergyAndAudioRecorder: energy_queue.put(energy) if isinstance(energy_queue, Queue): - self.stop = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout, callback_energy=energyRecordCallback) + self.stop, self.pause, self.resume = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout, callback_energy=energyRecordCallback) else: - self.stop = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout) + self.stop, self.pause, self.resume = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout) class SelectedMicEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): diff --git a/requirements.txt b/requirements.txt index 9567870c..13fe8df2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ sentencepiece==0.1.99 ctranslate2==4.1.0 faster-whisper==1.0.1 translators @ git+https://github.com/misyaguziya/translators@5.8.9 -SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.2 +SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4 tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.2 \ No newline at end of file From 0e4ad7eec3ade4d63b5f2bb0be6dce8e8df6fd12 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 7 May 2024 15:05:43 +0900 Subject: [PATCH 26/63] =?UTF-8?q?=F0=9F=9A=A7[WIP/TEST]=20Model=20:=20pack?= =?UTF-8?q?age=E3=81=AE=E5=87=A6=E7=90=86=E3=81=AB=E5=90=88=E3=82=8F?= =?UTF-8?q?=E3=81=9B=E3=81=A6=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 2 +- models/transcription/transcription_recorder.py | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/model.py b/model.py index bd60c615..e53e1f23 100644 --- a/model.py +++ b/model.py @@ -486,7 +486,7 @@ class Model: record_timeout=record_timeout, ) # self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, speaker_energy_queue) - self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue ,None) + self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue, None) self.speaker_transcriber = AudioTranscriber( speaker=True, source=self.speaker_audio_recorder.source, diff --git a/models/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index c5f6af56..db1879da 100644 --- a/models/transcription/transcription_recorder.py +++ b/models/transcription/transcription_recorder.py @@ -24,7 +24,7 @@ class BaseRecorder: def record_callback(_, audio): audio_queue.put((audio.get_raw_data(), datetime.now())) - self.stop = self.recorder.listen_in_background(self.source, record_callback, phrase_time_limit=self.record_timeout) + self.stop, self.pause, self.resume = self.recorder.listen_in_background(self.source, record_callback, phrase_time_limit=self.record_timeout) class SelectedMicRecorder(BaseRecorder): def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): @@ -68,7 +68,7 @@ class BaseEnergyRecorder: def recordCallback(_, energy): energy_queue.put(energy) - self.stop = self.recorder.listen_energy_in_background(self.source, recordCallback) + self.stop, self.pause, self.resume = self.recorder.listen_energy_in_background(self.source, recordCallback) class SelectedMicEnergyRecorder(BaseEnergyRecorder): def __init__(self, device): @@ -107,17 +107,14 @@ class BaseEnergyAndAudioRecorder: with self.source: self.recorder.adjust_for_ambient_noise(self.source) - def recordIntoQueue(self, audio_queue, energy_queue): + def recordIntoQueue(self, audio_queue, energy_queue=None): def audioRecordCallback(_, audio): audio_queue.put((audio.get_raw_data(), datetime.now())) def energyRecordCallback(energy): energy_queue.put(energy) - if isinstance(energy_queue, Queue): - self.stop, self.pause, self.resume = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout, callback_energy=energyRecordCallback) - else: - self.stop, self.pause, self.resume = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout) + self.stop, self.pause, self.resume = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout, callback_energy=energyRecordCallback) class SelectedMicEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): From d7f48d0366a316d92cf645744617c1d7db8e8fa5 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 7 May 2024 15:20:57 +0900 Subject: [PATCH 27/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20callba?= =?UTF-8?q?ck=5Fenergy=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=AE=E6=9D=A1=E4=BB=B6=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/transcription/transcription_recorder.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/models/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index db1879da..0e9b147d 100644 --- a/models/transcription/transcription_recorder.py +++ b/models/transcription/transcription_recorder.py @@ -114,7 +114,11 @@ class BaseEnergyAndAudioRecorder: def energyRecordCallback(energy): energy_queue.put(energy) - self.stop, self.pause, self.resume = self.recorder.listen_energy_and_audio_in_background(self.source, audioRecordCallback, phrase_time_limit=self.record_timeout, callback_energy=energyRecordCallback) + self.stop, self.pause, self.resume = self.recorder.listen_energy_and_audio_in_background( + source=self.source, + callback=audioRecordCallback, + phrase_time_limit=self.record_timeout, + callback_energy=energyRecordCallback if energy_queue is not None else None) class SelectedMicEnergyAndAudioRecorder(BaseEnergyAndAudioRecorder): def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): From 564167a9808dfd1048c885313b3d2f96f1896649 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 7 May 2024 16:39:00 +0900 Subject: [PATCH 28/63] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F[Remove]=20Model=20:?= =?UTF-8?q?=20=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 1 - 1 file changed, 1 deletion(-) diff --git a/controller.py b/controller.py index fb505fdc..6a7ef5b1 100644 --- a/controller.py +++ b/controller.py @@ -883,7 +883,6 @@ def callbackSetEnableOverlaySmallLog(value): if config.ENABLE_OVERLAY_SMALL_LOG is True and config.ENABLE_TRANSCRIPTION_RECEIVE is True: if model.overlay.initialized is False and model.overlay.checkSteamvrRunning() is True: model.startOverlay() - print("model.startOverlay()") elif config.ENABLE_OVERLAY_SMALL_LOG is False: pass From 28073fbd81cfa68a6fa373770f0c79e015262927 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 7 May 2024 16:40:45 +0900 Subject: [PATCH 29/63] =?UTF-8?q?=F0=9F=9A=A7=20[WIP/TEST]=20Model=20:=20O?= =?UTF-8?q?verlay=20=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA?= =?UTF-8?q?=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- img/test_chatbox.png | Bin 33053 -> 0 bytes model.py | 15 ++--- models/overlay/overlay_2.py | 129 +++++++++++++++++------------------- 3 files changed, 66 insertions(+), 78 deletions(-) delete mode 100644 img/test_chatbox.png diff --git a/img/test_chatbox.png b/img/test_chatbox.png deleted file mode 100644 index 9d96629967f0fae35ef0392ab6eb3b7dfaf43e09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33053 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%0AfkfgHl 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 diff --git a/model.py b/model.py index 6c220c41..251adf03 100644 --- a/model.py +++ b/model.py @@ -613,25 +613,24 @@ class Model: def updateOverlayPosition(self): pos = (config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]) - self.overlay.setPosition(pos) depth = config.OVERLAY_SMALL_LOG_SETTINGS["depth"] - self.overlay.setDepth(depth) + self.overlay.updatePosition(pos, depth) def updateOverlayTimes(self): display_duration = config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"] - self.overlay.setFadeTime(display_duration) + self.overlay.updateDisplayDuration(display_duration) fadeout_duration = config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"] - self.overlay.setFadeInterval(fadeout_duration) + self.overlay.updateFadeoutDuration(fadeout_duration) def updateOverlayImageOpacity(self): opacity = config.OVERLAY_SETTINGS["opacity"] - self.overlay.setTransparency(opacity) + self.overlay.updateOpacity(opacity, with_fade=True) def updateOverlayImageUiScaling(self): ui_scaling = config.OVERLAY_SETTINGS["ui_scaling"] - self.overlay.setUiScaling(ui_scaling) + self.overlay.updateUiScaling(ui_scaling) - def shutdownOverlay(self): - self.overlay.shutdown() + def stopOverlay(self): + self.overlay.setStopOverlay() model = Model() \ No newline at end of file diff --git a/models/overlay/overlay_2.py b/models/overlay/overlay_2.py index 929be276..68918baa 100644 --- a/models/overlay/overlay_2.py +++ b/models/overlay/overlay_2.py @@ -8,6 +8,7 @@ import openvr from PIL import Image # from queue import Queue from threading import Thread +import OverlayImage def mat34Id(): arr = openvr.HmdMatrix34_t() @@ -17,25 +18,26 @@ def mat34Id(): return arr class Overlay: - def __init__(self, x, y , depth, fade_time, fade_interval, transparency, ui_scaling): + def __init__(self, x, y , depth, fade_time, fade_interval, opacity, ui_scaling): self.initialized = False settings = { - "Color": [1, 1, 1], - "Transparency": transparency, - "Normalized_icon_X_position": x, - "Normalized_icon_Y_position": y, - "Icon_plane_depth": depth, - "Fade_time": fade_time, - "Fade_interval": fade_interval, - "Ui_scaling": ui_scaling, + "color": [1, 1, 1], + "opacity": opacity, + "x_pos": x, + "y_pos": y, + "depth": depth, + "fade_time": fade_time, + "fade_interval": fade_interval, + "ui_scaling": ui_scaling, } self.settings = settings self.system = None self.overlay = None self.handle = None - # self.image_queue = Queue() self.lastUpdate = time.monotonic() self.thread_overlay = None + self.fadeRatio = 1 + self.loop = True def init(self): try: @@ -44,76 +46,62 @@ class Overlay: self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "SOverlay_Speaker2log_UI") self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) - self.setColor(self.settings['Color']) - self.updateColor() - self.setTransparency(self.settings['Transparency']) - self.updateTransparency() - self.setUiScaling(self.settings['Ui_scaling']) - self.updateUiScaling() - self.setPosition((self.settings["Normalized_icon_X_position"], self.settings["Normalized_icon_Y_position"])) - self.updatePosition() + self.updateColor(self.settings["color"]) + self.updateOpacity(self.settings["opacity"]) + self.updateUiScaling(self.settings["Ui_scaling"]) + self.updatePosition( + (self.settings["x_pos"], self.settings["y_pos"]), + self.settings["depth"] + ) self.overlay.showOverlay(self.handle) self.initialized = True except Exception as e: print("Could not initialise OpenVR", e) - # def setImage(self, img): - # self.image_queue.put(img) - def updateImage(self, img): - # _ = self.image_queue.get() - # img = Image.open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", "test_chatbox.png")) width, height = img.size img = img.tobytes() img = (ctypes.c_char * len(img)).from_buffer_copy(img) self.overlay.setOverlayRaw(self.handle, img, width, height, 4) - self.updateTransparency() + self.updateOpacity(self.settings["opacity"]) self.lastUpdate = time.monotonic() def clearImage(self): - # while self.image_queue.empty() is False: - # self.image_queue.get() self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) - def setColor(self, col): + def updateColor(self, col): """ col is a 3-tuple representing (r, g, b) """ - self.settings["Color"] = col - - def updateColor(self): - r, g, b = self.settings["Color"] + self.settings["color"] = col + r, g, b = self.settings["color"] self.overlay.setOverlayColor(self.handle, r, g, b) - def setTransparency(self, transparency): - self.settings['Transparency'] = transparency + def updateOpacity(self, opacity, with_fade=False): + self.settings["opacity"] = opacity + self.overlay.setOverlayAlpha( + self.handle, + self.settings["opacity"] if with_fade is False else self.settings["opacity"] * self.fadeRatio) - def updateTransparency(self): - self.overlay.setOverlayAlpha(self.handle, self.settings['Transparency']) + def updateUiScaling(self, ui_scaling): + self.settings['ui_scaling'] = ui_scaling + self.overlay.setOverlayWidthInMeters(self.handle, self.settings['ui_scaling']) - def setUiScaling(self, ui_scaling): - self.settings['Ui_scaling'] = ui_scaling - - def updateUiScaling(self): - self.overlay.setOverlayWidthInMeters(self.handle, self.settings['Ui_scaling']) - - def setPosition(self, pos): + def updatePosition(self, pos, depth): """ pos is a 2-tuple representing normalized (x, y) + depth is a float representing the depth of the icon plane """ - self.settings["Normalized_icon_X_position"] = pos[0] - self.settings["Normalized_icon_Y_position"] = pos[1] + self.settings["x_pos"] = pos[0] + self.settings["y_pos"] = pos[1] + self.settings["depth"] = depth - def setDepth(self, depth): - self.settings["Icon_plane_depth"] = depth - - def updatePosition(self): self.transform = mat34Id() # no rotation required for HMD attachment # assign position - self.transform[0][3] = self.settings["Normalized_icon_X_position"] * self.settings['Icon_plane_depth'] - self.transform[1][3] = self.settings["Normalized_icon_Y_position"] * self.settings['Icon_plane_depth'] - self.transform[2][3] = - self.settings['Icon_plane_depth'] + self.transform[0][3] = self.settings["x_pos"] * self.settings['depth'] + self.transform[1][3] = self.settings["y_pos"] * self.settings['depth'] + self.transform[2][3] = - self.settings['depth'] self.overlay.setOverlayTransformTrackedDeviceRelative( self.handle, @@ -121,11 +109,11 @@ class Overlay: self.transform ) - def setFadeTime(self, fade_time): - self.settings['Fade_time'] = fade_time + def updateDisplayDuration(self, display_duration): + self.settings['display_duration'] = display_duration - def setFadeInterval(self, fade_interval): - self.settings['Fade_interval'] = fade_interval + def updateFadeoutDuration(self, fadeout_duration): + self.settings['fadeout_duration'] = fadeout_duration def checkActive(self): try: @@ -140,32 +128,30 @@ class Overlay: print(e) return False - def evaluateTransparencyFade(self, lastUpdate, currentTime): - if (currentTime - lastUpdate) > self.settings['Fade_time']: - timeThroughInterval = currentTime - lastUpdate - self.settings['Fade_time'] - self.fadeRatio = 1 - timeThroughInterval / self.settings['Fade_interval'] + def evaluateOpacityFade(self, lastUpdate, currentTime): + if (currentTime - lastUpdate) > self.settings['display_duration']: + timeThroughInterval = currentTime - lastUpdate - self.settings['display_duration'] + self.fadeRatio = 1 - timeThroughInterval / self.settings['fadeout_duration'] if self.fadeRatio < 0: self.fadeRatio = 0 - self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings['Transparency']) + self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings['opacity']) def update(self): - # self.updateUiScaling() - # self.updatePosition() - currTime = time.monotonic() - if self.settings['Fade_interval'] != 0: - self.evaluateTransparencyFade(self.lastUpdate, currTime) + if self.settings['fadeout_duration'] != 0: + self.evaluateOpacityFade(self.lastUpdate, currTime) else: - self.updateTransparency() + self.updateOpacity(self.settings["opacity"]) def mainloop(self): - while self.checkActive() is True: + self.loop = True + while self.checkActive() is True and self.loop is True: startTime = time.monotonic() self.update() sleepTime = (1 / 16) - (time.monotonic() - startTime) if sleepTime > 0: time.sleep(sleepTime) - self.shutdown() + self.shutdownOverlay() def main(self): self.init() @@ -177,9 +163,12 @@ class Overlay: self.thread_overlay.daemon = True self.thread_overlay.start() - def shutdown(self): + def setStopOverlay(self): + self.loop = False + + def shutdownOverlay(self): if self.thread_overlay is not None: - ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(self.thread_overlay.ident), ctypes.py_object(SystemExit)) + self.thread_overlay.join() self.thread_overlay = None if self.overlay is not None: self.overlay.destroyOverlay(self.handle) From db560197472070167707b8b522b1f9083682ec75 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 7 May 2024 16:43:05 +0900 Subject: [PATCH 30/63] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20Model=20:?= =?UTF-8?q?=20rename=20overlay=5F2.py=20->=20overlay.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 2 +- models/overlay/overlay.py | 345 +++++++++++++++--------------------- models/overlay/overlay_2.py | 205 --------------------- 3 files changed, 148 insertions(+), 404 deletions(-) delete mode 100644 models/overlay/overlay_2.py diff --git a/model.py b/model.py index 251adf03..39eacd56 100644 --- a/model.py +++ b/model.py @@ -26,7 +26,7 @@ from models.translation.translation_languages import translation_lang from models.transcription.transcription_languages import transcription_lang from models.translation.translation_utils import checkCTranslate2Weight from models.transcription.transcription_whisper import checkWhisperWeight -from models.overlay.overlay_2 import Overlay +from models.overlay.overlay import Overlay from models.overlay.overlay_image import OverlayImage from config import config diff --git a/models/overlay/overlay.py b/models/overlay/overlay.py index a2787151..68918baa 100644 --- a/models/overlay/overlay.py +++ b/models/overlay/overlay.py @@ -1,18 +1,15 @@ -import psutil +import os +import ctypes +from psutil import process_iter +# from os import path as os_path import ctypes import time -import asyncio import openvr from PIL import Image +# from queue import Queue +from threading import Thread +import OverlayImage -def checkSteamvrRunning(): - for proc in psutil.process_iter(): - if "vrserver.exe" == proc.name().lower(): - return True - return False - -# This code is based on the following source: -# [GOpy](https://github.com/MeroFune/GOpy) def mat34Id(): arr = openvr.HmdMatrix34_t() arr[0][0] = 1 @@ -20,51 +17,91 @@ def mat34Id(): arr[2][2] = 1 return arr -class UIElement: - def __init__(self, overlayRoot, key: str, name: str, settings: dict = None) -> None: - """ - pos is a 2-tuple representing (x, y) normalized position of the overlay on the screen - """ - self.overlay = overlayRoot - self.overlayKey = key - self.overlayName = name +class Overlay: + def __init__(self, x, y , depth, fade_time, fade_interval, opacity, ui_scaling): + self.initialized = False + settings = { + "color": [1, 1, 1], + "opacity": opacity, + "x_pos": x, + "y_pos": y, + "depth": depth, + "fade_time": fade_time, + "fade_interval": fade_interval, + "ui_scaling": ui_scaling, + } self.settings = settings - self.handle = self.overlay.createOverlay(self.overlayKey, self.overlayName) + self.system = None + self.overlay = None + self.handle = None + self.lastUpdate = time.monotonic() + self.thread_overlay = None + self.fadeRatio = 1 + self.loop = True - self.setImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) # blank image for default - self.setColor(self.settings['Color']) - self.setTransparency(self.settings['Transparency']) - self.overlay.setOverlayWidthInMeters( - self.handle, - self.settings['Ui_scaling'] - ) + def init(self): + try: + self.system = openvr.init(openvr.VRApplication_Background) + self.overlay = openvr.IVROverlay() + self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "SOverlay_Speaker2log_UI") - self.updatePosition() - self.overlay.showOverlay(self.handle) + self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) + self.updateColor(self.settings["color"]) + self.updateOpacity(self.settings["opacity"]) + self.updateUiScaling(self.settings["Ui_scaling"]) + self.updatePosition( + (self.settings["x_pos"], self.settings["y_pos"]), + self.settings["depth"] + ) + self.overlay.showOverlay(self.handle) + self.initialized = True + except Exception as e: + print("Could not initialise OpenVR", e) - def setImage(self, img): - # configure overlay appearance + def updateImage(self, img): width, height = img.size img = img.tobytes() img = (ctypes.c_char * len(img)).from_buffer_copy(img) self.overlay.setOverlayRaw(self.handle, img, width, height, 4) + self.updateOpacity(self.settings["opacity"]) + self.lastUpdate = time.monotonic() - def setColor(self, col): + def clearImage(self): + self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) + + def updateColor(self, col): """ col is a 3-tuple representing (r, g, b) """ - self.overlay.setOverlayColor(self.handle, col[0], col[1], col[2]) + self.settings["color"] = col + r, g, b = self.settings["color"] + self.overlay.setOverlayColor(self.handle, r, g, b) - def setTransparency(self, a): - self.overlay.setOverlayAlpha(self.handle, a) + def updateOpacity(self, opacity, with_fade=False): + self.settings["opacity"] = opacity + self.overlay.setOverlayAlpha( + self.handle, + self.settings["opacity"] if with_fade is False else self.settings["opacity"] * self.fadeRatio) + + def updateUiScaling(self, ui_scaling): + self.settings['ui_scaling'] = ui_scaling + self.overlay.setOverlayWidthInMeters(self.handle, self.settings['ui_scaling']) + + def updatePosition(self, pos, depth): + """ + pos is a 2-tuple representing normalized (x, y) + depth is a float representing the depth of the icon plane + """ + self.settings["x_pos"] = pos[0] + self.settings["y_pos"] = pos[1] + self.settings["depth"] = depth - def updatePosition(self): self.transform = mat34Id() # no rotation required for HMD attachment # assign position - self.transform[0][3] = self.settings["Normalized_icon_X_position"] * self.settings['Icon_plane_depth'] - self.transform[1][3] = self.settings["Normalized_icon_Y_position"] * self.settings['Icon_plane_depth'] - self.transform[2][3] = - self.settings['Icon_plane_depth'] + self.transform[0][3] = self.settings["x_pos"] * self.settings['depth'] + self.transform[1][3] = self.settings["y_pos"] * self.settings['depth'] + self.transform[2][3] = - self.settings['depth'] self.overlay.setOverlayTransformTrackedDeviceRelative( self.handle, @@ -72,127 +109,11 @@ class UIElement: self.transform ) - def setPosition(self, pos): - """ - pos is a 2-tuple representing normalized (x, y) - """ - self.settings["Normalized_icon_X_position"] = pos[0] - self.settings["Normalized_icon_Y_position"] = pos[1] + def updateDisplayDuration(self, display_duration): + self.settings['display_duration'] = display_duration - def setDepth(self, depth): - self.settings["Icon_plane_depth"] = depth - - def setUiScaling(self, ui_scaling): - self.overlay.setOverlayWidthInMeters( - self.handle, - ui_scaling, - ) - -class UIManager: - def __init__(self, overlay_key, overlay_name, settings): - self.overlay = openvr.IVROverlay() - self.settings = settings - self.overlayUI = UIElement( - self.overlay, - overlay_key, - overlay_name, - self.settings, - ) - self.fadeRatio = 1 - self.lastUpdate = time.monotonic() - - def update(self): - currTime = time.monotonic() - if self.settings['Fade_interval'] != 0: - self.evaluateTransparencyFade(self.lastUpdate, currTime) - - def uiClear(self): - self.overlayUI.setImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) - self.overlayUI.setTransparency(self.settings['Transparency']) - self.lastUpdate = time.monotonic() - - def uiUpdate(self, img): - self.overlayUI.setImage(img) - self.overlayUI.setTransparency(self.settings['Transparency']) - self.lastUpdate = time.monotonic() - - def evaluateTransparencyFade(self, lastUpdate, currentTime): - if (currentTime - lastUpdate) > self.settings['Fade_time']: - timeThroughInterval = currentTime - lastUpdate - self.settings['Fade_time'] - self.fadeRatio = 1 - timeThroughInterval / self.settings['Fade_interval'] - if self.fadeRatio < 0: - self.fadeRatio = 0 - - self.overlayUI.setTransparency(self.fadeRatio * self.settings['Transparency']) - - def posUpdate(self): - self.overlayUI.updatePosition() - - def setPosition(self, pos): - self.overlayUI.setPosition(pos) - - def setDepth(self, depth): - self.overlayUI.setDepth(depth) - - def setFadeTime(self, fade_time): - self.settings["Fade_time"] = fade_time - - def setFadeInterval(self, fade_interval): - self.settings["Fade_interval"] = fade_interval - - def setUiScaling(self, ui_scaling): - self.overlayUI.setUiScaling(ui_scaling) - - def setTransparency(self, transparency): - self.settings["Transparency"] = transparency - self.overlayUI.setTransparency(self.fadeRatio * self.settings['Transparency']) - -class Overlay: - def __init__(self, x, y , depth, fade_time, fade_interval, transparency, ui_scaling): - self.initialized = False - settings = { - "Color": [1, 1, 1], - "Transparency": transparency, - "Normalized_icon_X_position": x, - "Normalized_icon_Y_position": y, - "Icon_plane_depth": depth, - "Fade_time": fade_time, - "Fade_interval": fade_interval, - "Ui_scaling": ui_scaling, - } - self.settings = settings - self.system = None - - def init(self): - try: - if checkSteamvrRunning() is True: - self.system = openvr.init(openvr.VRApplication_Background) - self.initialized = True - except Exception as e: - print("Could not initialise OpenVR") - print(e) - - async def mainLoop(self): - while self.checkActive() is True: - startTime = time.monotonic() - self.uiManager.update() - - sleepTime = (1 / 60) - (time.monotonic() - startTime) - if sleepTime > 0: - await asyncio.sleep(sleepTime) - - async def initMain(self): - if self.initialized is True: - self.uiManager = UIManager("Overlay_Speaker2log", "SOverlay_Speaker2log_UI", self.settings) - await self.mainLoop() - - def startOverlay(self): - asyncio.run(self.initMain()) - - def shutdown(self): - self.system = None - self.initialized = False - openvr.shutdown() + def updateFadeoutDuration(self, fadeout_duration): + self.settings['fadeout_duration'] = fadeout_duration def checkActive(self): try: @@ -200,7 +121,6 @@ class Overlay: new_event = openvr.VREvent_t() while self.system.pollNextEvent(new_event): if new_event.eventType == openvr.VREvent_Quit: - self.shutdown() return False return True except Exception as e: @@ -208,49 +128,78 @@ class Overlay: print(e) return False + def evaluateOpacityFade(self, lastUpdate, currentTime): + if (currentTime - lastUpdate) > self.settings['display_duration']: + timeThroughInterval = currentTime - lastUpdate - self.settings['display_duration'] + self.fadeRatio = 1 - timeThroughInterval / self.settings['fadeout_duration'] + if self.fadeRatio < 0: + self.fadeRatio = 0 + self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings['opacity']) + + def update(self): + currTime = time.monotonic() + if self.settings['fadeout_duration'] != 0: + self.evaluateOpacityFade(self.lastUpdate, currTime) + else: + self.updateOpacity(self.settings["opacity"]) + + def mainloop(self): + self.loop = True + while self.checkActive() is True and self.loop is True: + startTime = time.monotonic() + self.update() + sleepTime = (1 / 16) - (time.monotonic() - startTime) + if sleepTime > 0: + time.sleep(sleepTime) + self.shutdownOverlay() + + def main(self): + self.init() + if self.initialized is True: + self.mainloop() + + def startOverlay(self): + self.thread_overlay = Thread(target=self.main) + self.thread_overlay.daemon = True + self.thread_overlay.start() + + def setStopOverlay(self): + self.loop = False + + def shutdownOverlay(self): + if self.thread_overlay is not None: + self.thread_overlay.join() + self.thread_overlay = None + if self.overlay is not None: + self.overlay.destroyOverlay(self.handle) + self.overlay = None + if self.system is not None: + openvr.shutdown() + self.system = None + self.initialized = False + + @staticmethod + def checkSteamvrRunning() -> bool: + _proc_name = "vrmonitor.exe" if os.name == 'nt' else "vrmonitor" + return _proc_name in (p.name() for p in process_iter()) + if __name__ == '__main__': from overlay_image import OverlayImage - from threading import Thread, Event - class threadFnc(Thread): - def __init__(self, fnc, end_fnc=None, daemon=True, *args, **kwargs): - super(threadFnc, self).__init__(daemon=daemon, *args, **kwargs) - self.fnc = fnc - self.end_fnc = end_fnc - self._stop = Event() - def stop(self): - self._stop.set() - def stopped(self): - return self._stop.is_set() - def run(self): - while True: - if self.stopped(): - if callable(self.end_fnc): - self.end_fnc() - return - self.fnc(*self._args, **self._kwargs) - - overlay = Overlay(0, 0, 1, 1, 1, 1, 1) overlay_image = OverlayImage() - if overlay.initialized is False: - overlay.init() - if overlay.initialized is True: - t = threadFnc(overlay.startOverlay) - t.start() + for i in range(100): + print(i) + overlay = Overlay(0, 0, 1, 1, 1, 1, 1) + overlay.startOverlay() + time.sleep(1) - time.sleep(1) - img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") - # img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", ui_type="sakura") - if overlay.initialized is True: - overlay.uiManager.uiUpdate(img) - time.sleep(10) + # Example usage + img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") + overlay.updateImage(img) + time.sleep(0.5) - img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "안녕하세요, 세계!안녕", "Korean") - if overlay.initialized is True: - overlay.uiManager.uiUpdate(img) - time.sleep(10) + img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") + overlay.updateImage(img) + time.sleep(0.5) - img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "你好世界!再见", "Chinese Simplified") - if overlay.initialized is True: - overlay.uiManager.uiUpdate(img) - time.sleep(10) \ No newline at end of file + overlay.shutdown() diff --git a/models/overlay/overlay_2.py b/models/overlay/overlay_2.py deleted file mode 100644 index 68918baa..00000000 --- a/models/overlay/overlay_2.py +++ /dev/null @@ -1,205 +0,0 @@ -import os -import ctypes -from psutil import process_iter -# from os import path as os_path -import ctypes -import time -import openvr -from PIL import Image -# from queue import Queue -from threading import Thread -import OverlayImage - -def mat34Id(): - arr = openvr.HmdMatrix34_t() - arr[0][0] = 1 - arr[1][1] = 1 - arr[2][2] = 1 - return arr - -class Overlay: - def __init__(self, x, y , depth, fade_time, fade_interval, opacity, ui_scaling): - self.initialized = False - settings = { - "color": [1, 1, 1], - "opacity": opacity, - "x_pos": x, - "y_pos": y, - "depth": depth, - "fade_time": fade_time, - "fade_interval": fade_interval, - "ui_scaling": ui_scaling, - } - self.settings = settings - self.system = None - self.overlay = None - self.handle = None - self.lastUpdate = time.monotonic() - self.thread_overlay = None - self.fadeRatio = 1 - self.loop = True - - def init(self): - try: - self.system = openvr.init(openvr.VRApplication_Background) - self.overlay = openvr.IVROverlay() - self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "SOverlay_Speaker2log_UI") - - self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) - self.updateColor(self.settings["color"]) - self.updateOpacity(self.settings["opacity"]) - self.updateUiScaling(self.settings["Ui_scaling"]) - self.updatePosition( - (self.settings["x_pos"], self.settings["y_pos"]), - self.settings["depth"] - ) - self.overlay.showOverlay(self.handle) - self.initialized = True - except Exception as e: - print("Could not initialise OpenVR", e) - - def updateImage(self, img): - width, height = img.size - img = img.tobytes() - img = (ctypes.c_char * len(img)).from_buffer_copy(img) - self.overlay.setOverlayRaw(self.handle, img, width, height, 4) - self.updateOpacity(self.settings["opacity"]) - self.lastUpdate = time.monotonic() - - def clearImage(self): - self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) - - def updateColor(self, col): - """ - col is a 3-tuple representing (r, g, b) - """ - self.settings["color"] = col - r, g, b = self.settings["color"] - self.overlay.setOverlayColor(self.handle, r, g, b) - - def updateOpacity(self, opacity, with_fade=False): - self.settings["opacity"] = opacity - self.overlay.setOverlayAlpha( - self.handle, - self.settings["opacity"] if with_fade is False else self.settings["opacity"] * self.fadeRatio) - - def updateUiScaling(self, ui_scaling): - self.settings['ui_scaling'] = ui_scaling - self.overlay.setOverlayWidthInMeters(self.handle, self.settings['ui_scaling']) - - def updatePosition(self, pos, depth): - """ - pos is a 2-tuple representing normalized (x, y) - depth is a float representing the depth of the icon plane - """ - self.settings["x_pos"] = pos[0] - self.settings["y_pos"] = pos[1] - self.settings["depth"] = depth - - self.transform = mat34Id() # no rotation required for HMD attachment - - # assign position - self.transform[0][3] = self.settings["x_pos"] * self.settings['depth'] - self.transform[1][3] = self.settings["y_pos"] * self.settings['depth'] - self.transform[2][3] = - self.settings['depth'] - - self.overlay.setOverlayTransformTrackedDeviceRelative( - self.handle, - openvr.k_unTrackedDeviceIndex_Hmd, - self.transform - ) - - def updateDisplayDuration(self, display_duration): - self.settings['display_duration'] = display_duration - - def updateFadeoutDuration(self, fadeout_duration): - self.settings['fadeout_duration'] = fadeout_duration - - def checkActive(self): - try: - if self.system is not None and self.initialized is True: - new_event = openvr.VREvent_t() - while self.system.pollNextEvent(new_event): - if new_event.eventType == openvr.VREvent_Quit: - return False - return True - except Exception as e: - print("Could not check SteamVR running") - print(e) - return False - - def evaluateOpacityFade(self, lastUpdate, currentTime): - if (currentTime - lastUpdate) > self.settings['display_duration']: - timeThroughInterval = currentTime - lastUpdate - self.settings['display_duration'] - self.fadeRatio = 1 - timeThroughInterval / self.settings['fadeout_duration'] - if self.fadeRatio < 0: - self.fadeRatio = 0 - self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings['opacity']) - - def update(self): - currTime = time.monotonic() - if self.settings['fadeout_duration'] != 0: - self.evaluateOpacityFade(self.lastUpdate, currTime) - else: - self.updateOpacity(self.settings["opacity"]) - - def mainloop(self): - self.loop = True - while self.checkActive() is True and self.loop is True: - startTime = time.monotonic() - self.update() - sleepTime = (1 / 16) - (time.monotonic() - startTime) - if sleepTime > 0: - time.sleep(sleepTime) - self.shutdownOverlay() - - def main(self): - self.init() - if self.initialized is True: - self.mainloop() - - def startOverlay(self): - self.thread_overlay = Thread(target=self.main) - self.thread_overlay.daemon = True - self.thread_overlay.start() - - def setStopOverlay(self): - self.loop = False - - def shutdownOverlay(self): - if self.thread_overlay is not None: - self.thread_overlay.join() - self.thread_overlay = None - if self.overlay is not None: - self.overlay.destroyOverlay(self.handle) - self.overlay = None - if self.system is not None: - openvr.shutdown() - self.system = None - self.initialized = False - - @staticmethod - def checkSteamvrRunning() -> bool: - _proc_name = "vrmonitor.exe" if os.name == 'nt' else "vrmonitor" - return _proc_name in (p.name() for p in process_iter()) - -if __name__ == '__main__': - from overlay_image import OverlayImage - overlay_image = OverlayImage() - - for i in range(100): - print(i) - overlay = Overlay(0, 0, 1, 1, 1, 1, 1) - overlay.startOverlay() - time.sleep(1) - - # Example usage - img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") - overlay.updateImage(img) - time.sleep(0.5) - - img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") - overlay.updateImage(img) - time.sleep(0.5) - - overlay.shutdown() From ade7ae26495e5b28fe73b2f335c89fb802ce8c1b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 7 May 2024 18:05:06 +0900 Subject: [PATCH 31/63] =?UTF-8?q?=F0=9F=97=91=EF=B8=8F[Remove]=20Model=20:?= =?UTF-8?q?=20=E3=83=86=E3=82=B9=E3=83=88=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/controller.py b/controller.py index 6a7ef5b1..668c78d1 100644 --- a/controller.py +++ b/controller.py @@ -165,7 +165,6 @@ def receiveSpeakerMessage(message): if model.overlay.initialized is True: overlay_image = model.createOverlayImageShort(message, translation) model.updateOverlay(overlay_image) - print("model.updateOverlay(overlay_image)") # overlay_image = model.createOverlayImageLong("receive", message, translation) # model.updateOverlay(overlay_image) @@ -429,7 +428,6 @@ def callbackToggleTranscriptionReceive(is_turned_on): if config.ENABLE_TRANSCRIPTION_RECEIVE is True and config.ENABLE_OVERLAY_SMALL_LOG is True: if model.overlay.initialized is False and model.overlay.checkSteamvrRunning() is True: model.startOverlay() - print("model.startOverlay()") elif config.ENABLE_TRANSCRIPTION_RECEIVE is False: pass From 6ace1ed524472bcb2e5f153a497391c9a47bbfd1 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 7 May 2024 18:08:01 +0900 Subject: [PATCH 32/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20overla?= =?UTF-8?q?y=20=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA?= =?UTF-8?q?=E3=83=B3=E3=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - settingsのkey名を変更 - updateOpacityのちらつきを修正 --- models/overlay/overlay.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/models/overlay/overlay.py b/models/overlay/overlay.py index 68918baa..0f552308 100644 --- a/models/overlay/overlay.py +++ b/models/overlay/overlay.py @@ -8,7 +8,6 @@ import openvr from PIL import Image # from queue import Queue from threading import Thread -import OverlayImage def mat34Id(): arr = openvr.HmdMatrix34_t() @@ -18,7 +17,7 @@ def mat34Id(): return arr class Overlay: - def __init__(self, x, y , depth, fade_time, fade_interval, opacity, ui_scaling): + def __init__(self, x, y , depth, display_duration, fadeout_duration, opacity, ui_scaling): self.initialized = False settings = { "color": [1, 1, 1], @@ -26,8 +25,8 @@ class Overlay: "x_pos": x, "y_pos": y, "depth": depth, - "fade_time": fade_time, - "fade_interval": fade_interval, + "display_duration": display_duration, + "fadeout_duration": fadeout_duration, "ui_scaling": ui_scaling, } self.settings = settings @@ -48,7 +47,7 @@ class Overlay: self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) self.updateColor(self.settings["color"]) self.updateOpacity(self.settings["opacity"]) - self.updateUiScaling(self.settings["Ui_scaling"]) + self.updateUiScaling(self.settings["ui_scaling"]) self.updatePosition( (self.settings["x_pos"], self.settings["y_pos"]), self.settings["depth"] @@ -79,9 +78,12 @@ class Overlay: def updateOpacity(self, opacity, with_fade=False): self.settings["opacity"] = opacity - self.overlay.setOverlayAlpha( - self.handle, - self.settings["opacity"] if with_fade is False else self.settings["opacity"] * self.fadeRatio) + + if with_fade is True: + if self.fadeRatio > 0: + self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings["opacity"]) + else: + self.overlay.setOverlayAlpha(self.handle, self.settings["opacity"]) def updateUiScaling(self, ui_scaling): self.settings['ui_scaling'] = ui_scaling @@ -202,4 +204,4 @@ if __name__ == '__main__': overlay.updateImage(img) time.sleep(0.5) - overlay.shutdown() + overlay.setStopOverlay() From 835f4739fd11438071e5b07ffb52531265581999 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 8 May 2024 01:29:02 +0900 Subject: [PATCH 33/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20mute?= =?UTF-8?q?=E5=90=8C=E6=9C=9F=E3=81=8C=E5=8B=95=E4=BD=9C=E3=81=97=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 起動時にmuteselfしているかどうか監視するように変更 - VRChatが起動指定な場合にgetOSCParameterValueがエラーになる問題を修正 - config移行の状態遷移によるmute状態の初期化問題を修正 --- controller.py | 8 +++++- model.py | 57 ++++++++++++++++++++++++++++------------- models/osc/osc_tools.py | 18 ++++++++----- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/controller.py b/controller.py index 43b11844..b4d2e2c3 100644 --- a/controller.py +++ b/controller.py @@ -858,7 +858,11 @@ def callbackSetEnableAutoExportMessageLogs(value): def callbackSetEnableVrcMicMuteSync(value): print("callbackSetEnableVrcMicMuteSync", value) config.ENABLE_VRC_MIC_MUTE_SYNC = value - model.changePutQueueMicAudio() + if config.ENABLE_VRC_MIC_MUTE_SYNC is True: + model.startCheckMuteSelfStatus() + else: + model.stopCheckMuteSelfStatus() + def callbackSetEnableSendMessageToVrc(value): print("callbackSetEnableSendMessageToVrc", value) @@ -981,6 +985,8 @@ def createMainWindow(splash): # init OSC receive model.startReceiveOSC() + if config.ENABLE_VRC_MIC_MUTE_SYNC is True: + model.startCheckMuteSelfStatus() splash.toProgress(3) # Last one. diff --git a/model.py b/model.py index e53e1f23..5b231868 100644 --- a/model.py +++ b/model.py @@ -77,7 +77,8 @@ class Model: self.translator = Translator() self.keyword_processor = KeywordProcessor() self.mic_audio_queue = None - self.mute_status = False + self.mic_mute_status = None + self.mic_mute_status_check = None def checkCTranslatorCTranslate2ModelWeight(self): return checkCTranslate2Weight(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE) @@ -224,20 +225,37 @@ class Model: def getMuteSelfStatus(): return getOSCParameterValue(address="/avatar/parameters/MuteSelf") + def startCheckMuteSelfStatus(self): + def checkMuteSelfStatus(): + if self.mic_mute_status is not None: + self.stopCheckMuteSelfStatus() + + status = self.getMuteSelfStatus() + if status is not None: + self.mic_mute_status = status + self.stopCheckMuteSelfStatus() + + if not isinstance(self.mic_mute_status_check, threadFnc): + self.mic_mute_status_check = threadFnc(checkMuteSelfStatus) + self.mic_mute_status_check.daemon = True + self.mic_mute_status_check.start() + + def stopCheckMuteSelfStatus(self): + if isinstance(self.mic_mute_status_check, threadFnc): + self.mic_mute_status_check.stop() + self.mic_mute_status_check = None + def startReceiveOSC(self): osc_parameter_prefix = "/avatar/parameters/" param_MuteSelf = "MuteSelf" - self.mute_status = self.getMuteSelfStatus() def change_handler_mute(address, osc_arguments): - if osc_arguments is True and self.mute_status is False: - self.mute_status = True - if config.ENABLE_VRC_MIC_MUTE_SYNC is True: - self.stopPutQueueMicAudio() - elif osc_arguments is False and self.mute_status is True: - self.mute_status = False - if config.ENABLE_VRC_MIC_MUTE_SYNC is True: - self.startPutQueueMicAudio() + if osc_arguments is True and self.mic_mute_status is False: + self.mic_mute_status = osc_arguments + self.changeMicTranscriptStatus() + elif osc_arguments is False and self.mic_mute_status is True: + self.mic_mute_status = osc_arguments + self.changeMicTranscriptStatus() dict_filter_and_target = { osc_parameter_prefix + param_MuteSelf: change_handler_mute, @@ -324,7 +342,6 @@ class Model: self.mic_audio_queue = Queue() # self.mic_energy_queue = Queue() - self.changePutQueueMicAudio() mic_device = choice_mic_device[0] record_timeout = config.INPUT_MIC_RECORD_TIMEOUT @@ -383,7 +400,9 @@ class Model: # self.mic_get_energy.daemon = True # self.mic_get_energy.start() - def startPutQueueMicAudio(self): + self.changeMicTranscriptStatus() + + def resumeMicTranscript(self): # キューをクリア if isinstance(self.mic_audio_queue, Queue): while not self.mic_audio_queue.empty(): @@ -397,7 +416,7 @@ class Model: if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): self.mic_audio_recorder.resume() - def stopPutQueueMicAudio(self): + def pauseMicTranscript(self): # 文字起こしを一時停止 # if isinstance(self.mic_print_transcript, threadFnc): # self.mic_print_transcript.pause() @@ -406,14 +425,16 @@ class Model: if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): self.mic_audio_recorder.pause() - def changePutQueueMicAudio(self): + def changeMicTranscriptStatus(self): if config.ENABLE_VRC_MIC_MUTE_SYNC is True: - if self.mute_status is True: - self.stopPutQueueMicAudio() + if self.mic_mute_status is True: + self.pauseMicTranscript() + elif self.mic_mute_status is False: + self.resumeMicTranscript() else: - self.startPutQueueMicAudio() + pass else: - self.startPutQueueMicAudio() + self.resumeMicTranscript() def stopMicTranscript(self): if isinstance(self.mic_print_transcript, threadFnc): diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py index e7aa7d23..0f0958c3 100644 --- a/models/osc/osc_tools.py +++ b/models/osc/osc_tools.py @@ -49,12 +49,18 @@ def sendChangeVoice(ip_address="127.0.0.1", port=9000): sleep(0.05) def getOSCParameterValue(address, server_name="VRChat-Client"): - browser = OSCQueryBrowser() - sleep(1) - service = browser.find_service_by_name(server_name) - oscq = OSCQueryClient(service) - mute_self_node = oscq.query_node(address) - return mute_self_node.value[0] + value = None + try: + browser = OSCQueryBrowser() + sleep(1) + service = browser.find_service_by_name(server_name) + if service is not None: + oscq = OSCQueryClient(service) + mute_self_node = oscq.query_node(address) + value = mute_self_node.value[0] + except Exception: + pass + return value def receiveOscParameters(dict_filter_and_target, ip_address="127.0.0.1", title="VRCT"): osc_port = get_open_udp_port() From 7535565009acd6408ded4ca900da99a99fd66352 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 8 May 2024 02:51:15 +0900 Subject: [PATCH 34/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20mute?= =?UTF-8?q?=E5=90=8C=E6=9C=9F=E5=87=A6=E7=90=86=E3=82=92=E3=83=A1=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E7=94=BB=E9=9D=A2=E3=81=8B=E3=82=89=E3=81=AE=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E3=81=A7=E5=A4=89=E6=9B=B4=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/model.py b/model.py index 5b231868..fc8ec5da 100644 --- a/model.py +++ b/model.py @@ -228,11 +228,13 @@ class Model: def startCheckMuteSelfStatus(self): def checkMuteSelfStatus(): if self.mic_mute_status is not None: + self.changeMicTranscriptStatus() self.stopCheckMuteSelfStatus() status = self.getMuteSelfStatus() if status is not None: self.mic_mute_status = status + self.changeMicTranscriptStatus() self.stopCheckMuteSelfStatus() if not isinstance(self.mic_mute_status_check, threadFnc): From f449b4ebf1e5d1264e7fae17cb4e3695cc2e40e0 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 8 May 2024 11:01:30 +0900 Subject: [PATCH 35/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20overla?= =?UTF-8?q?y=5Fimage=20=E7=94=9F=E6=88=90=E3=81=99=E3=82=8B=E7=94=BB?= =?UTF-8?q?=E5=83=8F=E3=82=B5=E3=82=A4=E3=82=BA=E3=82=92=E6=88=BB=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/overlay/overlay_image.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/models/overlay/overlay_image.py b/models/overlay/overlay_image.py index c52756bb..d1607179 100644 --- a/models/overlay/overlay_image.py +++ b/models/overlay/overlay_image.py @@ -140,9 +140,9 @@ class OverlayImage: def getUiSize(self): return { - "width": int(960), - "height": int(23), - "font_size": int(23), + "width": int(960*4), + "height": int(23*4), + "font_size": int(23*4), } def getUiColors(self, ui_type): From 32ad466d9d3912c4afe06a2764bf2d5dd81538c9 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 8 May 2024 11:45:28 +0900 Subject: [PATCH 36/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20?= =?UTF-8?q?=E3=83=9E=E3=83=BC=E3=82=B8=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B?= =?UTF-8?q?=E3=81=A6overlay=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - overlay off時にParameterを変更できるように修正 - overlay終了処理時を修正 - Easter Eggのコメントアウトを削除 --- config.py | 2 +- controller.py | 3 +- model.py | 4 +-- models/overlay/overlay.py | 67 ++++++++++++++++++++------------------- view.py | 4 +-- 5 files changed, 41 insertions(+), 39 deletions(-) diff --git a/config.py b/config.py index dd13ca4c..7bbd5c64 100644 --- a/config.py +++ b/config.py @@ -787,7 +787,7 @@ class Config: def OVERLAY_UI_TYPE(self, value): if isinstance(value, str): self._OVERLAY_UI_TYPE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + # saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @json_serializable('ENABLE_SEND_MESSAGE_TO_VRC') diff --git a/controller.py b/controller.py index fbcb930c..47f15f66 100644 --- a/controller.py +++ b/controller.py @@ -882,7 +882,8 @@ def callbackSetEnableOverlaySmallLog(value): if model.overlay.initialized is False and model.overlay.checkSteamvrRunning() is True: model.startOverlay() elif config.ENABLE_OVERLAY_SMALL_LOG is False: - pass + model.clearOverlayImage() + model.shutdownOverlay() def callbackSetOverlaySmallLogSettings(value, set_type:str): print("callbackSetOverlaySmallLogSettings", value, set_type) diff --git a/model.py b/model.py index 27c03c96..487b55be 100644 --- a/model.py +++ b/model.py @@ -697,7 +697,7 @@ class Model: ui_scaling = config.OVERLAY_SETTINGS["ui_scaling"] self.overlay.updateUiScaling(ui_scaling) - def stopOverlay(self): - self.overlay.setStopOverlay() + def shutdownOverlay(self): + self.overlay.shutdownOverlay() model = Model() \ No newline at end of file diff --git a/models/overlay/overlay.py b/models/overlay/overlay.py index 0f552308..01af327d 100644 --- a/models/overlay/overlay.py +++ b/models/overlay/overlay.py @@ -1,12 +1,9 @@ import os import ctypes from psutil import process_iter -# from os import path as os_path -import ctypes import time import openvr from PIL import Image -# from queue import Queue from threading import Thread def mat34Id(): @@ -43,6 +40,8 @@ class Overlay: self.system = openvr.init(openvr.VRApplication_Background) self.overlay = openvr.IVROverlay() self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "SOverlay_Speaker2log_UI") + self.overlay.showOverlay(self.handle) + self.initialized = True self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) self.updateColor(self.settings["color"]) @@ -52,42 +51,46 @@ class Overlay: (self.settings["x_pos"], self.settings["y_pos"]), self.settings["depth"] ) - self.overlay.showOverlay(self.handle) - self.initialized = True + except Exception as e: print("Could not initialise OpenVR", e) def updateImage(self, img): - width, height = img.size - img = img.tobytes() - img = (ctypes.c_char * len(img)).from_buffer_copy(img) - self.overlay.setOverlayRaw(self.handle, img, width, height, 4) - self.updateOpacity(self.settings["opacity"]) - self.lastUpdate = time.monotonic() + if self.initialized is True: + width, height = img.size + img = img.tobytes() + img = (ctypes.c_char * len(img)).from_buffer_copy(img) + self.overlay.setOverlayRaw(self.handle, img, width, height, 4) + self.updateOpacity(self.settings["opacity"]) + self.lastUpdate = time.monotonic() def clearImage(self): - self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) + if self.initialized is True: + self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) def updateColor(self, col): """ col is a 3-tuple representing (r, g, b) """ self.settings["color"] = col - r, g, b = self.settings["color"] - self.overlay.setOverlayColor(self.handle, r, g, b) + if self.initialized is True: + r, g, b = self.settings["color"] + self.overlay.setOverlayColor(self.handle, r, g, b) def updateOpacity(self, opacity, with_fade=False): self.settings["opacity"] = opacity - if with_fade is True: - if self.fadeRatio > 0: - self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings["opacity"]) - else: - self.overlay.setOverlayAlpha(self.handle, self.settings["opacity"]) + if self.initialized is True: + if with_fade is True: + if self.fadeRatio > 0: + self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings["opacity"]) + else: + self.overlay.setOverlayAlpha(self.handle, self.settings["opacity"]) def updateUiScaling(self, ui_scaling): self.settings['ui_scaling'] = ui_scaling - self.overlay.setOverlayWidthInMeters(self.handle, self.settings['ui_scaling']) + if self.initialized is True: + self.overlay.setOverlayWidthInMeters(self.handle, self.settings['ui_scaling']) def updatePosition(self, pos, depth): """ @@ -105,11 +108,12 @@ class Overlay: self.transform[1][3] = self.settings["y_pos"] * self.settings['depth'] self.transform[2][3] = - self.settings['depth'] - self.overlay.setOverlayTransformTrackedDeviceRelative( - self.handle, - openvr.k_unTrackedDeviceIndex_Hmd, - self.transform - ) + if self.initialized is True: + self.overlay.setOverlayTransformTrackedDeviceRelative( + self.handle, + openvr.k_unTrackedDeviceIndex_Hmd, + self.transform + ) def updateDisplayDuration(self, display_duration): self.settings['display_duration'] = display_duration @@ -153,7 +157,6 @@ class Overlay: sleepTime = (1 / 16) - (time.monotonic() - startTime) if sleepTime > 0: time.sleep(sleepTime) - self.shutdownOverlay() def main(self): self.init() @@ -165,17 +168,15 @@ class Overlay: self.thread_overlay.daemon = True self.thread_overlay.start() - def setStopOverlay(self): - self.loop = False - def shutdownOverlay(self): - if self.thread_overlay is not None: + if isinstance(self.thread_overlay, Thread): + self.loop = False self.thread_overlay.join() self.thread_overlay = None - if self.overlay is not None: + if isinstance(self.overlay, openvr.IVROverlay) and isinstance(self.handle, int): self.overlay.destroyOverlay(self.handle) self.overlay = None - if self.system is not None: + if isinstance(self.system, openvr.IVRSystem): openvr.shutdown() self.system = None self.initialized = False @@ -204,4 +205,4 @@ if __name__ == '__main__': overlay.updateImage(img) time.sleep(0.5) - overlay.setStopOverlay() + overlay.shutdownOverlay() diff --git a/view.py b/view.py index 8674e9a6..b1b8b730 100644 --- a/view.py +++ b/view.py @@ -101,7 +101,7 @@ class View(): self.view_variable = SimpleNamespace( # Common - # CALLBACK_ENABLE_EASTER_EGG=None, + CALLBACK_ENABLE_EASTER_EGG=None, CALLBACK_RESTART_SOFTWARE=None, CALLBACK_UPDATE_SOFTWARE=None, @@ -637,7 +637,7 @@ class View(): if common_registers is not None: - # self.view_variable.CALLBACK_ENABLE_EASTER_EGG=common_registers.get("callback_enable_easter_egg", None) + self.view_variable.CALLBACK_ENABLE_EASTER_EGG=common_registers.get("callback_enable_easter_egg", None) self.view_variable.CALLBACK_UPDATE_SOFTWARE=common_registers.get("callback_update_software", None) self.view_variable.CALLBACK_RESTART_SOFTWARE=common_registers.get("callback_restart_software", None) From 79054b44728765573edb70d484171a70efab4d89 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 8 May 2024 12:15:38 +0900 Subject: [PATCH 37/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20OSC=20?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AAthread=E3=81=AE=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=81=AE=E7=B5=82=E4=BA=86=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/osc/osc_tools.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py index 0f0958c3..67ceee5c 100644 --- a/models/osc/osc_tools.py +++ b/models/osc/osc_tools.py @@ -58,6 +58,9 @@ def getOSCParameterValue(address, server_name="VRChat-Client"): oscq = OSCQueryClient(service) mute_self_node = oscq.query_node(address) value = mute_self_node.value[0] + browser.zc.close() + browser.browser.cancel() + except Exception: pass return value From f24127f6311e4faeeb1fbdc57f7209f055c44589 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 8 May 2024 17:14:07 +0900 Subject: [PATCH 38/63] [Update] Main Window: Remove textbox tabs that seems to be not used and for make space. --- vrct_gui/_PrintToTextbox.py | 42 +-- .../main_window/widgets/create_textbox.py | 241 +++++++++--------- 2 files changed, 143 insertions(+), 140 deletions(-) diff --git a/vrct_gui/_PrintToTextbox.py b/vrct_gui/_PrintToTextbox.py index 5d5d7419..0c8597c3 100644 --- a/vrct_gui/_PrintToTextbox.py +++ b/vrct_gui/_PrintToTextbox.py @@ -31,28 +31,30 @@ class _PrintToTextbox(): self.textbox_font_size__main_text_font = None - self.all_textbox_widgets = [self.vrct_gui.textbox_all, self.vrct_gui.textbox_system, self.vrct_gui.textbox_sent, self.vrct_gui.textbox_received] + # self.all_textbox_widgets = [self.vrct_gui.textbox_all, self.vrct_gui.textbox_system, self.vrct_gui.textbox_sent, self.vrct_gui.textbox_received] + self.all_textbox_widgets = [self.vrct_gui.textbox_all] self.setTagsSettings(self.init_scaling) def printToTextbox(self, target_type, original_message=None, translated_message=None, to_print_to_textbox_all:bool=True): - self._printEachTextbox( - target_textbox=self._getTargetTextboxWidget(target_type), - print_type=target_type, - original_message=original_message, - translated_message=translated_message, - ) + # [Deprecated] Print to textbox to only all-tab. sent received system tabs are deprecated. + # self._printEachTextbox( + # target_textbox=self._getTargetTextboxWidget(target_type), + # print_type=target_type, + # original_message=original_message, + # translated_message=translated_message, + # ) # To automatically print the same log to the textbox_all widget as well. - if to_print_to_textbox_all is True: - self._printEachTextbox( - target_textbox=self._getTargetTextboxWidget("ALL"), - print_type=target_type, - original_message=original_message, - translated_message=translated_message, - ) + # if to_print_to_textbox_all is True: + self._printEachTextbox( + target_textbox=self._getTargetTextboxWidget("ALL"), + print_type=target_type, + original_message=original_message, + translated_message=translated_message, + ) def setTagsSettings(self, custom_font_size_scale:float=1.0): # Calculate Textbox's ui size by default size * textbox_ui_scale @@ -156,12 +158,12 @@ class _PrintToTextbox(): match (target_type): case "ALL": target_textbox = self.vrct_gui.textbox_all - case "SYSTEM": - target_textbox = self.vrct_gui.textbox_system - case "SENT": - target_textbox = self.vrct_gui.textbox_sent - case "RECEIVED": - target_textbox = self.vrct_gui.textbox_received + # case "SYSTEM": + # target_textbox = self.vrct_gui.textbox_system + # case "SENT": + # target_textbox = self.vrct_gui.textbox_sent + # case "RECEIVED": + # target_textbox = self.vrct_gui.textbox_received case (_): raise ValueError(f"No matching case for target_type: {target_type}") diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py index b3ed7446..ba048f26 100644 --- a/vrct_gui/main_window/widgets/create_textbox.py +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -5,53 +5,53 @@ from ...ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButton def createTextbox(settings, main_window, view_variable): - def switchTextbox(target_textbox_attr_name): - main_window.current_active_textbox.grid_remove() - main_window.current_active_textbox = getattr(main_window, target_textbox_attr_name) - main_window.current_active_textbox.grid() + # def switchTextbox(target_textbox_attr_name): + # main_window.current_active_textbox.grid_remove() + # main_window.current_active_textbox = getattr(main_window, target_textbox_attr_name) + # main_window.current_active_textbox.grid() - def switchToTextboxAll(e): - target_active_widget = getattr(main_window, "textbox_tab_all") - switchTextboxTabFunction(target_active_widget) - switchTextbox("textbox_all") + # def switchToTextboxAll(e): + # target_active_widget = getattr(main_window, "textbox_tab_all") + # switchTextboxTabFunction(target_active_widget) + # switchTextbox("textbox_all") - def switchToTextboxSent(e): - target_active_widget = getattr(main_window, "textbox_tab_sent") - switchTextboxTabFunction(target_active_widget) - switchTextbox("textbox_sent") + # def switchToTextboxSent(e): + # target_active_widget = getattr(main_window, "textbox_tab_sent") + # switchTextboxTabFunction(target_active_widget) + # switchTextbox("textbox_sent") - def switchToTextboxReceived(e): - target_active_widget = getattr(main_window, "textbox_tab_received") - switchTextboxTabFunction(target_active_widget) - switchTextbox("textbox_received") + # def switchToTextboxReceived(e): + # target_active_widget = getattr(main_window, "textbox_tab_received") + # switchTextboxTabFunction(target_active_widget) + # switchTextbox("textbox_received") - def switchToTextboxSystem(e): - target_active_widget = getattr(main_window, "textbox_tab_system") - switchTextboxTabFunction(target_active_widget) - switchTextbox("textbox_system") + # def switchToTextboxSystem(e): + # target_active_widget = getattr(main_window, "textbox_tab_system") + # switchTextboxTabFunction(target_active_widget) + # switchTextbox("textbox_system") - def switchTextboxTabFunction(target_active_widget): - switchActiveAndPassiveTextboxTabsColor(target_active_widget) - switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_textbox_tab, main_window.current_active_textbox_tab.passive_function, settings.ctm.TEXTBOX_TAB_BG_HOVERED_COLOR, settings.ctm.TEXTBOX_TAB_BG_CLICKED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) - main_window.current_active_textbox_tab = target_active_widget + # def switchTextboxTabFunction(target_active_widget): + # switchActiveAndPassiveTextboxTabsColor(target_active_widget) + # switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_textbox_tab, main_window.current_active_textbox_tab.passive_function, settings.ctm.TEXTBOX_TAB_BG_HOVERED_COLOR, settings.ctm.TEXTBOX_TAB_BG_CLICKED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) + # main_window.current_active_textbox_tab = target_active_widget - def switchActiveAndPassiveTextboxTabsColor(target_active_widget): - textbox_tabs = [ - getattr(main_window, "textbox_tab_all"), - getattr(main_window, "textbox_tab_sent"), - getattr(main_window, "textbox_tab_received"), - getattr(main_window, "textbox_tab_system") - ] + # def switchActiveAndPassiveTextboxTabsColor(target_active_widget): + # textbox_tabs = [ + # getattr(main_window, "textbox_tab_all"), + # getattr(main_window, "textbox_tab_sent"), + # getattr(main_window, "textbox_tab_received"), + # getattr(main_window, "textbox_tab_system") + # ] - switchTabsColor( - target_widget=target_active_widget, - tab_buttons=textbox_tabs, - active_bg_color=settings.ctm.TEXTBOX_BG_COLOR, - active_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, - passive_bg_color=settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR, - passive_text_color=settings.ctm.TEXTBOX_TAB_TEXT_PASSIVE_COLOR - ) + # switchTabsColor( + # target_widget=target_active_widget, + # tab_buttons=textbox_tabs, + # active_bg_color=settings.ctm.TEXTBOX_BG_COLOR, + # active_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, + # passive_bg_color=settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR, + # passive_text_color=settings.ctm.TEXTBOX_TAB_TEXT_PASSIVE_COLOR + # ) @@ -64,100 +64,101 @@ def createTextbox(settings, main_window, view_variable): main_window.main_textbox_container.grid_columnconfigure(0,weight=1) main_window.main_textbox_container.grid_rowconfigure(0,weight=1) - main_window.textbox_switch_tabs_container = CTkFrame(main_window.main_topbar_center_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) - main_window.textbox_switch_tabs_container.place(relx=0.07, rely=1.15, anchor="sw") + # main_window.textbox_switch_tabs_container = CTkFrame(main_window.main_topbar_center_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + # main_window.textbox_switch_tabs_container.place(relx=0.07, rely=1.15, anchor="sw") - main_window.textbox_switch_tabs_container.grid_columnconfigure((0,1,2,3), weight=1, uniform="textbox_tabs") + # main_window.textbox_switch_tabs_container.grid_columnconfigure((0,1,2,3), weight=1, uniform="textbox_tabs") - textbox_settings = [ - { - "textbox_tab_attr_name": "textbox_tab_all", - "command": switchToTextboxAll, - "textbox_attr_name": "textbox_all", - "textvariable": view_variable.VAR_LABEL_TEXTBOX_ALL - }, - { - "textbox_tab_attr_name": "textbox_tab_sent", - "command": switchToTextboxSent, - "textbox_attr_name": "textbox_sent", - "textvariable": view_variable.VAR_LABEL_TEXTBOX_SENT - }, - { - "textbox_tab_attr_name": "textbox_tab_received", - "command": switchToTextboxReceived, - "textbox_attr_name": "textbox_received", - "textvariable": view_variable.VAR_LABEL_TEXTBOX_RECEIVED - }, - { - "textbox_tab_attr_name": "textbox_tab_system", - "command": switchToTextboxSystem, - "textbox_attr_name": "textbox_system", - "textvariable": view_variable.VAR_LABEL_TEXTBOX_SYSTEM - }, - ] + # textbox_settings = [ + # { + # "textbox_tab_attr_name": "textbox_tab_all", + # "command": switchToTextboxAll, + # "textbox_attr_name": "textbox_all", + # "textvariable": view_variable.VAR_LABEL_TEXTBOX_ALL + # }, + # { + # "textbox_tab_attr_name": "textbox_tab_sent", + # "command": switchToTextboxSent, + # "textbox_attr_name": "textbox_sent", + # "textvariable": view_variable.VAR_LABEL_TEXTBOX_SENT + # }, + # { + # "textbox_tab_attr_name": "textbox_tab_received", + # "command": switchToTextboxReceived, + # "textbox_attr_name": "textbox_received", + # "textvariable": view_variable.VAR_LABEL_TEXTBOX_RECEIVED + # }, + # { + # "textbox_tab_attr_name": "textbox_tab_system", + # "command": switchToTextboxSystem, + # "textbox_attr_name": "textbox_system", + # "textvariable": view_variable.VAR_LABEL_TEXTBOX_SYSTEM + # }, + # ] - column=0 - for textbox_setting in textbox_settings: - setattr(main_window, textbox_setting["textbox_tab_attr_name"], - CTkFrame( - main_window.textbox_switch_tabs_container, - corner_radius=settings.uism.TEXTBOX_TAB_CORNER_RADIUS, - fg_color=settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR, - cursor="hand2", - width=0, - height=0 - ) - ) - target_widget = getattr(main_window, textbox_setting["textbox_tab_attr_name"]) - target_widget.grid(row=0, column=column, pady=0, padx=(0,2), sticky="ew") + # column=0 + # for textbox_setting in textbox_settings: + # setattr(main_window, textbox_setting["textbox_tab_attr_name"], + # CTkFrame( + # main_window.textbox_switch_tabs_container, + # corner_radius=settings.uism.TEXTBOX_TAB_CORNER_RADIUS, + # fg_color=settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR, + # cursor="hand2", + # width=0, + # height=0 + # ) + # ) + # target_widget = getattr(main_window, textbox_setting["textbox_tab_attr_name"]) + # target_widget.grid(row=0, column=column, pady=0, padx=(0,2), sticky="ew") - target_widget.grid_columnconfigure((0,2), weight=1) - setattr(main_window, "label_widget", CTkLabel( - target_widget, - textvariable=textbox_setting["textvariable"], - corner_radius=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_TAB_FONT_SIZE, weight="normal"), - height=0, - width=0, - anchor="center", - text_color=settings.ctm.TEXTBOX_TAB_TEXT_PASSIVE_COLOR, - )) - label_widget = getattr(main_window, "label_widget") - label_widget.grid(row=0, column=1, pady=settings.uism.TEXTBOX_TAB_PADY, padx=settings.uism.TEXTBOX_TAB_PADX) + # target_widget.grid_columnconfigure((0,2), weight=1) + # setattr(main_window, "label_widget", CTkLabel( + # target_widget, + # textvariable=textbox_setting["textvariable"], + # corner_radius=0, + # font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_TAB_FONT_SIZE, weight="normal"), + # height=0, + # width=0, + # anchor="center", + # text_color=settings.ctm.TEXTBOX_TAB_TEXT_PASSIVE_COLOR, + # )) + # label_widget = getattr(main_window, "label_widget") + # label_widget.grid(row=0, column=1, pady=settings.uism.TEXTBOX_TAB_PADY, padx=settings.uism.TEXTBOX_TAB_PADX) - bindEnterAndLeaveColor([target_widget, label_widget], settings.ctm.TEXTBOX_TAB_BG_HOVERED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) - bindButtonPressColor([target_widget, label_widget], settings.ctm.TEXTBOX_TAB_BG_CLICKED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) + # bindEnterAndLeaveColor([target_widget, label_widget], settings.ctm.TEXTBOX_TAB_BG_HOVERED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) + # bindButtonPressColor([target_widget, label_widget], settings.ctm.TEXTBOX_TAB_BG_CLICKED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) - target_widget.passive_function = textbox_setting["command"] - bindButtonReleaseFunction([target_widget, label_widget], textbox_setting["command"]) + # target_widget.passive_function = textbox_setting["command"] + # bindButtonReleaseFunction([target_widget, label_widget], textbox_setting["command"]) - setattr(main_window, textbox_setting["textbox_attr_name"], CTkTextbox( - main_window.main_textbox_container, - corner_radius=settings.uism.TEXTBOX_CORNER_RADIUS, - fg_color=settings.ctm.TEXTBOX_BG_COLOR, - text_color="lime", # Textbox's text_color is set when printing. so this is for prevent from non-setting text_color like the gloves used in food factories are blue. - wrap="word", - height=0, - )) - textbox_widget = getattr(main_window, textbox_setting["textbox_attr_name"]) - textbox_widget.grid(row=0, column=0, padx=settings.uism.TEXTBOX_PADX, pady=0, sticky="nsew") - textbox_widget.grid_remove() - textbox_widget.configure(state="disabled") + main_window.textbox_all = CTkTextbox( + main_window.main_textbox_container, + corner_radius=settings.uism.TEXTBOX_CORNER_RADIUS, + fg_color=settings.ctm.TEXTBOX_BG_COLOR, + text_color="lime", # Textbox's text_color is set when printing. so this is for prevent from non-setting text_color like the gloves used in food factories are blue. + wrap="word", + height=0, + ) + # main_window.textbox_all = getattr(main_window, textbox_setting["textbox_attr_name"]) + main_window.textbox_all.grid(row=0, column=0, padx=settings.uism.TEXTBOX_PADX, pady=0, sticky="nsew") + main_window.textbox_all.grid_remove() + main_window.textbox_all.configure(state="disabled") - column+=1 + # column+=1 # Set default active textbox tab - main_window.current_active_textbox_tab = getattr(main_window, "textbox_tab_all") - setDefaultActiveTab( - active_tab_widget=main_window.current_active_textbox_tab, - active_bg_color=settings.ctm.TEXTBOX_TAB_BG_ACTIVE_COLOR, - active_text_color=settings.ctm.TEXTBOX_TAB_TEXT_ACTIVE_COLOR - ) + # main_window.current_active_textbox_tab = getattr(main_window, "textbox_tab_all") + # setDefaultActiveTab( + # active_tab_widget=main_window.current_active_textbox_tab, + # active_bg_color=settings.ctm.TEXTBOX_TAB_BG_ACTIVE_COLOR, + # active_text_color=settings.ctm.TEXTBOX_TAB_TEXT_ACTIVE_COLOR + # ) - main_window.current_active_textbox = getattr(main_window, "textbox_all") - main_window.current_active_textbox.grid() + # main_window.current_active_textbox = getattr(main_window, "textbox_all") + # main_window.current_active_textbox.grid() + main_window.textbox_all.grid() From d1f4688b5c061bfbd99f1c993923905fe65f6fb3 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 9 May 2024 06:29:47 +0900 Subject: [PATCH 39/63] [Update] Main Window: Add VRC Mic Mute Sync quick settings. and the place where above on textbox is now show the state which whether it's enabled or disabled. --- controller.py | 7 + view.py | 41 +++++- .../main_window/createMainWindowWidgets.py | 87 +++++++++--- .../QuickSettingsWindow.py | 128 ++++++++++++++---- vrct_gui/ui_managers/Themes/_darkTheme.py | 2 + vrct_gui/ui_managers/UiScalingManager.py | 7 + 6 files changed, 227 insertions(+), 45 deletions(-) diff --git a/controller.py b/controller.py index 47f15f66..d634edc2 100644 --- a/controller.py +++ b/controller.py @@ -885,6 +885,11 @@ def callbackSetEnableOverlaySmallLog(value): model.clearOverlayImage() model.shutdownOverlay() + if config.ENABLE_OVERLAY_SMALL_LOG is True: + view.setStateOverlaySmallLog("enabled") + elif config.ENABLE_OVERLAY_SMALL_LOG is False: + view.setStateOverlaySmallLog("disabled") + def callbackSetOverlaySmallLogSettings(value, set_type:str): print("callbackSetOverlaySmallLogSettings", value, set_type) pre_settings = config.OVERLAY_SMALL_LOG_SETTINGS @@ -934,8 +939,10 @@ def callbackSetEnableVrcMicMuteSync(value): config.ENABLE_VRC_MIC_MUTE_SYNC = value if config.ENABLE_VRC_MIC_MUTE_SYNC is True: model.startCheckMuteSelfStatus() + view.setStateVrcMicMuteSync("enabled") else: model.stopCheckMuteSelfStatus() + view.setStateVrcMicMuteSync("disabled") def callbackSetEnableSendMessageToVrc(value): diff --git a/view.py b/view.py index b1b8b730..3195dd57 100644 --- a/view.py +++ b/view.py @@ -140,10 +140,16 @@ class View(): # Overlay Settings - VAR_OVERLAY_SETTINGS=StringVar(value="Overlay Settings"), + VAR_OVERLAY_SETTINGS=StringVar(value="Overlay (VR)"), CALLBACK_SET_OPEN_OVERLAY_SETTINGS_WINDOW=self._openVrSettingsWindow, VAR_TO_DEFAULT_OVERLAY_SETTINGS=StringVar(value=i18n.t("overlay_settings.restore_default_settings")), CALLBACK_SET_TO_DEFAULT_OVERLAY_SETTINGS=self._toDefaultOverlaySettings, + VAR_OVERLAY_SMALL_LOG_STATE=StringVar(value=""), + + + CALLBACK_SET_OPEN_VRC_MIC_MUTE_SYNC_SETTINGS_WINDOW=self._openVrcMicMuteSyncSettingsWindow, + VAR_VRC_MIC_MUTE_SYNC_SETTINGS=StringVar(value="VRC Mic Mute Sync"), + VAR_VRC_MIC_MUTE_SYNC_STATE=StringVar(value=""), VAR_LABEL_OVERLAY_OPACITY=StringVar(value=i18n.t("overlay_settings.opacity")), @@ -808,6 +814,18 @@ class View(): self.view_variable.VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON.set(i18n.t("main_window.swap_button_label")) self.useTranslationFeatureProcess("Disable") + + if config.ENABLE_VRC_MIC_MUTE_SYNC is True: + self.setStateVrcMicMuteSync("enabled") + elif config.ENABLE_VRC_MIC_MUTE_SYNC is False: + self.setStateVrcMicMuteSync("disabled") + + if config.ENABLE_OVERLAY_SMALL_LOG is True: + self.setStateOverlaySmallLog("enabled") + elif config.ENABLE_OVERLAY_SMALL_LOG is False: + self.setStateOverlaySmallLog("disabled") + + if config.CHOICE_MIC_HOST == "NoHost": self.view_variable.VAR_MIC_HOST.set("No Mic Host Detected") @@ -1281,6 +1299,22 @@ class View(): dropdown_menu_values=translation_dict, ) + def setStateVrcMicMuteSync(self, state:str): + if state == "enabled": + self.view_variable.VAR_VRC_MIC_MUTE_SYNC_STATE.set("Enabled") + vrct_gui.vrc_mic_mute_sync_settings_state_label.configure(text_color=self.settings.main.ctm.TOP_BAR_BUTTON_STATE_TEXT_ENABLED_COLOR) + elif state == "disabled": + self.view_variable.VAR_VRC_MIC_MUTE_SYNC_STATE.set("Disabled") + vrct_gui.vrc_mic_mute_sync_settings_state_label.configure(text_color=self.settings.main.ctm.TOP_BAR_BUTTON_STATE_TEXT_DISABLED_COLOR) + + def setStateOverlaySmallLog(self, state:str): + if state == "enabled": + self.view_variable.VAR_OVERLAY_SMALL_LOG_STATE.set("Enabled") + vrct_gui.overlay_settings_state_label.configure(text_color=self.settings.main.ctm.TOP_BAR_BUTTON_STATE_TEXT_ENABLED_COLOR) + elif state == "disabled": + self.view_variable.VAR_OVERLAY_SMALL_LOG_STATE.set("Disabled") + vrct_gui.overlay_settings_state_label.configure(text_color=self.settings.main.ctm.TOP_BAR_BUTTON_STATE_TEXT_DISABLED_COLOR) + # Config Window def enableConfigWindowCompactMode(self): @@ -1741,7 +1775,10 @@ class View(): vrct_gui._closeConfigWindow() def _openVrSettingsWindow(self): - vrct_gui.quick_settings_window.show() + vrct_gui.quick_settings_window.show(target="overlay") + + def _openVrcMicMuteSyncSettingsWindow(self): + vrct_gui.quick_settings_window.show(target="vrc_mic_mute_sync") # Window Control (Main Window Cover) def _openTheCoverOfMainWindow(self): diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 447dfb1d..3fb7d105 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -48,6 +48,61 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): # start from 3 main_topbar_column=3 + # VRChat Mic Mute Sync Settings Button + vrct_gui.vrc_mic_mute_sync_settings_container = CTkFrame( + vrct_gui.main_topbar_container, + corner_radius=settings.uism.UPDATE_AVAILABLE_BUTTON_CORNER_RADIUS, + fg_color=settings.ctm.MAIN_BG_COLOR, + cursor="hand2", + ) + vrct_gui.vrc_mic_mute_sync_settings_container.grid(row=0, column=main_topbar_column, padx=settings.uism.UPDATE_AVAILABLE_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="nsw") + vrct_gui.vrc_mic_mute_sync_settings_container.grid_rowconfigure((0,3), weight=1) + + vrct_gui.vrc_mic_mute_sync_settings_label = CTkLabel( + vrct_gui.vrc_mic_mute_sync_settings_container, + textvariable=view_variable.VAR_VRC_MIC_MUTE_SYNC_SETTINGS, + height=0, + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.QUICK_SETTINGS_BUTTON_FONT_SIZE, weight="normal"), + anchor="e", + text_color=settings.ctm.TOP_BAR_BUTTON_TEXT_COLOR, + # text_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_TEXT_COLOR, + ) + # This "right padx +1" is for fixing a bug that sticks out from the frame. I don't know why that happens... + vrct_gui.vrc_mic_mute_sync_settings_label.grid(row=1, column=1, padx=(0,settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX+1), pady=0) + + + vrct_gui.vrc_mic_mute_sync_settings_state_label = CTkLabel( + vrct_gui.vrc_mic_mute_sync_settings_container, + textvariable=view_variable.VAR_VRC_MIC_MUTE_SYNC_STATE, + height=0, + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.QUICK_SETTINGS_BUTTON_STATE_FONT_SIZE, weight="normal"), + anchor="e", + text_color=settings.ctm.TOP_BAR_BUTTON_TEXT_COLOR, + # text_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_TEXT_COLOR, + ) + # This "right padx +1" is for fixing a bug that sticks out from the frame. I don't know why that happens... + vrct_gui.vrc_mic_mute_sync_settings_state_label.grid(row=2, column=1, padx=(0,settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX+1), pady=0) + + + bindButtonFunctionAndColor( + target_widgets=[ + vrct_gui.vrc_mic_mute_sync_settings_container, + vrct_gui.vrc_mic_mute_sync_settings_label, + vrct_gui.vrc_mic_mute_sync_settings_state_label, + ], + enter_color=settings.ctm.TOP_BAR_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.TOP_BAR_BUTTON_BG_COLOR, + clicked_color=settings.ctm.TOP_BAR_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=lambda e: callFunctionIfCallable(view_variable.CALLBACK_SET_OPEN_VRC_MIC_MUTE_SYNC_SETTINGS_WINDOW), + ) + + + + + + main_topbar_column+=1 # Overlay Settings Button vrct_gui.overlay_settings_container = CTkFrame( vrct_gui.main_topbar_container, @@ -56,27 +111,14 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): cursor="hand2", ) vrct_gui.overlay_settings_container.grid(row=0, column=main_topbar_column, padx=settings.uism.UPDATE_AVAILABLE_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="nsw") - # vrct_gui.overlay_settings_container.grid_remove() - - - vrct_gui.overlay_settings_container.grid_rowconfigure((0,2), weight=1) - - vrct_gui.overlay_settings_icon = CTkLabel( - vrct_gui.overlay_settings_container, - text=None, - corner_radius=0, - height=0, - image=CTkImage(settings.image_file.CONFIGURATION_ICON_DISABLED, size=settings.uism.UPDATE_AVAILABLE_BUTTON_SIZE) - ) - vrct_gui.overlay_settings_icon.grid(row=1, column=0, padx=(settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX, settings.uism.UPDATE_AVAILABLE_PADX_BETWEEN_LABEL_AND_ICON), pady=0) - + vrct_gui.overlay_settings_container.grid_rowconfigure((0,3), weight=1) vrct_gui.overlay_settings_label = CTkLabel( vrct_gui.overlay_settings_container, textvariable=view_variable.VAR_OVERLAY_SETTINGS, height=0, corner_radius=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.UPDATE_AVAILABLE_BUTTON_FONT_SIZE, weight="normal"), + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.QUICK_SETTINGS_BUTTON_FONT_SIZE, weight="normal"), anchor="e", text_color=settings.ctm.TOP_BAR_BUTTON_TEXT_COLOR, # text_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_TEXT_COLOR, @@ -85,11 +127,24 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): vrct_gui.overlay_settings_label.grid(row=1, column=1, padx=(0,settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX+1), pady=0) + vrct_gui.overlay_settings_state_label = CTkLabel( + vrct_gui.overlay_settings_container, + textvariable=view_variable.VAR_OVERLAY_SMALL_LOG_STATE, + height=0, + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.QUICK_SETTINGS_BUTTON_STATE_FONT_SIZE, weight="normal"), + anchor="e", + text_color=settings.ctm.TOP_BAR_BUTTON_TEXT_COLOR, + ) + # This "right padx +1" is for fixing a bug that sticks out from the frame. I don't know why that happens... + vrct_gui.overlay_settings_state_label.grid(row=2, column=1, padx=(0,settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX+1), pady=0) + + bindButtonFunctionAndColor( target_widgets=[ vrct_gui.overlay_settings_container, vrct_gui.overlay_settings_label, - vrct_gui.overlay_settings_icon, + vrct_gui.overlay_settings_state_label, ], enter_color=settings.ctm.TOP_BAR_BUTTON_HOVERED_BG_COLOR, leave_color=settings.ctm.TOP_BAR_BUTTON_BG_COLOR, diff --git a/vrct_gui/quick_settings_window/QuickSettingsWindow.py b/vrct_gui/quick_settings_window/QuickSettingsWindow.py index 0d294b8e..4c2bb266 100644 --- a/vrct_gui/quick_settings_window/QuickSettingsWindow.py +++ b/vrct_gui/quick_settings_window/QuickSettingsWindow.py @@ -20,18 +20,21 @@ class QuickSettingsWindow(CTkToplevel): BG_HEX_COLOR = "#292a2d" - self.grid_columnconfigure(0, weight=1, minsize=400) - self.grid_rowconfigure(0, weight=1) self.qsw_background = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR) - self.qsw_background.grid(row=0, column=0, pady=(0,18), sticky="nsew") - self.qsw_background.grid_columnconfigure(0, weight=1) - - self.qsw_setting_box = CTkFrame(self.qsw_background, corner_radius=0, fg_color=BG_HEX_COLOR) - self.qsw_setting_box.grid(row=0, column=0, sticky="nsew") - self.qsw_setting_box.grid_columnconfigure(0, weight=1) + self.qsw_background.grid(row=0, column=0, pady=0, sticky="ew") + self.qsw_background.grid_columnconfigure(0, weight=1, minsize=400) - cqsb = _CreateQuickSettingBox(self.qsw_setting_box, vrct_gui, settings, view_variable) + self.qsw_background__overlay = CTkFrame(self.qsw_background, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR) + self.qsw_background__overlay.grid(row=0, column=0, pady=(0,18), sticky="ew") + self.qsw_background__overlay.grid_columnconfigure(0, weight=1) + + + self.qsw_setting_box__overlay = CTkFrame(self.qsw_background__overlay, corner_radius=0, fg_color=BG_HEX_COLOR) + self.qsw_setting_box__overlay.grid(row=0, column=0, sticky="ew") + self.qsw_setting_box__overlay.grid_columnconfigure(0, weight=1) + + cqsb = _CreateQuickSettingBox(self.qsw_setting_box__overlay, vrct_gui, settings, view_variable) createSettingBoxSlider = cqsb.createSettingBoxSlider createSettingBoxSwitch = cqsb.createSettingBoxSwitch @@ -41,20 +44,20 @@ class QuickSettingsWindow(CTkToplevel): # Overlay General Settings row=0 - def switchCallback(switch_widget): + def overlaySmallLogSwitchCallback(switch_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_OVERLAY_SMALL_LOG, switch_widget.get()) self.qsb__enable_overlay_small_log = createSettingBoxSwitch( for_var_label_text=view_variable.VAR_LABEL_ENABLE_OVERLAY_SMALL_LOG, switch_attr_name="qsb__enable_overlay_small_log_switch", - command=lambda: switchCallback(vrct_gui.qsb__enable_overlay_small_log_switch), + command=lambda: overlaySmallLogSwitchCallback(vrct_gui.qsb__enable_overlay_small_log_switch), variable=view_variable.VAR_ENABLE_OVERLAY_SMALL_LOG, ) self.qsb__enable_overlay_small_log.grid(row=row) row+=1 - def sliderCallback(e): + def overlayOpacitySliderCallback(e): value = round(e,2) callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SETTINGS, value, "opacity") view_variable.VAR_CURRENT_VALUE_OVERLAY_OPACITY.set(floatToPctStr(value)) @@ -65,14 +68,14 @@ class QuickSettingsWindow(CTkToplevel): slider_attr_name="qsb__overlay_opacity_slider", slider_range=view_variable.SLIDER_RANGE_OVERLAY_OPACITY, slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_OPACITY, - command=sliderCallback, + command=overlayOpacitySliderCallback, variable=view_variable.VAR_OVERLAY_OPACITY, ) self.qsb__overlay_opacity.grid(row=row) row+=1 - def sliderCallback(e): + def overlayUiScalingSliderCallback(e): value = round(e,2) callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SETTINGS, value, "ui_scaling") view_variable.VAR_CURRENT_VALUE_OVERLAY_UI_SCALING.set(floatToPctStr(value)) @@ -83,7 +86,7 @@ class QuickSettingsWindow(CTkToplevel): slider_attr_name="qsb__overlay_ui_scaling_slider", slider_range=view_variable.SLIDER_RANGE_OVERLAY_UI_SCALING, slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_UI_SCALING, - command=sliderCallback, + command=overlayUiScalingSliderCallback, variable=view_variable.VAR_OVERLAY_UI_SCALING, ) self.qsb__overlay_ui_scaling.grid(row=row) @@ -107,7 +110,7 @@ class QuickSettingsWindow(CTkToplevel): row+=1 - def sliderCallback(e): + def overlaySmallLogSettingsXPosSliderCallback(e): value = round(e,2) callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "x_pos") view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_POS.set(str(value)) @@ -118,14 +121,14 @@ class QuickSettingsWindow(CTkToplevel): slider_attr_name="qsb__overlay_small_log_settings_x_pos_slider", slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS, slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS, - command=sliderCallback, + command=overlaySmallLogSettingsXPosSliderCallback, variable=view_variable.VAR_OVERLAY_SMALL_LOG_X_POS, ) self.qsb__overlay_small_log_settings_x_pos.grid(row=row) row+=1 - def sliderCallback(e): + def overlaySmallLogSettingsYPosSliderCallback(e): value = round(e,2) callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "y_pos") view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS.set(str(value)) @@ -136,14 +139,14 @@ class QuickSettingsWindow(CTkToplevel): slider_attr_name="qsb__overlay_small_log_settings_y_pos_slider", slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS, slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS, - command=sliderCallback, + command=overlaySmallLogSettingsYPosSliderCallback, variable=view_variable.VAR_OVERLAY_SMALL_LOG_Y_POS, ) self.qsb__overlay_small_log_settings_y_pos.grid(row=row) row+=1 - def sliderCallback(e): + def overlaySmallLogSettingsDepthSliderCallback(e): value = round(e,2) callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "depth") view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH.set(str(value)) @@ -154,14 +157,14 @@ class QuickSettingsWindow(CTkToplevel): slider_attr_name="qsb__overlay_small_log_settings_depth_slider", slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH, slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH, - command=sliderCallback, + command=overlaySmallLogSettingsDepthSliderCallback, variable=view_variable.VAR_OVERLAY_SMALL_LOG_DEPTH, ) self.qsb__overlay_small_log_settings_depth.grid(row=row) row+=1 - def sliderCallback(e): + def overlaySmallLogSettingsDisplayDurationSliderCallback(e): value = int(e) callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "display_duration") view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DISPLAY_DURATION.set(f"{value} second(s)") @@ -172,7 +175,7 @@ class QuickSettingsWindow(CTkToplevel): slider_attr_name="qsb__overlay_small_log_settings_display_duration_slider", slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_DISPLAY_DURATION, slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DISPLAY_DURATION, - command=sliderCallback, + command=overlaySmallLogSettingsDisplayDurationSliderCallback, variable=view_variable.VAR_OVERLAY_SMALL_LOG_DISPLAY_DURATION, ) self.qsb__overlay_small_log_settings_display_duration.grid(row=row) @@ -180,7 +183,7 @@ class QuickSettingsWindow(CTkToplevel): row+=1 - def sliderCallback(e): + def overlaySmallLogSettingsFadeoutDurationSliderCallback(e): value = int(e) callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "fadeout_duration") view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_FADEOUT_DURATION.set(f"{value} second(s)") @@ -191,7 +194,7 @@ class QuickSettingsWindow(CTkToplevel): slider_attr_name="qsb__overlay_small_log_settings_fadeout_duration_slider", slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_FADEOUT_DURATION, slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_FADEOUT_DURATION, - command=sliderCallback, + command=overlaySmallLogSettingsFadeoutDurationSliderCallback, variable=view_variable.VAR_OVERLAY_SMALL_LOG_FADEOUT_DURATION, ) self.qsb__overlay_small_log_settings_fadeout_duration.grid(row=row) @@ -204,7 +207,7 @@ class QuickSettingsWindow(CTkToplevel): - self.qsw_setting_box_bottom = CTkFrame(self.qsw_background, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR) + self.qsw_setting_box_bottom = CTkFrame(self.qsw_background__overlay, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR) self.qsw_setting_box_bottom.grid(row=1, column=0, sticky="nsew") self.qsw_setting_box_bottom.grid_columnconfigure((0,2), weight=1) @@ -236,9 +239,80 @@ class QuickSettingsWindow(CTkToplevel): ) restore_default_settings_button.grid(row=0, column=0, pady=self.settings.uism.QSB__RESTORE_DEFAULT_SETTINGS_BUTTON_PADY) + self.qsw_background__overlay.grid_remove() + + + + + + + + + + + + + + + # VRChat mic mute sync + self.qsw_background__vrc_mic_mute_sync = CTkFrame(self.qsw_background, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR) + self.qsw_background__vrc_mic_mute_sync.grid(row=0, column=0, pady=(0,18), sticky="ew") + self.qsw_background__vrc_mic_mute_sync.grid_columnconfigure(0, weight=1) + + + self.qsw_setting_box__vrc_mic_mute_sync = CTkFrame(self.qsw_background__vrc_mic_mute_sync, corner_radius=0, fg_color=BG_HEX_COLOR) + self.qsw_setting_box__vrc_mic_mute_sync.grid(row=0, column=0, sticky="ew") + self.qsw_setting_box__vrc_mic_mute_sync.grid_columnconfigure(0, weight=1) + + + + + + + + + + cqsb = _CreateQuickSettingBox(self.qsw_setting_box__vrc_mic_mute_sync, vrct_gui, settings, view_variable) + createSettingBoxSlider = cqsb.createSettingBoxSlider + createSettingBoxSwitch = cqsb.createSettingBoxSwitch + + + row=0 + def enableVrcMicMuteSyncSwitchCallback(switch_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_VRC_MIC_MUTE_SYNC, switch_widget.get()) + + self.qsb__enable_vrc_mic_mute_sync = createSettingBoxSwitch( + for_var_label_text=view_variable.VAR_LABEL_ENABLE_VRC_MIC_MUTE_SYNC, + switch_attr_name="qsb__enable_vrc_mic_mute_sync_switch", + command=lambda: enableVrcMicMuteSyncSwitchCallback(vrct_gui.qsb__enable_vrc_mic_mute_sync_switch), + variable=view_variable.VAR_ENABLE_VRC_MIC_MUTE_SYNC, + ) + self.qsb__enable_vrc_mic_mute_sync.grid(row=row) + + + self.qsw_background__vrc_mic_mute_sync.grid_remove() + + + + + + + + def show(self, target:str): + if target == "overlay": + self.qsw_background__vrc_mic_mute_sync.grid_remove() + self.qsw_background__overlay.grid() + elif target == "vrc_mic_mute_sync": + self.qsw_background__overlay.grid_remove() + self.qsw_background__vrc_mic_mute_sync.grid() - def show(self): self.attributes("-alpha", 0) self.deiconify() + + self.qsw_background.update() + self.geometry("{}x{}".format(self.qsw_background.winfo_width(), self.qsw_background.winfo_height())) + + + setGeometryToCenterOfScreen(root_widget=self) fadeInAnimation(self, steps=5, interval=0.02) \ No newline at end of file diff --git a/vrct_gui/ui_managers/Themes/_darkTheme.py b/vrct_gui/ui_managers/Themes/_darkTheme.py index c90a8e5f..ed46a557 100644 --- a/vrct_gui/ui_managers/Themes/_darkTheme.py +++ b/vrct_gui/ui_managers/Themes/_darkTheme.py @@ -111,6 +111,8 @@ def _darkTheme(base_color): TOP_BAR_BUTTON_HOVERED_BG_COLOR = base_color.DARK_850_COLOR, TOP_BAR_BUTTON_CLICKED_BG_COLOR = base_color.DARK_900_COLOR, TOP_BAR_BUTTON_TEXT_COLOR = base_color.DARK_BASIC_TEXT_COLOR, + TOP_BAR_BUTTON_STATE_TEXT_ENABLED_COLOR = base_color.PRIMARY_300_COLOR, + TOP_BAR_BUTTON_STATE_TEXT_DISABLED_COLOR = base_color.DARK_600_COLOR, UPDATE_AVAILABLE_BUTTON_BG_COLOR = base_color.DARK_888_COLOR, UPDATE_AVAILABLE_BUTTON_HOVERED_BG_COLOR = base_color.DARK_850_COLOR, diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index b7e5f5e3..2c081c2c 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -123,6 +123,13 @@ class UiScalingManager(): self.main.TOP_BAR_BUTTON_PADY = (self._calculateUiSize(6),0) + + + + self.main.QUICK_SETTINGS_BUTTON_FONT_SIZE = self._calculateUiSize(12) + self.main.QUICK_SETTINGS_BUTTON_STATE_FONT_SIZE = self._calculateUiSize(10) + + self.main.UPDATE_AVAILABLE_BUTTON_CORNER_RADIUS = self._calculateUiSize(6) self.main.UPDATE_AVAILABLE_BUTTON_SIZE = (self._calculateUiSize(18), self._calculateUiSize(18)) self.main.UPDATE_AVAILABLE_BUTTON_FONT_SIZE = self._calculateUiSize(12) From 37cab3fe3c35e65e3ca68116d4e83fafffe3abe3 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 9 May 2024 06:43:08 +0900 Subject: [PATCH 40/63] [Update] Main Window: VRC Mic Mute Sync, Overlay. Quick Settings. add Japanese and light theme. --- locales/en.yml | 2 ++ locales/ja.yml | 2 ++ view.py | 8 ++++---- vrct_gui/ui_managers/Themes/_lightTheme.py | 2 ++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 82571147..d8fe3597 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -46,6 +46,8 @@ main_window: opened_web_page_vrct_documents: "Opened VRCT Documents page in your web browser.\nFor any issues, requests, or inquiries, please feel free to contact us through the links at the bottom of the documents page, the \"Contact Form,\" or via X (formerly Twitter)!" update_available: New version is here! + state_text_enabled: Enabled + state_text_disabled: Disabled cover_message: The functionality is temporarily disabled until the settings window is closed. diff --git a/locales/ja.yml b/locales/ja.yml index 893470d6..1d8ca719 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -45,6 +45,8 @@ main_window: opened_web_page_vrct_documents: "お使いのブラウザで、VRCTのドキュメントを開きました。使用方法などはそちらに記載されています。\n不具合、ご要望、その他お問い合わせはドキュメント最下部にあるLinks、「お問合せフォーム」もしくはX (元Twitter) にて気軽にご連絡ください!" update_available: 新しいバージョンが出ました! + state_text_enabled: 有効 + state_text_disabled: 無効 cover_message: 設定画面が閉じられるまで、一時的に機能を停止しています。 diff --git a/view.py b/view.py index 3195dd57..49234703 100644 --- a/view.py +++ b/view.py @@ -1301,18 +1301,18 @@ class View(): def setStateVrcMicMuteSync(self, state:str): if state == "enabled": - self.view_variable.VAR_VRC_MIC_MUTE_SYNC_STATE.set("Enabled") + self.view_variable.VAR_VRC_MIC_MUTE_SYNC_STATE.set(i18n.t("main_window.state_text_enabled")) vrct_gui.vrc_mic_mute_sync_settings_state_label.configure(text_color=self.settings.main.ctm.TOP_BAR_BUTTON_STATE_TEXT_ENABLED_COLOR) elif state == "disabled": - self.view_variable.VAR_VRC_MIC_MUTE_SYNC_STATE.set("Disabled") + self.view_variable.VAR_VRC_MIC_MUTE_SYNC_STATE.set(i18n.t("main_window.state_text_disabled")) vrct_gui.vrc_mic_mute_sync_settings_state_label.configure(text_color=self.settings.main.ctm.TOP_BAR_BUTTON_STATE_TEXT_DISABLED_COLOR) def setStateOverlaySmallLog(self, state:str): if state == "enabled": - self.view_variable.VAR_OVERLAY_SMALL_LOG_STATE.set("Enabled") + self.view_variable.VAR_OVERLAY_SMALL_LOG_STATE.set(i18n.t("main_window.state_text_enabled")) vrct_gui.overlay_settings_state_label.configure(text_color=self.settings.main.ctm.TOP_BAR_BUTTON_STATE_TEXT_ENABLED_COLOR) elif state == "disabled": - self.view_variable.VAR_OVERLAY_SMALL_LOG_STATE.set("Disabled") + self.view_variable.VAR_OVERLAY_SMALL_LOG_STATE.set(i18n.t("main_window.state_text_disabled")) vrct_gui.overlay_settings_state_label.configure(text_color=self.settings.main.ctm.TOP_BAR_BUTTON_STATE_TEXT_DISABLED_COLOR) diff --git a/vrct_gui/ui_managers/Themes/_lightTheme.py b/vrct_gui/ui_managers/Themes/_lightTheme.py index 503be63a..b4e2d96d 100644 --- a/vrct_gui/ui_managers/Themes/_lightTheme.py +++ b/vrct_gui/ui_managers/Themes/_lightTheme.py @@ -111,6 +111,8 @@ def _lightTheme(base_color): TOP_BAR_BUTTON_HOVERED_BG_COLOR = base_color.LIGHT_300_COLOR, TOP_BAR_BUTTON_CLICKED_BG_COLOR = base_color.LIGHT_350_COLOR, TOP_BAR_BUTTON_TEXT_COLOR = base_color.LIGHT_BASIC_TEXT_COLOR, + TOP_BAR_BUTTON_STATE_TEXT_ENABLED_COLOR = base_color.PRIMARY_400_COLOR, + TOP_BAR_BUTTON_STATE_TEXT_DISABLED_COLOR = base_color.LIGHT_600_COLOR, UPDATE_AVAILABLE_BUTTON_TEXT_COLOR = base_color.PRIMARY_400_COLOR, ), From e76d42172d7cb7c321f88488aa670e8388d6e27f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 9 May 2024 07:00:14 +0900 Subject: [PATCH 41/63] =?UTF-8?q?[Update/bugfix]=20=E6=96=87=E8=A8=80?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=80=82=E3=82=A4=E3=83=B3=E3=83=87=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=83=9F=E3=82=B9=E3=81=A7=E6=97=A5=E6=9C=AC=E8=AA=9E?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C=E3=81=A7=E3=81=8D=E3=81=A6=E3=81=84=E3=81=AA?= =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 4 ++-- locales/ja.yml | 4 ++-- view.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index d8fe3597..af75675c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -246,8 +246,8 @@ config_window: desc: Automatically export the conversation messages as a text file. vrc_mic_mute_sync: - label: VRChat Mic Mute Sync - desc: VRCT will not send the message to VRChat while VRChat's mic is muted. + label: VRC Mic Mute Sync + desc: "VRCT will not send the message to VRChat while VRChat's mic is muted.\n*There is a bit latency and Push-To-Talk is not supported." send_message_to_vrc: label: Send Message To VRChat diff --git a/locales/ja.yml b/locales/ja.yml index 1d8ca719..8376fce0 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -245,8 +245,8 @@ config_window: desc: テキストファイルとしてログがlogsフォルダ内に保存されます。 vrc_mic_mute_sync: - label: VRChatマイクミュート同期機能 - desc: VRChatのマイクがミュートされている間は、メッセージをVRChatに送信しません。 + label: VRCマイクミュート同期 + desc: "VRChatのマイクがミュートされている間は、メッセージをVRChatに送信しません。\n※若干の遅延はあります。また、Push-To-Talkは非対応です。" send_message_to_vrc: diff --git a/view.py b/view.py index 49234703..9762ed95 100644 --- a/view.py +++ b/view.py @@ -148,7 +148,7 @@ class View(): CALLBACK_SET_OPEN_VRC_MIC_MUTE_SYNC_SETTINGS_WINDOW=self._openVrcMicMuteSyncSettingsWindow, - VAR_VRC_MIC_MUTE_SYNC_SETTINGS=StringVar(value="VRC Mic Mute Sync"), + VAR_VRC_MIC_MUTE_SYNC_SETTINGS=StringVar(value=i18n.t("config_window.vrc_mic_mute_sync.label")), VAR_VRC_MIC_MUTE_SYNC_STATE=StringVar(value=""), From fa87946d4cff09598ba76bdb512bf025a66b9eaf Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 9 May 2024 07:03:15 +0900 Subject: [PATCH 42/63] =?UTF-8?q?[bugfix]=20Config=20Window:=20=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E9=A0=85=E7=9B=AE=E5=A4=89=E6=9B=B4=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=80=81=E9=BB=92=E3=82=AB=E3=83=90=E3=83=BC=E3=81=8C=E5=89=8D?= =?UTF-8?q?=E3=81=AB=E5=87=BA=E3=81=A6=E3=81=8D=E3=81=A6=E3=81=97=E3=81=BE?= =?UTF-8?q?=E3=81=86=E3=81=AE=E3=82=92=E7=84=A1=E7=90=86=E3=82=84=E3=82=8A?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=80=82=20=E3=83=86=E3=82=AD=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=83=9C=E3=83=83=E3=82=AF=E3=82=B9=E4=B8=8A=E9=83=A8?= =?UTF-8?q?=E3=80=81=E3=82=B9=E3=83=86=E3=83=BC=E3=82=BF=E3=82=B9=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E9=96=A2=E6=95=B0setStateOverlaySmallLog=E3=81=A8setS?= =?UTF-8?q?tateVrcMicMuteSync=E5=AE=9F=E8=A1=8C=E6=99=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/view.py b/view.py index 9762ed95..939e000a 100644 --- a/view.py +++ b/view.py @@ -1307,6 +1307,8 @@ class View(): self.view_variable.VAR_VRC_MIC_MUTE_SYNC_STATE.set(i18n.t("main_window.state_text_disabled")) vrct_gui.vrc_mic_mute_sync_settings_state_label.configure(text_color=self.settings.main.ctm.TOP_BAR_BUTTON_STATE_TEXT_DISABLED_COLOR) + vrct_gui.config_window.after(200, vrct_gui.config_window.lift) + def setStateOverlaySmallLog(self, state:str): if state == "enabled": self.view_variable.VAR_OVERLAY_SMALL_LOG_STATE.set(i18n.t("main_window.state_text_enabled")) @@ -1315,6 +1317,8 @@ class View(): self.view_variable.VAR_OVERLAY_SMALL_LOG_STATE.set(i18n.t("main_window.state_text_disabled")) vrct_gui.overlay_settings_state_label.configure(text_color=self.settings.main.ctm.TOP_BAR_BUTTON_STATE_TEXT_DISABLED_COLOR) + vrct_gui.config_window.after(200, vrct_gui.config_window.lift) + # Config Window def enableConfigWindowCompactMode(self): From 6bd2ad41330f26f196b6b78bb5a910c00bea83c9 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 9 May 2024 07:07:33 +0900 Subject: [PATCH 43/63] =?UTF-8?q?[Update]=20=E6=96=87=E8=A8=80=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E3=80=81=E3=82=A4=E3=83=BC=E3=82=B9=E3=82=BF=E3=83=BC?= =?UTF-8?q?=E3=82=A8=E3=83=83=E3=82=B0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en.yml b/locales/en.yml index af75675c..34c63e00 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -18,7 +18,7 @@ main_window: textbox_tab_system: System textbox_system_message: - enabled_easter_egg: Whoa! You caught us! There is something...like...easter-egg-ish function has enabled! + enabled_easter_egg: Whoa! You caught us! There is something...like...easter-egg-ish function has enabled! It'll affect to Overlay(VR) for now;). enabled_translation: Translation feature is turned on. disabled_translation: Translation feature is turned off. enabled_voice2chatbox: Transcription from the microphone has started. From 23c2fa11190cd9aeffaec4b8694406498981d3c9 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 9 May 2024 11:51:24 +0900 Subject: [PATCH 44/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E5=87=A6=E7=90=86=E3=81=AE=E3=83=9E=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E5=87=A6=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit まだ一部バグあり --- controller.py | 9 ++--- model.py | 36 ++++++++++++-------- models/translation/translation_translator.py | 23 +++++++++---- 3 files changed, 44 insertions(+), 24 deletions(-) diff --git a/controller.py b/controller.py index 47f15f66..634d3181 100644 --- a/controller.py +++ b/controller.py @@ -61,10 +61,11 @@ def messageFormatter(format_type:str, translation, message): return osc_message def changeToCTranslate2Process(): - config.CHOICE_INPUT_TRANSLATOR = "CTranslate2" - config.CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" - updateTranslationEngineAndEngineList() - view.printToTextbox_TranslationEngineLimitError() + if config.CHOICE_INPUT_TRANSLATOR != "CTranslate2" or config.CHOICE_OUTPUT_TRANSLATOR != "CTranslate2": + config.CHOICE_INPUT_TRANSLATOR = "CTranslate2" + config.CHOICE_OUTPUT_TRANSLATOR = "CTranslate2" + updateTranslationEngineAndEngineList() + view.printToTextbox_TranslationEngineLimitError() # func transcription send message def sendMicMessage(message): diff --git a/model.py b/model.py index 487b55be..7d89a0a9 100644 --- a/model.py +++ b/model.py @@ -183,13 +183,17 @@ class Model: # 翻訳失敗時のフェールセーフ処理 if translation is False: translation_success_flag = False - translation = self.translator.translate( - translator_name="CTranslate2", - source_language=source_language, - target_language=target_language, - target_country=target_country, - message=message - ) + while True: + translation = self.translator.translate( + translator_name="CTranslate2", + source_language=source_language, + target_language=target_language, + target_country=target_country, + message=message + ) + if translation is not False: + break + sleep(0.1) return translation, translation_success_flag def getOutputTranslate(self, message): @@ -210,13 +214,17 @@ class Model: # 翻訳失敗時のフェールセーフ処理 if translation is False: translation_success_flag = False - translation = self.translator.translate( - translator_name="CTranslate2", - source_language=source_language, - target_language=target_language, - target_country=target_country, - message=message - ) + while True: + translation = self.translator.translate( + translator_name="CTranslate2", + source_language=source_language, + target_language=target_language, + target_country=target_country, + message=message + ) + if translation is not False: + break + sleep(0.1) return translation, translation_success_flag def addKeywords(self): diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index a71d0f55..d2717747 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -52,6 +52,18 @@ class Translator(): self.ctranslate2_translator = None self.ctranslate2_tokenizer = None + def translateCTranslate2(self, message, source_language, target_language): + try: + self.ctranslate2_tokenizer.src_lang = source_language + source = self.ctranslate2_tokenizer.convert_ids_to_tokens(self.ctranslate2_tokenizer.encode(message)) + target_prefix = [self.ctranslate2_tokenizer.lang_code_to_token[target_language]] + results = self.ctranslate2_translator.translate_batch([source], target_prefix=[target_prefix]) + target = results[0].hypotheses[0][1:] + result = self.ctranslate2_tokenizer.decode(self.ctranslate2_tokenizer.convert_tokens_to_ids(target)) + except Exception: + result = False + return result + @staticmethod def getLanguageCode(translator_name, target_country, source_language, target_language): match translator_name: @@ -115,12 +127,11 @@ class Translator(): to_language=target_language, ) case "CTranslate2": - self.ctranslate2_tokenizer.src_lang = source_language - source = self.ctranslate2_tokenizer.convert_ids_to_tokens(self.ctranslate2_tokenizer.encode(message)) - target_prefix = [self.ctranslate2_tokenizer.lang_code_to_token[target_language]] - results = self.ctranslate2_translator.translate_batch([source], target_prefix=[target_prefix]) - target = results[0].hypotheses[0][1:] - result = self.ctranslate2_tokenizer.decode(self.ctranslate2_tokenizer.convert_tokens_to_ids(target)) + result = self.translateCTranslate2( + message=message, + source_language=source_language, + target_language=target_language, + ) except Exception: import traceback with open('error.log', 'a') as f: From 7339cf10a36f6b926f0f9d64675f0adb95899935 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 9 May 2024 19:29:26 +0900 Subject: [PATCH 45/63] [Refactor/Update] Quick Settings Window(Overlay, VRC Mic Mute Sync): Apply UI Scaling. and remove the codes that is no longer in use. --- .../QuickSettingsWindow.py | 21 +++------------ .../_CreateQuickSettingBox.py | 27 +------------------ vrct_gui/ui_managers/AboutVrctManager.py | 4 +-- vrct_gui/ui_managers/UiScalingManager.py | 2 ++ 4 files changed, 9 insertions(+), 45 deletions(-) diff --git a/vrct_gui/quick_settings_window/QuickSettingsWindow.py b/vrct_gui/quick_settings_window/QuickSettingsWindow.py index 4c2bb266..1f44f523 100644 --- a/vrct_gui/quick_settings_window/QuickSettingsWindow.py +++ b/vrct_gui/quick_settings_window/QuickSettingsWindow.py @@ -1,6 +1,6 @@ from utils import callFunctionIfCallable, floatToPctStr -from customtkinter import CTkImage, CTkLabel, CTkToplevel, CTkProgressBar, CTkFrame, CTkSlider +from customtkinter import CTkToplevel, CTkFrame from ..ui_utils import getImagePath, setGeometryToCenterOfScreen, fadeInAnimation, createLabelButton from ._CreateQuickSettingBox import _CreateQuickSettingBox @@ -22,11 +22,11 @@ class QuickSettingsWindow(CTkToplevel): self.qsw_background = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR) self.qsw_background.grid(row=0, column=0, pady=0, sticky="ew") - self.qsw_background.grid_columnconfigure(0, weight=1, minsize=400) + self.qsw_background.grid_columnconfigure(0, weight=1, minsize=self.settings.uism.QSB__MIN_WIDTH) self.qsw_background__overlay = CTkFrame(self.qsw_background, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR) - self.qsw_background__overlay.grid(row=0, column=0, pady=(0,18), sticky="ew") + self.qsw_background__overlay.grid(row=0, column=0, pady=self.settings.uism.QSB__BOX_PADY, sticky="ew") self.qsw_background__overlay.grid_columnconfigure(0, weight=1) @@ -246,17 +246,9 @@ class QuickSettingsWindow(CTkToplevel): - - - - - - - - # VRChat mic mute sync self.qsw_background__vrc_mic_mute_sync = CTkFrame(self.qsw_background, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR) - self.qsw_background__vrc_mic_mute_sync.grid(row=0, column=0, pady=(0,18), sticky="ew") + self.qsw_background__vrc_mic_mute_sync.grid(row=0, column=0, pady=self.settings.uism.QSB__BOX_PADY, sticky="ew") self.qsw_background__vrc_mic_mute_sync.grid_columnconfigure(0, weight=1) @@ -269,9 +261,6 @@ class QuickSettingsWindow(CTkToplevel): - - - cqsb = _CreateQuickSettingBox(self.qsw_setting_box__vrc_mic_mute_sync, vrct_gui, settings, view_variable) createSettingBoxSlider = cqsb.createSettingBoxSlider createSettingBoxSwitch = cqsb.createSettingBoxSwitch @@ -312,7 +301,5 @@ class QuickSettingsWindow(CTkToplevel): self.qsw_background.update() self.geometry("{}x{}".format(self.qsw_background.winfo_width(), self.qsw_background.winfo_height())) - - setGeometryToCenterOfScreen(root_widget=self) fadeInAnimation(self, steps=5, interval=0.02) \ No newline at end of file diff --git a/vrct_gui/quick_settings_window/_CreateQuickSettingBox.py b/vrct_gui/quick_settings_window/_CreateQuickSettingBox.py index e2bbe271..3bb10ebc 100644 --- a/vrct_gui/quick_settings_window/_CreateQuickSettingBox.py +++ b/vrct_gui/quick_settings_window/_CreateQuickSettingBox.py @@ -1,9 +1,5 @@ from typing import Union - -from utils import callFunctionIfCallable - -from customtkinter import CTkImage, CTkLabel, CTkToplevel, CTkProgressBar, CTkFrame, CTkSlider, CTkFont, CTkSwitch -from ..ui_utils import openImageKeepAspectRatio, getImageFileFromUiUtils, setGeometryToCenterOfScreen, fadeInAnimation +from customtkinter import CTkLabel, CTkFrame, CTkSlider, CTkFont, CTkSwitch class _CreateQuickSettingBox(): def __init__(self, parent_frame, vrct_gui, settings, view_variable): @@ -13,13 +9,6 @@ class _CreateQuickSettingBox(): self.parent_frame = parent_frame - - - - - - - def _createSettingBoxFrame(self, for_var_label_text=None, for_var_current_value=None): setting_box_frame = CTkFrame(self.parent_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) @@ -63,10 +52,6 @@ class _CreateQuickSettingBox(): - - - - # Items setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) setting_box_item_frame.grid(row=1, column=0, padx=0, sticky="nsew") @@ -81,16 +66,6 @@ class _CreateQuickSettingBox(): - - - - - - - - - - def createSettingBoxSlider( self, for_var_label_text, diff --git a/vrct_gui/ui_managers/AboutVrctManager.py b/vrct_gui/ui_managers/AboutVrctManager.py index 9e45db63..0b5a8797 100644 --- a/vrct_gui/ui_managers/AboutVrctManager.py +++ b/vrct_gui/ui_managers/AboutVrctManager.py @@ -1,7 +1,7 @@ from types import SimpleNamespace -from ..ui_utils import calculateUiSize, getImageFileFromUiUtils_AboutVrct, bindButtonReleaseFunction, createButtonWithImage, bindButtonFunctionAndColor -from customtkinter import CTkFrame, CTkLabel, CTkImage, CTkFont +from ..ui_utils import calculateUiSize, getImageFileFromUiUtils_AboutVrct, bindButtonFunctionAndColor +from customtkinter import CTkFrame, CTkLabel, CTkImage IMAGE_STANDARD_SCALING = 2 class AboutVrctManager(): diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 2c081c2c..a25ce5c9 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -202,6 +202,8 @@ class UiScalingManager(): # Quick Settings Box + self.config_window.QSB__MIN_WIDTH = self._calculateUiSize(400) + self.config_window.QSB__BOX_PADY = (0, self._calculateUiSize(18)) self.config_window.QSB__IPADX = self._calculateUiSize(20) self.config_window.QSB__IPADY = (self._calculateUiSize(14), self._calculateUiSize(8)) self.config_window.QSB__LABEL_BOTTOM_PADY = self._calculateUiSize(6) From bbde617414d7e761de55f88c38a1f8aa5a65753d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 9 May 2024 20:46:38 +0900 Subject: [PATCH 46/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E5=87=A6=E7=90=86=E3=81=AE=E3=83=9E=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E5=87=A6=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 5 +-- model.py | 7 ++-- models/translation/translation_translator.py | 37 ++++++++++++-------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/controller.py b/controller.py index 634d3181..cb826080 100644 --- a/controller.py +++ b/controller.py @@ -400,10 +400,11 @@ def callbackSelectedTranslationEngine(selected_translation_engine): def callbackToggleTranslation(is_turned_on): config.ENABLE_TRANSLATION = is_turned_on if config.ENABLE_TRANSLATION is True: - model.changeTranslatorCTranslate2Model() + if model.isLoadedCTranslate2Model() is False: + model.changeTranslatorCTranslate2Model() view.printToTextbox_enableTranslation() else: - model.clearTranslatorCTranslate2Model() + # model.clearTranslatorCTranslate2Model() view.printToTextbox_disableTranslation() def callbackToggleTranscriptionSend(is_turned_on): diff --git a/model.py b/model.py index 7d89a0a9..d5d5c3e7 100644 --- a/model.py +++ b/model.py @@ -103,8 +103,11 @@ class Model: def changeTranslatorCTranslate2Model(self): self.translator.changeCTranslate2Model(config.PATH_LOCAL, config.CTRANSLATE2_WEIGHT_TYPE) - def clearTranslatorCTranslate2Model(self): - self.translator.clearCTranslate2Model() + def isLoadedCTranslate2Model(self): + return self.translator.isLoadedCTranslate2Model() + + # def clearTranslatorCTranslate2Model(self): + # self.translator.clearCTranslate2Model() def checkTranscriptionWhisperModelWeight(self): return checkWhisperWeight(config.PATH_LOCAL, config.WHISPER_WEIGHT_TYPE) diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index d2717747..0ef71b88 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -14,6 +14,7 @@ class Translator(): self.deepl_client = None self.ctranslate2_translator = None self.ctranslate2_tokenizer = None + self.is_loaded_ctranslate2_model = False def authenticationDeepLAuthKey(self, authkey): result = True @@ -44,24 +45,30 @@ class Translator(): print("Error: changeCTranslate2Model()", e) tokenizer_path = os.path.join("./weights", "ctranslate2", directory_name, "tokenizer") self.ctranslate2_tokenizer = transformers.AutoTokenizer.from_pretrained(tokenizer, cache_dir=tokenizer_path) + self.is_loaded_ctranslate2_model = True - def clearCTranslate2Model(self): - del self.ctranslate2_translator - del self.ctranslate2_tokenizer - gc.collect() - self.ctranslate2_translator = None - self.ctranslate2_tokenizer = None + def isLoadedCTranslate2Model(self): + return self.is_loaded_ctranslate2_model + + # def clearCTranslate2Model(self): + # del self.ctranslate2_translator + # del self.ctranslate2_tokenizer + # gc.collect() + # self.ctranslate2_translator = None + # self.ctranslate2_tokenizer = None def translateCTranslate2(self, message, source_language, target_language): - try: - self.ctranslate2_tokenizer.src_lang = source_language - source = self.ctranslate2_tokenizer.convert_ids_to_tokens(self.ctranslate2_tokenizer.encode(message)) - target_prefix = [self.ctranslate2_tokenizer.lang_code_to_token[target_language]] - results = self.ctranslate2_translator.translate_batch([source], target_prefix=[target_prefix]) - target = results[0].hypotheses[0][1:] - result = self.ctranslate2_tokenizer.decode(self.ctranslate2_tokenizer.convert_tokens_to_ids(target)) - except Exception: - result = False + result = False + if self.is_loaded_ctranslate2_model is True: + try: + self.ctranslate2_tokenizer.src_lang = source_language + source = self.ctranslate2_tokenizer.convert_ids_to_tokens(self.ctranslate2_tokenizer.encode(message)) + target_prefix = [self.ctranslate2_tokenizer.lang_code_to_token[target_language]] + results = self.ctranslate2_translator.translate_batch([source], target_prefix=[target_prefix]) + target = results[0].hypotheses[0][1:] + result = self.ctranslate2_tokenizer.decode(self.ctranslate2_tokenizer.convert_tokens_to_ids(target)) + except Exception: + pass return result @staticmethod From 1aeeda4828eba1a1b3cad725115c2a467c93b8fb Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 9 May 2024 21:54:35 +0900 Subject: [PATCH 47/63] [Update] About VRCT: adjust bottom padding for About VRCT as an exception. --- .../_createSettingBoxContainer.py | 4 ++-- .../createSideMenuAndSettingsBoxContainers.py | 5 +++-- .../setting_box_about_vrct/createSettingBox_AboutVrct.py | 2 +- vrct_gui/ui_managers/AboutVrctManager.py | 2 +- vrct_gui/ui_managers/UiScalingManager.py | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py index 28b7c552..a140ecc3 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py @@ -1,7 +1,7 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel -def _createSettingBoxContainer(config_window, settings, view_variable, setting_box_container_settings): +def _createSettingBoxContainer(config_window, settings, view_variable, setting_box_container_settings, bottom_margin): def createSectionTitle(container_widget, var_section_title): @@ -22,7 +22,7 @@ def _createSettingBoxContainer(config_window, settings, view_variable, setting_b # Setting box container setting_box_container_widget = CTkFrame(config_window.main_setting_box_bg_wrapper, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) setattr(config_window, setting_box_container_settings["setting_box_container_attr_name"], setting_box_container_widget) - setting_box_container_widget.grid(row=0, pady=settings.uism.SB__BOTTOM_MARGIN) + setting_box_container_widget.grid(row=0, pady=bottom_margin) setting_box_container_widget.grid_remove() diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index d70a0eba..85e2d20e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -52,7 +52,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl config_window.main_setting_box_bg_wrapper = CTkFrame(config_window.main_setting_box_scrollable_container, corner_radius=0, width=0, height=0, fg_color=settings.ctm.MAIN_BG_COLOR) - config_window.main_setting_box_bg_wrapper.grid(row=0, column=0, pady=settings.uism.SB__BOTTOM_MARGIN, sticky="n") + config_window.main_setting_box_bg_wrapper.grid(row=0, column=0, pady=0, sticky="n") @@ -187,12 +187,13 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl side_menu_row+=1 + bottom_margin = 0 if sm_and_sbc_setting["setting_box_container_settings"]["setting_box_container_attr_name"] == "setting_box_container_about_vrct" else settings.uism.SB__BOTTOM_MARGIN _createSettingBoxContainer( config_window=config_window, settings=settings, view_variable=view_variable, setting_box_container_settings=sm_and_sbc_setting["setting_box_container_settings"], - + bottom_margin=bottom_margin, ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_about_vrct/createSettingBox_AboutVrct.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_about_vrct/createSettingBox_AboutVrct.py index 261a4a79..3ef4b4fc 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_about_vrct/createSettingBox_AboutVrct.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_about_vrct/createSettingBox_AboutVrct.py @@ -611,4 +611,4 @@ def createSettingBox_AboutVrct(setting_box_wrapper, config_window, settings, vie vrchat_disclaimer_label = settings.about_vrct.embedImageCTkLabel(vrchat_disclaimer_contents_wrapper, "vrchat_disclaimer.png") - vrchat_disclaimer_label.grid(column=0, row=0, padx=0, pady=(about_vrct_uism.VRCHAT_DISCLAIMER_SECTION_TOP_PADDING, 0), sticky="nsew") \ No newline at end of file + vrchat_disclaimer_label.grid(column=0, row=0, padx=0, pady=about_vrct_uism.VRCHAT_DISCLAIMER_SECTION_PADY, sticky="nsew") \ No newline at end of file diff --git a/vrct_gui/ui_managers/AboutVrctManager.py b/vrct_gui/ui_managers/AboutVrctManager.py index 0b5a8797..42cdc6fe 100644 --- a/vrct_gui/ui_managers/AboutVrctManager.py +++ b/vrct_gui/ui_managers/AboutVrctManager.py @@ -17,7 +17,7 @@ class AboutVrctManager(): self.uism.SECTION_BOTTOM_PADY = self._calculateUiSize(22) self.uism.PROJECT_LINKS_SECTION_BOTTOM_PADDING = self._calculateUiSize(18) # Exception pady - self.uism.VRCHAT_DISCLAIMER_SECTION_TOP_PADDING = self._calculateUiSize(80) # Exception pady + self.uism.VRCHAT_DISCLAIMER_SECTION_PADY = (self._calculateUiSize(80), self._calculateUiSize(20)) # Exception pady self.uism.THE_DEVELOPERS_SECTION_TITLE_BOTTOM_PADY = self._calculateUiSize(8) self.uism.DEVS_CONTACTS_Y1 = self._calculateUiSize(118) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index a25ce5c9..3d4e2c30 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -264,7 +264,7 @@ class UiScalingManager(): self.config_window.SB__IPADX = self._calculateUiSize(20) self.config_window.SB__IPADY = self._calculateUiSize(12) - self.config_window.SB__BOTTOM_MARGIN = (0, self._calculateUiSize(60)) + self.config_window.SB__BOTTOM_MARGIN = (0, self._calculateUiSize(120)) self.config_window.SB__FAKE_BOTTOM_BORDER_SIZE = (0, self._calculateUiSize(1, is_allowed_odd=True)) self.config_window.SB__SECTION_TITLE_FONT_SIZE = self._calculateUiSize(20) From cdea680f0807d0553754cb9b5bf8f9a2e4f319e5 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 10 May 2024 16:44:36 +0900 Subject: [PATCH 48/63] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20Model=20:?= =?UTF-8?q?=20translation=20=E3=83=A2=E3=83=87=E3=83=AB=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=89=E6=99=82=E3=81=AE=E3=83=9E=E3=82=B9=E3=82=AF=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit モデルデータをgcするコードを削除 重複する処理を関数化 --- controller.py | 1 - model.py | 94 +++++++++----------- models/translation/translation_translator.py | 9 +- 3 files changed, 44 insertions(+), 60 deletions(-) diff --git a/controller.py b/controller.py index cb826080..dc5ceef2 100644 --- a/controller.py +++ b/controller.py @@ -404,7 +404,6 @@ def callbackToggleTranslation(is_turned_on): model.changeTranslatorCTranslate2Model() view.printToTextbox_enableTranslation() else: - # model.clearTranslatorCTranslate2Model() view.printToTextbox_disableTranslation() def callbackToggleTranscriptionSend(is_turned_on): diff --git a/model.py b/model.py index d5d5c3e7..f350cfc7 100644 --- a/model.py +++ b/model.py @@ -106,9 +106,6 @@ class Model: def isLoadedCTranslate2Model(self): return self.translator.isLoadedCTranslate2Model() - # def clearTranslatorCTranslate2Model(self): - # self.translator.clearCTranslate2Model() - def checkTranscriptionWhisperModelWeight(self): return checkWhisperWeight(config.PATH_LOCAL, config.WHISPER_WEIGHT_TYPE) @@ -168,67 +165,62 @@ class Model: compatible_engines.remove('DeepL_API') return compatible_engines + def getTranslate(self, translator_name, source_language, target_language, target_country, message): + success_flag = False + translation = self.translator.translate( + translator_name=translator_name, + source_language=source_language, + target_language=target_language, + target_country=target_country, + message=message + ) + + # 翻訳失敗時のフェールセーフ処理 + if translation is True: + success_flag = True + else: + while True: + translation = self.translator.translate( + translator_name="CTranslate2", + source_language=source_language, + target_language=target_language, + target_country=target_country, + message=message + ) + if translation is not False: + break + sleep(0.1) + return translation, success_flag + def getInputTranslate(self, message): - translation_success_flag = True translator_name=config.CHOICE_INPUT_TRANSLATOR source_language=config.SOURCE_LANGUAGE target_language=config.TARGET_LANGUAGE target_country = config.TARGET_COUNTRY - translation = self.translator.translate( - translator_name=translator_name, - source_language=source_language, - target_language=target_language, - target_country=target_country, - message=message - ) - - # 翻訳失敗時のフェールセーフ処理 - if translation is False: - translation_success_flag = False - while True: - translation = self.translator.translate( - translator_name="CTranslate2", - source_language=source_language, - target_language=target_language, - target_country=target_country, - message=message - ) - if translation is not False: - break - sleep(0.1) - return translation, translation_success_flag + translation, success_flag = self.getTranslate( + translator_name, + source_language, + target_language, + target_country, + message + ) + return translation, success_flag def getOutputTranslate(self, message): - translation_success_flag = True translator_name=config.CHOICE_OUTPUT_TRANSLATOR source_language=config.TARGET_LANGUAGE target_language=config.SOURCE_LANGUAGE target_country=config.SOURCE_COUNTRY - translation = self.translator.translate( - translator_name=translator_name, - source_language=source_language, - target_language=target_language, - target_country=target_country, - message=message - ) - - # 翻訳失敗時のフェールセーフ処理 - if translation is False: - translation_success_flag = False - while True: - translation = self.translator.translate( - translator_name="CTranslate2", - source_language=source_language, - target_language=target_language, - target_country=target_country, - message=message - ) - if translation is not False: - break - sleep(0.1) - return translation, translation_success_flag + translation, success_flag = self.getTranslate( + translator_name, + source_language, + target_language, + target_country, + message + ) + return translation, success_flag def addKeywords(self): for f in config.INPUT_MIC_WORD_FILTER: diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index 0ef71b88..56c5cf64 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -1,4 +1,3 @@ -import gc import os from deepl import Translator as deepl_Translator from translators import translate_text as other_web_Translator @@ -27,6 +26,7 @@ class Translator(): return result def changeCTranslate2Model(self, path, model_type): + self.is_loaded_ctranslate2_model = False directory_name = ctranslate2_weights[model_type]["directory_name"] tokenizer = ctranslate2_weights[model_type]["tokenizer"] weight_path = os.path.join(path, "weights", "ctranslate2", directory_name) @@ -50,13 +50,6 @@ class Translator(): def isLoadedCTranslate2Model(self): return self.is_loaded_ctranslate2_model - # def clearCTranslate2Model(self): - # del self.ctranslate2_translator - # del self.ctranslate2_tokenizer - # gc.collect() - # self.ctranslate2_translator = None - # self.ctranslate2_tokenizer = None - def translateCTranslate2(self, message, source_language, target_language): result = False if self.is_loaded_ctranslate2_model is True: From 2e1d0591b73b641ce2abb79d9b06e6d825ec4261 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 11 May 2024 00:40:11 +0900 Subject: [PATCH 49/63] [Update] Add UI and config: Overlay Rotation X,Y,Z. --- config.py | 5 +- controller.py | 9 +++ view.py | 19 ++++++ .../QuickSettingsWindow.py | 58 +++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/config.py b/config.py index 7bbd5c64..b29882bd 100644 --- a/config.py +++ b/config.py @@ -770,7 +770,7 @@ class Config: if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SMALL_LOG_SETTINGS.keys()): for key, value in value.items(): match (key): - case "x_pos" | "y_pos" | "depth": + case "x_pos" | "y_pos" | "depth" | "x_rotation" | "y_rotation" | "z_rotation": if isinstance(value, float): self._OVERLAY_SMALL_LOG_SETTINGS[key] = value case "display_duration" | "fadeout_duration": @@ -1074,6 +1074,9 @@ class Config: "x_pos": 0.0, "y_pos": -0.41, "depth": 1.0, + "x_rotation": 0.0, + "y_rotation": 0.0, + "z_rotation": 0.0, "display_duration": 5, "fadeout_duration": 2, } diff --git a/controller.py b/controller.py index 714335f0..9646ded6 100644 --- a/controller.py +++ b/controller.py @@ -903,6 +903,15 @@ def callbackSetOverlaySmallLogSettings(value, set_type:str): model.updateOverlayPosition() case "depth": model.updateOverlayPosition() + case "x_rotation": + pass + # update rotation + case "y_rotation": + pass + # update rotation + case "z_rotation": + pass + # update rotation case "display_duration": model.updateOverlayTimes() case "fadeout_duration": diff --git a/view.py b/view.py index 939e000a..ad701bbe 100644 --- a/view.py +++ b/view.py @@ -186,6 +186,25 @@ class View(): VAR_OVERLAY_SMALL_LOG_DEPTH=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), + VAR_LABEL_OVERLAY_SMALL_LOG_X_ROTATION=StringVar(value="x_rotation"), + SLIDER_RANGE_OVERLAY_SMALL_LOG_X_ROTATION=(-1, 1), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_ROTATION=200, + VAR_OVERLAY_SMALL_LOG_X_ROTATION=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_ROTATION=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"]), + + VAR_LABEL_OVERLAY_SMALL_LOG_Y_ROTATION=StringVar(value="y_rotation"), + SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_ROTATION=(-1, 1), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_ROTATION=200, + VAR_OVERLAY_SMALL_LOG_Y_ROTATION=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_ROTATION=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"]), + + VAR_LABEL_OVERLAY_SMALL_LOG_Z_ROTATION=StringVar(value="z_rotation"), + SLIDER_RANGE_OVERLAY_SMALL_LOG_Z_ROTATION=(-1, 1), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Z_ROTATION=200, + VAR_OVERLAY_SMALL_LOG_Z_ROTATION=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Z_ROTATION=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"]), + + VAR_LABEL_OVERLAY_SMALL_LOG_DISPLAY_DURATION=StringVar(value=i18n.t("overlay_settings.display_duration")), SLIDER_RANGE_OVERLAY_SMALL_LOG_DISPLAY_DURATION=(1, 60), NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DISPLAY_DURATION=59, diff --git a/vrct_gui/quick_settings_window/QuickSettingsWindow.py b/vrct_gui/quick_settings_window/QuickSettingsWindow.py index 1f44f523..beba415c 100644 --- a/vrct_gui/quick_settings_window/QuickSettingsWindow.py +++ b/vrct_gui/quick_settings_window/QuickSettingsWindow.py @@ -163,6 +163,64 @@ class QuickSettingsWindow(CTkToplevel): self.qsb__overlay_small_log_settings_depth.grid(row=row) + + row+=1 + def overlaySmallLogSettingsXRotationSliderCallback(e): + value = round(e,2) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "x_rotation") + view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_ROTATION.set(str(value)) + + self.qsb__overlay_small_log_settings_x_rotation = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_SMALL_LOG_X_ROTATION, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_ROTATION, + slider_attr_name="qsb__overlay_small_log_settings_x_rotation_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_X_ROTATION, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_ROTATION, + command=overlaySmallLogSettingsXRotationSliderCallback, + variable=view_variable.VAR_OVERLAY_SMALL_LOG_X_ROTATION, + ) + self.qsb__overlay_small_log_settings_x_rotation.grid(row=row) + + + + row+=1 + def overlaySmallLogSettingsYRotationSliderCallback(e): + value = round(e,2) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "y_rotation") + view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_ROTATION.set(str(value)) + + self.qsb__overlay_small_log_settings_y_rotation = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_SMALL_LOG_Y_ROTATION, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_ROTATION, + slider_attr_name="qsb__overlay_small_log_settings_y_rotation_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_ROTATION, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_ROTATION, + command=overlaySmallLogSettingsYRotationSliderCallback, + variable=view_variable.VAR_OVERLAY_SMALL_LOG_Y_ROTATION, + ) + self.qsb__overlay_small_log_settings_y_rotation.grid(row=row) + + + + row+=1 + def overlaySmallLogSettingsZRotationSliderCallback(e): + value = round(e,2) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "z_rotation") + view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Z_ROTATION.set(str(value)) + + self.qsb__overlay_small_log_settings_z_rotation = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_SMALL_LOG_Z_ROTATION, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Z_ROTATION, + slider_attr_name="qsb__overlay_small_log_settings_z_rotation_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_Z_ROTATION, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Z_ROTATION, + command=overlaySmallLogSettingsZRotationSliderCallback, + variable=view_variable.VAR_OVERLAY_SMALL_LOG_Z_ROTATION, + ) + self.qsb__overlay_small_log_settings_z_rotation.grid(row=row) + + + row+=1 def overlaySmallLogSettingsDisplayDurationSliderCallback(e): value = int(e) From 212a3e62d1c1c5f018cb308035898bf65ec0f5f1 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 11 May 2024 22:05:01 +0900 Subject: [PATCH 50/63] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20Model=20:?= =?UTF-8?q?=20overlay=20rotation=20and=20bind=20lefthand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 9 ++--- model.py | 8 ++++- models/overlay/overlay.py | 72 ++++++++++++++++++++++++++++----------- requirements.txt | 1 + view.py | 6 ++-- 5 files changed, 66 insertions(+), 30 deletions(-) diff --git a/controller.py b/controller.py index 9646ded6..8e79c6f8 100644 --- a/controller.py +++ b/controller.py @@ -904,14 +904,11 @@ def callbackSetOverlaySmallLogSettings(value, set_type:str): case "depth": model.updateOverlayPosition() case "x_rotation": - pass - # update rotation + model.updateOverlayPosition() case "y_rotation": - pass - # update rotation + model.updateOverlayPosition() case "z_rotation": - pass - # update rotation + model.updateOverlayPosition() case "display_duration": model.updateOverlayTimes() case "fadeout_duration": diff --git a/model.py b/model.py index f350cfc7..b557d628 100644 --- a/model.py +++ b/model.py @@ -85,6 +85,9 @@ class Model: config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["depth"], + 0, + 0, + 0, config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"], config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"], config.OVERLAY_SETTINGS["opacity"], @@ -684,7 +687,10 @@ class Model: def updateOverlayPosition(self): pos = (config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]) depth = config.OVERLAY_SMALL_LOG_SETTINGS["depth"] - self.overlay.updatePosition(pos, depth) + x_rotation = config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"] + y_rotation = config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"] + z_rotation = config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"] + self.overlay.updatePosition(pos, depth, x_rotation, y_rotation, z_rotation) def updateOverlayTimes(self): display_duration = config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"] diff --git a/models/overlay/overlay.py b/models/overlay/overlay.py index 01af327d..51b5fea0 100644 --- a/models/overlay/overlay.py +++ b/models/overlay/overlay.py @@ -5,16 +5,19 @@ import time import openvr from PIL import Image from threading import Thread +import numpy as np +import quaternion def mat34Id(): arr = openvr.HmdMatrix34_t() - arr[0][0] = 1 - arr[1][1] = 1 - arr[2][2] = 1 + # arr[0][0] = 1 + # arr[1][1] = 1 + # arr[2][2] = 1 + # print(arr) return arr class Overlay: - def __init__(self, x, y , depth, display_duration, fadeout_duration, opacity, ui_scaling): + def __init__(self, x, y , depth, x_rotation, y_rotation, z_rotation, display_duration, fadeout_duration, opacity, ui_scaling): self.initialized = False settings = { "color": [1, 1, 1], @@ -22,6 +25,9 @@ class Overlay: "x_pos": x, "y_pos": y, "depth": depth, + "x_rotation": x_rotation, + "y_rotation": y_rotation, + "z_rotation": z_rotation, "display_duration": display_duration, "fadeout_duration": fadeout_duration, "ui_scaling": ui_scaling, @@ -39,6 +45,7 @@ class Overlay: try: self.system = openvr.init(openvr.VRApplication_Background) self.overlay = openvr.IVROverlay() + self.overlay_system = openvr.IVRSystem() self.handle = self.overlay.createOverlay("Overlay_Speaker2log", "SOverlay_Speaker2log_UI") self.overlay.showOverlay(self.handle) self.initialized = True @@ -49,7 +56,10 @@ class Overlay: self.updateUiScaling(self.settings["ui_scaling"]) self.updatePosition( (self.settings["x_pos"], self.settings["y_pos"]), - self.settings["depth"] + self.settings["depth"], + self.settings["x_rotation"], + self.settings["y_rotation"], + self.settings["z_rotation"], ) except Exception as e: @@ -92,26 +102,39 @@ class Overlay: if self.initialized is True: self.overlay.setOverlayWidthInMeters(self.handle, self.settings['ui_scaling']) - def updatePosition(self, pos, depth): + def updatePosition(self, pos, depth, x_rotation, y_rotation, z_rotation): """ pos is a 2-tuple representing normalized (x, y) depth is a float representing the depth of the icon plane + x_rotation, y_rotation, z_rotation are floats representing the rotation of the icon plane """ self.settings["x_pos"] = pos[0] self.settings["y_pos"] = pos[1] self.settings["depth"] = depth + self.settings["x_rotation"] = x_rotation + self.settings["y_rotation"] = y_rotation + self.settings["z_rotation"] = z_rotation self.transform = mat34Id() # no rotation required for HMD attachment + # assign rotation + rot = np.quaternion(1, self.settings["x_rotation"], self.settings["y_rotation"], self.settings["z_rotation"]) + rot = quaternion.as_rotation_matrix(rot) + # self.transform[:3, :3] = rot + for i in range(3): + for j in range(3): + self.transform[i][j] = rot[i][j] + # assign position self.transform[0][3] = self.settings["x_pos"] * self.settings['depth'] self.transform[1][3] = self.settings["y_pos"] * self.settings['depth'] self.transform[2][3] = - self.settings['depth'] + leftControllerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_LeftHand) if self.initialized is True: self.overlay.setOverlayTransformTrackedDeviceRelative( self.handle, - openvr.k_unTrackedDeviceIndex_Hmd, + leftControllerIndex, #openvr.k_unTrackedDeviceIndex_Hmd, self.transform ) @@ -190,19 +213,28 @@ if __name__ == '__main__': from overlay_image import OverlayImage overlay_image = OverlayImage() - for i in range(100): - print(i) - overlay = Overlay(0, 0, 1, 1, 1, 1, 1) - overlay.startOverlay() - time.sleep(1) + overlay = Overlay(0, 0, 1, 1, 0, 1, 1) + overlay.startOverlay() + time.sleep(1) - # Example usage - img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") - overlay.updateImage(img) - time.sleep(0.5) + # Example usage + img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") + overlay.updateImage(img) + time.sleep(100000) + + # for i in range(100): + # print(i) + # overlay = Overlay(0, 0, 1, 1, 1, 1, 1) + # overlay.startOverlay() + # time.sleep(1) - img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") - overlay.updateImage(img) - time.sleep(0.5) + # # Example usage + # img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") + # overlay.updateImage(img) + # time.sleep(0.5) - overlay.shutdownOverlay() + # img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") + # overlay.updateImage(img) + # time.sleep(0.5) + + # overlay.shutdownOverlay() diff --git a/requirements.txt b/requirements.txt index 3e4c175b..0d979429 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ sentencepiece==0.1.99 ctranslate2==4.1.0 faster-whisper==1.0.1 openvr==1.26.701 +numpy-quaternion==2023.0.3 translators @ git+https://github.com/misyaguziya/translators@5.8.9 SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4 tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.2 \ No newline at end of file diff --git a/view.py b/view.py index ad701bbe..28150c55 100644 --- a/view.py +++ b/view.py @@ -169,19 +169,19 @@ class View(): CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS=None, VAR_LABEL_OVERLAY_SMALL_LOG_X_POS=StringVar(value=i18n.t("overlay_settings.x_position")), - SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-0.5, 0.5), + SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-2, 2), NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=100, VAR_OVERLAY_SMALL_LOG_X_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), VAR_LABEL_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=i18n.t("overlay_settings.y_position")), - SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-0.8, 0.8), + SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-2, 2), NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=160, VAR_OVERLAY_SMALL_LOG_Y_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), VAR_LABEL_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=i18n.t("overlay_settings.depth")), - SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH=(0.5, 1.5), + SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH=(0, 2), NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH=100, VAR_OVERLAY_SMALL_LOG_DEPTH=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), From 095eaac4203395fbc983571fcdc1d3b67585af25 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 15 May 2024 12:58:58 +0900 Subject: [PATCH 51/63] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20Model=20:?= =?UTF-8?q?=20overlay=20=E5=B7=A6=E6=89=8B=E5=91=A8=E3=82=8A=E3=81=AE?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HMD,左手の初期値を追加 - 初期値からの変化量をUIから調整できるように変更 - quaternionは不要なため、削除 --- config.py | 4 +- models/overlay/overlay.py | 114 ++++++++++++++++++++++---------- models/overlay/overlay_utils.py | 87 ++++++++++++++++++++++++ requirements.txt | 1 - view.py | 28 ++++---- 5 files changed, 183 insertions(+), 51 deletions(-) create mode 100644 models/overlay/overlay_utils.py diff --git a/config.py b/config.py index b29882bd..61f77746 100644 --- a/config.py +++ b/config.py @@ -1072,8 +1072,8 @@ class Config: self._ENABLE_OVERLAY_SMALL_LOG = False self._OVERLAY_SMALL_LOG_SETTINGS = { "x_pos": 0.0, - "y_pos": -0.41, - "depth": 1.0, + "y_pos": 0.0, + "depth": 0.0, "x_rotation": 0.0, "y_rotation": 0.0, "z_rotation": 0.0, diff --git a/models/overlay/overlay.py b/models/overlay/overlay.py index 51b5fea0..2e9cb9c7 100644 --- a/models/overlay/overlay.py +++ b/models/overlay/overlay.py @@ -1,19 +1,56 @@ import os import ctypes -from psutil import process_iter import time -import openvr -from PIL import Image +from psutil import process_iter from threading import Thread +import openvr import numpy as np -import quaternion +from PIL import Image +try: + from . import overlay_utils as utils +except ImportError: + import overlay_utils as utils -def mat34Id(): +def mat34Id(array): arr = openvr.HmdMatrix34_t() - # arr[0][0] = 1 - # arr[1][1] = 1 - # arr[2][2] = 1 - # print(arr) + for i in range(3): + for j in range(4): + arr[i][j] = array[i][j] + return arr + +def getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation): + arr = np.zeros((3, 4)) + rot = utils.euler_to_rotation_matrix((x_rotation, y_rotation, z_rotation)) + + for i in range(3): + for j in range(3): + arr[i][j] = rot[i][j] + + arr[0][3] = x_pos * depth + arr[1][3] = y_pos * depth + arr[2][3] = - depth + return arr + +def getHMDBaseMatrix(): + x_pos = 0.0 + y_pos = -0.4 + depth = 1.0 + x_rotation = 0.0 + y_rotation = 0.0 + z_rotation = 0.0 + + arr = getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation) + return arr + +def getLeftHandBaseMatrix(): + x_pos = 0.0 + y_pos = -0.06 + depth = -0.14 + x_rotation = -62.0 + y_rotation = 154.0 + z_rotation = 71.0 + + arr = getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation) return arr class Overlay: @@ -108,6 +145,7 @@ class Overlay: depth is a float representing the depth of the icon plane x_rotation, y_rotation, z_rotation are floats representing the rotation of the icon plane """ + self.settings["x_pos"] = pos[0] self.settings["y_pos"] = pos[1] self.settings["depth"] = depth @@ -115,26 +153,20 @@ class Overlay: self.settings["y_rotation"] = y_rotation self.settings["z_rotation"] = z_rotation - self.transform = mat34Id() # no rotation required for HMD attachment + base_matrix = getHMDBaseMatrix() + # base_matrix = getLeftHandBaseMatrix() + translation = (self.settings["x_pos"], self.settings["y_pos"], - self.settings['depth']) + rotation = (self.settings["x_rotation"], self.settings["y_rotation"], self.settings["z_rotation"]) + transform = utils.transform_matrix(base_matrix, translation, rotation) + self.transform = mat34Id(transform) - # assign rotation - rot = np.quaternion(1, self.settings["x_rotation"], self.settings["y_rotation"], self.settings["z_rotation"]) - rot = quaternion.as_rotation_matrix(rot) - # self.transform[:3, :3] = rot - for i in range(3): - for j in range(3): - self.transform[i][j] = rot[i][j] - - # assign position - self.transform[0][3] = self.settings["x_pos"] * self.settings['depth'] - self.transform[1][3] = self.settings["y_pos"] * self.settings['depth'] - self.transform[2][3] = - self.settings['depth'] - - leftControllerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_LeftHand) + hmdIndex = openvr.k_unTrackedDeviceIndex_Hmd + # leftControllerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_LeftHand) + # rightControllerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_RightHand) if self.initialized is True: self.overlay.setOverlayTransformTrackedDeviceRelative( self.handle, - leftControllerIndex, #openvr.k_unTrackedDeviceIndex_Hmd, + hmdIndex, self.transform ) @@ -210,17 +242,17 @@ class Overlay: return _proc_name in (p.name() for p in process_iter()) if __name__ == '__main__': - from overlay_image import OverlayImage - overlay_image = OverlayImage() + # from overlay_image import OverlayImage + # overlay_image = OverlayImage() - overlay = Overlay(0, 0, 1, 1, 0, 1, 1) - overlay.startOverlay() - time.sleep(1) + # overlay = Overlay(0, 0, 1, 1, 0, 1, 1) + # overlay.startOverlay() + # time.sleep(1) - # Example usage - img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") - overlay.updateImage(img) - time.sleep(100000) + # # Example usage + # img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese") + # overlay.updateImage(img) + # time.sleep(100000) # for i in range(100): # print(i) @@ -238,3 +270,17 @@ if __name__ == '__main__': # time.sleep(0.5) # overlay.shutdownOverlay() + + x_pos = 0 + y_pos = 0 + depth = 0 + x_rotation = 0 + y_rotation = 0 + z_rotation = 0 + + base_matrix = getLeftHandBaseMatrix() + translation = (x_pos * depth, y_pos * depth, depth) + rotation = (x_rotation, y_rotation, z_rotation) + transform = utils.transform_matrix(base_matrix, translation, rotation) + transform = mat34Id(transform) + print(transform) \ No newline at end of file diff --git a/models/overlay/overlay_utils.py b/models/overlay/overlay_utils.py new file mode 100644 index 00000000..0a379dd0 --- /dev/null +++ b/models/overlay/overlay_utils.py @@ -0,0 +1,87 @@ +import numpy as np + +def toHomogeneous(matrix): + homogeneous_matrix = np.vstack([matrix, [0, 0, 0, 1]]) + return homogeneous_matrix + +# 移動行列を生成する関数 +def calcTranslationMatrix(translation): + tx, ty, tz = translation + return np.array([ + [1, 0, 0, tx], + [0, 1, 0, ty], + [0, 0, 1, tz], + [0, 0, 0, 1] + ]) + +# X軸周りの回転行列を生成する関数 +def calcRotationMatrixX(angle): + c = np.cos(np.pi/180*angle) + s = np.sin(np.pi/180*angle) + return np.array([ + [1, 0, 0, 0], + [0, c, -s, 0], + [0, s, c, 0], + [0, 0, 0, 1] + ]) + +# Y軸周りの回転行列を生成する関数 +def calcRotationMatrixY(angle): + c = np.cos(np.pi/180*angle) + s = np.sin(np.pi/180*angle) + return np.array([ + [c, 0, s, 0], + [0, 1, 0, 0], + [-s, 0, c, 0], + [0, 0, 0, 1] + ]) + +# Z軸周りの回転行列を生成する関数 +def calcRotationMatrixZ(angle): + c = np.cos(np.pi/180*angle) + s = np.sin(np.pi/180*angle) + return np.array([ + [c, -s, 0, 0], + [s, c, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]) + +# 3x4行列の座標を基準として回転や移動を行う関数 +def transform_matrix(base_matrix, translation, rotation): + homogeneous_base_matrix = toHomogeneous(base_matrix) + translation_matrix = calcTranslationMatrix(translation) + rotation_matrix_x = calcRotationMatrixX(rotation[0]) + rotation_matrix_y = calcRotationMatrixY(rotation[1]) + rotation_matrix_z = calcRotationMatrixZ(rotation[2]) + rotation_matrix = np.dot(rotation_matrix_z, np.dot(rotation_matrix_y, rotation_matrix_x)) + transformation_matrix = translation_matrix.copy() + transformation_matrix[:3, :3] = rotation_matrix[:3, :3] + result_matrix = np.dot(homogeneous_base_matrix, transformation_matrix) + return result_matrix[:3, :] + +def euler_to_rotation_matrix(angles): + phi = angles[0] * np.pi / 180 + theta = angles[1] * np.pi / 180 + psi = angles[2]* np.pi / 180 + R_x = np.array([[1, 0, 0], + [0, np.cos(phi), -np.sin(phi)], + [0, np.sin(phi), np.cos(phi)]]) + R_y = np.array([[np.cos(theta), 0, np.sin(theta)], + [0, 1, 0], + [-np.sin(theta), 0, np.cos(theta)]]) + R_z = np.array([[np.cos(psi), -np.sin(psi), 0], + [np.sin(psi), np.cos(psi), 0], + [0, 0, 1]]) + return np.dot(R_z, np.dot(R_y, R_x)) + +if __name__ == "__main__": + base_matrix = np.array([ + [1, 0, 0, 1], + [0, 1, 0, 1], + [0, 0, 1, 1] + ]) + translation = [1, 2, 3] + rotation = [0, 0, 90] + result_matrix = transform_matrix(base_matrix, translation, rotation) + print(result_matrix) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0d979429..3e4c175b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,6 @@ sentencepiece==0.1.99 ctranslate2==4.1.0 faster-whisper==1.0.1 openvr==1.26.701 -numpy-quaternion==2023.0.3 translators @ git+https://github.com/misyaguziya/translators@5.8.9 SpeechRecognition @ git+https://github.com/misyaguziya/custom_speech_recognition@3.10.4 tinyoscquery @ git+https://github.com/cyberkitsune/tinyoscquery@0.1.2 \ No newline at end of file diff --git a/view.py b/view.py index 28150c55..11c250f8 100644 --- a/view.py +++ b/view.py @@ -169,38 +169,38 @@ class View(): CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS=None, VAR_LABEL_OVERLAY_SMALL_LOG_X_POS=StringVar(value=i18n.t("overlay_settings.x_position")), - SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-2, 2), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=100, + SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-5, 5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=1000, VAR_OVERLAY_SMALL_LOG_X_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), VAR_LABEL_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=i18n.t("overlay_settings.y_position")), - SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-2, 2), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=160, + SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-5, 5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=1000, VAR_OVERLAY_SMALL_LOG_Y_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), VAR_LABEL_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=i18n.t("overlay_settings.depth")), - SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH=(0, 2), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH=100, + SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH=(-5, 5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH=1000, VAR_OVERLAY_SMALL_LOG_DEPTH=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), VAR_LABEL_OVERLAY_SMALL_LOG_X_ROTATION=StringVar(value="x_rotation"), - SLIDER_RANGE_OVERLAY_SMALL_LOG_X_ROTATION=(-1, 1), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_ROTATION=200, + SLIDER_RANGE_OVERLAY_SMALL_LOG_X_ROTATION=(-180, 180), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_ROTATION=360, VAR_OVERLAY_SMALL_LOG_X_ROTATION=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_ROTATION=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"]), VAR_LABEL_OVERLAY_SMALL_LOG_Y_ROTATION=StringVar(value="y_rotation"), - SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_ROTATION=(-1, 1), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_ROTATION=200, + SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_ROTATION=(-180, 180), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_ROTATION=360, VAR_OVERLAY_SMALL_LOG_Y_ROTATION=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_ROTATION=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"]), VAR_LABEL_OVERLAY_SMALL_LOG_Z_ROTATION=StringVar(value="z_rotation"), - SLIDER_RANGE_OVERLAY_SMALL_LOG_Z_ROTATION=(-1, 1), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Z_ROTATION=200, + SLIDER_RANGE_OVERLAY_SMALL_LOG_Z_ROTATION=(-180, 180), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Z_ROTATION=360, VAR_OVERLAY_SMALL_LOG_Z_ROTATION=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Z_ROTATION=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"]), @@ -1165,8 +1165,8 @@ class View(): } INIT_OVERLAY_SMALL_LOG_SETTINGS = { "x_pos": 0.0, - "y_pos": -0.41, - "depth": 1.0, + "y_pos": 0.0, + "depth": 0.0, "display_duration": 5, "fadeout_duration": 2, } From dd643daa78850209b05ae9829a9b78342a6d1e7b Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 15 May 2024 13:26:05 +0900 Subject: [PATCH 52/63] =?UTF-8?q?[Update]=20Quick=20Settings=20Window(Over?= =?UTF-8?q?lay):=20=E3=83=BBrotation=E5=91=A8=E3=82=8A=E3=80=81=E6=96=87?= =?UTF-8?q?=E8=A8=80=E4=BF=AE=E6=AD=A3=E3=80=81=E6=97=A5=E6=9C=AC=E8=AA=9E?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C=E3=80=82=20=E3=83=BB=E5=88=9D=E6=9C=9F?= =?UTF-8?q?=E5=80=A4=E3=81=AB=E6=88=BB=E3=81=99=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=81=B8=E3=81=AE=E5=AF=BE=E5=BF=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 3 +++ locales/ja.yml | 3 +++ view.py | 24 +++++++++++++++++++++--- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 34c63e00..673f5535 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -75,6 +75,9 @@ overlay_settings: x_position: X-axis (left-right) y_position: Y-axis (up-down) depth: Z-axis (front-back) + x_rotation: X-axis rotation + y_rotation: Y-axis rotation + z_rotation: Z-axis rotation display_duration: Display duration fadeout_duration: Fadeout duration diff --git a/locales/ja.yml b/locales/ja.yml index 8376fce0..6adaf169 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -74,6 +74,9 @@ overlay_settings: x_position: X軸(左右) y_position: Y軸(上下) depth: Z軸(前後) + x_rotation: X軸の回転 + y_rotation: Y軸の回転 + z_rotation: Z軸の回転 display_duration: 表示時間 fadeout_duration: フェードアウト時間 diff --git a/view.py b/view.py index 11c250f8..ec842c90 100644 --- a/view.py +++ b/view.py @@ -186,19 +186,19 @@ class View(): VAR_OVERLAY_SMALL_LOG_DEPTH=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), - VAR_LABEL_OVERLAY_SMALL_LOG_X_ROTATION=StringVar(value="x_rotation"), + VAR_LABEL_OVERLAY_SMALL_LOG_X_ROTATION=StringVar(value=i18n.t("overlay_settings.x_rotation")), SLIDER_RANGE_OVERLAY_SMALL_LOG_X_ROTATION=(-180, 180), NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_ROTATION=360, VAR_OVERLAY_SMALL_LOG_X_ROTATION=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_ROTATION=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"]), - VAR_LABEL_OVERLAY_SMALL_LOG_Y_ROTATION=StringVar(value="y_rotation"), + VAR_LABEL_OVERLAY_SMALL_LOG_Y_ROTATION=StringVar(value=i18n.t("overlay_settings.y_rotation")), SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_ROTATION=(-180, 180), NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_ROTATION=360, VAR_OVERLAY_SMALL_LOG_Y_ROTATION=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_ROTATION=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"]), - VAR_LABEL_OVERLAY_SMALL_LOG_Z_ROTATION=StringVar(value="z_rotation"), + VAR_LABEL_OVERLAY_SMALL_LOG_Z_ROTATION=StringVar(value=i18n.t("overlay_settings.z_rotation")), SLIDER_RANGE_OVERLAY_SMALL_LOG_Z_ROTATION=(-180, 180), NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Z_ROTATION=360, VAR_OVERLAY_SMALL_LOG_Z_ROTATION=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"]), @@ -1167,6 +1167,9 @@ class View(): "x_pos": 0.0, "y_pos": 0.0, "depth": 0.0, + "x_rotation": 0.0, + "y_rotation": 0.0, + "z_rotation": 0.0, "display_duration": 5, "fadeout_duration": 2, } @@ -1182,6 +1185,9 @@ class View(): self.setLatestConfigVariable("OverlaySmallLogXPos") self.setLatestConfigVariable("OverlaySmallLogYPos") self.setLatestConfigVariable("OverlaySmallLogDepth") + self.setLatestConfigVariable("OverlaySmallLogXRotation") + self.setLatestConfigVariable("OverlaySmallLogYRotation") + self.setLatestConfigVariable("OverlaySmallLogZRotation") self.setLatestConfigVariable("OverlaySmallLogDisplayDuration") self.setLatestConfigVariable("OverlaySmallLogFadeoutDuration") @@ -1948,6 +1954,18 @@ class View(): self.view_variable.VAR_OVERLAY_SMALL_LOG_DEPTH.set(config.OVERLAY_SMALL_LOG_SETTINGS["depth"]) self.view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH.set(config.OVERLAY_SMALL_LOG_SETTINGS["depth"]) + case "OverlaySmallLogXRotation": + self.view_variable.VAR_OVERLAY_SMALL_LOG_X_ROTATION.set(config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"]) + self.view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_ROTATION.set(config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"]) + + case "OverlaySmallLogYRotation": + self.view_variable.VAR_OVERLAY_SMALL_LOG_Y_ROTATION.set(config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"]) + self.view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_ROTATION.set(config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"]) + + case "OverlaySmallLogZRotation": + self.view_variable.VAR_OVERLAY_SMALL_LOG_Z_ROTATION.set(config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"]) + self.view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Z_ROTATION.set(config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"]) + case "OverlaySmallLogDisplayDuration": self.view_variable.VAR_OVERLAY_SMALL_LOG_DISPLAY_DURATION.set(config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"]) self.view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DISPLAY_DURATION.set(f"{config.OVERLAY_SMALL_LOG_SETTINGS['display_duration']} second(s)") From 85a1decfb46783e63e34f5e8144d7168cedc071d Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 15 May 2024 13:33:22 +0900 Subject: [PATCH 53/63] =?UTF-8?q?[Refactor]=20controller.py:=20match=20cas?= =?UTF-8?q?e=E6=96=87=E3=81=A7=E3=80=81=E5=90=8C=E3=81=98=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=81=AE=E3=82=82=E3=81=AE=E3=82=92=E3=81=BE=E3=81=A8?= =?UTF-8?q?=E3=82=81=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/controller.py b/controller.py index 8e79c6f8..6bc134b6 100644 --- a/controller.py +++ b/controller.py @@ -897,21 +897,9 @@ def callbackSetOverlaySmallLogSettings(value, set_type:str): pre_settings[set_type] = value config.OVERLAY_SMALL_LOG_SETTINGS = pre_settings match (set_type): - case "x_pos": + case "x_pos" | "y_pos" | "depth" | "x_rotation" | "y_rotation" | "z_rotation": model.updateOverlayPosition() - case "y_pos": - model.updateOverlayPosition() - case "depth": - model.updateOverlayPosition() - case "x_rotation": - model.updateOverlayPosition() - case "y_rotation": - model.updateOverlayPosition() - case "z_rotation": - model.updateOverlayPosition() - case "display_duration": - model.updateOverlayTimes() - case "fadeout_duration": + case "display_duration" | "fadeout_duration": model.updateOverlayTimes() # Others Tab From 34a372686bee538a0da35b7c33ce9030a3136a1b Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 15 May 2024 14:28:23 +0900 Subject: [PATCH 54/63] =?UTF-8?q?[Update]=20Quick=20Settings=20Window:=20?= =?UTF-8?q?=E3=82=B9=E3=83=A9=E3=82=A4=E3=83=80=E3=83=BC=E7=B3=BB=E3=81=AE?= =?UTF-8?q?=E5=B9=85=E3=81=8C=E5=9B=BA=E5=AE=9A=E3=81=95=E3=82=8C=E3=82=8B?= =?UTF-8?q?=E3=81=AE=E3=82=92=E9=98=B2=E3=81=90=E3=81=9F=E3=82=81=E3=80=81?= =?UTF-8?q?=E3=82=A6=E3=82=A3=E3=83=B3=E3=83=89=E3=82=A6=E3=81=AE=E5=B9=85?= =?UTF-8?q?=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=81=A6=E5=8F=AF=E5=A4=89?= =?UTF-8?q?=E7=9A=84=E3=81=AB=E3=80=82=20Quick=20Settings=20Window(Overlay?= =?UTF-8?q?):=20position,=20depth=E3=81=AE=E8=A8=AD=E5=AE=9A=E7=AF=84?= =?UTF-8?q?=E5=9B=B2=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=81=A6=E3=80=81?= =?UTF-8?q?0.01=E5=8D=98=E4=BD=8D=E3=81=A7=E8=A8=AD=E5=AE=9A=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=88=BB=E3=81=BF?= =?UTF-8?q?=E6=95=B0=E3=82=92=E5=A2=97=E5=8A=A0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 6 +++--- vrct_gui/quick_settings_window/QuickSettingsWindow.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/view.py b/view.py index ec842c90..dabcf99c 100644 --- a/view.py +++ b/view.py @@ -170,19 +170,19 @@ class View(): VAR_LABEL_OVERLAY_SMALL_LOG_X_POS=StringVar(value=i18n.t("overlay_settings.x_position")), SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-5, 5), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=1000, + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=10000, VAR_OVERLAY_SMALL_LOG_X_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), VAR_LABEL_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=i18n.t("overlay_settings.y_position")), SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-5, 5), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=1000, + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=10000, VAR_OVERLAY_SMALL_LOG_Y_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), VAR_LABEL_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=i18n.t("overlay_settings.depth")), SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH=(-5, 5), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH=1000, + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH=10000, VAR_OVERLAY_SMALL_LOG_DEPTH=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), diff --git a/vrct_gui/quick_settings_window/QuickSettingsWindow.py b/vrct_gui/quick_settings_window/QuickSettingsWindow.py index beba415c..570a3dbc 100644 --- a/vrct_gui/quick_settings_window/QuickSettingsWindow.py +++ b/vrct_gui/quick_settings_window/QuickSettingsWindow.py @@ -19,6 +19,7 @@ class QuickSettingsWindow(CTkToplevel): self.configure(fg_color=self.settings.ctm.SB__BG_COLOR) BG_HEX_COLOR = "#292a2d" + self.grid_columnconfigure(0, weight=1) self.qsw_background = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR) self.qsw_background.grid(row=0, column=0, pady=0, sticky="ew") From 818fa307b9b25a1097c291c694e6e7db13efa47c Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 15 May 2024 14:41:48 +0900 Subject: [PATCH 55/63] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20Model=20:?= =?UTF-8?q?=20overlay=20=E5=8F=B3=E6=89=8B=E5=91=A8=E3=82=8A=E3=81=AE?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/overlay/overlay.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/models/overlay/overlay.py b/models/overlay/overlay.py index 2e9cb9c7..d9d09610 100644 --- a/models/overlay/overlay.py +++ b/models/overlay/overlay.py @@ -38,7 +38,6 @@ def getHMDBaseMatrix(): x_rotation = 0.0 y_rotation = 0.0 z_rotation = 0.0 - arr = getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation) return arr @@ -49,7 +48,16 @@ def getLeftHandBaseMatrix(): x_rotation = -62.0 y_rotation = 154.0 z_rotation = 71.0 + arr = getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation) + return arr +def getRightHandBaseMatrix(): + x_pos = 0.0 + y_pos = -0.06 + depth = -0.14 + x_rotation = -62.0 + y_rotation = -154.0 + z_rotation = -71.0 arr = getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation) return arr @@ -139,7 +147,7 @@ class Overlay: if self.initialized is True: self.overlay.setOverlayWidthInMeters(self.handle, self.settings['ui_scaling']) - def updatePosition(self, pos, depth, x_rotation, y_rotation, z_rotation): + def updatePosition(self, pos, depth, x_rotation, y_rotation, z_rotation, tracker="HMD"): """ pos is a 2-tuple representing normalized (x, y) depth is a float representing the depth of the icon plane @@ -153,20 +161,29 @@ class Overlay: self.settings["y_rotation"] = y_rotation self.settings["z_rotation"] = z_rotation - base_matrix = getHMDBaseMatrix() - # base_matrix = getLeftHandBaseMatrix() + match tracker: + case "HMD": + base_matrix = getHMDBaseMatrix() + trackerIndex = openvr.k_unTrackedDeviceIndex_Hmd + case "LeftHand": + base_matrix = getLeftHandBaseMatrix() + trackerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_LeftHand) + case "RightHand": + base_matrix = getRightHandBaseMatrix() + trackerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_RightHand) + case _: + base_matrix = getHMDBaseMatrix() + trackerIndex = openvr.k_unTrackedDeviceIndex_Hmd + translation = (self.settings["x_pos"], self.settings["y_pos"], - self.settings['depth']) rotation = (self.settings["x_rotation"], self.settings["y_rotation"], self.settings["z_rotation"]) transform = utils.transform_matrix(base_matrix, translation, rotation) self.transform = mat34Id(transform) - hmdIndex = openvr.k_unTrackedDeviceIndex_Hmd - # leftControllerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_LeftHand) - # rightControllerIndex = self.overlay_system.getTrackedDeviceIndexForControllerRole(openvr.TrackedControllerRole_RightHand) if self.initialized is True: self.overlay.setOverlayTransformTrackedDeviceRelative( self.handle, - hmdIndex, + trackerIndex, self.transform ) From 811734c50b8a7558ec248addd4f1edf7199b175b Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 15 May 2024 15:19:33 +0900 Subject: [PATCH 56/63] [Update] About VRCT: update some poster showcases worlds. --- .../showcased_worlds/bar_asagao.png | Bin 8970 -> 9008 bytes img/about_vrct/showcased_worlds/cafe_cian.png | Bin 7381 -> 7407 bytes img/about_vrct/showcased_worlds/ippaidou.png | Bin 6016 -> 6041 bytes .../japanese_culture_osenbeito.png | Bin 9640 -> 9674 bytes .../silakan_datang_ke_rumahku.png | Bin 7720 -> 7752 bytes .../showcased_worlds/sushi_stand_guruguru.png | Bin 7166 -> 7194 bytes .../about_vrct_store.py | 12 ++++++------ 7 files changed, 6 insertions(+), 6 deletions(-) diff --git a/img/about_vrct/showcased_worlds/bar_asagao.png b/img/about_vrct/showcased_worlds/bar_asagao.png index 001523b41d51686a26a6940148d04de926b3966b..9965198c26df72eb7be68191fdf1a9568917d138 100644 GIT binary patch delta 8999 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 N002ovPDHLkV1iZZrgi`T delta 8960 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 ai~k3Eedz~Ny#=lS0000MT!(DQlvUB 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 diff --git a/img/about_vrct/showcased_worlds/ippaidou.png b/img/about_vrct/showcased_worlds/ippaidou.png index 9d673f6152aa2a124cc683f6ee49ff0127da0b65..ea2f4534e00e088d92060d54a618b03698324b16 100644 GIT binary patch delta 6008 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 N002ovPDHLkV1n)rt3m(( diff --git a/img/about_vrct/showcased_worlds/japanese_culture_osenbeito.png b/img/about_vrct/showcased_worlds/japanese_culture_osenbeito.png index fc8f2b36efde4ef619c6256b03de536202250861..9630bda2cd05fcfe0207186b901ac3621f7f3428 100644 GIT binary patch literal 9674 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 literal 9640 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 diff --git a/img/about_vrct/showcased_worlds/silakan_datang_ke_rumahku.png b/img/about_vrct/showcased_worlds/silakan_datang_ke_rumahku.png index 12071f1e59469dbbcec35c17427c274b05684033..2f9448fc30bb5031f1e77756c0fb2e8ac8864459 100644 GIT binary patch delta 7733 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 vY!y8#jsZm+W=|hh<*4%(SQ<2qrOl-&G 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 Ef>^NQ&;S4c delta 7142 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 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_about_vrct/about_vrct_store.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_about_vrct/about_vrct_store.py index a431619f..2ec00ef1 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_about_vrct/about_vrct_store.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_about_vrct/about_vrct_store.py @@ -11,13 +11,13 @@ poster_showcase_worlds_settings = [ { "author_name": "miu_jepang", "data": [ - { "image_file_name": "ippaidou.png", "x_post_num": None }, + { "image_file_name": "ippaidou.png", "x_post_num": "1787801976354513319" }, { "image_file_name": "nihongokurabu.png", "x_post_num": "1779004631936614893" }, { "image_file_name": "language_exchange_tervern.png", "x_post_num": "1779749425923150317" }, - { "image_file_name": "japanese_culture_osenbeito.png", "x_post_num": None }, - { "image_file_name": "silakan_datang_ke_rumahku.png", "x_post_num": None }, + { "image_file_name": "japanese_culture_osenbeito.png", "x_post_num": "1788522972409721137" }, + { "image_file_name": "silakan_datang_ke_rumahku.png", "x_post_num": "1788522607631056941" }, { "image_file_name": "uj_club.png", "x_post_num": "1780791654196388201" }, - { "image_file_name": "sushi_stand_guruguru.png", "x_post_num": None }, + { "image_file_name": "sushi_stand_guruguru.png", "x_post_num": "1788523302404952218" }, ] }, @@ -84,7 +84,7 @@ poster_showcase_worlds_settings = [ { "author_name": "yolm", "data": [ - { "image_file_name": "cafe_cian.png", "x_post_num": None }, + { "image_file_name": "cafe_cian.png", "x_post_num": "1787802552907739504" }, ] }, @@ -108,7 +108,7 @@ poster_showcase_worlds_settings = [ { "author_name": "1ban_meno", "data": [ - { "image_file_name": "bar_asagao.png", "x_post_num": None }, + { "image_file_name": "bar_asagao.png", "x_post_num": "1788523857642758370" }, ] }, From bd6ea56627206883ef4c31c703ba3340c6e735d1 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 15 May 2024 16:25:19 +0900 Subject: [PATCH 57/63] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20Model/Vie?= =?UTF-8?q?w=20:=20rename=20"depth"=20->=20"=20z=5Fpos"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 4 +- controller.py | 2 +- locales/en.yml | 2 +- locales/ja.yml | 2 +- model.py | 22 +++--- models/overlay/overlay.py | 75 ++++++++++--------- models/overlay/overlay_image.py | 2 +- view.py | 20 ++--- .../QuickSettingsWindow.py | 24 +++--- 9 files changed, 78 insertions(+), 75 deletions(-) diff --git a/config.py b/config.py index 61f77746..eef83045 100644 --- a/config.py +++ b/config.py @@ -770,7 +770,7 @@ class Config: if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SMALL_LOG_SETTINGS.keys()): for key, value in value.items(): match (key): - case "x_pos" | "y_pos" | "depth" | "x_rotation" | "y_rotation" | "z_rotation": + case "x_pos" | "y_pos" | "z_pos" | "x_rotation" | "y_rotation" | "z_rotation": if isinstance(value, float): self._OVERLAY_SMALL_LOG_SETTINGS[key] = value case "display_duration" | "fadeout_duration": @@ -1073,7 +1073,7 @@ class Config: self._OVERLAY_SMALL_LOG_SETTINGS = { "x_pos": 0.0, "y_pos": 0.0, - "depth": 0.0, + "z_pos": 0.0, "x_rotation": 0.0, "y_rotation": 0.0, "z_rotation": 0.0, diff --git a/controller.py b/controller.py index 6bc134b6..4e505c27 100644 --- a/controller.py +++ b/controller.py @@ -897,7 +897,7 @@ def callbackSetOverlaySmallLogSettings(value, set_type:str): pre_settings[set_type] = value config.OVERLAY_SMALL_LOG_SETTINGS = pre_settings match (set_type): - case "x_pos" | "y_pos" | "depth" | "x_rotation" | "y_rotation" | "z_rotation": + case "x_pos" | "y_pos" | "z_pos" | "x_rotation" | "y_rotation" | "z_rotation": model.updateOverlayPosition() case "display_duration" | "fadeout_duration": model.updateOverlayTimes() diff --git a/locales/en.yml b/locales/en.yml index 673f5535..9c5d47a6 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -74,7 +74,7 @@ overlay_settings: ui_scaling: UI Scaling x_position: X-axis (left-right) y_position: Y-axis (up-down) - depth: Z-axis (front-back) + z_position: Z-axis (front-back) x_rotation: X-axis rotation y_rotation: Y-axis rotation z_rotation: Z-axis rotation diff --git a/locales/ja.yml b/locales/ja.yml index 6adaf169..93eb8757 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -73,7 +73,7 @@ overlay_settings: ui_scaling: サイズ x_position: X軸(左右) y_position: Y軸(上下) - depth: Z軸(前後) + z_position: Z軸(前後) x_rotation: X軸の回転 y_rotation: Y軸の回転 z_rotation: Z軸の回転 diff --git a/model.py b/model.py index b557d628..943c49bf 100644 --- a/model.py +++ b/model.py @@ -84,10 +84,10 @@ class Model: self.overlay = Overlay( config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"], - config.OVERLAY_SMALL_LOG_SETTINGS["depth"], - 0, - 0, - 0, + config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"], + config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"], + config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"], config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"], config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"], config.OVERLAY_SETTINGS["opacity"], @@ -685,12 +685,14 @@ class Model: self.overlay.startOverlay() def updateOverlayPosition(self): - pos = (config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]) - depth = config.OVERLAY_SMALL_LOG_SETTINGS["depth"] - x_rotation = config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"] - y_rotation = config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"] - z_rotation = config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"] - self.overlay.updatePosition(pos, depth, x_rotation, y_rotation, z_rotation) + self.overlay.updatePosition( + config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"], + config.OVERLAY_SMALL_LOG_SETTINGS["y_rotation"], + config.OVERLAY_SMALL_LOG_SETTINGS["z_rotation"], + ) def updateOverlayTimes(self): display_duration = config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"] diff --git a/models/overlay/overlay.py b/models/overlay/overlay.py index d9d09610..35ecc82b 100644 --- a/models/overlay/overlay.py +++ b/models/overlay/overlay.py @@ -18,7 +18,7 @@ def mat34Id(array): arr[i][j] = array[i][j] return arr -def getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation): +def getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation): arr = np.zeros((3, 4)) rot = utils.euler_to_rotation_matrix((x_rotation, y_rotation, z_rotation)) @@ -26,50 +26,50 @@ def getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation): for j in range(3): arr[i][j] = rot[i][j] - arr[0][3] = x_pos * depth - arr[1][3] = y_pos * depth - arr[2][3] = - depth + arr[0][3] = x_pos * z_pos + arr[1][3] = y_pos * z_pos + arr[2][3] = - z_pos return arr def getHMDBaseMatrix(): x_pos = 0.0 y_pos = -0.4 - depth = 1.0 + z_pos = 1.0 x_rotation = 0.0 y_rotation = 0.0 z_rotation = 0.0 - arr = getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation) + arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation) return arr def getLeftHandBaseMatrix(): x_pos = 0.0 y_pos = -0.06 - depth = -0.14 + z_pos = -0.14 x_rotation = -62.0 y_rotation = 154.0 z_rotation = 71.0 - arr = getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation) + arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation) return arr def getRightHandBaseMatrix(): x_pos = 0.0 y_pos = -0.06 - depth = -0.14 + z_pos = -0.14 x_rotation = -62.0 y_rotation = -154.0 z_rotation = -71.0 - arr = getBaseMatrix(x_pos, y_pos, depth, x_rotation, y_rotation, z_rotation) + arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation) return arr class Overlay: - def __init__(self, x, y , depth, x_rotation, y_rotation, z_rotation, display_duration, fadeout_duration, opacity, ui_scaling): + def __init__(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, display_duration, fadeout_duration, opacity, ui_scaling): self.initialized = False settings = { "color": [1, 1, 1], "opacity": opacity, - "x_pos": x, - "y_pos": y, - "depth": depth, + "x_pos": x_pos, + "y_pos": y_pos, + "z_pos": z_pos, "x_rotation": x_rotation, "y_rotation": y_rotation, "z_rotation": z_rotation, @@ -100,8 +100,9 @@ class Overlay: self.updateOpacity(self.settings["opacity"]) self.updateUiScaling(self.settings["ui_scaling"]) self.updatePosition( - (self.settings["x_pos"], self.settings["y_pos"]), - self.settings["depth"], + self.settings["x_pos"], + self.settings["y_pos"], + self.settings["z_pos"], self.settings["x_rotation"], self.settings["y_rotation"], self.settings["z_rotation"], @@ -143,20 +144,20 @@ class Overlay: self.overlay.setOverlayAlpha(self.handle, self.settings["opacity"]) def updateUiScaling(self, ui_scaling): - self.settings['ui_scaling'] = ui_scaling + self.settings["ui_scaling"] = ui_scaling if self.initialized is True: - self.overlay.setOverlayWidthInMeters(self.handle, self.settings['ui_scaling']) + self.overlay.setOverlayWidthInMeters(self.handle, self.settings["ui_scaling"]) - def updatePosition(self, pos, depth, x_rotation, y_rotation, z_rotation, tracker="HMD"): + def updatePosition(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, tracker="HMD"): """ - pos is a 2-tuple representing normalized (x, y) - depth is a float representing the depth of the icon plane - x_rotation, y_rotation, z_rotation are floats representing the rotation of the icon plane + x_pos, y_pos, z_pos are floats representing the position of overlay + x_rotation, y_rotation, z_rotation are floats representing the rotation of overlay + tracker is a string representing the tracker to use ("HMD", "LeftHand", "RightHand") """ - self.settings["x_pos"] = pos[0] - self.settings["y_pos"] = pos[1] - self.settings["depth"] = depth + self.settings["x_pos"] = x_pos + self.settings["y_pos"] = y_pos + self.settings["z_pos"] = z_pos self.settings["x_rotation"] = x_rotation self.settings["y_rotation"] = y_rotation self.settings["z_rotation"] = z_rotation @@ -175,7 +176,7 @@ class Overlay: base_matrix = getHMDBaseMatrix() trackerIndex = openvr.k_unTrackedDeviceIndex_Hmd - translation = (self.settings["x_pos"], self.settings["y_pos"], - self.settings['depth']) + translation = (self.settings["x_pos"], self.settings["y_pos"], - self.settings["z_pos"]) rotation = (self.settings["x_rotation"], self.settings["y_rotation"], self.settings["z_rotation"]) transform = utils.transform_matrix(base_matrix, translation, rotation) self.transform = mat34Id(transform) @@ -188,10 +189,10 @@ class Overlay: ) def updateDisplayDuration(self, display_duration): - self.settings['display_duration'] = display_duration + self.settings["display_duration"] = display_duration def updateFadeoutDuration(self, fadeout_duration): - self.settings['fadeout_duration'] = fadeout_duration + self.settings["fadeout_duration"] = fadeout_duration def checkActive(self): try: @@ -207,16 +208,16 @@ class Overlay: return False def evaluateOpacityFade(self, lastUpdate, currentTime): - if (currentTime - lastUpdate) > self.settings['display_duration']: - timeThroughInterval = currentTime - lastUpdate - self.settings['display_duration'] - self.fadeRatio = 1 - timeThroughInterval / self.settings['fadeout_duration'] + if (currentTime - lastUpdate) > self.settings["display_duration"]: + timeThroughInterval = currentTime - lastUpdate - self.settings["display_duration"] + self.fadeRatio = 1 - timeThroughInterval / self.settings["fadeout_duration"] if self.fadeRatio < 0: self.fadeRatio = 0 - self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings['opacity']) + self.overlay.setOverlayAlpha(self.handle, self.fadeRatio * self.settings["opacity"]) def update(self): currTime = time.monotonic() - if self.settings['fadeout_duration'] != 0: + if self.settings["fadeout_duration"] != 0: self.evaluateOpacityFade(self.lastUpdate, currTime) else: self.updateOpacity(self.settings["opacity"]) @@ -255,10 +256,10 @@ class Overlay: @staticmethod def checkSteamvrRunning() -> bool: - _proc_name = "vrmonitor.exe" if os.name == 'nt' else "vrmonitor" + _proc_name = "vrmonitor.exe" if os.name == "nt" else "vrmonitor" return _proc_name in (p.name() for p in process_iter()) -if __name__ == '__main__': +if __name__ == "__main__": # from overlay_image import OverlayImage # overlay_image = OverlayImage() @@ -290,13 +291,13 @@ if __name__ == '__main__': x_pos = 0 y_pos = 0 - depth = 0 + z_pos = 0 x_rotation = 0 y_rotation = 0 z_rotation = 0 base_matrix = getLeftHandBaseMatrix() - translation = (x_pos * depth, y_pos * depth, depth) + translation = (x_pos * z_pos, y_pos * z_pos, z_pos) rotation = (x_rotation, y_rotation, z_rotation) transform = utils.transform_matrix(base_matrix, translation, rotation) transform = mat34Id(transform) diff --git a/models/overlay/overlay_image.py b/models/overlay/overlay_image.py index d1607179..c3150f5e 100644 --- a/models/overlay/overlay_image.py +++ b/models/overlay/overlay_image.py @@ -23,7 +23,7 @@ class OverlayImage: @staticmethod def concatenateImagesVertically(img1: Image, img2: Image) -> Image: - dst = Image.new('RGBA', (img1.width, img1.height + img2.height)) + dst = Image.new("RGBA", (img1.width, img1.height + img2.height)) dst.paste(img1, (0, 0)) dst.paste(img2, (0, img1.height)) return dst diff --git a/view.py b/view.py index dabcf99c..c3185f4f 100644 --- a/view.py +++ b/view.py @@ -180,11 +180,11 @@ class View(): VAR_OVERLAY_SMALL_LOG_Y_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), - VAR_LABEL_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=i18n.t("overlay_settings.depth")), - SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH=(-5, 5), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH=10000, - VAR_OVERLAY_SMALL_LOG_DEPTH=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), - VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), + VAR_LABEL_OVERLAY_SMALL_LOG_Z_POS=StringVar(value=i18n.t("overlay_settings.z_position")), + SLIDER_RANGE_OVERLAY_SMALL_LOG_Z_POS=(-5, 5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Z_POS=10000, + VAR_OVERLAY_SMALL_LOG_Z_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Z_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"]), VAR_LABEL_OVERLAY_SMALL_LOG_X_ROTATION=StringVar(value=i18n.t("overlay_settings.x_rotation")), SLIDER_RANGE_OVERLAY_SMALL_LOG_X_ROTATION=(-180, 180), @@ -1166,7 +1166,7 @@ class View(): INIT_OVERLAY_SMALL_LOG_SETTINGS = { "x_pos": 0.0, "y_pos": 0.0, - "depth": 0.0, + "z_pos": 0.0, "x_rotation": 0.0, "y_rotation": 0.0, "z_rotation": 0.0, @@ -1184,7 +1184,7 @@ class View(): self.setLatestConfigVariable("OverlaySmallLogXPos") self.setLatestConfigVariable("OverlaySmallLogYPos") - self.setLatestConfigVariable("OverlaySmallLogDepth") + self.setLatestConfigVariable("OverlaySmallLogZPos") self.setLatestConfigVariable("OverlaySmallLogXRotation") self.setLatestConfigVariable("OverlaySmallLogYRotation") self.setLatestConfigVariable("OverlaySmallLogZRotation") @@ -1950,9 +1950,9 @@ class View(): self.view_variable.VAR_OVERLAY_SMALL_LOG_Y_POS.set(config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]) self.view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS.set(config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]) - case "OverlaySmallLogDepth": - self.view_variable.VAR_OVERLAY_SMALL_LOG_DEPTH.set(config.OVERLAY_SMALL_LOG_SETTINGS["depth"]) - self.view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH.set(config.OVERLAY_SMALL_LOG_SETTINGS["depth"]) + case "OverlaySmallLogZPos": + self.view_variable.VAR_OVERLAY_SMALL_LOG_Z_POS.set(config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"]) + self.view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Z_POS.set(config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"]) case "OverlaySmallLogXRotation": self.view_variable.VAR_OVERLAY_SMALL_LOG_X_ROTATION.set(config.OVERLAY_SMALL_LOG_SETTINGS["x_rotation"]) diff --git a/vrct_gui/quick_settings_window/QuickSettingsWindow.py b/vrct_gui/quick_settings_window/QuickSettingsWindow.py index 570a3dbc..993243a9 100644 --- a/vrct_gui/quick_settings_window/QuickSettingsWindow.py +++ b/vrct_gui/quick_settings_window/QuickSettingsWindow.py @@ -147,21 +147,21 @@ class QuickSettingsWindow(CTkToplevel): row+=1 - def overlaySmallLogSettingsDepthSliderCallback(e): + def overlaySmallLogSettingsZPosSliderCallback(e): value = round(e,2) - callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "depth") - view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH.set(str(value)) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "z_pos") + view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Z_POS.set(str(value)) - self.qsb__overlay_small_log_settings_depth = createSettingBoxSlider( - for_var_label_text=view_variable.VAR_LABEL_OVERLAY_SMALL_LOG_DEPTH, - for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH, - slider_attr_name="qsb__overlay_small_log_settings_depth_slider", - slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH, - slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH, - command=overlaySmallLogSettingsDepthSliderCallback, - variable=view_variable.VAR_OVERLAY_SMALL_LOG_DEPTH, + self.qsb__overlay_small_log_settings_z_pos = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_SMALL_LOG_Z_POS, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Z_POS, + slider_attr_name="qsb__overlay_small_log_settings_z_pos_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_Z_POS, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Z_POS, + command=overlaySmallLogSettingsZPosSliderCallback, + variable=view_variable.VAR_OVERLAY_SMALL_LOG_Z_POS, ) - self.qsb__overlay_small_log_settings_depth.grid(row=row) + self.qsb__overlay_small_log_settings_z_pos.grid(row=row) From 1ca9c6d9361b1e25857be7665b46999070c3b548 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 17 May 2024 15:43:34 +0900 Subject: [PATCH 58/63] =?UTF-8?q?[bugfix]=20Splash=20Window:=20=E3=83=A2?= =?UTF-8?q?=E3=83=87=E3=83=AB=E3=83=80=E3=82=A6=E3=83=B3=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=89=E6=99=82=E3=81=AB=E3=80=81=E5=BF=9C=E7=AD=94=E3=81=AA?= =?UTF-8?q?=E3=81=97=E3=81=AB=E3=81=AA=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/splash_window/SplashWindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrct_gui/splash_window/SplashWindow.py b/vrct_gui/splash_window/SplashWindow.py index f1a186c4..9d313931 100644 --- a/vrct_gui/splash_window/SplashWindow.py +++ b/vrct_gui/splash_window/SplashWindow.py @@ -221,7 +221,7 @@ class SplashWindow(CTkToplevel): ) self.weight_download_progressbar_widget.configure(progress_color=progress_color) self.weight_download_progressbar_widget.set(progress) - self.update_idletasks() + self.update() def showSplash(self): From e38987d9168462419954998fa952c40da3441adc Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 18 May 2024 20:57:29 +0900 Subject: [PATCH 59/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20?= =?UTF-8?q?=E4=B8=80=E9=83=A8=E5=AE=9F=E8=A3=85=E6=BC=8F=E3=82=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - translation成功時に文字列が帰ってることを検証する処理に変更 - ミュート同期ボタンON/OFF時にマイク文字起こしの状態を変更する処理を追加 --- controller.py | 1 + model.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/controller.py b/controller.py index 4e505c27..ec46dbbe 100644 --- a/controller.py +++ b/controller.py @@ -938,6 +938,7 @@ def callbackSetEnableVrcMicMuteSync(value): else: model.stopCheckMuteSelfStatus() view.setStateVrcMicMuteSync("disabled") + model.changeMicTranscriptStatus() def callbackSetEnableSendMessageToVrc(value): diff --git a/model.py b/model.py index 943c49bf..d65d3582 100644 --- a/model.py +++ b/model.py @@ -179,7 +179,7 @@ class Model: ) # 翻訳失敗時のフェールセーフ処理 - if translation is True: + if isinstance(translation, str): success_flag = True else: while True: From c29ac5da5c18cc2383c2b317a9851c3f97fbf8bc Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 20 May 2024 10:13:47 +0900 Subject: [PATCH 60/63] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20?= =?UTF-8?q?=E4=BB=A5=E4=B8=8B=E3=81=AE=E6=9D=A1=E4=BB=B6=E3=81=AE=E5=A0=B4?= =?UTF-8?q?=E5=90=88=E3=81=ABMic=E6=96=87=E5=AD=97=E8=B5=B7=E3=81=93?= =?UTF-8?q?=E3=81=97=E7=B5=82=E4=BA=86=E6=99=82=E3=81=AB=E7=8A=B6=E6=85=8B?= =?UTF-8?q?=E3=82=92=E5=BE=A9=E5=B8=B0=E5=87=BA=E6=9D=A5=E3=81=AA=E3=81=84?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VRChat側のマイクはミュートのまま、vrc mic mute syncオンの時で、 Voice2Chatboxオンにして、オフにしたらそのままメイン画面disabledのまま帰ってこない --- model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/model.py b/model.py index d65d3582..bdade399 100644 --- a/model.py +++ b/model.py @@ -498,6 +498,7 @@ class Model: self.mic_print_transcript.join() self.mic_print_transcript = None if isinstance(self.mic_audio_recorder, SelectedMicEnergyAndAudioRecorder): + self.mic_audio_recorder.resume() self.mic_audio_recorder.stop() self.mic_audio_recorder = None # if isinstance(self.mic_get_energy, threadFnc): From 2d90a847cc4187f3db2f494d95a1ad980c1cfd03 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 20 May 2024 10:48:10 +0900 Subject: [PATCH 61/63] [UPdate] About VRCT: Add some poster showcased worlds. and add page number 4. --- .../chakachaka_multipurpose_room.png | Bin 0 -> 7403 bytes .../coffee_keisyoku_chakachaka.png | Bin 7587 -> 0 bytes .../showcased_worlds/kr_jp_exchange.png | Bin 0 -> 9877 bytes .../showcased_worlds/smokerz_guild_v2.png | Bin 0 -> 6879 bytes .../stretch_club_starting_from_minus.png | Bin 0 -> 13997 bytes .../showcased_worlds/sushi_guru_annex.png | Bin 0 -> 6521 bytes .../about_vrct_store.py | 28 +++++++++++++++++- .../createSettingBox_AboutVrct.py | 3 ++ 8 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 img/about_vrct/showcased_worlds/chakachaka_multipurpose_room.png delete mode 100644 img/about_vrct/showcased_worlds/coffee_keisyoku_chakachaka.png create mode 100644 img/about_vrct/showcased_worlds/kr_jp_exchange.png create mode 100644 img/about_vrct/showcased_worlds/smokerz_guild_v2.png create mode 100644 img/about_vrct/showcased_worlds/stretch_club_starting_from_minus.png create mode 100644 img/about_vrct/showcased_worlds/sushi_guru_annex.png diff --git a/img/about_vrct/showcased_worlds/chakachaka_multipurpose_room.png b/img/about_vrct/showcased_worlds/chakachaka_multipurpose_room.png new file mode 100644 index 0000000000000000000000000000000000000000..4829e2b307ac5e80b4d5928a2e1fd3f6fd71b540 GIT binary patch literal 7403 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 literal 0 HcmV?d00001 diff --git a/img/about_vrct/showcased_worlds/coffee_keisyoku_chakachaka.png b/img/about_vrct/showcased_worlds/coffee_keisyoku_chakachaka.png deleted file mode 100644 index 47bbc41af3daa16ea193acdc936c45426b62d134..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7587 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 FV1jX+ve^It diff --git a/img/about_vrct/showcased_worlds/kr_jp_exchange.png b/img/about_vrct/showcased_worlds/kr_jp_exchange.png new file mode 100644 index 0000000000000000000000000000000000000000..c92eb13e7f130e66663d71eded2de806d9195079 GIT binary patch literal 9877 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 Hu0mjfH77O( literal 0 HcmV?d00001 diff --git a/img/about_vrct/showcased_worlds/smokerz_guild_v2.png b/img/about_vrct/showcased_worlds/smokerz_guild_v2.png new file mode 100644 index 0000000000000000000000000000000000000000..a02edf1c66ee778a47b5426254a4e7ffcca99753 GIT binary patch literal 6879 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 Date: Wed, 22 May 2024 22:02:38 +0900 Subject: [PATCH 62/63] [Update] Quick Settings(Overlay): adjust overlay XYZ settings to much narrow range, cuz it's hard to adjust by VR user. --- view.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/view.py b/view.py index c3185f4f..73332775 100644 --- a/view.py +++ b/view.py @@ -169,20 +169,26 @@ class View(): CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS=None, VAR_LABEL_OVERLAY_SMALL_LOG_X_POS=StringVar(value=i18n.t("overlay_settings.x_position")), - SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-5, 5), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=10000, + # SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-5, 5), + # NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=10000, + SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-0.5, 0.5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=100, VAR_OVERLAY_SMALL_LOG_X_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), VAR_LABEL_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=i18n.t("overlay_settings.y_position")), - SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-5, 5), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=10000, + # SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-5, 5), + # NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=10000, + SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-0.8, 0.8), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=160, VAR_OVERLAY_SMALL_LOG_Y_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), VAR_LABEL_OVERLAY_SMALL_LOG_Z_POS=StringVar(value=i18n.t("overlay_settings.z_position")), - SLIDER_RANGE_OVERLAY_SMALL_LOG_Z_POS=(-5, 5), - NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Z_POS=10000, + # SLIDER_RANGE_OVERLAY_SMALL_LOG_Z_POS=(-5, 5), + # NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Z_POS=10000, + SLIDER_RANGE_OVERLAY_SMALL_LOG_Z_POS=(-0.5, 1.5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Z_POS=100, VAR_OVERLAY_SMALL_LOG_Z_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"]), VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Z_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["z_pos"]), From 0265211149ae676a54737c5bde0bb4d1f6ea5b66 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 23 May 2024 16:28:08 +0900 Subject: [PATCH 63/63] =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F[Update]=20version?= =?UTF-8?q?=202.2.3=20->=202.2.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index eef83045..bdeca14e 100644 --- a/config.py +++ b/config.py @@ -916,7 +916,7 @@ class Config: def init_config(self): # Read Only - self._VERSION = "2.2.3" + self._VERSION = "2.2.4" self._ENABLE_SPEAKER2CHATBOX = False # Speaker2Chatbox self._ENABLE_SPEAKER2CHATBOX_PASS_CONFIRMATION = "VRCT=0YEN" self._PATH_LOCAL = os_path.dirname(sys.argv[0])