[ref] overlayのリファクタリングとテストを追加
This commit is contained in:
@@ -3,6 +3,8 @@ import ctypes
|
||||
import time
|
||||
from psutil import process_iter
|
||||
from threading import Thread
|
||||
from typing import Any, Dict, Optional, Sequence
|
||||
|
||||
import openvr
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
@@ -18,14 +20,26 @@ try:
|
||||
except ImportError:
|
||||
import overlay_utils as utils
|
||||
|
||||
def mat34Id(array):
|
||||
def mat34Id(array: Sequence[Sequence[float]]) -> Any:
|
||||
"""Convert a 3x4 nested sequence into an openvr.HmdMatrix34_t instance.
|
||||
|
||||
Args:
|
||||
array: 3x4 numeric sequence
|
||||
|
||||
Returns:
|
||||
openvr HmdMatrix34_t compatible object
|
||||
"""
|
||||
arr = openvr.HmdMatrix34_t()
|
||||
for i in range(3):
|
||||
for j in range(4):
|
||||
arr[i][j] = array[i][j]
|
||||
return arr
|
||||
|
||||
def getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation):
|
||||
def getBaseMatrix(x_pos: float, y_pos: float, z_pos: float, x_rotation: float, y_rotation: float, z_rotation: float) -> np.ndarray:
|
||||
"""Create a 3x4 base matrix for an overlay given position and Euler rotations.
|
||||
|
||||
Returns a numpy array of shape (3,4).
|
||||
"""
|
||||
arr = np.zeros((3, 4))
|
||||
rot = utils.euler_to_rotation_matrix((x_rotation, y_rotation, z_rotation))
|
||||
|
||||
@@ -38,7 +52,7 @@ def getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation):
|
||||
arr[2][3] = - z_pos
|
||||
return arr
|
||||
|
||||
def getHMDBaseMatrix():
|
||||
def getHMDBaseMatrix() -> np.ndarray:
|
||||
x_pos = 0.0
|
||||
y_pos = -0.4
|
||||
z_pos = 1.0
|
||||
@@ -48,7 +62,7 @@ def getHMDBaseMatrix():
|
||||
arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation)
|
||||
return arr
|
||||
|
||||
def getLeftHandBaseMatrix():
|
||||
def getLeftHandBaseMatrix() -> np.ndarray:
|
||||
x_pos = 0.3
|
||||
y_pos = 0.1
|
||||
z_pos = -0.31
|
||||
@@ -58,7 +72,7 @@ def getLeftHandBaseMatrix():
|
||||
arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation)
|
||||
return arr
|
||||
|
||||
def getRightHandBaseMatrix():
|
||||
def getRightHandBaseMatrix() -> np.ndarray:
|
||||
x_pos = -0.3
|
||||
y_pos = 0.1
|
||||
z_pos = -0.31
|
||||
@@ -69,24 +83,25 @@ def getRightHandBaseMatrix():
|
||||
return arr
|
||||
|
||||
class Overlay:
|
||||
def __init__(self, settings_dict):
|
||||
self.system = None
|
||||
self.overlay = None
|
||||
self.handle = None
|
||||
self.init_process = False
|
||||
self.initialized = False
|
||||
self.loop = False
|
||||
self.thread_overlay = None
|
||||
"""Manage OpenVR overlays for multiple sizes (e.g. 'small'/'large')."""
|
||||
def __init__(self, settings_dict: Dict[str, Dict[str, Any]]) -> None:
|
||||
self.system: Optional[Any] = None
|
||||
self.overlay: Optional[Any] = None
|
||||
self.handle: Dict[str, Any] = {}
|
||||
self.init_process: bool = False
|
||||
self.initialized: bool = False
|
||||
self.loop: bool = False
|
||||
self.thread_overlay: Optional[Thread] = None
|
||||
|
||||
self.settings = {}
|
||||
self.lastUpdate = {}
|
||||
self.fadeRatio = {}
|
||||
self.settings: Dict[str, Dict[str, Any]] = {}
|
||||
self.lastUpdate: Dict[str, float] = {}
|
||||
self.fadeRatio: Dict[str, float] = {}
|
||||
for key, value in settings_dict.items():
|
||||
self.settings[key] = value
|
||||
self.lastUpdate[key] = time.monotonic()
|
||||
self.fadeRatio[key] = 1
|
||||
self.fadeRatio[key] = 1.0
|
||||
|
||||
def init(self):
|
||||
def init(self) -> None:
|
||||
try:
|
||||
self.system = openvr.init(openvr.VRApplication_Background)
|
||||
self.overlay = openvr.IVROverlay()
|
||||
@@ -119,7 +134,7 @@ class Overlay:
|
||||
except Exception:
|
||||
errorLogging()
|
||||
|
||||
def updateImage(self, img, size):
|
||||
def updateImage(self, img: Image.Image, size: str) -> None:
|
||||
if self.initialized is True:
|
||||
width, height = img.size
|
||||
img = img.tobytes()
|
||||
@@ -139,7 +154,7 @@ class Overlay:
|
||||
self.updateOpacity(self.settings[size]["opacity"], size)
|
||||
self.lastUpdate[size] = time.monotonic()
|
||||
|
||||
def clearImage(self, size):
|
||||
def clearImage(self, size: str) -> None:
|
||||
if self.initialized is True:
|
||||
self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)), size)
|
||||
|
||||
@@ -151,7 +166,7 @@ class Overlay:
|
||||
r, g, b = col
|
||||
self.overlay.setOverlayColor(self.handle[size], r, g, b)
|
||||
|
||||
def updateOpacity(self, opacity, size, with_fade=False):
|
||||
def updateOpacity(self, opacity: float, size: str, with_fade: bool = False) -> None:
|
||||
self.settings[size]["opacity"] = opacity
|
||||
|
||||
if self.initialized is True:
|
||||
@@ -161,12 +176,12 @@ class Overlay:
|
||||
else:
|
||||
self.overlay.setOverlayAlpha(self.handle[size], self.settings[size]["opacity"])
|
||||
|
||||
def updateUiScaling(self, ui_scaling, size):
|
||||
def updateUiScaling(self, ui_scaling: float, size: str) -> None:
|
||||
self.settings[size]["ui_scaling"] = ui_scaling
|
||||
if self.initialized is True:
|
||||
self.overlay.setOverlayWidthInMeters(self.handle[size], self.settings[size]["ui_scaling"])
|
||||
|
||||
def updatePosition(self, x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation, tracker, size):
|
||||
def updatePosition(self, x_pos: float, y_pos: float, z_pos: float, x_rotation: float, y_rotation: float, z_rotation: float, tracker: str, size: str) -> None:
|
||||
"""
|
||||
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
|
||||
@@ -208,13 +223,13 @@ class Overlay:
|
||||
transform
|
||||
)
|
||||
|
||||
def updateDisplayDuration(self, display_duration, size):
|
||||
def updateDisplayDuration(self, display_duration: float, size: str) -> None:
|
||||
self.settings[size]["display_duration"] = display_duration
|
||||
|
||||
def updateFadeoutDuration(self, fadeout_duration, size):
|
||||
def updateFadeoutDuration(self, fadeout_duration: float, size: str) -> None:
|
||||
self.settings[size]["fadeout_duration"] = fadeout_duration
|
||||
|
||||
def checkActive(self):
|
||||
def checkActive(self) -> bool:
|
||||
try:
|
||||
if self.system is not None and self.initialized is True:
|
||||
new_event = openvr.VREvent_t()
|
||||
@@ -226,7 +241,7 @@ class Overlay:
|
||||
errorLogging()
|
||||
return False
|
||||
|
||||
def evaluateOpacityFade(self, size):
|
||||
def evaluateOpacityFade(self, size: str) -> None:
|
||||
currentTime = time.monotonic()
|
||||
if (currentTime - self.lastUpdate[size]) > self.settings[size]["display_duration"]:
|
||||
timeThroughInterval = currentTime - self.lastUpdate[size] - self.settings[size]["display_duration"]
|
||||
@@ -235,13 +250,13 @@ class Overlay:
|
||||
self.fadeRatio[size] = 0
|
||||
self.overlay.setOverlayAlpha(self.handle[size], self.fadeRatio[size] * self.settings[size]["opacity"])
|
||||
|
||||
def update(self, size):
|
||||
def update(self, size: str) -> None:
|
||||
if self.settings[size]["fadeout_duration"] != 0:
|
||||
self.evaluateOpacityFade(size)
|
||||
else:
|
||||
self.updateOpacity(self.settings[size]["opacity"], size)
|
||||
|
||||
def mainloop(self):
|
||||
def mainloop(self) -> None:
|
||||
self.loop = True
|
||||
while self.checkActive() is True and self.loop is True:
|
||||
startTime = time.monotonic()
|
||||
@@ -251,21 +266,21 @@ class Overlay:
|
||||
if sleepTime > 0:
|
||||
time.sleep(sleepTime)
|
||||
|
||||
def main(self):
|
||||
def main(self) -> None:
|
||||
while self.checkSteamvrRunning() is False:
|
||||
time.sleep(10)
|
||||
self.init()
|
||||
if self.initialized is True:
|
||||
self.mainloop()
|
||||
|
||||
def startOverlay(self):
|
||||
def startOverlay(self) -> None:
|
||||
if self.initialized is False and self.init_process is False:
|
||||
self.init_process = True
|
||||
self.thread_overlay = Thread(target=self.main)
|
||||
self.thread_overlay.daemon = True
|
||||
self.thread_overlay.start()
|
||||
|
||||
def shutdownOverlay(self):
|
||||
def shutdownOverlay(self) -> None:
|
||||
if self.initialized is True and self.init_process is False:
|
||||
if isinstance(self.thread_overlay, Thread):
|
||||
self.loop = False
|
||||
@@ -281,7 +296,7 @@ class Overlay:
|
||||
self.system = None
|
||||
self.initialized = False
|
||||
|
||||
def reStartOverlay(self):
|
||||
def reStartOverlay(self) -> None:
|
||||
self.shutdownOverlay()
|
||||
self.startOverlay()
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from os import path as os_path
|
||||
from datetime import datetime
|
||||
from typing import Tuple
|
||||
from typing import Tuple, List, Optional
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
try:
|
||||
from utils import errorLogging
|
||||
@@ -18,8 +18,14 @@ class OverlayImage:
|
||||
"Chinese Traditional": "NotoSansTC-Regular.ttf",
|
||||
}
|
||||
|
||||
def __init__(self, root_path: str=None):
|
||||
self.message_log = []
|
||||
def __init__(self, root_path: Optional[str] = None) -> None:
|
||||
"""Overlay image helper.
|
||||
|
||||
Args:
|
||||
root_path: optional project root to resolve bundled fonts. If omitted,
|
||||
defaults to repository `fonts` directory.
|
||||
"""
|
||||
self.message_log: List[dict] = []
|
||||
if root_path is None:
|
||||
self.root_path = os_path.join(os_path.dirname(__file__), "..", "..", "..", "fonts")
|
||||
else:
|
||||
@@ -58,7 +64,7 @@ class OverlayImage:
|
||||
}
|
||||
return colors
|
||||
|
||||
def createTextboxSmallLog(self, text:str, language:str, text_color:tuple, base_width:int, base_height:int, font_size:int) -> Image:
|
||||
def createTextboxSmallLog(self, text: str, language: str, text_color: Tuple[int, int, int], base_width: int, base_height: int, font_size: int) -> Image:
|
||||
font_family = self.LANGUAGES.get(language, self.LANGUAGES["Default"])
|
||||
img = Image.new("RGBA", (base_width, base_height), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
@@ -92,7 +98,7 @@ class OverlayImage:
|
||||
draw.text((text_x, text_y), text, text_color, anchor="mm", stroke_width=0, font=font, align="center")
|
||||
return img
|
||||
|
||||
def createOverlayImageSmallLog(self, message: str, your_language: str, translation: list = [], target_language: list = []) -> Image:
|
||||
def createOverlayImageSmallLog(self, message: str, your_language: str, translation: List[str] = [], target_language: List[str] = []) -> Image:
|
||||
# UI設定を取得
|
||||
ui_size = self.getUiSizeSmallLog()
|
||||
width, height, font_size = ui_size["width"], ui_size["height"], ui_size["font_size"]
|
||||
@@ -162,7 +168,7 @@ class OverlayImage:
|
||||
"text_color_time": (120, 120, 120)
|
||||
}
|
||||
|
||||
def createTextImageLargeLog(self, message_type:str, size:str, text:str, language:str) -> Image:
|
||||
def createTextImageLargeLog(self, message_type: str, size: str, text: str, language: str) -> Image:
|
||||
ui_size = self.getUiSizeLargeLog()
|
||||
font_size = ui_size["font_size_large"] if size == "large" else ui_size["font_size_small"]
|
||||
text_color = self.getUiColorLargeLog()[f"text_color_{size}"]
|
||||
@@ -200,7 +206,7 @@ class OverlayImage:
|
||||
draw.multiline_text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font, align=align)
|
||||
return img
|
||||
|
||||
def createTextImageMessageType(self, message_type:str, date_time:str) -> Image:
|
||||
def createTextImageMessageType(self, message_type: str, date_time: str) -> Image:
|
||||
ui_size = self.getUiSizeLargeLog()
|
||||
font_size = ui_size["font_size_small"]
|
||||
ui_padding = ui_size["padding"]
|
||||
@@ -242,7 +248,7 @@ class OverlayImage:
|
||||
draw.text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font)
|
||||
return img
|
||||
|
||||
def createTextboxLargeLog(self, message_type: str, message: str = None, your_language: str = None, translation: list = [], target_language: list = [], date_time: str = None) -> Image:
|
||||
def createTextboxLargeLog(self, message_type: str, message: Optional[str] = None, your_language: Optional[str] = None, translation: List[str] = [], target_language: List[str] = [], date_time: Optional[str] = None) -> Image:
|
||||
# テキスト画像のリストを作成
|
||||
images = [self.createTextImageMessageType(message_type, date_time)]
|
||||
|
||||
@@ -272,7 +278,7 @@ class OverlayImage:
|
||||
|
||||
return combined_img
|
||||
|
||||
def createOverlayImageLargeLog(self, message_type:str, message:str=None, your_language:str=None, translation:list=[], target_language:list=[]) -> Image:
|
||||
def createOverlayImageLargeLog(self, message_type: str, message: Optional[str] = None, your_language: Optional[str] = None, translation: List[str] = [], target_language: List[str] = []) -> Image:
|
||||
ui_color = self.getUiColorLargeLog()
|
||||
background_color = ui_color["background_color"]
|
||||
background_outline_color = ui_color["background_outline_color"]
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
import numpy as np
|
||||
from typing import Sequence
|
||||
|
||||
def toHomogeneous(matrix):
|
||||
|
||||
def toHomogeneous(matrix: np.ndarray) -> np.ndarray:
|
||||
"""Convert a 3x4 base matrix to a 4x4 homogeneous matrix.
|
||||
|
||||
Args:
|
||||
matrix: 3x4 numpy array
|
||||
|
||||
Returns:
|
||||
4x4 numpy array with last row [0, 0, 0, 1]
|
||||
"""
|
||||
homogeneous_matrix = np.vstack([matrix, [0, 0, 0, 1]])
|
||||
return homogeneous_matrix
|
||||
|
||||
# 移動行列を生成する関数
|
||||
def calcTranslationMatrix(translation):
|
||||
def calcTranslationMatrix(translation: Sequence[float]) -> np.ndarray:
|
||||
"""Create a 4x4 translation matrix from a 3-element translation.
|
||||
|
||||
Args:
|
||||
translation: (tx, ty, tz)
|
||||
|
||||
Returns:
|
||||
4x4 numpy translation matrix
|
||||
"""
|
||||
tx, ty, tz = translation
|
||||
return np.array([
|
||||
[1, 0, 0, tx],
|
||||
@@ -15,9 +33,10 @@ def calcTranslationMatrix(translation):
|
||||
])
|
||||
|
||||
# X軸周りの回転行列を生成する関数
|
||||
def calcRotationMatrixX(angle):
|
||||
c = np.cos(np.pi/180*angle)
|
||||
s = np.sin(np.pi/180*angle)
|
||||
def calcRotationMatrixX(angle: float) -> np.ndarray:
|
||||
"""Rotation matrix around X axis for given angle in degrees."""
|
||||
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],
|
||||
@@ -26,9 +45,10 @@ def calcRotationMatrixX(angle):
|
||||
])
|
||||
|
||||
# Y軸周りの回転行列を生成する関数
|
||||
def calcRotationMatrixY(angle):
|
||||
c = np.cos(np.pi/180*angle)
|
||||
s = np.sin(np.pi/180*angle)
|
||||
def calcRotationMatrixY(angle: float) -> np.ndarray:
|
||||
"""Rotation matrix around Y axis for given angle in degrees."""
|
||||
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],
|
||||
@@ -37,9 +57,10 @@ def calcRotationMatrixY(angle):
|
||||
])
|
||||
|
||||
# Z軸周りの回転行列を生成する関数
|
||||
def calcRotationMatrixZ(angle):
|
||||
c = np.cos(np.pi/180*angle)
|
||||
s = np.sin(np.pi/180*angle)
|
||||
def calcRotationMatrixZ(angle: float) -> np.ndarray:
|
||||
"""Rotation matrix around Z axis for given angle in degrees."""
|
||||
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],
|
||||
@@ -48,7 +69,17 @@ def calcRotationMatrixZ(angle):
|
||||
])
|
||||
|
||||
# 3x4行列の座標を基準として回転や移動を行う関数
|
||||
def transform_matrix(base_matrix, translation, rotation):
|
||||
def transform_matrix(base_matrix: np.ndarray, translation: Sequence[float], rotation: Sequence[float]) -> np.ndarray:
|
||||
"""Apply translation and Euler rotations to a 3x4 base matrix.
|
||||
|
||||
Args:
|
||||
base_matrix: 3x4 base transform matrix
|
||||
translation: (tx, ty, tz)
|
||||
rotation: (x_deg, y_deg, z_deg)
|
||||
|
||||
Returns:
|
||||
Transformed 3x4 matrix (numpy.ndarray)
|
||||
"""
|
||||
homogeneous_base_matrix = toHomogeneous(base_matrix)
|
||||
translation_matrix = calcTranslationMatrix(translation)
|
||||
rotation_matrix_x = calcRotationMatrixX(rotation[0])
|
||||
@@ -60,10 +91,18 @@ def transform_matrix(base_matrix, translation, rotation):
|
||||
result_matrix = np.dot(homogeneous_base_matrix, transformation_matrix)
|
||||
return result_matrix[:3, :]
|
||||
|
||||
def euler_to_rotation_matrix(angles):
|
||||
def euler_to_rotation_matrix(angles: Sequence[float]) -> np.ndarray:
|
||||
"""Convert Euler angles in degrees to a 3x3 rotation matrix.
|
||||
|
||||
Args:
|
||||
angles: (x_deg, y_deg, z_deg)
|
||||
|
||||
Returns:
|
||||
3x3 rotation matrix
|
||||
"""
|
||||
phi = angles[0] * np.pi / 180
|
||||
theta = angles[1] * np.pi / 180
|
||||
psi = angles[2]* 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)]])
|
||||
|
||||
Reference in New Issue
Block a user