vmcp.typing
typing
module of vmcp
package.
1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# SPDX-License-Identifier: AGPL-3.0-or-later 4 5"""``typing`` module of ``vmcp`` package.""" 6 7from typing import overload 8from dataclasses import dataclass 9from enum import Enum 10from math import ( 11 cos, 12 sin, 13 atan2, 14 asin, 15 sqrt 16) 17from time import time 18 19 20# Availability 21class ModelState(Enum): 22 """VMC model state.""" 23 24 NOT_LOADED = 0 25 LOADED = 1 26 27 28class CalibrationState(Enum): 29 """VMC calibration state.""" 30 31 UNCALIBRATED = 0 32 WAITING_FOR_CALIBRATING = 1 33 CALIBRATING = 2 34 CALIBRATED = 3 35 36 37class CalibrationMode(Enum): 38 """VMC calibration mode.""" 39 40 NORMAL = 0 41 MR_HAND = 1 42 MR_FLOOR = 2 43 44 45class TrackingState(Enum): 46 """VMC tracking state.""" 47 48 BAD = 0 49 OK = 1 50 51 52# Devices 53class DeviceType(str, Enum): 54 """VMC device types.""" 55 56 HMD = "Hmd" 57 CONTROLLER = "Con" 58 TRACKER = "Tra" 59 60 61# 3D Space 62class Bone(str, Enum): 63 """All 55 HumanBodyBones from Unity.""" 64 65 HIPS = "Hips" 66 LEFT_UPPER_LEG = "LeftUpperLeg" 67 RIGHT_UPPER_LEG = "RightUpperLeg" 68 LEFT_LOWER_LEG = "LeftLowerLeg" 69 RIGHT_LOWER_LEG = "RightLowerLeg" 70 LEFT_FOOT = "LeftFoot" 71 RIGHT_FOOT = "RightFoot" 72 SPINE = "Spine" 73 CHEST = "Chest" 74 UPPER_CHEST = "UpperChest" 75 NECK = "Neck" 76 HEAD = "Head" 77 LEFT_SHOULDER = "LeftShoulder" 78 RIGHT_SHOULDER = "RightShoulder" 79 LEFT_UPPER_ARM = "LeftUpperArm" 80 RIGHT_UPPER_ARM = "RightUpperArm" 81 LEFT_LOWER_ARM = "LeftLowerArm" 82 RIGHT_LOWER_ARM = "RightLowerArm" 83 LEFT_HAND = "LeftHand" 84 RIGHT_HAND = "RightHand" 85 LEFT_TOES = "LeftToes" 86 RIGHT_TOES = "RightToes" 87 LEFT_EYE = "LeftEye" 88 RIGHT_EYE = "RightEye" 89 JAW = "Jaw" 90 LEFT_THUMB_PROXIMAL = "LeftThumbProximal" 91 LEFT_THUMB_INTERMEDIATE = "LeftThumbIntermediate" 92 LEFT_THUMB_DISTAL = "LeftThumbDistal" 93 LEFT_INDEX_PROXIMAL = "LeftIndexProximal" 94 LEFT_INDEX_INTERMEDIATE = "LeftIndexIntermediate" 95 LEFT_INDEX_DISTAL = "LeftIndexDistal" 96 LEFT_MIDDLE_PROXIMAL = "LeftMiddleProximal" 97 LEFT_MIDDLE_INTERMEDIATE = "LeftMiddleIntermediate" 98 LEFT_MIDDLE_DISTAL = "LeftMiddleDistal" 99 LEFT_RING_PROXIMAL = "LeftRingProximal" 100 LEFT_RING_INTERMEDIATE = "LeftRingIntermediate" 101 LEFT_RING_DISTAL = "LeftRingDistal" 102 LEFT_LITTLE_PROXIMAL = "LeftLittleProximal" 103 LEFT_LITTLE_INTERMEDIATE = "LeftLittleIntermediate" 104 LEFT_LITTLE_DISTAL = "LeftLittleDistal" 105 RIGHT_THUMB_PROXIMAL = "RightThumbProximal" 106 RIGHT_THUMB_INTERMEDIATE = "RightThumbIntermediate" 107 RIGHT_THUMB_DISTAL = "RightThumbDistal" 108 RIGHT_INDEX_PROXIMAL = "RightIndexProximal" 109 RIGHT_INDEX_INTERMEDIATE = "RightIndexIntermediate" 110 RIGHT_INDEX_DISTAL = "RightIndexDistal" 111 RIGHT_MIDDLE_PROXIMAL = "RightMiddleProximal" 112 RIGHT_MIDDLE_INTERMEDIATE = "RightMiddleIntermediate" 113 RIGHT_MIDDLE_DISTAL = "RightMiddleDistal" 114 RIGHT_RING_PROXIMAL = "RightRingProximal" 115 RIGHT_RING_INTERMEDIATE = "RightRingIntermediate" 116 RIGHT_RING_DISTAL = "RightRingDistal" 117 RIGHT_LITTLE_PROXIMAL = "RightLittleProximal" 118 RIGHT_LITTLE_INTERMEDIATE = "RightLittleIntermediate" 119 RIGHT_LITTLE_DISTAL = "RightLittleDistal" 120 121 122class BlendShapeKey(str, Enum): 123 """BlendShapeKey interface.""" 124 125 126@dataclass(frozen=True, slots=True) 127class CoordinateVector: 128 """Cartesian coordinate vector.""" 129 130 x: float # pylint: disable=invalid-name 131 """float: x coordinate.""" 132 133 y: float # pylint: disable=invalid-name 134 """float: y coordinate.""" 135 136 z: float # pylint: disable=invalid-name 137 """float: z coordinate.""" 138 139 @classmethod 140 def identity(cls) -> 'CoordinateVector': 141 """Create an identity position. 142 143 Returns: 144 Position: 145 Created identity position (x, y, z). 146 147 >>> CoordinateVector.identity() 148 CoordinateVector(0.0, 0.0, 0.0) 149 150 """ 151 return cls(0.0, 0.0, 0.0) 152 153 154@dataclass(frozen=True, slots=True) 155class Quaternion: 156 """Quaternion.""" 157 158 x: float # pylint: disable=invalid-name 159 """float: x component.""" 160 161 y: float # pylint: disable=invalid-name 162 """float: y component.""" 163 164 z: float # pylint: disable=invalid-name 165 """float: z component.""" 166 167 w: float # pylint: disable=invalid-name 168 """float: w component.""" 169 170 @classmethod 171 def identity(cls) -> 'Quaternion': 172 """Create an identity quaternion. 173 174 Returns: 175 Quaternion: 176 Created identity quaternion (x, y, z, w). 177 178 >>> Quaternion.identity() 179 0.0, 0.0, 0.0, 1.0 180 181 """ 182 return cls(0.0, 0.0, 0.0, 1.0) 183 184 @classmethod 185 def from_rotvec( 186 cls, 187 x: float, # pylint: disable=invalid-name 188 y: float, # pylint: disable=invalid-name 189 z: float # pylint: disable=invalid-name 190 ) -> 'Quaternion': 191 """Create from rotation vector. 192 193 Args: 194 x (float): 195 x dimension. 196 y (float): 197 y dimension. 198 z (float): 199 z dimension. 200 201 .. _Source: 202 https://github.com/scipy/scipy/blob/main/scipy/spatial/transform/_rotation.pyx#L872-L876 203 204 """ 205 angle: float = sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)) 206 207 # Stolen from the `scipy` package 208 if angle <= 1e-3: 209 # small angle 210 angle2 = angle * angle 211 scale = 0.5 - angle2 / 48 + angle2 * angle2 / 3840 212 else: 213 # large angle 214 scale = sin(angle / 2) / angle 215 216 return cls( 217 x * scale, 218 y * scale, 219 z * scale, 220 cos(angle / 2) 221 ) 222 223 @classmethod 224 def from_euler( 225 cls, 226 phi: float, 227 theta: float, 228 psi: float 229 ) -> 'Quaternion': 230 """Create from euler angles. 231 232 Args: 233 phi (float): 234 Rotation angle around the X axis (radian). 235 theta (float): 236 Rotation angle around the Y axis (radian). 237 psi (float): 238 Rotation angle around the Y axis (radian). 239 240 Returns: 241 Quaternion: 242 Created quaternion (x, y, z, w). 243 244 .. _Source: 245 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 246 247 """ 248 # x axis rotation angle 249 cos_phi_half = cos(phi / 2) 250 sin_phi_half = sin(phi / 2) 251 # y axis rotation angle 252 cos_theta_half = cos(theta / 2) 253 sin_theta_half = sin(theta / 2) 254 # z axis rotation angle 255 cos_psi_half = cos(psi / 2) 256 sin_psi_half = sin(psi / 2) 257 # Calculation 258 return cls( 259 x=float( 260 sin_phi_half * cos_theta_half * cos_psi_half - 261 cos_phi_half * sin_theta_half * sin_psi_half 262 ), 263 y=float( 264 cos_phi_half * sin_theta_half * cos_psi_half + 265 sin_phi_half * cos_theta_half * sin_psi_half 266 ), 267 z=float( 268 cos_phi_half * cos_theta_half * sin_psi_half - 269 sin_phi_half * sin_theta_half * cos_psi_half 270 ), 271 w=float( 272 cos_phi_half * cos_theta_half * cos_psi_half + 273 sin_phi_half * sin_theta_half * sin_psi_half 274 ) 275 ) 276 277 def to_euler(self) -> tuple[float, float, float]: 278 """Convert to euler angles. 279 280 Returns: 281 tuple[float, float, float]: 282 Phi, theta and psi (radian). 283 284 .. _Source: 285 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 286 287 """ 288 # x axis rotation angle 289 term0 = 2 * (self.w * self.x + self.y * self.z) 290 term1 = 1 - 2 * (self.x * self.x + self.y * self.y) 291 phi = atan2(term0, term1) 292 # y axis rotation angle 293 term2 = 2 * (self.w * self.y - self.z * self.x) 294 term2 = 1 if term2 > 1 else term2 295 term2 = -1 if term2 < -1 else term2 296 theta = asin(term2) 297 # y axis rotation angle 298 term3 = 2 * (self.w * self.z + self.x * self.y) 299 term4 = 1 - 2 * (self.y * self.y + self.z * self.z) 300 psi = atan2(term3, term4) 301 return phi, theta, psi 302 303 def conjugate(self) -> 'Quaternion': 304 """Return conjugated quaternion. 305 306 Returns: 307 Quaternion: 308 Conjugated quaternion (x, y, z, w). 309 310 .. _Source: 311 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 312 313 """ 314 # Calculation 315 return Quaternion( 316 x=-self.x, 317 y=-self.y, 318 z=-self.z, 319 w=self.w 320 ) 321 322 def multiply_by( 323 self, 324 quaternion: 'Quaternion' 325 ) -> 'Quaternion': 326 """Multiplicate quaternion with an given one and return the product. 327 328 Returns: 329 Quaternion: 330 Multiplication product quaternion (x, y, z, w) 331 332 .. _Source: 333 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 334 335 """ 336 # Calculation 337 return Quaternion( 338 x=float( 339 self.w * quaternion.x + 340 self.x * quaternion.w + 341 self.y * quaternion.z - 342 self.z * quaternion.y 343 ), 344 y=float( 345 self.w * quaternion.y + 346 self.y * quaternion.w + 347 self.z * quaternion.x - 348 self.x * quaternion.z 349 ), 350 z=float( 351 self.w * quaternion.z + 352 self.z * quaternion.w + 353 self.x * quaternion.y - 354 self.y * quaternion.x 355 ), 356 w=float( 357 self.w * quaternion.w - 358 self.x * quaternion.x - 359 self.y * quaternion.y - 360 self.z * quaternion.z 361 ) 362 ) 363 364 365@dataclass(frozen=False, slots=True, init=False) 366class Scale: 367 """Scale.""" 368 369 width: float 370 """float: Width dimension.""" 371 372 height: float 373 """float: Height dimension.""" 374 375 length: float 376 """float: Length dimension.""" 377 378 @overload 379 def __init__( 380 self, 381 uniformly: float 382 ) -> None: 383 """Scale constructor. 384 385 Args: 386 uniformly (float): 387 All dimensions. 388 389 """ 390 391 @overload 392 def __init__( 393 self, 394 width: float, 395 height: float, 396 length: float 397 ) -> None: 398 """Scale constructor. 399 400 Args: 401 width (float): 402 Width dimension. 403 height (float): 404 Height dimension. 405 length (float): 406 Length dimension. 407 408 """ 409 410 def __init__( 411 self, 412 *args: float, 413 **kwargs: float 414 ) -> None: 415 """Implement scale constructor. 416 417 Args: 418 *args (float): 419 1 OR 3 dimension arguments. 420 Raises: 421 ValueError: 422 If invalid arguments are given. 423 424 """ 425 args += tuple(kwargs.values()) 426 match len(args): 427 case 1: 428 self.width = self.height = self.length = args[0] 429 case 3: 430 self.width = args[0] 431 self.height = args[1] 432 self.length = args[2] 433 case _: 434 raise ValueError(f"Invalid parameters given: {str(args)}") 435 436 437# Communication 438class Timestamp(float): 439 """Timestamp since the epoch in seconds with it's fractions.""" 440 441 def __new__( 442 cls, 443 at: float = None 444 ) -> 'Timestamp': 445 """Create a new instance of class ``Timestamp``. 446 447 Args: 448 at (Optional[float]): 449 Seconds with it's fractions since the epoch. 450 451 Returns: 452 Timestamp: 453 Object instance. 454 455 """ 456 return super().__new__(cls, time() if at is None else at) 457 458 def elapsed(self) -> float: 459 """Return elapsed time in seconds with it's fractions as float. 460 461 Returns: 462 float: 463 Elapsed time in seconds with it's fractions. 464 465 >>> Timestamp().elapsed() 466 0.0 467 468 """ 469 return time() - self 470 471 def __str__(self) -> str: 472 """Return string representation of the object. 473 474 Returns: 475 str: 476 Representation of the object. 477 478 """ 479 return super().__repr__() 480 481 def __repr__(self) -> str: 482 """Return a string, representing the object in a reconstructable way. 483 484 Returns: 485 str: 486 Representing the object in a reconstructable way. 487 488 """ 489 return f"{self.__class__.__qualname__}(at={self})"
VMC model state.
Inherited Members
- enum.Enum
- name
- value
29class CalibrationState(Enum): 30 """VMC calibration state.""" 31 32 UNCALIBRATED = 0 33 WAITING_FOR_CALIBRATING = 1 34 CALIBRATING = 2 35 CALIBRATED = 3
VMC calibration state.
Inherited Members
- enum.Enum
- name
- value
38class CalibrationMode(Enum): 39 """VMC calibration mode.""" 40 41 NORMAL = 0 42 MR_HAND = 1 43 MR_FLOOR = 2
VMC calibration mode.
Inherited Members
- enum.Enum
- name
- value
VMC tracking state.
Inherited Members
- enum.Enum
- name
- value
54class DeviceType(str, Enum): 55 """VMC device types.""" 56 57 HMD = "Hmd" 58 CONTROLLER = "Con" 59 TRACKER = "Tra"
VMC device types.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
63class Bone(str, Enum): 64 """All 55 HumanBodyBones from Unity.""" 65 66 HIPS = "Hips" 67 LEFT_UPPER_LEG = "LeftUpperLeg" 68 RIGHT_UPPER_LEG = "RightUpperLeg" 69 LEFT_LOWER_LEG = "LeftLowerLeg" 70 RIGHT_LOWER_LEG = "RightLowerLeg" 71 LEFT_FOOT = "LeftFoot" 72 RIGHT_FOOT = "RightFoot" 73 SPINE = "Spine" 74 CHEST = "Chest" 75 UPPER_CHEST = "UpperChest" 76 NECK = "Neck" 77 HEAD = "Head" 78 LEFT_SHOULDER = "LeftShoulder" 79 RIGHT_SHOULDER = "RightShoulder" 80 LEFT_UPPER_ARM = "LeftUpperArm" 81 RIGHT_UPPER_ARM = "RightUpperArm" 82 LEFT_LOWER_ARM = "LeftLowerArm" 83 RIGHT_LOWER_ARM = "RightLowerArm" 84 LEFT_HAND = "LeftHand" 85 RIGHT_HAND = "RightHand" 86 LEFT_TOES = "LeftToes" 87 RIGHT_TOES = "RightToes" 88 LEFT_EYE = "LeftEye" 89 RIGHT_EYE = "RightEye" 90 JAW = "Jaw" 91 LEFT_THUMB_PROXIMAL = "LeftThumbProximal" 92 LEFT_THUMB_INTERMEDIATE = "LeftThumbIntermediate" 93 LEFT_THUMB_DISTAL = "LeftThumbDistal" 94 LEFT_INDEX_PROXIMAL = "LeftIndexProximal" 95 LEFT_INDEX_INTERMEDIATE = "LeftIndexIntermediate" 96 LEFT_INDEX_DISTAL = "LeftIndexDistal" 97 LEFT_MIDDLE_PROXIMAL = "LeftMiddleProximal" 98 LEFT_MIDDLE_INTERMEDIATE = "LeftMiddleIntermediate" 99 LEFT_MIDDLE_DISTAL = "LeftMiddleDistal" 100 LEFT_RING_PROXIMAL = "LeftRingProximal" 101 LEFT_RING_INTERMEDIATE = "LeftRingIntermediate" 102 LEFT_RING_DISTAL = "LeftRingDistal" 103 LEFT_LITTLE_PROXIMAL = "LeftLittleProximal" 104 LEFT_LITTLE_INTERMEDIATE = "LeftLittleIntermediate" 105 LEFT_LITTLE_DISTAL = "LeftLittleDistal" 106 RIGHT_THUMB_PROXIMAL = "RightThumbProximal" 107 RIGHT_THUMB_INTERMEDIATE = "RightThumbIntermediate" 108 RIGHT_THUMB_DISTAL = "RightThumbDistal" 109 RIGHT_INDEX_PROXIMAL = "RightIndexProximal" 110 RIGHT_INDEX_INTERMEDIATE = "RightIndexIntermediate" 111 RIGHT_INDEX_DISTAL = "RightIndexDistal" 112 RIGHT_MIDDLE_PROXIMAL = "RightMiddleProximal" 113 RIGHT_MIDDLE_INTERMEDIATE = "RightMiddleIntermediate" 114 RIGHT_MIDDLE_DISTAL = "RightMiddleDistal" 115 RIGHT_RING_PROXIMAL = "RightRingProximal" 116 RIGHT_RING_INTERMEDIATE = "RightRingIntermediate" 117 RIGHT_RING_DISTAL = "RightRingDistal" 118 RIGHT_LITTLE_PROXIMAL = "RightLittleProximal" 119 RIGHT_LITTLE_INTERMEDIATE = "RightLittleIntermediate" 120 RIGHT_LITTLE_DISTAL = "RightLittleDistal"
All 55 HumanBodyBones from Unity.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
BlendShapeKey interface.
Inherited Members
- enum.Enum
- name
- value
- builtins.str
- encode
- replace
- split
- rsplit
- join
- capitalize
- casefold
- title
- center
- count
- expandtabs
- find
- partition
- index
- ljust
- lower
- lstrip
- rfind
- rindex
- rjust
- rstrip
- rpartition
- splitlines
- strip
- swapcase
- translate
- upper
- startswith
- endswith
- removeprefix
- removesuffix
- isascii
- islower
- isupper
- istitle
- isspace
- isdecimal
- isdigit
- isnumeric
- isalpha
- isalnum
- isidentifier
- isprintable
- zfill
- format
- format_map
- maketrans
127@dataclass(frozen=True, slots=True) 128class CoordinateVector: 129 """Cartesian coordinate vector.""" 130 131 x: float # pylint: disable=invalid-name 132 """float: x coordinate.""" 133 134 y: float # pylint: disable=invalid-name 135 """float: y coordinate.""" 136 137 z: float # pylint: disable=invalid-name 138 """float: z coordinate.""" 139 140 @classmethod 141 def identity(cls) -> 'CoordinateVector': 142 """Create an identity position. 143 144 Returns: 145 Position: 146 Created identity position (x, y, z). 147 148 >>> CoordinateVector.identity() 149 CoordinateVector(0.0, 0.0, 0.0) 150 151 """ 152 return cls(0.0, 0.0, 0.0)
Cartesian coordinate vector.
140 @classmethod 141 def identity(cls) -> 'CoordinateVector': 142 """Create an identity position. 143 144 Returns: 145 Position: 146 Created identity position (x, y, z). 147 148 >>> CoordinateVector.identity() 149 CoordinateVector(0.0, 0.0, 0.0) 150 151 """ 152 return cls(0.0, 0.0, 0.0)
Create an identity position.
Returns: Position: Created identity position (x, y, z).
>>> CoordinateVector.identity()
CoordinateVector(0.0, 0.0, 0.0)
155@dataclass(frozen=True, slots=True) 156class Quaternion: 157 """Quaternion.""" 158 159 x: float # pylint: disable=invalid-name 160 """float: x component.""" 161 162 y: float # pylint: disable=invalid-name 163 """float: y component.""" 164 165 z: float # pylint: disable=invalid-name 166 """float: z component.""" 167 168 w: float # pylint: disable=invalid-name 169 """float: w component.""" 170 171 @classmethod 172 def identity(cls) -> 'Quaternion': 173 """Create an identity quaternion. 174 175 Returns: 176 Quaternion: 177 Created identity quaternion (x, y, z, w). 178 179 >>> Quaternion.identity() 180 0.0, 0.0, 0.0, 1.0 181 182 """ 183 return cls(0.0, 0.0, 0.0, 1.0) 184 185 @classmethod 186 def from_rotvec( 187 cls, 188 x: float, # pylint: disable=invalid-name 189 y: float, # pylint: disable=invalid-name 190 z: float # pylint: disable=invalid-name 191 ) -> 'Quaternion': 192 """Create from rotation vector. 193 194 Args: 195 x (float): 196 x dimension. 197 y (float): 198 y dimension. 199 z (float): 200 z dimension. 201 202 .. _Source: 203 https://github.com/scipy/scipy/blob/main/scipy/spatial/transform/_rotation.pyx#L872-L876 204 205 """ 206 angle: float = sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)) 207 208 # Stolen from the `scipy` package 209 if angle <= 1e-3: 210 # small angle 211 angle2 = angle * angle 212 scale = 0.5 - angle2 / 48 + angle2 * angle2 / 3840 213 else: 214 # large angle 215 scale = sin(angle / 2) / angle 216 217 return cls( 218 x * scale, 219 y * scale, 220 z * scale, 221 cos(angle / 2) 222 ) 223 224 @classmethod 225 def from_euler( 226 cls, 227 phi: float, 228 theta: float, 229 psi: float 230 ) -> 'Quaternion': 231 """Create from euler angles. 232 233 Args: 234 phi (float): 235 Rotation angle around the X axis (radian). 236 theta (float): 237 Rotation angle around the Y axis (radian). 238 psi (float): 239 Rotation angle around the Y axis (radian). 240 241 Returns: 242 Quaternion: 243 Created quaternion (x, y, z, w). 244 245 .. _Source: 246 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 247 248 """ 249 # x axis rotation angle 250 cos_phi_half = cos(phi / 2) 251 sin_phi_half = sin(phi / 2) 252 # y axis rotation angle 253 cos_theta_half = cos(theta / 2) 254 sin_theta_half = sin(theta / 2) 255 # z axis rotation angle 256 cos_psi_half = cos(psi / 2) 257 sin_psi_half = sin(psi / 2) 258 # Calculation 259 return cls( 260 x=float( 261 sin_phi_half * cos_theta_half * cos_psi_half - 262 cos_phi_half * sin_theta_half * sin_psi_half 263 ), 264 y=float( 265 cos_phi_half * sin_theta_half * cos_psi_half + 266 sin_phi_half * cos_theta_half * sin_psi_half 267 ), 268 z=float( 269 cos_phi_half * cos_theta_half * sin_psi_half - 270 sin_phi_half * sin_theta_half * cos_psi_half 271 ), 272 w=float( 273 cos_phi_half * cos_theta_half * cos_psi_half + 274 sin_phi_half * sin_theta_half * sin_psi_half 275 ) 276 ) 277 278 def to_euler(self) -> tuple[float, float, float]: 279 """Convert to euler angles. 280 281 Returns: 282 tuple[float, float, float]: 283 Phi, theta and psi (radian). 284 285 .. _Source: 286 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 287 288 """ 289 # x axis rotation angle 290 term0 = 2 * (self.w * self.x + self.y * self.z) 291 term1 = 1 - 2 * (self.x * self.x + self.y * self.y) 292 phi = atan2(term0, term1) 293 # y axis rotation angle 294 term2 = 2 * (self.w * self.y - self.z * self.x) 295 term2 = 1 if term2 > 1 else term2 296 term2 = -1 if term2 < -1 else term2 297 theta = asin(term2) 298 # y axis rotation angle 299 term3 = 2 * (self.w * self.z + self.x * self.y) 300 term4 = 1 - 2 * (self.y * self.y + self.z * self.z) 301 psi = atan2(term3, term4) 302 return phi, theta, psi 303 304 def conjugate(self) -> 'Quaternion': 305 """Return conjugated quaternion. 306 307 Returns: 308 Quaternion: 309 Conjugated quaternion (x, y, z, w). 310 311 .. _Source: 312 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 313 314 """ 315 # Calculation 316 return Quaternion( 317 x=-self.x, 318 y=-self.y, 319 z=-self.z, 320 w=self.w 321 ) 322 323 def multiply_by( 324 self, 325 quaternion: 'Quaternion' 326 ) -> 'Quaternion': 327 """Multiplicate quaternion with an given one and return the product. 328 329 Returns: 330 Quaternion: 331 Multiplication product quaternion (x, y, z, w) 332 333 .. _Source: 334 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 335 336 """ 337 # Calculation 338 return Quaternion( 339 x=float( 340 self.w * quaternion.x + 341 self.x * quaternion.w + 342 self.y * quaternion.z - 343 self.z * quaternion.y 344 ), 345 y=float( 346 self.w * quaternion.y + 347 self.y * quaternion.w + 348 self.z * quaternion.x - 349 self.x * quaternion.z 350 ), 351 z=float( 352 self.w * quaternion.z + 353 self.z * quaternion.w + 354 self.x * quaternion.y - 355 self.y * quaternion.x 356 ), 357 w=float( 358 self.w * quaternion.w - 359 self.x * quaternion.x - 360 self.y * quaternion.y - 361 self.z * quaternion.z 362 ) 363 )
Quaternion.
171 @classmethod 172 def identity(cls) -> 'Quaternion': 173 """Create an identity quaternion. 174 175 Returns: 176 Quaternion: 177 Created identity quaternion (x, y, z, w). 178 179 >>> Quaternion.identity() 180 0.0, 0.0, 0.0, 1.0 181 182 """ 183 return cls(0.0, 0.0, 0.0, 1.0)
Create an identity quaternion.
Returns: Quaternion: Created identity quaternion (x, y, z, w).
>>> Quaternion.identity()
0.0, 0.0, 0.0, 1.0
185 @classmethod 186 def from_rotvec( 187 cls, 188 x: float, # pylint: disable=invalid-name 189 y: float, # pylint: disable=invalid-name 190 z: float # pylint: disable=invalid-name 191 ) -> 'Quaternion': 192 """Create from rotation vector. 193 194 Args: 195 x (float): 196 x dimension. 197 y (float): 198 y dimension. 199 z (float): 200 z dimension. 201 202 .. _Source: 203 https://github.com/scipy/scipy/blob/main/scipy/spatial/transform/_rotation.pyx#L872-L876 204 205 """ 206 angle: float = sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)) 207 208 # Stolen from the `scipy` package 209 if angle <= 1e-3: 210 # small angle 211 angle2 = angle * angle 212 scale = 0.5 - angle2 / 48 + angle2 * angle2 / 3840 213 else: 214 # large angle 215 scale = sin(angle / 2) / angle 216 217 return cls( 218 x * scale, 219 y * scale, 220 z * scale, 221 cos(angle / 2) 222 )
Create from rotation vector.
Args: x (float): x dimension. y (float): y dimension. z (float): z dimension.
224 @classmethod 225 def from_euler( 226 cls, 227 phi: float, 228 theta: float, 229 psi: float 230 ) -> 'Quaternion': 231 """Create from euler angles. 232 233 Args: 234 phi (float): 235 Rotation angle around the X axis (radian). 236 theta (float): 237 Rotation angle around the Y axis (radian). 238 psi (float): 239 Rotation angle around the Y axis (radian). 240 241 Returns: 242 Quaternion: 243 Created quaternion (x, y, z, w). 244 245 .. _Source: 246 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 247 248 """ 249 # x axis rotation angle 250 cos_phi_half = cos(phi / 2) 251 sin_phi_half = sin(phi / 2) 252 # y axis rotation angle 253 cos_theta_half = cos(theta / 2) 254 sin_theta_half = sin(theta / 2) 255 # z axis rotation angle 256 cos_psi_half = cos(psi / 2) 257 sin_psi_half = sin(psi / 2) 258 # Calculation 259 return cls( 260 x=float( 261 sin_phi_half * cos_theta_half * cos_psi_half - 262 cos_phi_half * sin_theta_half * sin_psi_half 263 ), 264 y=float( 265 cos_phi_half * sin_theta_half * cos_psi_half + 266 sin_phi_half * cos_theta_half * sin_psi_half 267 ), 268 z=float( 269 cos_phi_half * cos_theta_half * sin_psi_half - 270 sin_phi_half * sin_theta_half * cos_psi_half 271 ), 272 w=float( 273 cos_phi_half * cos_theta_half * cos_psi_half + 274 sin_phi_half * sin_theta_half * sin_psi_half 275 ) 276 )
Create from euler angles.
Args: phi (float): Rotation angle around the X axis (radian). theta (float): Rotation angle around the Y axis (radian). psi (float): Rotation angle around the Y axis (radian).
Returns: Quaternion: Created quaternion (x, y, z, w).
278 def to_euler(self) -> tuple[float, float, float]: 279 """Convert to euler angles. 280 281 Returns: 282 tuple[float, float, float]: 283 Phi, theta and psi (radian). 284 285 .. _Source: 286 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 287 288 """ 289 # x axis rotation angle 290 term0 = 2 * (self.w * self.x + self.y * self.z) 291 term1 = 1 - 2 * (self.x * self.x + self.y * self.y) 292 phi = atan2(term0, term1) 293 # y axis rotation angle 294 term2 = 2 * (self.w * self.y - self.z * self.x) 295 term2 = 1 if term2 > 1 else term2 296 term2 = -1 if term2 < -1 else term2 297 theta = asin(term2) 298 # y axis rotation angle 299 term3 = 2 * (self.w * self.z + self.x * self.y) 300 term4 = 1 - 2 * (self.y * self.y + self.z * self.z) 301 psi = atan2(term3, term4) 302 return phi, theta, psi
Convert to euler angles.
Returns: tuple[float, float, float]: Phi, theta and psi (radian).
304 def conjugate(self) -> 'Quaternion': 305 """Return conjugated quaternion. 306 307 Returns: 308 Quaternion: 309 Conjugated quaternion (x, y, z, w). 310 311 .. _Source: 312 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 313 314 """ 315 # Calculation 316 return Quaternion( 317 x=-self.x, 318 y=-self.y, 319 z=-self.z, 320 w=self.w 321 )
Return conjugated quaternion.
Returns: Quaternion: Conjugated quaternion (x, y, z, w).
323 def multiply_by( 324 self, 325 quaternion: 'Quaternion' 326 ) -> 'Quaternion': 327 """Multiplicate quaternion with an given one and return the product. 328 329 Returns: 330 Quaternion: 331 Multiplication product quaternion (x, y, z, w) 332 333 .. _Source: 334 https://www.meccanismocomplesso.org/en/hamiltons-quaternions-and-3d-rotation-with-python 335 336 """ 337 # Calculation 338 return Quaternion( 339 x=float( 340 self.w * quaternion.x + 341 self.x * quaternion.w + 342 self.y * quaternion.z - 343 self.z * quaternion.y 344 ), 345 y=float( 346 self.w * quaternion.y + 347 self.y * quaternion.w + 348 self.z * quaternion.x - 349 self.x * quaternion.z 350 ), 351 z=float( 352 self.w * quaternion.z + 353 self.z * quaternion.w + 354 self.x * quaternion.y - 355 self.y * quaternion.x 356 ), 357 w=float( 358 self.w * quaternion.w - 359 self.x * quaternion.x - 360 self.y * quaternion.y - 361 self.z * quaternion.z 362 ) 363 )
Multiplicate quaternion with an given one and return the product.
Returns: Quaternion: Multiplication product quaternion (x, y, z, w)
366@dataclass(frozen=False, slots=True, init=False) 367class Scale: 368 """Scale.""" 369 370 width: float 371 """float: Width dimension.""" 372 373 height: float 374 """float: Height dimension.""" 375 376 length: float 377 """float: Length dimension.""" 378 379 @overload 380 def __init__( 381 self, 382 uniformly: float 383 ) -> None: 384 """Scale constructor. 385 386 Args: 387 uniformly (float): 388 All dimensions. 389 390 """ 391 392 @overload 393 def __init__( 394 self, 395 width: float, 396 height: float, 397 length: float 398 ) -> None: 399 """Scale constructor. 400 401 Args: 402 width (float): 403 Width dimension. 404 height (float): 405 Height dimension. 406 length (float): 407 Length dimension. 408 409 """ 410 411 def __init__( 412 self, 413 *args: float, 414 **kwargs: float 415 ) -> None: 416 """Implement scale constructor. 417 418 Args: 419 *args (float): 420 1 OR 3 dimension arguments. 421 Raises: 422 ValueError: 423 If invalid arguments are given. 424 425 """ 426 args += tuple(kwargs.values()) 427 match len(args): 428 case 1: 429 self.width = self.height = self.length = args[0] 430 case 3: 431 self.width = args[0] 432 self.height = args[1] 433 self.length = args[2] 434 case _: 435 raise ValueError(f"Invalid parameters given: {str(args)}")
Scale.
411 def __init__( 412 self, 413 *args: float, 414 **kwargs: float 415 ) -> None: 416 """Implement scale constructor. 417 418 Args: 419 *args (float): 420 1 OR 3 dimension arguments. 421 Raises: 422 ValueError: 423 If invalid arguments are given. 424 425 """ 426 args += tuple(kwargs.values()) 427 match len(args): 428 case 1: 429 self.width = self.height = self.length = args[0] 430 case 3: 431 self.width = args[0] 432 self.height = args[1] 433 self.length = args[2] 434 case _: 435 raise ValueError(f"Invalid parameters given: {str(args)}")
Implement scale constructor.
Args: *args (float): 1 OR 3 dimension arguments. Raises: ValueError: If invalid arguments are given.
439class Timestamp(float): 440 """Timestamp since the epoch in seconds with it's fractions.""" 441 442 def __new__( 443 cls, 444 at: float = None 445 ) -> 'Timestamp': 446 """Create a new instance of class ``Timestamp``. 447 448 Args: 449 at (Optional[float]): 450 Seconds with it's fractions since the epoch. 451 452 Returns: 453 Timestamp: 454 Object instance. 455 456 """ 457 return super().__new__(cls, time() if at is None else at) 458 459 def elapsed(self) -> float: 460 """Return elapsed time in seconds with it's fractions as float. 461 462 Returns: 463 float: 464 Elapsed time in seconds with it's fractions. 465 466 >>> Timestamp().elapsed() 467 0.0 468 469 """ 470 return time() - self 471 472 def __str__(self) -> str: 473 """Return string representation of the object. 474 475 Returns: 476 str: 477 Representation of the object. 478 479 """ 480 return super().__repr__() 481 482 def __repr__(self) -> str: 483 """Return a string, representing the object in a reconstructable way. 484 485 Returns: 486 str: 487 Representing the object in a reconstructable way. 488 489 """ 490 return f"{self.__class__.__qualname__}(at={self})"
Timestamp since the epoch in seconds with it's fractions.
442 def __new__( 443 cls, 444 at: float = None 445 ) -> 'Timestamp': 446 """Create a new instance of class ``Timestamp``. 447 448 Args: 449 at (Optional[float]): 450 Seconds with it's fractions since the epoch. 451 452 Returns: 453 Timestamp: 454 Object instance. 455 456 """ 457 return super().__new__(cls, time() if at is None else at)
Create a new instance of class Timestamp
.
Args: at (Optional[float]): Seconds with it's fractions since the epoch.
Returns: Timestamp: Object instance.
459 def elapsed(self) -> float: 460 """Return elapsed time in seconds with it's fractions as float. 461 462 Returns: 463 float: 464 Elapsed time in seconds with it's fractions. 465 466 >>> Timestamp().elapsed() 467 0.0 468 469 """ 470 return time() - self
Return elapsed time in seconds with it's fractions as float.
Returns: float: Elapsed time in seconds with it's fractions.
>>> Timestamp().elapsed()
0.0
Inherited Members
- builtins.float
- conjugate
- as_integer_ratio
- fromhex
- hex
- is_integer
- real
- imag