vmcp.events

events module of vmcp package.

  1#!/usr/bin/env python3
  2# -*- coding: utf-8 -*-
  3# SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5"""``events`` module of ``vmcp`` package."""
  6
  7from dataclasses import dataclass
  8from abc import ABCMeta, abstractmethod
  9from typing import (
 10    ClassVar,
 11    Any
 12)
 13from .typing import (
 14    CoordinateVector,
 15    Quaternion,
 16    Scale,
 17    Bone,
 18    DeviceType,
 19    ModelState,
 20    CalibrationState,
 21    CalibrationMode,
 22    TrackingState
 23)
 24
 25
 26@dataclass(frozen=True, slots=True)  # type: ignore
 27class Event(metaclass=ABCMeta):
 28    """Abstract event class."""
 29
 30    ADDRESS_PATTERN: ClassVar[str]
 31    """str: OSC address pattern matching."""
 32
 33    channel: str
 34    """str: Channel name."""
 35
 36    address: str
 37    """str: Called OSC address."""
 38
 39    def __post_init__(self) -> None:
 40        """Post event initialization.
 41
 42        Raises:
 43            NotImplementedError:
 44                If abstract `Event` is instantiated directly.
 45
 46        """
 47        # pylint: disable=unidiomatic-typecheck
 48        if type(self) is Event:
 49            raise NotImplementedError
 50
 51    @classmethod
 52    @abstractmethod
 53    def from_message_data(
 54        cls,
 55        channel: str,
 56        address: str,
 57        arguments: tuple[Any, ...]
 58    ) -> 'Event':
 59        """Abstract event constructor.
 60
 61        Args:
 62            channel (str):
 63                Channel name.
 64            address (str):
 65                OSC address.
 66            arguments (tuple[Any, ...]):
 67                OSC arguments.
 68
 69        Returns:
 70            Event:
 71                Instance.
 72
 73        Raises:
 74            ValueError:
 75                If invalid arguments are given.
 76
 77        """
 78
 79
 80@dataclass(frozen=True, slots=True)  # type: ignore
 81class TransformEvent(Event, metaclass=ABCMeta):
 82    """Abstract transform event class."""
 83
 84    joint: str
 85    """str: Joint name."""
 86
 87    position: CoordinateVector
 88    """CoordinateVector: 3D position."""
 89
 90    rotation: Quaternion
 91    """Quaternion: 3D rotation."""
 92
 93    def __post_init__(self) -> None:
 94        """Post event initialization.
 95
 96        Raises:
 97            NotImplementedError:
 98                If abstract `TransformEvent` is instantiated directly.
 99
100        """
101        # pylint: disable=unidiomatic-typecheck
102        if type(self) is TransformEvent:
103            raise NotImplementedError
104
105
106@dataclass(frozen=True, slots=True)
107class RootTransformEvent(TransformEvent):
108    """Root transform event."""
109
110    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/Root/Pos"
111    """str: OSC address pattern matching."""
112
113    scale: Scale
114    """Scale: Additional scaling (division)."""
115
116    offset: CoordinateVector
117    """CoordinateVector: Additional (negative) offset."""
118
119    @classmethod
120    def from_message_data(
121        cls,
122        channel: str,
123        address: str,
124        arguments: tuple[Any, ...]
125    ) -> 'RootTransformEvent':
126        """Abstract event constructor.
127
128        Args:
129            channel (str):
130                Channel name.
131            address (str):
132                OSC address.
133            arguments (tuple[Any, ...]):
134                OSC arguments.
135
136        Returns:
137            RootTransformEvent:
138                Instance.
139
140        Raises:
141            ValueError:
142                If invalid arguments are given.
143
144        """
145        scale_width: float = 1.0
146        scale_heigth: float = 1.0
147        scale_length: float = 1.0
148        offset_x: float = 0.0
149        offset_y: float = 0.0
150        offset_z: float = 0.0
151        match len(arguments):
152            case 14:
153                # Since VMC protocol specification v2.1.0.
154                scale_width = arguments[8]
155                scale_heigth = arguments[9]
156                scale_length = arguments[10]
157                offset_x = arguments[11]
158                offset_y = arguments[12]
159                offset_z = arguments[13]
160            case 8:
161                # Since VMC protocol specification v2.0.0.
162                pass
163            case _:
164                raise ValueError(f"Invalid arguments: {arguments}")
165        return cls(
166            str(channel),
167            str(address),
168            joint=str(arguments[0]),
169            position=CoordinateVector(
170                x=arguments[1],
171                y=arguments[2],
172                z=arguments[3]
173            ),
174            rotation=Quaternion(
175                x=arguments[4],
176                y=arguments[5],
177                z=arguments[6],
178                w=arguments[7]
179            ),
180            scale=Scale(
181                scale_width,
182                scale_heigth,
183                scale_length
184            ),
185            offset=CoordinateVector(
186                x=offset_x,
187                y=offset_y,
188                z=offset_z
189            )
190        )
191
192
193@dataclass(frozen=True, slots=True)
194class BoneTransformEvent(TransformEvent):
195    """Bone transform event."""
196
197    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/Bone/Pos"
198    """str: OSC address pattern matching."""
199
200    joint: Bone
201    """Bone: Joint bone."""
202
203    @classmethod
204    def from_message_data(
205        cls,
206        channel: str,
207        address: str,
208        arguments: tuple[Any, ...]
209    ) -> 'BoneTransformEvent':
210        """Abstract event constructor.
211
212        Args:
213            channel (str):
214                Channel name.
215            address (str):
216                OSC address.
217            arguments (tuple[Any, ...]):
218                OSC arguments.
219
220        Returns:
221            BoneTransformEvent:
222                Instance.
223
224        Raises:
225            ValueError:
226                If invalid arguments are given.
227
228        """
229        return cls(
230            str(channel),
231            str(address),
232            joint=Bone(arguments[0]),
233            position=CoordinateVector(
234                x=arguments[1],
235                y=arguments[2],
236                z=arguments[3]
237            ),
238            rotation=Quaternion(
239                x=arguments[4],
240                y=arguments[5],
241                z=arguments[6],
242                w=arguments[7]
243            )
244        )
245
246
247@dataclass(frozen=True, slots=True)
248class DeviceTransformEvent(TransformEvent):
249    """Device transform receiving event."""
250
251    ADDRESS_PATTERN: ClassVar[str] = (
252        f"/VMC/Ext/{{{','.join([t.value for t in DeviceType])}}}//"
253    )
254    """str: OSC address pattern matching."""
255
256    joint: str
257    """str: OpenVR device label or serial."""
258
259    device_type: DeviceType
260    """DeviceType: Device type."""
261
262    is_local: bool
263    """bool: If transform is relative to avatar, word space otherwise."""
264
265    @classmethod
266    def from_message_data(
267        cls,
268        channel: str,
269        address: str,
270        arguments: tuple[Any, ...]
271    ) -> 'DeviceTransformEvent':
272        """Abstract event constructor.
273
274        Args:
275            channel (str):
276                Channel name.
277            address (str):
278                OSC address.
279            arguments (tuple[Any, ...]):
280                OSC arguments.
281
282        Returns:
283            DeviceTransformEvent:
284                Instance.
285
286        Raises:
287            ValueError:
288                If invalid arguments are given.
289
290        """
291        return cls(
292            str(channel),
293            str(address),
294            joint=str(arguments[0]),
295            position=CoordinateVector(
296                x=arguments[1],
297                y=arguments[2],
298                z=arguments[3]
299            ),
300            rotation=Quaternion(
301                x=arguments[4],
302                y=arguments[5],
303                z=arguments[6],
304                w=arguments[7]
305            ),
306            device_type=DeviceType(address.split('/')[3]),
307            is_local=bool(address.count('/') > 4)
308        )
309
310
311@dataclass(frozen=True, slots=True)
312class BlendShapeEvent(Event):
313    """Blend shape event."""
314
315    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/Blend/Val"
316    """str: OSC address pattern matching."""
317
318    key: str
319    """str: Blend shape key."""
320
321    value: float
322    """float: Blend shape value."""
323
324    @classmethod
325    def from_message_data(
326        cls,
327        channel: str,
328        address: str,
329        arguments: tuple[Any, ...]
330    ) -> 'BlendShapeEvent':
331        """Abstract event constructor.
332
333        Args:
334            channel (str):
335                Channel name.
336            address (str):
337                OSC address.
338            arguments (tuple[Any, ...]):
339                OSC arguments.
340
341        Returns:
342            BlendShapeEvent:
343                Instance.
344
345        Raises:
346            ValueError:
347                If invalid arguments are given.
348
349        """
350        return cls(
351            str(channel),
352            str(address),
353            key=arguments[0],
354            value=arguments[1]
355        )
356
357
358@dataclass(frozen=True, slots=True)
359class BlendShapeApplyEvent(Event):
360    """Blend shape apply event."""
361
362    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/Blend/Apply"
363    """str: OSC address pattern matching."""
364
365    @classmethod
366    def from_message_data(
367        cls,
368        channel: str,
369        address: str,
370        arguments: tuple[Any, ...]
371    ) -> 'BlendShapeApplyEvent':
372        """Abstract event constructor.
373
374        Args:
375            channel (str):
376                Channel name.
377            address (str):
378                OSC address.
379            arguments (tuple[Any, ...]):
380                OSC arguments.
381
382        Returns:
383            BlendShapeApplyEvent:
384                Instance.
385
386        Raises:
387            ValueError:
388                If invalid arguments are given.
389
390        """
391        if len(arguments) != 0:
392            raise ValueError(f"Arguments not empty: {arguments}")
393        return cls(
394            channel,
395            address
396        )
397
398
399@dataclass(frozen=True, slots=True)
400class StateEvent(Event):
401    """Availability state event."""
402
403    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/OK"
404    """str: OSC address pattern matching."""
405
406    model_state: ModelState
407    """ModelState: Model state."""
408
409    calibration_state: CalibrationState | None
410    """CalibrationState | None: Calibration state."""
411
412    calibration_mode: CalibrationMode | None
413    """CalibrationMode | None: Calibration mode."""
414
415    tracking_state: TrackingState | None
416    """TrackingState | None: Tracking state."""
417
418    @classmethod
419    def from_message_data(
420        cls,
421        channel: str,
422        address: str,
423        arguments: tuple[Any, ...]
424    ) -> 'StateEvent':
425        """Abstract event constructor.
426
427        Args:
428            channel (str):
429                Channel name.
430            address (str):
431                OSC address.
432            arguments (tuple[Any, ...]):
433                OSC arguments.
434
435        Returns:
436            StateEvent:
437                Instance.
438
439        Raises:
440            ValueError:
441                If invalid arguments are given.
442
443        """
444        calibration_state: CalibrationState | None = None
445        calibration_mode: CalibrationMode | None = None
446        tracking_state: TrackingState | None = None
447        match len(arguments):
448            case 4:
449                # Since VMC protocol specification v2.7.0.
450                calibration_state = CalibrationState(arguments[1])
451                calibration_mode = CalibrationMode(arguments[2])
452                tracking_state = TrackingState(arguments[3])
453            case 3:
454                # Since VMC protocol specification v2.5.0.
455                calibration_state = CalibrationState(arguments[1])
456                calibration_mode = CalibrationMode(arguments[2])
457            case 1:
458                pass
459            case _:
460                raise ValueError(f"Invalid arguments: {arguments}")
461        return cls(
462            str(channel),
463            str(address),
464            model_state=ModelState(arguments[0]),
465            calibration_state=calibration_state,
466            calibration_mode=calibration_mode,
467            tracking_state=tracking_state
468        )
469
470
471@dataclass(frozen=True, slots=True)
472class RelativeTimeEvent(Event):
473    """Relative time event."""
474
475    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/T"
476    """str: OSC address pattern matching."""
477
478    delta: float
479    """float: Relative frame time."""
480
481    @classmethod
482    def from_message_data(
483        cls,
484        channel: str,
485        address: str,
486        arguments: tuple[Any, ...]
487    ) -> 'RelativeTimeEvent':
488        """Abstract event constructor.
489
490        Args:
491            channel (str):
492                Channel name.
493            address (str):
494                OSC address.
495            arguments (tuple[Any, ...]):
496                OSC arguments.
497
498        Returns:
499            RelativeTimeEvent:
500                Instance.
501
502        Raises:
503            ValueError:
504                If invalid arguments are given.
505
506        """
507        if len(arguments) != 1:
508            raise ValueError(f"Invalid arguments: {arguments}")
509        return cls(
510            str(channel),
511            str(address),
512            delta=float(arguments[0])
513        )
@dataclass(frozen=True, slots=True)
class Event:
27@dataclass(frozen=True, slots=True)  # type: ignore
28class Event(metaclass=ABCMeta):
29    """Abstract event class."""
30
31    ADDRESS_PATTERN: ClassVar[str]
32    """str: OSC address pattern matching."""
33
34    channel: str
35    """str: Channel name."""
36
37    address: str
38    """str: Called OSC address."""
39
40    def __post_init__(self) -> None:
41        """Post event initialization.
42
43        Raises:
44            NotImplementedError:
45                If abstract `Event` is instantiated directly.
46
47        """
48        # pylint: disable=unidiomatic-typecheck
49        if type(self) is Event:
50            raise NotImplementedError
51
52    @classmethod
53    @abstractmethod
54    def from_message_data(
55        cls,
56        channel: str,
57        address: str,
58        arguments: tuple[Any, ...]
59    ) -> 'Event':
60        """Abstract event constructor.
61
62        Args:
63            channel (str):
64                Channel name.
65            address (str):
66                OSC address.
67            arguments (tuple[Any, ...]):
68                OSC arguments.
69
70        Returns:
71            Event:
72                Instance.
73
74        Raises:
75            ValueError:
76                If invalid arguments are given.
77
78        """

