Source code for simvx.core.nodes_3d.remote_transform

"""RemoteTransform3D -- pushes transform to a remote node."""

from __future__ import annotations

import logging

from ..descriptors import Property
from ..math.types import Quat, Vec3
from .node3d import Node3D

log = logging.getLogger(__name__)


[docs] class RemoteTransform3D(Node3D): """Pushes this node's transform to a remote Node3D each frame. Useful for making a node follow another without parenting, or for synchronising transforms across different branches of the scene tree. Set ``remote_path`` to a node path (e.g. ``'../Camera3D'``). Each frame the resolved node's position/rotation/scale are overwritten with this node's values, according to the ``update_*`` flags. """ remote_path = Property("", hint="Path to the target node (e.g. '../Camera3D')") update_position = Property(True, hint="Push position to remote node") update_rotation = Property(True, hint="Push rotation to remote node") update_scale = Property(False, hint="Push scale to remote node") use_global_coordinates = Property(True, hint="Use global transforms instead of local") def __init__(self, **kwargs): super().__init__(**kwargs) self._cached_remote: Node3D | None = None self._cached_path: str = "" def _resolve_remote(self) -> Node3D | None: """Resolve remote_path to a Node3D, caching the result.""" path = self.remote_path if not path: self._cached_remote = None self._cached_path = "" return None if path == self._cached_path and self._cached_remote is not None: if self._cached_remote._tree is not None: return self._cached_remote try: node = self.get_node(path) except (ValueError, KeyError, AttributeError): if path != self._cached_path: log.warning("RemoteTransform3D '%s': invalid remote_path '%s'", self.name, path) self._cached_remote = None self._cached_path = path return None if not isinstance(node, Node3D): if path != self._cached_path: log.warning("RemoteTransform3D '%s': target '%s' is not a Node3D", self.name, path) self._cached_remote = None self._cached_path = path return None self._cached_remote = node self._cached_path = path return node
[docs] def process(self, dt: float): remote = self._resolve_remote() if remote is None: return if self.use_global_coordinates: if self.update_position: remote.world_position = self.world_position if self.update_rotation: remote.world_rotation = self.world_rotation if self.update_scale: # Node3D has no world_scale setter -- compute local scale if remote.parent and isinstance(remote.parent, Node3D): ps = remote.parent.world_scale remote.scale = Vec3( self.world_scale.x / ps.x if ps.x != 0 else 1.0, self.world_scale.y / ps.y if ps.y != 0 else 1.0, self.world_scale.z / ps.z if ps.z != 0 else 1.0, ) else: remote.scale = Vec3(self.world_scale) else: if self.update_position: remote.position = Vec3(self.position) if self.update_rotation: remote.rotation = Quat(self.rotation) if self.update_scale: remote.scale = Vec3(self.scale)