[ref] overlayのリファクタリングとテストを追加
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
目的: OpenVR を使ったオーバーレイ表示(複数サイズ: small/large)を管理する `Overlay` クラスを提供します。
|
目的: OpenVR を使ったオーバーレイ表示(複数サイズ: small/large)を管理する `Overlay` クラスを提供します。
|
||||||
|
|
||||||
主要メソッド:
|
主要メソッド:
|
||||||
- __init__(self, settings_dict)
|
- __init__(self, settings_dict: dict)
|
||||||
- init(self) -> None
|
- init(self) -> None
|
||||||
- startOverlay(self) -> None
|
- startOverlay(self) -> None
|
||||||
- shutdownOverlay(self) -> None
|
- shutdownOverlay(self) -> None
|
||||||
@@ -18,6 +18,34 @@
|
|||||||
- OpenVR (SteamVR) が稼働していることが前提です。`checkSteamvrRunning()` で `vrmonitor.exe` の存在チェックを行います。
|
- OpenVR (SteamVR) が稼働していることが前提です。`checkSteamvrRunning()` で `vrmonitor.exe` の存在チェックを行います。
|
||||||
- 例外が発生した場合は `errorLogging()` を呼んでスタックトレースを残します。
|
- 例外が発生した場合は `errorLogging()` を呼んでスタックトレースを残します。
|
||||||
|
|
||||||
|
短い使用例:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from models.overlay.overlay_image import OverlayImage
|
||||||
|
from models.overlay.overlay import Overlay
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
"small": {
|
||||||
|
"x_pos": 0.0, "y_pos": 0.0, "z_pos": 0.0,
|
||||||
|
"x_rotation": 0.0, "y_rotation": 0.0, "z_rotation": 0.0,
|
||||||
|
"display_duration": 5, "fadeout_duration": 2,
|
||||||
|
"opacity": 1.0, "ui_scaling": 1.0, "tracker": "HMD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay_img = OverlayImage()
|
||||||
|
overlay = Overlay(settings)
|
||||||
|
overlay.startOverlay()
|
||||||
|
|
||||||
|
# wait until initialized
|
||||||
|
while not overlay.initialized:
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# push a simple blank image
|
||||||
|
overlay.updateImage(Image.new("RGBA", (256, 64), (255,255,255,255)), "small")
|
||||||
|
```
|
||||||
|
|
||||||
## モジュール構成(補足)
|
## モジュール構成(補足)
|
||||||
|
|
||||||
- overlay.py — OpenVR を使ったオーバーレイ管理。Overlay クラスは複数サイズ(small/large)を扱い、位置/回転/透明度/フェードを制御する。
|
- overlay.py — OpenVR を使ったオーバーレイ管理。Overlay クラスは複数サイズ(small/large)を扱い、位置/回転/透明度/フェードを制御する。
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import ctypes
|
|||||||
import time
|
import time
|
||||||
from psutil import process_iter
|
from psutil import process_iter
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
from typing import Any, Dict, Optional, Sequence
|
||||||
|
|
||||||
import openvr
|
import openvr
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
@@ -18,14 +20,26 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import overlay_utils as utils
|
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()
|
arr = openvr.HmdMatrix34_t()
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
for j in range(4):
|
for j in range(4):
|
||||||
arr[i][j] = array[i][j]
|
arr[i][j] = array[i][j]
|
||||||
return arr
|
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))
|
arr = np.zeros((3, 4))
|
||||||
rot = utils.euler_to_rotation_matrix((x_rotation, y_rotation, z_rotation))
|
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
|
arr[2][3] = - z_pos
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
def getHMDBaseMatrix():
|
def getHMDBaseMatrix() -> np.ndarray:
|
||||||
x_pos = 0.0
|
x_pos = 0.0
|
||||||
y_pos = -0.4
|
y_pos = -0.4
|
||||||
z_pos = 1.0
|
z_pos = 1.0
|
||||||
@@ -48,7 +62,7 @@ def getHMDBaseMatrix():
|
|||||||
arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation)
|
arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation)
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
def getLeftHandBaseMatrix():
|
def getLeftHandBaseMatrix() -> np.ndarray:
|
||||||
x_pos = 0.3
|
x_pos = 0.3
|
||||||
y_pos = 0.1
|
y_pos = 0.1
|
||||||
z_pos = -0.31
|
z_pos = -0.31
|
||||||
@@ -58,7 +72,7 @@ def getLeftHandBaseMatrix():
|
|||||||
arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation)
|
arr = getBaseMatrix(x_pos, y_pos, z_pos, x_rotation, y_rotation, z_rotation)
|
||||||
return arr
|
return arr
|
||||||
|
|
||||||
def getRightHandBaseMatrix():
|
def getRightHandBaseMatrix() -> np.ndarray:
|
||||||
x_pos = -0.3
|
x_pos = -0.3
|
||||||
y_pos = 0.1
|
y_pos = 0.1
|
||||||
z_pos = -0.31
|
z_pos = -0.31
|
||||||
@@ -69,24 +83,25 @@ def getRightHandBaseMatrix():
|
|||||||
return arr
|
return arr
|
||||||
|
|
||||||
class Overlay:
|
class Overlay:
|
||||||
def __init__(self, settings_dict):
|
"""Manage OpenVR overlays for multiple sizes (e.g. 'small'/'large')."""
|
||||||
self.system = None
|
def __init__(self, settings_dict: Dict[str, Dict[str, Any]]) -> None:
|
||||||
self.overlay = None
|
self.system: Optional[Any] = None
|
||||||
self.handle = None
|
self.overlay: Optional[Any] = None
|
||||||
self.init_process = False
|
self.handle: Dict[str, Any] = {}
|
||||||
self.initialized = False
|
self.init_process: bool = False
|
||||||
self.loop = False
|
self.initialized: bool = False
|
||||||
self.thread_overlay = None
|
self.loop: bool = False
|
||||||
|
self.thread_overlay: Optional[Thread] = None
|
||||||
|
|
||||||
self.settings = {}
|
self.settings: Dict[str, Dict[str, Any]] = {}
|
||||||
self.lastUpdate = {}
|
self.lastUpdate: Dict[str, float] = {}
|
||||||
self.fadeRatio = {}
|
self.fadeRatio: Dict[str, float] = {}
|
||||||
for key, value in settings_dict.items():
|
for key, value in settings_dict.items():
|
||||||
self.settings[key] = value
|
self.settings[key] = value
|
||||||
self.lastUpdate[key] = time.monotonic()
|
self.lastUpdate[key] = time.monotonic()
|
||||||
self.fadeRatio[key] = 1
|
self.fadeRatio[key] = 1.0
|
||||||
|
|
||||||
def init(self):
|
def init(self) -> None:
|
||||||
try:
|
try:
|
||||||
self.system = openvr.init(openvr.VRApplication_Background)
|
self.system = openvr.init(openvr.VRApplication_Background)
|
||||||
self.overlay = openvr.IVROverlay()
|
self.overlay = openvr.IVROverlay()
|
||||||
@@ -119,7 +134,7 @@ class Overlay:
|
|||||||
except Exception:
|
except Exception:
|
||||||
errorLogging()
|
errorLogging()
|
||||||
|
|
||||||
def updateImage(self, img, size):
|
def updateImage(self, img: Image.Image, size: str) -> None:
|
||||||
if self.initialized is True:
|
if self.initialized is True:
|
||||||
width, height = img.size
|
width, height = img.size
|
||||||
img = img.tobytes()
|
img = img.tobytes()
|
||||||
@@ -139,7 +154,7 @@ class Overlay:
|
|||||||
self.updateOpacity(self.settings[size]["opacity"], size)
|
self.updateOpacity(self.settings[size]["opacity"], size)
|
||||||
self.lastUpdate[size] = time.monotonic()
|
self.lastUpdate[size] = time.monotonic()
|
||||||
|
|
||||||
def clearImage(self, size):
|
def clearImage(self, size: str) -> None:
|
||||||
if self.initialized is True:
|
if self.initialized is True:
|
||||||
self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)), size)
|
self.updateImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0)), size)
|
||||||
|
|
||||||
@@ -151,7 +166,7 @@ class Overlay:
|
|||||||
r, g, b = col
|
r, g, b = col
|
||||||
self.overlay.setOverlayColor(self.handle[size], r, g, b)
|
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
|
self.settings[size]["opacity"] = opacity
|
||||||
|
|
||||||
if self.initialized is True:
|
if self.initialized is True:
|
||||||
@@ -161,12 +176,12 @@ class Overlay:
|
|||||||
else:
|
else:
|
||||||
self.overlay.setOverlayAlpha(self.handle[size], self.settings[size]["opacity"])
|
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
|
self.settings[size]["ui_scaling"] = ui_scaling
|
||||||
if self.initialized is True:
|
if self.initialized is True:
|
||||||
self.overlay.setOverlayWidthInMeters(self.handle[size], self.settings[size]["ui_scaling"])
|
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_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
|
x_rotation, y_rotation, z_rotation are floats representing the rotation of overlay
|
||||||
@@ -208,13 +223,13 @@ class Overlay:
|
|||||||
transform
|
transform
|
||||||
)
|
)
|
||||||
|
|
||||||
def updateDisplayDuration(self, display_duration, size):
|
def updateDisplayDuration(self, display_duration: float, size: str) -> None:
|
||||||
self.settings[size]["display_duration"] = display_duration
|
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
|
self.settings[size]["fadeout_duration"] = fadeout_duration
|
||||||
|
|
||||||
def checkActive(self):
|
def checkActive(self) -> bool:
|
||||||
try:
|
try:
|
||||||
if self.system is not None and self.initialized is True:
|
if self.system is not None and self.initialized is True:
|
||||||
new_event = openvr.VREvent_t()
|
new_event = openvr.VREvent_t()
|
||||||
@@ -226,7 +241,7 @@ class Overlay:
|
|||||||
errorLogging()
|
errorLogging()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def evaluateOpacityFade(self, size):
|
def evaluateOpacityFade(self, size: str) -> None:
|
||||||
currentTime = time.monotonic()
|
currentTime = time.monotonic()
|
||||||
if (currentTime - self.lastUpdate[size]) > self.settings[size]["display_duration"]:
|
if (currentTime - self.lastUpdate[size]) > self.settings[size]["display_duration"]:
|
||||||
timeThroughInterval = 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.fadeRatio[size] = 0
|
||||||
self.overlay.setOverlayAlpha(self.handle[size], self.fadeRatio[size] * self.settings[size]["opacity"])
|
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:
|
if self.settings[size]["fadeout_duration"] != 0:
|
||||||
self.evaluateOpacityFade(size)
|
self.evaluateOpacityFade(size)
|
||||||
else:
|
else:
|
||||||
self.updateOpacity(self.settings[size]["opacity"], size)
|
self.updateOpacity(self.settings[size]["opacity"], size)
|
||||||
|
|
||||||
def mainloop(self):
|
def mainloop(self) -> None:
|
||||||
self.loop = True
|
self.loop = True
|
||||||
while self.checkActive() is True and self.loop is True:
|
while self.checkActive() is True and self.loop is True:
|
||||||
startTime = time.monotonic()
|
startTime = time.monotonic()
|
||||||
@@ -251,21 +266,21 @@ class Overlay:
|
|||||||
if sleepTime > 0:
|
if sleepTime > 0:
|
||||||
time.sleep(sleepTime)
|
time.sleep(sleepTime)
|
||||||
|
|
||||||
def main(self):
|
def main(self) -> None:
|
||||||
while self.checkSteamvrRunning() is False:
|
while self.checkSteamvrRunning() is False:
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
self.init()
|
self.init()
|
||||||
if self.initialized is True:
|
if self.initialized is True:
|
||||||
self.mainloop()
|
self.mainloop()
|
||||||
|
|
||||||
def startOverlay(self):
|
def startOverlay(self) -> None:
|
||||||
if self.initialized is False and self.init_process is False:
|
if self.initialized is False and self.init_process is False:
|
||||||
self.init_process = True
|
self.init_process = True
|
||||||
self.thread_overlay = Thread(target=self.main)
|
self.thread_overlay = Thread(target=self.main)
|
||||||
self.thread_overlay.daemon = True
|
self.thread_overlay.daemon = True
|
||||||
self.thread_overlay.start()
|
self.thread_overlay.start()
|
||||||
|
|
||||||
def shutdownOverlay(self):
|
def shutdownOverlay(self) -> None:
|
||||||
if self.initialized is True and self.init_process is False:
|
if self.initialized is True and self.init_process is False:
|
||||||
if isinstance(self.thread_overlay, Thread):
|
if isinstance(self.thread_overlay, Thread):
|
||||||
self.loop = False
|
self.loop = False
|
||||||
@@ -281,7 +296,7 @@ class Overlay:
|
|||||||
self.system = None
|
self.system = None
|
||||||
self.initialized = False
|
self.initialized = False
|
||||||
|
|
||||||
def reStartOverlay(self):
|
def reStartOverlay(self) -> None:
|
||||||
self.shutdownOverlay()
|
self.shutdownOverlay()
|
||||||
self.startOverlay()
|
self.startOverlay()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from os import path as os_path
|
from os import path as os_path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Tuple
|
from typing import Tuple, List, Optional
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
try:
|
try:
|
||||||
from utils import errorLogging
|
from utils import errorLogging
|
||||||
@@ -18,8 +18,14 @@ class OverlayImage:
|
|||||||
"Chinese Traditional": "NotoSansTC-Regular.ttf",
|
"Chinese Traditional": "NotoSansTC-Regular.ttf",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, root_path: str=None):
|
def __init__(self, root_path: Optional[str] = None) -> None:
|
||||||
self.message_log = []
|
"""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:
|
if root_path is None:
|
||||||
self.root_path = os_path.join(os_path.dirname(__file__), "..", "..", "..", "fonts")
|
self.root_path = os_path.join(os_path.dirname(__file__), "..", "..", "..", "fonts")
|
||||||
else:
|
else:
|
||||||
@@ -58,7 +64,7 @@ class OverlayImage:
|
|||||||
}
|
}
|
||||||
return colors
|
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"])
|
font_family = self.LANGUAGES.get(language, self.LANGUAGES["Default"])
|
||||||
img = Image.new("RGBA", (base_width, base_height), (0, 0, 0, 0))
|
img = Image.new("RGBA", (base_width, base_height), (0, 0, 0, 0))
|
||||||
draw = ImageDraw.Draw(img)
|
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")
|
draw.text((text_x, text_y), text, text_color, anchor="mm", stroke_width=0, font=font, align="center")
|
||||||
return img
|
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設定を取得
|
||||||
ui_size = self.getUiSizeSmallLog()
|
ui_size = self.getUiSizeSmallLog()
|
||||||
width, height, font_size = ui_size["width"], ui_size["height"], ui_size["font_size"]
|
width, height, font_size = ui_size["width"], ui_size["height"], ui_size["font_size"]
|
||||||
@@ -242,7 +248,7 @@ class OverlayImage:
|
|||||||
draw.text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font)
|
draw.text((text_x, text_y), text, text_color, anchor=anchor, stroke_width=0, font=font)
|
||||||
return img
|
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)]
|
images = [self.createTextImageMessageType(message_type, date_time)]
|
||||||
|
|
||||||
@@ -272,7 +278,7 @@ class OverlayImage:
|
|||||||
|
|
||||||
return combined_img
|
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()
|
ui_color = self.getUiColorLargeLog()
|
||||||
background_color = ui_color["background_color"]
|
background_color = ui_color["background_color"]
|
||||||
background_outline_color = ui_color["background_outline_color"]
|
background_outline_color = ui_color["background_outline_color"]
|
||||||
|
|||||||
@@ -1,11 +1,29 @@
|
|||||||
import numpy as np
|
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]])
|
homogeneous_matrix = np.vstack([matrix, [0, 0, 0, 1]])
|
||||||
return homogeneous_matrix
|
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
|
tx, ty, tz = translation
|
||||||
return np.array([
|
return np.array([
|
||||||
[1, 0, 0, tx],
|
[1, 0, 0, tx],
|
||||||
@@ -15,7 +33,8 @@ def calcTranslationMatrix(translation):
|
|||||||
])
|
])
|
||||||
|
|
||||||
# X軸周りの回転行列を生成する関数
|
# X軸周りの回転行列を生成する関数
|
||||||
def calcRotationMatrixX(angle):
|
def calcRotationMatrixX(angle: float) -> np.ndarray:
|
||||||
|
"""Rotation matrix around X axis for given angle in degrees."""
|
||||||
c = np.cos(np.pi / 180 * angle)
|
c = np.cos(np.pi / 180 * angle)
|
||||||
s = np.sin(np.pi / 180 * angle)
|
s = np.sin(np.pi / 180 * angle)
|
||||||
return np.array([
|
return np.array([
|
||||||
@@ -26,7 +45,8 @@ def calcRotationMatrixX(angle):
|
|||||||
])
|
])
|
||||||
|
|
||||||
# Y軸周りの回転行列を生成する関数
|
# Y軸周りの回転行列を生成する関数
|
||||||
def calcRotationMatrixY(angle):
|
def calcRotationMatrixY(angle: float) -> np.ndarray:
|
||||||
|
"""Rotation matrix around Y axis for given angle in degrees."""
|
||||||
c = np.cos(np.pi / 180 * angle)
|
c = np.cos(np.pi / 180 * angle)
|
||||||
s = np.sin(np.pi / 180 * angle)
|
s = np.sin(np.pi / 180 * angle)
|
||||||
return np.array([
|
return np.array([
|
||||||
@@ -37,7 +57,8 @@ def calcRotationMatrixY(angle):
|
|||||||
])
|
])
|
||||||
|
|
||||||
# Z軸周りの回転行列を生成する関数
|
# Z軸周りの回転行列を生成する関数
|
||||||
def calcRotationMatrixZ(angle):
|
def calcRotationMatrixZ(angle: float) -> np.ndarray:
|
||||||
|
"""Rotation matrix around Z axis for given angle in degrees."""
|
||||||
c = np.cos(np.pi / 180 * angle)
|
c = np.cos(np.pi / 180 * angle)
|
||||||
s = np.sin(np.pi / 180 * angle)
|
s = np.sin(np.pi / 180 * angle)
|
||||||
return np.array([
|
return np.array([
|
||||||
@@ -48,7 +69,17 @@ def calcRotationMatrixZ(angle):
|
|||||||
])
|
])
|
||||||
|
|
||||||
# 3x4行列の座標を基準として回転や移動を行う関数
|
# 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)
|
homogeneous_base_matrix = toHomogeneous(base_matrix)
|
||||||
translation_matrix = calcTranslationMatrix(translation)
|
translation_matrix = calcTranslationMatrix(translation)
|
||||||
rotation_matrix_x = calcRotationMatrixX(rotation[0])
|
rotation_matrix_x = calcRotationMatrixX(rotation[0])
|
||||||
@@ -60,7 +91,15 @@ def transform_matrix(base_matrix, translation, rotation):
|
|||||||
result_matrix = np.dot(homogeneous_base_matrix, transformation_matrix)
|
result_matrix = np.dot(homogeneous_base_matrix, transformation_matrix)
|
||||||
return result_matrix[:3, :]
|
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
|
phi = angles[0] * np.pi / 180
|
||||||
theta = angles[1] * np.pi / 180
|
theta = angles[1] * np.pi / 180
|
||||||
psi = angles[2] * np.pi / 180
|
psi = angles[2] * np.pi / 180
|
||||||
|
|||||||
30
src-python/tests/test_overlay_imports.py
Normal file
30
src-python/tests/test_overlay_imports.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
sys.path.append(r"d:\WORKSPACE\WORK\VRChatProject\VRCT\src-python")
|
||||||
|
|
||||||
|
from models.overlay import overlay_image, overlay_utils
|
||||||
|
|
||||||
|
|
||||||
|
def test_overlay_image_create():
|
||||||
|
oi = overlay_image.OverlayImage()
|
||||||
|
img = oi.createOverlayImageSmallLog("hello", "English", [], [])
|
||||||
|
assert isinstance(img, Image.Image)
|
||||||
|
|
||||||
|
|
||||||
|
def test_utils_transform():
|
||||||
|
import numpy as np
|
||||||
|
base = np.array([
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 0, 1, 0]
|
||||||
|
])
|
||||||
|
res = overlay_utils.transform_matrix(base, (0, 0, 0), (0, 0, 0))
|
||||||
|
assert res.shape == (3, 4)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_overlay_image_create()
|
||||||
|
test_utils_transform()
|
||||||
|
print('tests passed')
|
||||||
Reference in New Issue
Block a user