Abstract event class.

ADDRESS_PATTERN: ClassVar[str]

str: OSC address pattern matching.

channel: str

str: Channel name.

address: str

str: Called OSC address.

@classmethod
@abstractmethod
def from_message_data( cls, channel: str, address: str, arguments: tuple[typing.Any, ...]) -> vmcp.events.Event:
52    @classmethod
53    @abstractmethod
54    def from_message_data(
55        cls,
56        channel: str,
57        address: str,
58        arguments: tuple[Any, ...]
59    ) -> 'Event':
60        """Abstract event constructor.
61
62        Args:
63            channel (str):
64                Channel name.
65            address (str):
66                OSC address.
67            arguments (tuple[Any, ...]):
68                OSC arguments.
69
70        Returns:
71            Event:
72                Instance.
73
74        Raises:
75            ValueError:
76                If invalid arguments are given.
77
78        """

Abstract event constructor.

Args: channel (str): Channel name. address (str): OSC address. arguments (tuple[Any, ...]): OSC arguments.

Returns: Event: Instance.

Raises: ValueError: If invalid arguments are given.

@dataclass(frozen=True, slots=True)
class TransformEvent(Event):
 81@dataclass(frozen=True, slots=True)  # type: ignore
 82class TransformEvent(Event, metaclass=ABCMeta):
 83    """Abstract transform event class."""
 84
 85    joint: str
 86    """str: Joint name."""
 87
 88    position: CoordinateVector
 89    """CoordinateVector: 3D position."""
 90
 91    rotation: Quaternion
 92    """Quaternion: 3D rotation."""
 93
 94    def __post_init__(self) -> None:
 95        """Post event initialization.
 96
 97        Raises:
 98            NotImplementedError:
 99                If abstract `TransformEvent` is instantiated directly.
100
101        """
102        # pylint: disable=unidiomatic-typecheck
103        if type(self) is TransformEvent:
104            raise NotImplementedError

