vantagekit 0.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,95 @@
1
+ """A small badge over the viewport, top left, naming the camera view.
2
+
3
+ Drawn into the active viewport window's own overlay frame (ViewportWindow.get_frame),
4
+ so it moves and resizes with the viewport. The frame gets a build function (the pattern
5
+ Kit's own overlays use), so Kit can rebuild it at any time, including after an extension
6
+ reload hands the same named frame to a new instance. Every element lets mouse events
7
+ through, so the badge never blocks picking or navigation.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import carb
13
+ import omni.ui as ui
14
+
15
+ # ABGR colors
16
+ COLORS = {
17
+ "free": 0xFFBBBBBB,
18
+ "chase": 0xFF40D0FF,
19
+ "pov": 0xFF60FF90,
20
+ "orbit": 0xFFFFB050,
21
+ }
22
+ NAMES = {"free": "FREE", "chase": "CHASE", "pov": "POINT OF VIEW", "orbit": "ORBIT"}
23
+ BACKGROUND = 0x5A000000 # about 35 percent black: the scene shows through
24
+
25
+
26
+ class ViewOverlay:
27
+ def __init__(self, name: str = "vantagekit.view"):
28
+ self._frame = None
29
+ self._label = None
30
+ self._hint = None
31
+ self._state = ("free", "", "")
32
+ try:
33
+ from omni.kit.viewport.utility import get_active_viewport_window
34
+
35
+ win = get_active_viewport_window()
36
+ self._frame = win.get_frame(name) if win is not None else None
37
+ except Exception as e:
38
+ carb.log_warn(f"[vantagekit] no viewport overlay: {e}")
39
+ if self._frame is None:
40
+ return
41
+ self._frame.set_build_fn(self._build)
42
+ self._frame.rebuild()
43
+
44
+ @property
45
+ def ready(self) -> bool:
46
+ return self._frame is not None
47
+
48
+ def _build(self) -> None:
49
+ mode, title, hint = self._state
50
+ with ui.VStack(opaque_for_mouse_events=False):
51
+ ui.Spacer(height=36, opaque_for_mouse_events=False)
52
+ with ui.HStack(height=0, opaque_for_mouse_events=False):
53
+ ui.Spacer(width=16, opaque_for_mouse_events=False)
54
+ with ui.ZStack(width=0, height=0, opaque_for_mouse_events=False):
55
+ ui.Rectangle(style={"background_color": BACKGROUND, "border_radius": 4})
56
+ with ui.VStack(width=0, height=0, opaque_for_mouse_events=False):
57
+ with ui.HStack(height=0, opaque_for_mouse_events=False):
58
+ ui.Spacer(width=10, opaque_for_mouse_events=False)
59
+ self._label = ui.Label(title, width=0, style=self._title_style(mode))
60
+ ui.Spacer(width=10, opaque_for_mouse_events=False)
61
+ with ui.HStack(height=0, opaque_for_mouse_events=False):
62
+ ui.Spacer(width=10, opaque_for_mouse_events=False)
63
+ self._hint = ui.Label(
64
+ hint, width=0, visible=bool(hint), style={"font_size": 13, "color": 0xFFCCCCCC}
65
+ )
66
+ ui.Spacer(width=10, opaque_for_mouse_events=False)
67
+ ui.Spacer(height=4, opaque_for_mouse_events=False)
68
+ ui.Spacer(opaque_for_mouse_events=False)
69
+ ui.Spacer(opaque_for_mouse_events=False)
70
+
71
+ @staticmethod
72
+ def _title_style(mode: str) -> dict:
73
+ return {"font_size": 22, "color": COLORS.get(mode, 0xFFFFFFFF)}
74
+
75
+ def show(self, mode: str, title: str, hint: str = "") -> None:
76
+ """Update the badge; cheap to call every frame (it only changes on a new value)."""
77
+ state = (mode, title, hint)
78
+ if state == self._state and self._label is not None:
79
+ return
80
+ self._state = state
81
+ if self._label is None or self._hint is None:
82
+ if self._frame is not None:
83
+ self._frame.rebuild()
84
+ return
85
+ self._label.text = title
86
+ self._label.style = self._title_style(mode)
87
+ self._hint.text = hint
88
+ self._hint.visible = bool(hint)
89
+
90
+ def destroy(self) -> None:
91
+ if self._frame is not None:
92
+ # detach our build function so a later rebuild cannot call into this instance
93
+ self._frame.set_build_fn(lambda: None)
94
+ self._frame.clear()
95
+ self._frame = self._label = self._hint = None
vantagekit/kit/rig.py ADDED
@@ -0,0 +1,346 @@
1
+ """CameraRig: drives one USD camera in free, chase, point-of-view and orbit modes.
2
+
3
+ The rig reads the stage up axis and meters per unit, so the same settings (meters and
4
+ seconds) work on Y-up and Z-up stages in any unit. In free mode it writes nothing and Kit's
5
+ native navigation moves the camera; in the attached modes it writes one cached
6
+ xformOp:transform per frame and the camera is locked against native navigation
7
+ (NativeNavigation). Mode changes are the only time settings or op order change.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import math
13
+ from dataclasses import dataclass
14
+ from typing import Optional, Sequence, Tuple
15
+
16
+ import carb
17
+ import omni.usd
18
+ from pxr import Gf, Usd, UsdGeom
19
+
20
+ from ..core.frame import UpFrame, Vec3, add, scale
21
+ from ..core.rig_math import (
22
+ chase_orbit_view,
23
+ chase_perspective,
24
+ look_at_matrix,
25
+ orbit_eye,
26
+ orbit_from_eye,
27
+ pov_view,
28
+ )
29
+ from ..core.smoothing import Smoothed3, SmoothedAngle, wrap_angle
30
+ from ..core.targets import TargetCycler
31
+ from .native_nav import NativeNavigation
32
+
33
+ MODES = ("free", "chase", "pov", "orbit")
34
+
35
+
36
+ @dataclass
37
+ class RigSettings:
38
+ views: Tuple[str, ...] = ("chase", "pov", "orbit")
39
+ body_forward: Optional[Sequence[float]] = None # target's forward axis; None: +Z (Y up), +X (Z up)
40
+ # chase: the default perspective, behind and above, horizon level. The camera looks
41
+ # at the pivot chase_look_up_m above the target; orbit turns around the same pivot,
42
+ # and leaving orbit for chase keeps the perspective set there.
43
+ chase_back_m: float = 12.5
44
+ chase_up_m: float = 1.8
45
+ chase_look_up_m: float = 1.8
46
+ chase_tau_target_s: float = 0.05
47
+ chase_tau_heading_s: float = 0.35
48
+ # point of view: fixed in the target's body frame
49
+ pov_forward_m: float = 6.0
50
+ pov_up_m: float = 2.5
51
+ pov_right_m: float = 0.0
52
+ # orbit
53
+ orbit_tau_target_s: float = 0.15
54
+ orbit_range_m: float = 40.0
55
+ orbit_min_m: float = 2.0
56
+ orbit_max_m: float = 20000.0
57
+ orbit_elevation_deg: float = 20.0
58
+ orbit_min_elevation_deg: float = -10.0
59
+ orbit_max_elevation_deg: float = 85.0
60
+
61
+
62
+ class CameraRig:
63
+ def __init__(
64
+ self,
65
+ camera_path: str,
66
+ targets: TargetCycler,
67
+ settings: RigSettings | None = None,
68
+ native: NativeNavigation | None = None,
69
+ usd_context=None,
70
+ name: str = "vantagekit",
71
+ ):
72
+ self.camera_path = str(camera_path)
73
+ self.targets = targets
74
+ self.settings = settings or RigSettings()
75
+ self.native = native or NativeNavigation()
76
+ self.name = name
77
+ self._ctx = usd_context or omni.usd.get_context()
78
+ self.mode = "free"
79
+ self.view = self.settings.views[0]
80
+ self.frame = UpFrame("Y")
81
+ self._op = None
82
+ self._target = Smoothed3(self.settings.chase_tau_target_s)
83
+ self._heading = SmoothedAngle(self.settings.chase_tau_heading_s)
84
+ self._last_heading = 0.0
85
+ self.orbit_yaw = 0.0
86
+ self.orbit_elevation = math.radians(self.settings.orbit_elevation_deg)
87
+ self.orbit_range_m = self.settings.orbit_range_m
88
+ # Chase perspective, relative to the target's heading: (yaw, elevation, meters).
89
+ # Leaving orbit for chase keeps the perspective set in orbit; home() resets it.
90
+ self.chase_yaw_rel, self.chase_elevation, self.chase_range_m = self.default_chase()
91
+ self.home_view: Optional[Tuple[Vec3, Vec3]] = None # free-mode home (eye, look)
92
+
93
+ def default_chase(self) -> Tuple[float, float, float]:
94
+ s = self.settings
95
+ return chase_perspective(s.chase_back_m, s.chase_up_m, s.chase_look_up_m)
96
+
97
+ # stage ----------------------------------------------------------------------
98
+ @property
99
+ def stage(self) -> Usd.Stage | None:
100
+ return self._ctx.get_stage()
101
+
102
+ def refresh_frame(self) -> UpFrame:
103
+ stage = self.stage
104
+ if stage is not None:
105
+ self.frame = UpFrame(
106
+ str(UsdGeom.GetStageUpAxis(stage)),
107
+ UsdGeom.GetStageMetersPerUnit(stage),
108
+ self.settings.body_forward,
109
+ )
110
+ return self.frame
111
+
112
+ def reset_stage(self) -> None:
113
+ """Call after a stage opens: drops cached ops and returns to free mode."""
114
+ self._op = None
115
+ self.mode = "free"
116
+ self.native.reset()
117
+ self.refresh_frame()
118
+ self.native.set_native(True, self.stage, self.camera_path)
119
+
120
+ def _prim(self):
121
+ stage = self.stage
122
+ return stage.GetPrimAtPath(self.camera_path) if stage else None
123
+
124
+ def eye(self) -> Vec3 | None:
125
+ prim = self._prim()
126
+ if not prim or not prim.IsValid():
127
+ return None
128
+ t = UsdGeom.Xformable(prim).ComputeLocalToWorldTransform(Usd.TimeCode.Default()).ExtractTranslation()
129
+ return (t[0], t[1], t[2])
130
+
131
+ # modes ----------------------------------------------------------------------
132
+ @property
133
+ def attached(self) -> bool:
134
+ return self.mode != "free"
135
+
136
+ def set_mode(self, mode: str) -> str:
137
+ if mode not in MODES:
138
+ raise ValueError(f"mode must be one of {MODES}, not {mode!r}")
139
+ if mode == self.mode:
140
+ return self.mode
141
+ stage = self.stage
142
+ if mode != "free":
143
+ prim = self._prim()
144
+ if not prim or not prim.IsValid():
145
+ carb.log_warn(f"[{self.name}] no camera at {self.camera_path}; staying in {self.mode}")
146
+ return self.mode
147
+ if self.targets.ensure(near=self.eye()) is None:
148
+ carb.log_warn(f"[{self.name}] no target to attach to; staying in {self.mode}")
149
+ return self.mode
150
+ self.refresh_frame()
151
+ if self.mode == "free":
152
+ self._prepare_op(prim)
153
+ self._activate_camera()
154
+ self.view = mode
155
+ self._enter(mode, self.mode)
156
+ self.mode = mode
157
+ self.native.set_native(mode == "free", stage, self.camera_path)
158
+ carb.log_info(f"[{self.name}] camera mode {mode} (target {self.targets.selected})")
159
+ return self.mode
160
+
161
+ def step_target(self, direction: int):
162
+ return self.targets.step(direction)
163
+
164
+ def _pivot(self, target: Sequence[float]) -> Vec3:
165
+ """Chase and orbit both turn around this point above the target, so switching
166
+ between them does not move the camera."""
167
+ f = self.frame
168
+ return add(target, scale(f.up, f.to_units(self.settings.chase_look_up_m)))
169
+
170
+ def _enter(self, mode: str, prev: str) -> None:
171
+ s = self.settings
172
+ pose = self.targets.pose()
173
+ if pose is None:
174
+ return
175
+ # Keep the smoothed target and heading when moving between attached views
176
+ if prev == "free" or self._target.value is None:
177
+ self._target.snap(pose.position)
178
+ h = self.frame.heading(pose.orientation)
179
+ self._last_heading = h if h is not None else self._last_heading
180
+ self._heading.snap(self._last_heading)
181
+ self._target.tau = s.orbit_tau_target_s if mode == "orbit" else s.chase_tau_target_s
182
+ heading = self._heading.value if self._heading.value is not None else self._last_heading
183
+ # Chase and orbit share one vantage (angle from the heading, elevation, range).
184
+ # Point of view is a fixed seat on the target and never changes it, so chase, pov,
185
+ # orbit returns to the chase vantage instead of orbiting from the seat's eye (which
186
+ # started the orbit a few meters out, 2026-11-08).
187
+ if prev == "orbit" and mode in ("chase", "pov"):
188
+ # keep the perspective set in orbit, now turning with the target's heading
189
+ self.chase_yaw_rel = wrap_angle(self.orbit_yaw - heading)
190
+ self.chase_elevation = self.orbit_elevation
191
+ self.chase_range_m = self.orbit_range_m
192
+ if mode == "orbit":
193
+ if prev in ("chase", "pov"):
194
+ # start the orbit at the chase vantage
195
+ self.orbit_yaw = heading + self.chase_yaw_rel
196
+ self._set_elevation(self.chase_elevation)
197
+ self._set_range_m(self.chase_range_m)
198
+ else:
199
+ eye = self.eye()
200
+ if eye is not None:
201
+ yaw, elev, dist = orbit_from_eye(self._pivot(self._target.value), eye, self.frame)
202
+ self.orbit_yaw = yaw
203
+ self._set_elevation(elev)
204
+ self._set_range_m(dist * self.frame.meters_per_unit)
205
+
206
+ def home(self) -> None:
207
+ """Attached: back to the default chase perspective (behind the target), in chase
208
+ and in orbit. Free: the saved home view, if any."""
209
+ if not self.attached:
210
+ if self.home_view is not None:
211
+ self.place(*self.home_view)
212
+ return
213
+ self.chase_yaw_rel, self.chase_elevation, self.chase_range_m = self.default_chase()
214
+ heading = self._heading.value if self._heading.value is not None else self._last_heading
215
+ self.orbit_yaw = heading + self.chase_yaw_rel
216
+ self._set_elevation(self.chase_elevation)
217
+ self._set_range_m(self.chase_range_m)
218
+
219
+ # orbit input ------------------------------------------------------------------
220
+ def _set_elevation(self, e: float) -> None:
221
+ lo = math.radians(self.settings.orbit_min_elevation_deg)
222
+ hi = math.radians(self.settings.orbit_max_elevation_deg)
223
+ self.orbit_elevation = max(lo, min(hi, e))
224
+
225
+ def _set_range_m(self, r: float) -> None:
226
+ self.orbit_range_m = max(self.settings.orbit_min_m, min(self.settings.orbit_max_m, r))
227
+
228
+ def orbit_by(self, d_yaw: float = 0.0, d_elevation: float = 0.0, range_factor: float = 1.0) -> None:
229
+ self.orbit_yaw += d_yaw
230
+ self._set_elevation(self.orbit_elevation + d_elevation)
231
+ self._set_range_m(self.orbit_range_m * range_factor)
232
+
233
+ # per frame --------------------------------------------------------------------
234
+ def update(self, dt: float) -> bool:
235
+ """Advance one frame; returns True when the camera was written."""
236
+ if not self.attached:
237
+ return False
238
+ pose = self.targets.pose()
239
+ if pose is None:
240
+ return False
241
+ f = self.frame
242
+ s = self.settings
243
+ h = f.heading(pose.orientation)
244
+ if h is not None:
245
+ self._last_heading = h
246
+ target = self._target.update(pose.position, dt)
247
+ heading = self._heading.update(self._last_heading, dt)
248
+ if self.mode == "chase":
249
+ eye, look, up = chase_orbit_view(
250
+ target,
251
+ heading,
252
+ self.chase_yaw_rel,
253
+ self.chase_elevation,
254
+ f.to_units(self.chase_range_m),
255
+ f.to_units(s.chase_look_up_m),
256
+ f,
257
+ )
258
+ elif self.mode == "pov":
259
+ eye, look, up = pov_view(
260
+ pose.position,
261
+ pose.orientation,
262
+ f.to_units(s.pov_forward_m),
263
+ f.to_units(s.pov_up_m),
264
+ f.to_units(s.pov_right_m),
265
+ f,
266
+ )
267
+ else: # orbit
268
+ look, up = self._pivot(target), f.up
269
+ eye = orbit_eye(look, self.orbit_yaw, self.orbit_elevation, f.to_units(self.orbit_range_m), f)
270
+ return self._write(look_at_matrix(eye, look, up))
271
+
272
+ def place(self, eye: Sequence[float], look: Sequence[float]) -> bool:
273
+ """Put the camera at eye looking at look (stage units), in any mode."""
274
+ prim = self._prim()
275
+ if not prim or not prim.IsValid():
276
+ return False
277
+ if self._op is None or not self._op_valid(prim):
278
+ self._prepare_op(prim)
279
+ return self._write(look_at_matrix(eye, look, self.refresh_frame().up))
280
+
281
+ # USD ----------------------------------------------------------------------------
282
+ def _activate_camera(self) -> None:
283
+ try:
284
+ from omni.kit.viewport.utility import get_active_viewport
285
+
286
+ vp = get_active_viewport()
287
+ if vp is not None and str(vp.camera_path) != self.camera_path:
288
+ vp.camera_path = self.camera_path
289
+ except Exception as e:
290
+ carb.log_warn(f"[{self.name}] could not switch the viewport camera: {e}")
291
+
292
+ def _op_valid(self, prim) -> bool:
293
+ ops = UsdGeom.Xformable(prim).GetOrderedXformOps()
294
+ return len(ops) == 1 and ops[0].GetOpType() == UsdGeom.XformOp.TypeTransform
295
+
296
+ def _prepare_op(self, prim) -> None:
297
+ """One xformOp:transform holding the camera's current local transform, so taking
298
+ over the camera does not move it."""
299
+ xf = UsdGeom.Xformable(prim)
300
+ local = xf.GetLocalTransformation(Usd.TimeCode.Default())
301
+ if self._op_valid(prim):
302
+ self._op = xf.GetOrderedXformOps()[0]
303
+ return
304
+ xf.ClearXformOpOrder()
305
+ self._op = xf.AddTransformOp()
306
+ self._op.Set(local)
307
+
308
+ def _write(self, rows) -> bool:
309
+ prim = self._prim()
310
+ if not prim or not prim.IsValid():
311
+ return False
312
+ if self._op is None or not self._op.GetAttr().IsValid():
313
+ self._prepare_op(prim)
314
+ world = Gf.Matrix4d(*[c for row in rows for c in row])
315
+ parent = prim.GetParent()
316
+ if parent and parent.IsValid() and not parent.IsPseudoRoot():
317
+ pw = UsdGeom.Xformable(parent).ComputeLocalToWorldTransform(Usd.TimeCode.Default())
318
+ world = world * pw.GetInverse()
319
+ self._op.Set(world)
320
+ return True
321
+
322
+ def status(self) -> dict:
323
+ return {
324
+ "mode": self.mode,
325
+ "view": self.view,
326
+ "target": self.targets.selected,
327
+ "camera": self.camera_path,
328
+ "up_axis": self.frame.axis,
329
+ "meters_per_unit": self.frame.meters_per_unit,
330
+ "eye": self.eye(),
331
+ "orbit": {
332
+ "yaw_deg": math.degrees(self.orbit_yaw),
333
+ "elevation_deg": math.degrees(self.orbit_elevation),
334
+ "range_m": self.orbit_range_m,
335
+ },
336
+ "chase": {
337
+ "yaw_rel_deg": math.degrees(self.chase_yaw_rel),
338
+ "elevation_deg": math.degrees(self.chase_elevation),
339
+ "range_m": self.chase_range_m,
340
+ },
341
+ }
342
+
343
+ def dispose(self) -> None:
344
+ self.native.dispose(self.stage, self.camera_path)
345
+ self._op = None
346
+ self.mode = "free"
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.5
2
+ Name: vantagekit
3
+ Version: 0.6.1
4
+ Summary: Camera and input controls for viewers of terrain, ocean and vessel scenes: orbit, chase and point-of-view camera math, frame-rate independent smoothing, and bindings as data, with Omniverse Kit adapters.
5
+ Project-URL: Demo, https://lhzn-io.github.io/vantagekit/demo/
6
+ Project-URL: Issues, https://github.com/lhzn-io/vantagekit/issues
7
+ Project-URL: Source, https://github.com/lhzn-io/vantagekit
8
+ Author-email: Long Horizon Observatory <dfry@lhzn.io>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Long Horizon Observatory
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: camera,gamepad,input,navigation,omniverse,viewer
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: Intended Audience :: Science/Research
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Topic :: Multimedia :: Graphics :: 3D Rendering
38
+ Classifier: Topic :: Scientific/Engineering :: Visualization
39
+ Requires-Python: >=3.10
40
+ Description-Content-Type: text/markdown
41
+
42
+ # vantagekit (Python)
43
+
44
+ Camera and input controls for viewers of terrain, ocean and vessel scenes. This package is the
45
+ Python half of [vantagekit](https://github.com/lhzn-io/vantagekit), whose JavaScript modules give
46
+ three.js and MapLibre GL JS viewers the same navigation in the browser
47
+ ([live demo](https://lhzn-io.github.io/vantagekit/demo/)).
48
+
49
+ ```bash
50
+ pip install vantagekit
51
+ ```
52
+
53
+ ## vantagekit.core
54
+
55
+ Standard library only, so it runs anywhere Python 3.10 or later does: in tests, in tools, or in a
56
+ viewer built on any renderer.
57
+
58
+ - **Stage frames** for Y-up and Z-up scenes in any unit (`UpFrame`), with headings and yaw from
59
+ orientations.
60
+ - **Camera math** for orbit, chase and point-of-view cameras around a moving target (`orbit_eye`,
61
+ `look_at_matrix`). Every distance is in metres and every time constant in seconds.
62
+ - **Smoothing** that does not depend on frame rate (`Smoothed3`, `SmoothedAngle`, `exp_alpha`):
63
+ the filter is solved exactly for a target moving steadily between frames, so the lag behind a
64
+ moving target is its speed times the time constant at any frame time.
65
+ - **Bindings as data** (`Binding`, `DEFAULT_BINDINGS`): press, hold, axis and delta kinds, exact
66
+ modifiers, and ordered layers, so an app layer can take the sticks from the camera while it is on.
67
+ - **Target selection** (`CallbackTargets`, `Pose`).
68
+
69
+ ```python
70
+ from vantagekit.core import SmoothedAngle, UpFrame, orbit_eye
71
+ ```
72
+
73
+ ## vantagekit.kit
74
+
75
+ Adapters for Omniverse Kit 109 and 110 (Isaac Sim 6): a camera rig with free, chase,
76
+ point-of-view and orbit modes, a handover to Kit's own navigation, a keyboard, mouse and gamepad
77
+ input router, and a view badge. These import `carb` and `omni` and so work only inside Kit. A Kit
78
+ app usually loads them as the `lhzn.vantagekit` extension from the repository's `kit/exts` folder;
79
+ see the [main README](https://github.com/lhzn-io/vantagekit#omniverse-kit).
80
+
81
+ ## Versions
82
+
83
+ The package version follows the repository's release tags, so `vantagekit==0.6.1` matches the
84
+ JavaScript modules served from `@v0.6.1`. MIT licence, Long Horizon Observatory.
@@ -0,0 +1,18 @@
1
+ vantagekit/__init__.py,sha256=Dxr_68qGe3KmAbZyw2XycRuhhk6KdZ-uQ6JDhCptMfQ,224
2
+ vantagekit/core/__init__.py,sha256=BRDw-oV58f_ug6BPyA7awTfvVXu2HLzamYe7PrONNis,885
3
+ vantagekit/core/bindings.py,sha256=ieEvbEFXcajBeG61-mFr-raM7bgJ2cghJrh25giO3W4,9165
4
+ vantagekit/core/frame.py,sha256=TJ7tPOGebaKWNBMPxz-rXQA-Z-UHaQ5t61uNNcu0PWM,3925
5
+ vantagekit/core/rig_math.py,sha256=rB5h99daqe6jTfgLkblgLMGsUtSKYBoyeDeTxV8f6_0,4378
6
+ vantagekit/core/smoothing.py,sha256=sX49eI6icS3y5B8cByMp16U5__lSUKzYyQa7gGUbres,4552
7
+ vantagekit/core/targets.py,sha256=0K5JYo0yMjpWXkKQYwpVwSi_Ywp4CuD5x1rFblPlSGM,3009
8
+ vantagekit/kit/__init__.py,sha256=MQe8Uv4lOmCBoyJLkD1Js37_P5ZmalQqN8EKjsM4tcU,569
9
+ vantagekit/kit/controls.py,sha256=D4NUVa5xPpog9Q78WSf8Phin1zzzDcveD9C076mZRrQ,8164
10
+ vantagekit/kit/events.py,sha256=VB1nsbFU6qYf1AkpiKgtKySURO9ZuLgHK2sEhkq9X8s,1797
11
+ vantagekit/kit/input.py,sha256=RYRpOGo2uq46BASsA__SIagn9ngEInm4rI-kIIRZ7Z0,6584
12
+ vantagekit/kit/native_nav.py,sha256=oOHn2ZD8_1oIToBPsE36tHO3pg8aHtCOiuZDKmzGgF4,3542
13
+ vantagekit/kit/overlay.py,sha256=-byQznozLt6SS_tn-FF5qFz5hx2YjNZhiQhkFTdH9ao,4132
14
+ vantagekit/kit/rig.py,sha256=t-khZBK9eoUTffi8hhzg0xv4Z6pjvWYLU6hUCe8eLOw,14376
15
+ vantagekit-0.6.1.dist-info/METADATA,sha256=liTqVJCX-gF4Rf7d4-NOwYsAMvSK-59sNk9XQZVemWg,4344
16
+ vantagekit-0.6.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
17
+ vantagekit-0.6.1.dist-info/licenses/LICENSE,sha256=VqXVp0x0MUmTZo0dT3YluvQiXyrUVAIhd5gEv_BdA28,1081
18
+ vantagekit-0.6.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Long Horizon Observatory
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.