Canal de simulation
Vue d'ensemble
Le canal de simulation est un canal WebSocket bidirectionnel à haute fréquence utilisé pour échanger des informations sur l'état des appareils et envoyer des commandes de session ou des commandes aux appareils.
URL par défaut : ws://localhost:10001
Le port peut être modifié dans la configuration.
Contrat principal (important) :
- Lorsqu'un client se connecte, le service envoie un message d'inventaire initial contenant la liste complète des appareils.
- Ensuite, le serveur envoie exactement un message de mise à jour d'état pour chaque message reçu du client.
- Chaque rapport d'état contient l'état (et le statut) de tous les appareils.
Cette page présente le protocole et les commandes de base du canal de simulation.
Des modules ou fonctionnalités supplémentaires peuvent s'enregistrer et étendre le système en y ajoutant leurs propres commandes et/ou champs d'état. Ceux-ci sont décrits séparément dans la section consacrée aux modules.
Règles de protocole
Une réponse par message client
Le service envoie un message « State Update » contenant l'état de tous les appareils pour chaque message reçu du client.
Cela signifie :
- Si vous avez besoin d'un instantané récent de l'état de l'appareil, vous devez envoyer une commande (une commande de session, une commande de sondage ou une commande de périphérique).
- Le canal fonctionne comme une boucle « tick » déclenchée par les messages du client.
Interrogation sans application de forces (commandes de sondage)
Si vous souhaitez observer les changements d'état sans appliquer de forces ni modifier les paramètres de simulation, utilisez les commandes de sondage :
probe_positionpourinverse3probe_orientationpour les poignées Verse
Les commandes de sondage ne contiennent aucune donnée de commande et se contentent de déclencher des requêtes d'informations sur l'appareil, afin que les données de position et d'orientation soient à jour lors de la prochaine mise à jour d'état.
Les sessions de simulation qui envoient déjà des commandes de contrôle (set_cursor_force, set_cursor_position, etc.) font pas Il n'est pas nécessaire d'envoyer des commandes de sondage : l'état de l'appareil est toujours inclus dans la trame de réponse. Les sondages sont destinés à surveillance uniquement sessions (par exemple, Haply ) qui doivent interroger l'état d'un appareil sans le commander.
Configuration vs état
- Les Inventaire initial le message inclut l'appareil
config,stateetstatus. - Régulier Le point sur la situation les messages incluent l'appareil
stateetstatus.
Si vous avez à nouveau besoin d'un instantané incluant la configuration, utilisez session.force_render_full_state.
configure contre commands
Les messages de périphérique prennent en charge deux tables distinctes : configure et commands. Ils ont des fonctions différentes :
| Carte | Objectif | Fréquence |
|---|---|---|
configure | Configuration persistante en une seule opération : préréglage, base, montage, filtres, paramètres du module | Envoyer une seule fois ou en cas de modification |
commands | Par pas, non persistant : force, position, couples | Doit être envoyé à chaque tick |
Entrées dans commands (set_cursor_force, set_cursor_position, set_angular_torques, set_angular_position) sont appliquées une fois par tick et n'est pas enregistrée -- si vous cessez de leur envoyer des données, l'effet s'arrête immédiatement et l'appareil revient en mode veille. Utilisez configure pour tout ce qui doit être conservé d'un cycle à l'autre sans être renvoyé.
set_transform est un cas particulierset_transform vit sous commands mais est persistant — le service conserve
la dernière transformation que vous avez envoyée jusqu'à ce que vous en envoyiez une nouvelle. Vous n'avez pas besoin avoir pour l'envoyer
à chaque tick. Cela dit, comme son objectif est la navigation dans la caméra ou la scène,
sa diffusion au rythme des ticks (par exemple, une transformation par image lorsque l'utilisateur effectue un panoramique dans la
scène) correspond au mode d'utilisation attendu pour une navigation en mouvement continu.
Systèmes de coordonnées
Haply par défaut un système de coordonnées droitier avec l'axe Z vers le haut.
Deux entrées de configuration influent sur la manière dont les coordonnées sont interprétées et renvoyées :
session.configure.basis: modifie la correspondance entre Haply et le système de coordonnées de votre application (voir Base ci-dessous).inverse3[*].configure.preset: sélectionne une configuration d'usine nommée qui définit l'origine de l'espace de travail (par exemplearm_front_centeredmet(0, 0, 0)(au centre de l'espace de travail). Reportez-vous à la section « Configuration » de chaque appareil.
Formats de messages
Cette section décrit les grandes lignes des types d'enveloppes et de messages. Des exemples complets sont fournis plus loin dans ce document.
Groupes de périphériques
Les messages sont regroupés par type d'appareil au niveau supérieur (par exemple inverse3, verse_grip, wireless_verse_grip). Chaque clé de type d'appareil correspond à un tableau d'objets spécifiques à chaque appareil.
Inventaire initial (serveur → client)
Envoyé immédiatement après l'établissement d'une connexion WebSocket.
Chaque entrée comprend :
device_idconfigstatestatus
Voir : Exemple : Charge utile de l'inventaire initial
Mise à jour de l'état (serveur → client)
Envoyé une fois pour chaque message client.
Chaque entrée comprend :
device_idstatestatus
Voir : Exemple : Charge utile de mise à jour d'état
Enveloppe de commande de session (client → serveur)
Les commandes de session sont des actions qui s'appliquent à la connexion ou à la session en cours et ne sont pas spécifiques à un périphérique.
{
"session": {
"<command_name>": {
"...": "..."
}
}
}
Enveloppe de commande de périphérique (client → serveur)
Les commandes destinées aux périphériques sont envoyées sous la clé du type de périphérique sous forme de tableau, ce qui permet d'envoyer des commandes à un ou plusieurs périphériques dans un seul message. Chaque entrée de périphérique prend en charge à la fois un configure carte et un commands carte.
{
"<device_type>": [
{
"device_id": "<id>",
"configure": {
"<config_key>": { "...": "..." }
},
"commands": {
"<command_name>": { "...": "..." }
}
}
]
}
Vous pouvez inclure plusieurs entrées dans un même message afin de commander plusieurs appareils du même type.
Notez que commands Il s'agit d'un dictionnaire pouvant contenir plusieurs commandes pour un périphérique donné, mais une seule par type de commande.
Les execute champ
Chaque commande ou option de configuration accepte un "execute" booléen (par défaut true). Définir "execute": false pour que l'entrée soit analysée mais non appliquée. Cela s'avère utile pour les sérialiseurs basés sur la réflexion (par exemple, Unity JsonUtility) qui transmettent systématiquement toutes les données.
"set_cursor_force": { "execute": false, "vector": { "x": 0.0, "y": 0.0, "z": 0.0 } }
Référence des commandes
Session
Forcer le rendu de l'état complet
Demandez un aperçu de tous les états et configurations des appareils.
{
"session": {
"force_render_full_state": {}
}
}
Configuration de la session
La configuration persistante au niveau de la session est transmise via session.configure. Cela correspond au niveau de l'appareil configure motif de carte.
Profil
Définit le nom du profil de session — utilisé par Haply pour identifier la simulation et enregistrer les réglages spécifiques à chaque application sur l'appareil.
{
"session": {
"configure": {
"profile": {
"name": "my_profile"
}
}
}
}
Base (au niveau de la session)
Définit le mappage de base pour l'ensemble de la session. Le mappage de base décrit la manière dont Haply sont converties dans le système de coordonnées de votre application ; une fois défini, tous les états des périphériques sont renvoyés dans ce système de coordonnées et toutes les valeurs que vous envoyez y sont interprétées.
La chaîne de permutation est exprimée par rapport au système de coordonnées Haply(droite / Z vers le haut) — une permutation de X, Y, Z, éventuellement précédé de
+ ou -. Exemples :
XYZ,ZYX,+Y-Z+X,X-ZYYZXsignifie que tonYHaply a peut-être raison, tonZest l'attaquant Haply, votreXC'est au tour Haply.
Exemple de Z-up pour gauchers (Unreal, X-YZ) :
{
"session": {
"configure": {
"basis": { "permutation": "X-YZ" }
}
}
}
session.set_basisLa convention relative aux signes des axes a changé entre l'ancienne version session.set_basis et
session.configure.basis. Une permutation qui fonctionnait avec l'ancienne commande
peut produire une transformation inversée avec la nouvelle — inversez les signes des axes
si nécessaire (par exemple X-ZY devient XZ-Y).
Toutes les commandes des appareils
Test (tous les appareils)
Utilisez des commandes de sondage pour obtenir des informations actualisées sur la position et l'orientation sans appliquer de forces ni apporter d'autres modifications à la simulation.
- inverse3:
probe_position - Poignées Verse (
verse_grip,wireless_verse_grip) :probe_orientation
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"probe_position": {}
}
}
],
"verse_grip": [
{
"device_id": "049D",
"commands": {
"probe_orientation": {}
}
}
],
"wireless_verse_grip": [
{
"device_id": "049D",
"commands": {
"probe_orientation": {}
}
}
]
}
Définir la transformation (tous les appareils)
Définir la transformation de l'espace de travail (de l'espace de l'appareil à l'espace de l'application) pour un appareil.
Contrairement aux autres commands entrées, set_transform est persistant — le
service conserve la dernière valeur que vous avez envoyée jusqu’à ce que vous en envoyiez une nouvelle. Vous n’avez pas besoin
de la transmettre à chaque tick, mais comme elle sert à la navigation dans la caméra ou la scène,
la diffuser à la fréquence des ticks correspond au mode d’utilisation prévu pour un mouvement continu
(par exemple, une transformation par image lorsque l’utilisateur effectue un panoramique dans la scène).
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_transform": {
"transform": {
"position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
}
}
}
}
]
}
Pour les poignées Verse et les poignées Verse sans fil, seule la rotation composante
de la transformation a un effet — la poignée indique uniquement l'orientation, il
n'y a donc rien à position ou scale mettre à l'échelle ou transposer. Le position et
scale Ces champs sont acceptés (et répercutés dans l'instantané) uniquement pour assurer la cohérence du schéma
avec Inverse3; ils sont laissés à leurs valeurs par défaut
(position = {0,0,0}, scale = {1,1,1}) est la pratique courante.
Configuration Inverse3
Les entrées de configuration sont envoyées dans le configure carte. Elles sont persistantes : elles sont envoyées une seule fois ou en cas de modification.
Préréglage
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"preset": { "preset": "arm_front_centered" }
}
}
]
}
Valeurs disponibles : device_defaults, arm_front, arm_front_centered, led_front, led_front_centered, custom.
Base (au niveau de l'appareil)
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"basis": { "permutation": "ZXY" }
}
}
]
}
Mont
Définit la transformation de montage du périphérique. Remarque : transform à l'intérieur de l'emballage mount.
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"mount": {
"transform": {
"position": { "x": 0.0, "y": 0.0, "z": 0.0 },
"rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 },
"scale": { "x": 1.0, "y": 1.0, "z": 1.0 }
}
}
}
}
]
}
Les commande côté (client vers serveur) encapsule la transformation : "mount": { "transform": { ... } }.
Le instantané du côté (serveur vers client) est plat : "mount": { "position": {...}, "rotation": {...}, "scale": {...} }.
C'est voulu : les commandes utilisent un conteneur unifié, tandis que les instantanés sérialisent directement la transformation. Veillez à ne pas copier la forme de l'instantané dans le corps d'une commande.
Amortissement
Permet de contrôler l'amortissement uniforme et/ou directionnel. Au moins un champ doit être renseigné.
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"damping": { "scalar": 0.5 }
}
}
]
}
Vous pouvez également régler l'amortissement directionnel, ou les deux à la fois :
"damping": { "scalar": 0.5, "vector": { "x": 0.0, "y": 1.0, "z": 0.0 } }
Force Gate
Définit le gain d'atténuation de la porte de force (de 0,0 à 1,0).
{
"inverse3": [
{
"device_id": "049D",
"configure": {
"force_gate": { "gain": 0.3 }
}
}
]
}
Inverse3
Pour envoyer des commandes à un inverse3 appareil, inclure une entrée correspondant à device_id sous le inverse3 touche. Les commandes s'appliquent à chaque tick et doivent être renvoyées à chaque tick pour rester actives.
Commander un appareil :
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Commandez plusieurs appareils en un seul message :
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
},
{
"device_id": "049E",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Définir la position du curseur
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_position": {
"position": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Définir la force du curseur
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_cursor_force": {
"vector": {
"x": 1.0,
"y": 2.0,
"z": 3.0
}
}
}
}
]
}
Définir la position angulaire
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_position": {
"angles": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
Définir les couples angulaires
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_angular_torques": {
"torques": {
"a0": 1.0,
"a1": 2.0,
"a2": 3.0
}
}
}
}
]
}
Configuration de Verse Grip
Les appareils Verse Grip et Wireless Verse Grip prennent en charge les mêmes fonctionnalités configure les clés comme Inverse3 preset, basiset mount.
Poignée Verse prolongée
Les set_extension_data Cette commande fait partie du protocole étendu pour les poignées Verse ; elle est utilisée avec les versions de poignées qui prennent en charge le protocole de communication d'extension de la carte.
Données sur l'extension de la poignée
Longueurs de données prises en charge :
- Jusqu'à 20 octets en amont (client → appareil).
- Jusqu'à 12 octets en sortie (appareil → client), renvoyés dans le message « State Update » sous la forme
state.extension_data.
Spécifications des données :
- Longueur du tableau : 20 octets
- Plage de valeurs : chaque valeur est comprise entre 0 et 255
{
"wireless_verse_grip": [
{
"device_id": "049D",
"commands": {
"set_extension_data": {
"extension_data": [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
]
}
}
}
]
}
Exemples
Exemple : charge utile de l'inventaire initial
Le service envoie un message contenant la liste complète des appareils lorsqu'une connexion WebSocket est établie. Le message initial contient les éléments suivants : JSON format :
{
"inverse3": [
{
"device_id": "04BA",
"config": {
"type": "inverse3",
"port": "COM13",
"device_info": {
"major_version": 7,
"minor_version": 1,
"id": "04BA",
"device_type": 4,
"uuid": "2D35F80DD9005F599B68F49944CB04BA"
},
"extended_device_id": "2D35F80DD9005F599B68F49944CB04BA",
"extended_firmware_version": "8C20FDC8010AA1E15AA133CDA2534874",
"gravity_compensation": {
"enabled": true,
"scaling_factor": 1
},
"handedness": "right",
"streaming_mode": "USB",
"torque_scaling": {
"enabled": true
},
"home_return": {
"enabled": false
},
"filters": {
"force_gate": { "gain": 0.3 },
"damping": { "scalar": 0 }
},
"preset": "defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"angular_position": {
"x": -69.31704,
"y": 137.62952,
"z": 19.832787
},
"angular_velocity": {
"x": 0,
"y": 0,
"z": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"current_cursor_force": { "x": 0, "y": 0, "z": 0 },
"current_cursor_position": { "x": 0, "y": 0, "z": 0 },
"current_angular_torques": { "x": 0, "y": 0, "z": 0 },
"current_angular_position": { "x": 0, "y": 0, "z": 0 },
"control_domain": "cartesian",
"control_mode": "force",
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"config": {
"type": "verse_grip",
"port": "COM3",
"preset": "device_defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"config": {
"type": "wireless_verse_grip",
"port": "COM6",
"major_version": 1,
"minor_version": 4,
"hardware_version": 1,
"streaming_mode": "Radio",
"preset": "device_defaults",
"basis": { "permutation": "XYZ" },
"mount": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
}
},
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"connected": true,
"awake": true,
"ready": true
}
}
]
}
Exemple : charge utile de mise à jour d'état
Pour chaque message reçu, le service enverra un message de mise à jour de l'état contenant l'état de tous les appareils.
Si vous souhaitez connaître l'état de la machine, vous devez lui envoyer un message au préalable (par exemple, une commande de sondage ou une valeur de force, même si ces valeurs sont égales à zéro). Ceci est particulièrement important lorsque vous utilisez des dispositifs comme sources d'entrée (par exemple, la position de suivi) sans appliquer de forces.
Le message de mise à jour de l'état contient les informations suivantes JSON format :
{
"inverse3": [
{
"device_id": "04BA",
"state": {
"cursor_position": {
"x": 0.07842738,
"y": -0.14836666,
"z": 0.14297646
},
"cursor_velocity": {
"x": -0.011969013,
"y": 0.0012009288,
"z": -0.043197
},
"angular_position": {
"x": -69.31704,
"y": 137.62952,
"z": 19.832787
},
"angular_velocity": {
"x": 0,
"y": 0,
"z": 0
},
"body_orientation": {
"x": -0.01940918,
"y": 0.7026367,
"z": 0.00048828125,
"w": 0.7113037
},
"current_cursor_force": { "x": 0, "y": 0, "z": 0 },
"current_cursor_position": { "x": 0, "y": 0, "z": 0 },
"current_angular_torques": { "x": 0, "y": 0, "z": 0 },
"current_angular_position": { "x": 0, "y": 0, "z": 0 },
"control_domain": "cartesian",
"control_mode": "force",
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"calibrated": false,
"in_use": false,
"power_supply": true,
"ready": true,
"started": true
}
}
],
"verse_grip": [
{
"device_id": "61548",
"state": {
"button": false,
"hall": 0,
"orientation": {
"x": -0.5019531,
"y": 0.8632202,
"z": -0.048095703,
"w": -0.022338867
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"error": 0,
"ready": true
}
}
],
"wireless_verse_grip": [
{
"device_id": "0",
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"connected": true,
"awake": true,
"ready": true
}
}
],
"custom_verse_grip": [
{
"device_id": "0",
"state": {
"battery_level": 0.816,
"battery_voltage": 3.77,
"buttons": {
"a": false,
"b": false,
"c": false
},
"hall": 16,
"orientation": {
"x": -0.019866943,
"y": -0.017486572,
"z": 0.05508423,
"w": -0.9963989
},
"extension_data": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"transform": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 1, "y": 1, "z": 1 }
},
"transform_velocity": {
"position": { "x": 0, "y": 0, "z": 0 },
"rotation": { "x": 0, "y": 0, "z": 0, "w": 1 },
"scale": { "x": 0, "y": 0, "z": 0 }
}
},
"status": {
"ready": true
}
}
]
}