Abstract transform event class.

joint: str

str: Joint name.

CoordinateVector: 3D position.

Quaternion: 3D rotation.

channel: str

str: Channel name.

address: str

str: Called OSC address.

@dataclass(frozen=True, slots=True)
class RootTransformEvent(TransformEvent):
107@dataclass(frozen=True, slots=True)
108class RootTransformEvent(TransformEvent):
109    """Root transform event."""
110
111    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/Root/Pos"
112    """str: OSC address pattern matching."""
113
114    scale: Scale
115    """Scale: Additional scaling (division)."""
116
117    offset: CoordinateVector
118    """CoordinateVector: Additional (negative) offset."""
119
120    @classmethod
121    def from_message_data(
122        cls,
123        channel: str,
124        address: str,
125        arguments: tuple[Any, ...]
126    ) -> 'RootTransformEvent':
127        """Abstract event constructor.
128
129        Args:
130            channel (str):
131                Channel name.
132            address (str):
133                OSC address.
134            arguments (tuple[Any, ...]):
135                OSC arguments.
136
137        Returns:
138            RootTransformEvent:
139                Instance.
140
141        Raises:
142            ValueError:
143                If invalid arguments are given.
144
145        """
146        scale_width: float = 1.0
147        scale_heigth: float = 1.0
148        scale_length: float = 1.0
149        offset_x: float = 0.0
150        offset_y: float = 0.0
151        offset_z: float = 0.0
152        match len(arguments):
153            case 14:
154                # Since VMC protocol specification v2.1.0.
155                scale_width = arguments[8]
156                scale_heigth = arguments[9]
157                scale_length = arguments[10]
158                offset_x = arguments[11]
159                offset_y = arguments[12]
160                offset_z = arguments[13]
161            case 8:
162                # Since VMC protocol specification v2.0.0.
163                pass
164            case _:
165                raise ValueError(f"Invalid arguments: {arguments}")
166        return cls(
167            str(channel),
168            str(address),
169            joint=str(arguments[0]),
170            position=CoordinateVector(
171                x=arguments[1],
172                y=arguments[2],
173                z=arguments[3]
174            ),
175            rotation=Quaternion(
176                x=arguments[4],
177                y=arguments[5],
178                z=arguments[6],
179                w=arguments[7]
180            ),
181            scale=Scale(
182                scale_width,
183                scale_heigth,
184                scale_length
185            ),
186            offset=CoordinateVector(
187                x=offset_x,
188                y=offset_y,
189                z=offset_z
190            )
191        )

Root transform event.

RootTransformEvent( channel: str, address: str, joint: str, position: vmcp.typing.CoordinateVector, rotation: vmcp.typing.Quaternion, scale: vmcp.typing.Scale, offset: vmcp.typing.CoordinateVector)
ADDRESS_PATTERN: ClassVar[str] = '/VMC/Ext/Root/Pos'

str: OSC address pattern matching.

Scale: Additional scaling (division).

CoordinateVector: Additional (negative) offset.

@classmethod
def from_message_data( cls, channel: str, address: str, arguments: tuple[typing.Any, ...]) -> vmcp.events.RootTransformEvent:
120    @classmethod
121    def from_message_data(
122        cls,
123        channel: str,
124        address: str,
125        arguments: tuple[Any, ...]
126    ) -> 'RootTransformEvent':
127        """Abstract event constructor.
128
129        Args:
130            channel (str):
131                Channel name.
132            address (str):
133                OSC address.
134            arguments (tuple[Any, ...]):
135                OSC arguments.
136
137        Returns:
138            RootTransformEvent:
139                Instance.
140
141        Raises:
142            ValueError:
143                If invalid arguments are given.
144
145        """
146        scale_width: float = 1.0
147        scale_heigth: float = 1.0
148        scale_length: float = 1.0
149        offset_x: float = 0.0
150        offset_y: float = 0.0
151        offset_z: float = 0.0
152        match len(arguments):
153            case 14:
154                # Since VMC protocol specification v2.1.0.
155                scale_width = arguments[8]
156                scale_heigth = arguments[9]
157                scale_length = arguments[10]
158                offset_x = arguments[11]
159                offset_y = arguments[12]
160                offset_z = arguments[13]
161            case 8:
162                # Since VMC protocol specification v2.0.0.
163                pass
164            case _:
165                raise ValueError(f"Invalid arguments: {arguments}")
166        return cls(
167            str(channel),
168            str(address),
169            joint=str(arguments[0]),
170            position=CoordinateVector(
171                x=arguments[1],
172                y=arguments[2],
173                z=arguments[3]
174            ),
175            rotation=Quaternion(
176                x=arguments[4],
177                y=arguments[5],
178                z=arguments[6],
179                w=arguments[7]
180            ),
181            scale=Scale(
182                scale_width,
183                scale_heigth,
184                scale_length
185            ),
186            offset=CoordinateVector(
187                x=offset_x,
188                y=offset_y,
189                z=offset_z
190            )
191        )

Abstract event constructor.

Args: channel (str): Channel name. address (str): OSC address. arguments (tuple[Any, ...]): OSC arguments.

Returns: RootTransformEvent: Instance.

Raises: ValueError: If invalid arguments are given.

joint: str

str: Joint name.

CoordinateVector: 3D position.

Quaternion: 3D rotation.

channel: str

str: Channel name.

address: str

str: Called OSC address.

@dataclass(frozen=True, slots=True)
class BoneTransformEvent(TransformEvent):
194@dataclass(frozen=True, slots=True)
195class BoneTransformEvent(TransformEvent):
196    """Bone transform event."""
197
198    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/Bone/Pos"
199    """str: OSC address pattern matching."""
200
201    joint: Bone
202    """Bone: Joint bone."""
203
204    @classmethod
205    def from_message_data(
206        cls,
207        channel: str,
208        address: str,
209        arguments: tuple[Any, ...]
210    ) -> 'BoneTransformEvent':
211        """Abstract event constructor.
212
213        Args:
214            channel (str):
215                Channel name.
216            address (str):
217                OSC address.
218            arguments (tuple[Any, ...]):
219                OSC arguments.
220
221        Returns:
222            BoneTransformEvent:
223                Instance.
224
225        Raises:
226            ValueError:
227                If invalid arguments are given.
228
229        """
230        return cls(
231            str(channel),
232            str(address),
233            joint=Bone(arguments[0]),
234            position=CoordinateVector(
235                x=arguments[1],
236                y=arguments[2],
237                z=arguments[3]
238            ),
239            rotation=Quaternion(
240                x=arguments[4],
241                y=arguments[5],
242                z=arguments[6],
243                w=arguments[7]
244            )
245        )

Bone transform event.

BoneTransformEvent( channel: str, address: str, joint: vmcp.typing.Bone, position: vmcp.typing.CoordinateVector, rotation: vmcp.typing.Quaternion)
ADDRESS_PATTERN: ClassVar[str] = '/VMC/Ext/Bone/Pos'

str: OSC address pattern matching.

Bone: Joint bone.

@classmethod
def from_message_data( cls, channel: str, address: str, arguments: tuple[typing.Any, ...]) -> vmcp.events.BoneTransformEvent:
204    @classmethod
205    def from_message_data(
206        cls,
207        channel: str,
208        address: str,
209        arguments: tuple[Any, ...]
210    ) -> 'BoneTransformEvent':
211        """Abstract event constructor.
212
213        Args:
214            channel (str):
215                Channel name.
216            address (str):
217                OSC address.
218            arguments (tuple[Any, ...]):
219                OSC arguments.
220
221        Returns:
222            BoneTransformEvent:
223                Instance.
224
225        Raises:
226            ValueError:
227                If invalid arguments are given.
228
229        """
230        return cls(
231            str(channel),
232            str(address),
233            joint=Bone(arguments[0]),
234            position=CoordinateVector(
235                x=arguments[1],
236                y=arguments[2],
237                z=arguments[3]
238            ),
239            rotation=Quaternion(
240                x=arguments[4],
241                y=arguments[5],
242                z=arguments[6],
243                w=arguments[7]
244            )
245        )

Abstract event constructor.

Args: channel (str): Channel name. address (str): OSC address. arguments (tuple[Any, ...]): OSC arguments.

Returns: BoneTransformEvent: Instance.

Raises: ValueError: If invalid arguments are given.

CoordinateVector: 3D position.

Quaternion: 3D rotation.

channel: str

str: Channel name.

address: str

str: Called OSC address.

@dataclass(frozen=True, slots=True)
class DeviceTransformEvent(TransformEvent):
248@dataclass(frozen=True, slots=True)
249class DeviceTransformEvent(TransformEvent):
250    """Device transform receiving event."""
251
252    ADDRESS_PATTERN: ClassVar[str] = (
253        f"/VMC/Ext/{{{','.join([t.value for t in DeviceType])}}}//"
254    )
255    """str: OSC address pattern matching."""
256
257    joint: str
258    """str: OpenVR device label or serial."""
259
260    device_type: DeviceType
261    """DeviceType: Device type."""
262
263    is_local: bool
264    """bool: If transform is relative to avatar, word space otherwise."""
265
266    @classmethod
267    def from_message_data(
268        cls,
269        channel: str,
270        address: str,
271        arguments: tuple[Any, ...]
272    ) -> 'DeviceTransformEvent':
273        """Abstract event constructor.
274
275        Args:
276            channel (str):
277                Channel name.
278            address (str):
279                OSC address.
280            arguments (tuple[Any, ...]):
281                OSC arguments.
282
283        Returns:
284            DeviceTransformEvent:
285                Instance.
286
287        Raises:
288            ValueError:
289                If invalid arguments are given.
290
291        """
292        return cls(
293            str(channel),
294            str(address),
295            joint=str(arguments[0]),
296            position=CoordinateVector(
297                x=arguments[1],
298                y=arguments[2],
299                z=arguments[3]
300            ),
301            rotation=Quaternion(
302                x=arguments[4],
303                y=arguments[5],
304                z=arguments[6],
305                w=arguments[7]
306            ),
307            device_type=DeviceType(address.split('/')[3]),
308            is_local=bool(address.count('/') > 4)
309        )

Device transform receiving event.

DeviceTransformEvent( channel: str, address: str, joint: str, position: vmcp.typing.CoordinateVector, rotation: vmcp.typing.Quaternion, device_type: vmcp.typing.DeviceType, is_local: bool)
ADDRESS_PATTERN: ClassVar[str] = '/VMC/Ext/{Hmd,Con,Tra}//'

str: OSC address pattern matching.

joint: str

str: OpenVR device label or serial.

device_type: vmcp.typing.DeviceType

DeviceType: Device type.

is_local: bool

bool: If transform is relative to avatar, word space otherwise.

@classmethod
def from_message_data( cls, channel: str, address: str, arguments: tuple[typing.Any, ...]) -> vmcp.events.DeviceTransformEvent:
266    @classmethod
267    def from_message_data(
268        cls,
269        channel: str,
270        address: str,
271        arguments: tuple[Any, ...]
272    ) -> 'DeviceTransformEvent':
273        """Abstract event constructor.
274
275        Args:
276            channel (str):
277                Channel name.
278            address (str):
279                OSC address.
280            arguments (tuple[Any, ...]):
281                OSC arguments.
282
283        Returns:
284            DeviceTransformEvent:
285                Instance.
286
287        Raises:
288            ValueError:
289                If invalid arguments are given.
290
291        """
292        return cls(
293            str(channel),
294            str(address),
295            joint=str(arguments[0]),
296            position=CoordinateVector(
297                x=arguments[1],
298                y=arguments[2],
299                z=arguments[3]
300            ),
301            rotation=Quaternion(
302                x=arguments[4],
303                y=arguments[5],
304                z=arguments[6],
305                w=arguments[7]
306            ),
307            device_type=DeviceType(address.split('/')[3]),
308            is_local=bool(address.count('/') > 4)
309        )

Abstract event constructor.

Args: channel (str): Channel name. address (str): OSC address. arguments (tuple[Any, ...]): OSC arguments.

Returns: DeviceTransformEvent: Instance.

Raises: ValueError: If invalid arguments are given.

CoordinateVector: 3D position.

Quaternion: 3D rotation.

channel: str

str: Channel name.

address: str

str: Called OSC address.

@dataclass(frozen=True, slots=True)
class BlendShapeEvent(Event):
312@dataclass(frozen=True, slots=True)
313class BlendShapeEvent(Event):
314    """Blend shape event."""
315
316    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/Blend/Val"
317    """str: OSC address pattern matching."""
318
319    key: str
320    """str: Blend shape key."""
321
322    value: float
323    """float: Blend shape value."""
324
325    @classmethod
326    def from_message_data(
327        cls,
328        channel: str,
329        address: str,
330        arguments: tuple[Any, ...]
331    ) -> 'BlendShapeEvent':
332        """Abstract event constructor.
333
334        Args:
335            channel (str):
336                Channel name.
337            address (str):
338                OSC address.
339            arguments (tuple[Any, ...]):
340                OSC arguments.
341
342        Returns:
343            BlendShapeEvent:
344                Instance.
345
346        Raises:
347            ValueError:
348                If invalid arguments are given.
349
350        """
351        return cls(
352            str(channel),
353            str(address),
354            key=arguments[0],
355            value=arguments[1]
356        )

Blend shape event.

BlendShapeEvent(channel: str, address: str, key: str, value: float)
ADDRESS_PATTERN: ClassVar[str] = '/VMC/Ext/Blend/Val'

str: OSC address pattern matching.

key: str

str: Blend shape key.

value: float

float: Blend shape value.

@classmethod
def from_message_data( cls, channel: str, address: str, arguments: tuple[typing.Any, ...]) -> vmcp.events.BlendShapeEvent:
325    @classmethod
326    def from_message_data(
327        cls,
328        channel: str,
329        address: str,
330        arguments: tuple[Any, ...]
331    ) -> 'BlendShapeEvent':
332        """Abstract event constructor.
333
334        Args:
335            channel (str):
336                Channel name.
337            address (str):
338                OSC address.
339            arguments (tuple[Any, ...]):
340                OSC arguments.
341
342        Returns:
343            BlendShapeEvent:
344                Instance.
345
346        Raises:
347            ValueError:
348                If invalid arguments are given.
349
350        """
351        return cls(
352            str(channel),
353            str(address),
354            key=arguments[0],
355            value=arguments[1]
356        )

Abstract event constructor.

Args: channel (str): Channel name. address (str): OSC address. arguments (tuple[Any, ...]): OSC arguments.

Returns: BlendShapeEvent: Instance.

Raises: ValueError: If invalid arguments are given.

channel: str

str: Channel name.

address: str

str: Called OSC address.

@dataclass(frozen=True, slots=True)
class BlendShapeApplyEvent(Event):
359@dataclass(frozen=True, slots=True)
360class BlendShapeApplyEvent(Event):
361    """Blend shape apply event."""
362
363    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/Blend/Apply"
364    """str: OSC address pattern matching."""
365
366    @classmethod
367    def from_message_data(
368        cls,
369        channel: str,
370        address: str,
371        arguments: tuple[Any, ...]
372    ) -> 'BlendShapeApplyEvent':
373        """Abstract event constructor.
374
375        Args:
376            channel (str):
377                Channel name.
378            address (str):
379                OSC address.
380            arguments (tuple[Any, ...]):
381                OSC arguments.
382
383        Returns:
384            BlendShapeApplyEvent:
385                Instance.
386
387        Raises:
388            ValueError:
389                If invalid arguments are given.
390
391        """
392        if len(arguments) != 0:
393            raise ValueError(f"Arguments not empty: {arguments}")
394        return cls(
395            channel,
396            address
397        )

Blend shape apply event.

BlendShapeApplyEvent(channel: str, address: str)
ADDRESS_PATTERN: ClassVar[str] = '/VMC/Ext/Blend/Apply'

str: OSC address pattern matching.

@classmethod
def from_message_data( cls, channel: str, address: str, arguments: tuple[typing.Any, ...]) -> vmcp.events.BlendShapeApplyEvent:
366    @classmethod
367    def from_message_data(
368        cls,
369        channel: str,
370        address: str,
371        arguments: tuple[Any, ...]
372    ) -> 'BlendShapeApplyEvent':
373        """Abstract event constructor.
374
375        Args:
376            channel (str):
377                Channel name.
378            address (str):
379                OSC address.
380            arguments (tuple[Any, ...]):
381                OSC arguments.
382
383        Returns:
384            BlendShapeApplyEvent:
385                Instance.
386
387        Raises:
388            ValueError:
389                If invalid arguments are given.
390
391        """
392        if len(arguments) != 0:
393            raise ValueError(f"Arguments not empty: {arguments}")
394        return cls(
395            channel,
396            address
397        )

Abstract event constructor.

Args: channel (str): Channel name. address (str): OSC address. arguments (tuple[Any, ...]): OSC arguments.

Returns: BlendShapeApplyEvent: Instance.

Raises: ValueError: If invalid arguments are given.

channel: str

str: Channel name.

address: str

str: Called OSC address.

@dataclass(frozen=True, slots=True)
class StateEvent(Event):
400@dataclass(frozen=True, slots=True)
401class StateEvent(Event):
402    """Availability state event."""
403
404    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/OK"
405    """str: OSC address pattern matching."""
406
407    model_state: ModelState
408    """ModelState: Model state."""
409
410    calibration_state: CalibrationState | None
411    """CalibrationState | None: Calibration state."""
412
413    calibration_mode: CalibrationMode | None
414    """CalibrationMode | None: Calibration mode."""
415
416    tracking_state: TrackingState | None
417    """TrackingState | None: Tracking state."""
418
419    @classmethod
420    def from_message_data(
421        cls,
422        channel: str,
423        address: str,
424        arguments: tuple[Any, ...]
425    ) -> 'StateEvent':
426        """Abstract event constructor.
427
428        Args:
429            channel (str):
430                Channel name.
431            address (str):
432                OSC address.
433            arguments (tuple[Any, ...]):
434                OSC arguments.
435
436        Returns:
437            StateEvent:
438                Instance.
439
440        Raises:
441            ValueError:
442                If invalid arguments are given.
443
444        """
445        calibration_state: CalibrationState | None = None
446        calibration_mode: CalibrationMode | None = None
447        tracking_state: TrackingState | None = None
448        match len(arguments):
449            case 4:
450                # Since VMC protocol specification v2.7.0.
451                calibration_state = CalibrationState(arguments[1])
452                calibration_mode = CalibrationMode(arguments[2])
453                tracking_state = TrackingState(arguments[3])
454            case 3:
455                # Since VMC protocol specification v2.5.0.
456                calibration_state = CalibrationState(arguments[1])
457                calibration_mode = CalibrationMode(arguments[2])
458            case 1:
459                pass
460            case _:
461                raise ValueError(f"Invalid arguments: {arguments}")
462        return cls(
463            str(channel),
464            str(address),
465            model_state=ModelState(arguments[0]),
466            calibration_state=calibration_state,
467            calibration_mode=calibration_mode,
468            tracking_state=tracking_state
469        )

Availability state event.

StateEvent( channel: str, address: str, model_state: vmcp.typing.ModelState, calibration_state: vmcp.typing.CalibrationState | None, calibration_mode: vmcp.typing.CalibrationMode | None, tracking_state: vmcp.typing.TrackingState | None)
ADDRESS_PATTERN: ClassVar[str] = '/VMC/Ext/OK'

str: OSC address pattern matching.

model_state: vmcp.typing.ModelState

ModelState: Model state.

calibration_state: vmcp.typing.CalibrationState | None

CalibrationState | None: Calibration state.

calibration_mode: vmcp.typing.CalibrationMode | None

CalibrationMode | None: Calibration mode.

tracking_state: vmcp.typing.TrackingState | None

TrackingState | None: Tracking state.

@classmethod
def from_message_data( cls, channel: str, address: str, arguments: tuple[typing.Any, ...]) -> vmcp.events.StateEvent:
419    @classmethod
420    def from_message_data(
421        cls,
422        channel: str,
423        address: str,
424        arguments: tuple[Any, ...]
425    ) -> 'StateEvent':
426        """Abstract event constructor.
427
428        Args:
429            channel (str):
430                Channel name.
431            address (str):
432                OSC address.
433            arguments (tuple[Any, ...]):
434                OSC arguments.
435
436        Returns:
437            StateEvent:
438                Instance.
439
440        Raises:
441            ValueError:
442                If invalid arguments are given.
443
444        """
445        calibration_state: CalibrationState | None = None
446        calibration_mode: CalibrationMode | None = None
447        tracking_state: TrackingState | None = None
448        match len(arguments):
449            case 4:
450                # Since VMC protocol specification v2.7.0.
451                calibration_state = CalibrationState(arguments[1])
452                calibration_mode = CalibrationMode(arguments[2])
453                tracking_state = TrackingState(arguments[3])
454            case 3:
455                # Since VMC protocol specification v2.5.0.
456                calibration_state = CalibrationState(arguments[1])
457                calibration_mode = CalibrationMode(arguments[2])
458            case 1:
459                pass
460            case _:
461                raise ValueError(f"Invalid arguments: {arguments}")
462        return cls(
463            str(channel),
464            str(address),
465            model_state=ModelState(arguments[0]),
466            calibration_state=calibration_state,
467            calibration_mode=calibration_mode,
468            tracking_state=tracking_state
469        )

Abstract event constructor.

Args: channel (str): Channel name. address (str): OSC address. arguments (tuple[Any, ...]): OSC arguments.

Returns: StateEvent: Instance.

Raises: ValueError: If invalid arguments are given.

channel: str

str: Channel name.

address: str

str: Called OSC address.

@dataclass(frozen=True, slots=True)
class RelativeTimeEvent(Event):
472@dataclass(frozen=True, slots=True)
473class RelativeTimeEvent(Event):
474    """Relative time event."""
475
476    ADDRESS_PATTERN: ClassVar[str] = "/VMC/Ext/T"
477    """str: OSC address pattern matching."""
478
479    delta: float
480    """float: Relative frame time."""
481
482    @classmethod
483    def from_message_data(
484        cls,
485        channel: str,
486        address: str,
487        arguments: tuple[Any, ...]
488    ) -> 'RelativeTimeEvent':
489        """Abstract event constructor.
490
491        Args:
492            channel (str):
493                Channel name.
494            address (str):
495                OSC address.
496            arguments (tuple[Any, ...]):
497                OSC arguments.
498
499        Returns:
500            RelativeTimeEvent:
501                Instance.
502
503        Raises:
504            ValueError:
505                If invalid arguments are given.
506
507        """
508        if len(arguments) != 1:
509            raise ValueError(f"Invalid arguments: {arguments}")
510        return cls(
511            str(channel),
512            str(address),
513            delta=float(arguments[0])
514        )

Relative time event.

RelativeTimeEvent(channel: str, address: str, delta: float)
ADDRESS_PATTERN: ClassVar[str] = '/VMC/Ext/T'

str: OSC address pattern matching.

delta: float

float: Relative frame time.

@classmethod
def from_message_data( cls, channel: str, address: str, arguments: tuple[typing.Any, ...]) -> vmcp.events.RelativeTimeEvent:
482    @classmethod
483    def from_message_data(
484        cls,
485        channel: str,
486        address: str,
487        arguments: tuple[Any, ...]
488    ) -> 'RelativeTimeEvent':
489        """Abstract event constructor.
490
491        Args:
492            channel (str):
493                Channel name.
494            address (str):
495                OSC address.
496            arguments (tuple[Any, ...]):
497                OSC arguments.
498
499        Returns:
500            RelativeTimeEvent:
501                Instance.
502
503        Raises:
504            ValueError:
505                If invalid arguments are given.
506
507        """
508        if len(arguments) != 1:
509            raise ValueError(f"Invalid arguments: {arguments}")
510        return cls(
511            str(channel),
512            str(address),
513            delta=float(arguments[0])
514        )

Abstract event constructor.

Args: channel (str): Channel name. address (str): OSC address. arguments (tuple[Any, ...]): OSC arguments.

Returns: RelativeTimeEvent: Instance.

Raises: ValueError: If invalid arguments are given.

channel: str

str: Channel name.

address: str

str: Called OSC address.