simscope 0.1.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.
Files changed (45) hide show
  1. simscope/__init__.py +6 -0
  2. simscope/__main__.py +8 -0
  3. simscope/_assets/simscope-app.css +2 -0
  4. simscope/_assets/simscope-app.js +4311 -0
  5. simscope/_assets/simscope-player.js +4325 -0
  6. simscope/_assets/simscope-web.LICENSES.txt +407 -0
  7. simscope/_icon.py +22 -0
  8. simscope/_mjviser.py +203 -0
  9. simscope/annotations.py +1132 -0
  10. simscope/cli.py +482 -0
  11. simscope/core.py +257 -0
  12. simscope/derived.py +697 -0
  13. simscope/export.py +799 -0
  14. simscope/highlights.py +947 -0
  15. simscope/importers.py +874 -0
  16. simscope/index.py +579 -0
  17. simscope/io/__init__.py +45 -0
  18. simscope/io/blockfile.py +938 -0
  19. simscope/io/cas.py +294 -0
  20. simscope/io/codecs.py +566 -0
  21. simscope/io/errors.py +9 -0
  22. simscope/io/manifest.py +358 -0
  23. simscope/io/pack.py +563 -0
  24. simscope/io/scene.py +239 -0
  25. simscope/isaaclab.py +1460 -0
  26. simscope/library.py +705 -0
  27. simscope/mujoco.py +578 -0
  28. simscope/py.typed +0 -0
  29. simscope/recorder.py +784 -0
  30. simscope/server/__init__.py +9 -0
  31. simscope/server/app.py +149 -0
  32. simscope/server/blocks.py +191 -0
  33. simscope/server/jobs.py +166 -0
  34. simscope/server/routes.py +707 -0
  35. simscope/server/security.py +218 -0
  36. simscope/server/state.py +751 -0
  37. simscope/server/static.py +84 -0
  38. simscope/transforms.py +147 -0
  39. simscope-0.1.1.dist-info/METADATA +132 -0
  40. simscope-0.1.1.dist-info/RECORD +45 -0
  41. simscope-0.1.1.dist-info/WHEEL +4 -0
  42. simscope-0.1.1.dist-info/entry_points.txt +3 -0
  43. simscope-0.1.1.dist-info/licenses/LICENSE.md +201 -0
  44. simscope-0.1.1.dist-info/licenses/THIRD_PARTY_NOTICES.md +267 -0
  45. simscope-0.1.1.dist-info/licenses/src/simscope/_assets/simscope-web.LICENSES.txt +407 -0
simscope/core.py ADDED
@@ -0,0 +1,257 @@
1
+ """Simulator-neutral data model for scenes and rollouts.
2
+
3
+ These types are the in-memory contract shared by the adapters, the storage
4
+ layer, the viewer, and the exporter. The on-disk encoding is ``simscope.io``.
5
+
6
+ Conventions: Z-up, right-handed, metres, seconds. A pose is
7
+ ``[px, py, pz, qx, qy, qz, qw]`` with the quaternion in xyzw order.
8
+ """
9
+
10
+ import dataclasses
11
+ import math
12
+ from typing import Literal
13
+
14
+ import numpy as np
15
+ import numpy.typing as npt
16
+
17
+ POSE_DIM = 7
18
+ """Floats per pose: position xyz, then quaternion xyzw."""
19
+
20
+ GeomKind = Literal[
21
+ "box", "sphere", "capsule", "cylinder", "ellipsoid", "plane", "mesh"
22
+ ]
23
+ GEOM_KINDS: tuple[GeomKind, ...] = (
24
+ "box",
25
+ "sphere",
26
+ "capsule",
27
+ "cylinder",
28
+ "ellipsoid",
29
+ "plane",
30
+ "mesh",
31
+ )
32
+
33
+ GeomRole = Literal["visual", "collision"]
34
+
35
+ StreamKind = Literal["pose", "scalar", "vector", "arrows", "points", "polyline"]
36
+ STREAM_KINDS: tuple[StreamKind, ...] = (
37
+ "pose",
38
+ "scalar",
39
+ "vector",
40
+ "arrows",
41
+ "points",
42
+ "polyline",
43
+ )
44
+
45
+ Vec3 = tuple[float, float, float]
46
+ Quat = tuple[float, float, float, float]
47
+ Rgba = tuple[float, float, float, float]
48
+
49
+ IDENTITY_QUAT: Quat = (0.0, 0.0, 0.0, 1.0)
50
+ UNIT_SCALE: Vec3 = (1.0, 1.0, 1.0)
51
+
52
+
53
+ @dataclasses.dataclass(frozen=True, eq=False)
54
+ class Mesh:
55
+ """A triangle mesh in its geom's local frame.
56
+
57
+ Attributes:
58
+ vertices: Vertex positions, shape ``[V, 3]``, dtype float32.
59
+ faces: Triangle vertex indices, shape ``[F, 3]``, dtype uint32.
60
+ normals: Optional per-vertex normals, shape ``[V, 3]``, float32.
61
+ uvs: Optional per-vertex texture coordinates, shape ``[V, 2]``,
62
+ float32.
63
+ """
64
+
65
+ vertices: npt.NDArray[np.float32]
66
+ faces: npt.NDArray[np.uint32]
67
+ normals: npt.NDArray[np.float32] | None = None
68
+ uvs: npt.NDArray[np.float32] | None = None
69
+
70
+ def __post_init__(self) -> None:
71
+ """Validates array shapes and dtypes.
72
+
73
+ Raises:
74
+ ValueError: If an array has the wrong shape or dtype.
75
+ """
76
+ n_verts = _check_array("vertices", self.vertices, np.float32, 3)
77
+ _check_array("faces", self.faces, np.uint32, 3)
78
+ normals, uvs = self.normals, self.uvs
79
+ if (
80
+ normals is not None
81
+ and _check_array("normals", normals, np.float32, 3) != n_verts
82
+ ):
83
+ raise ValueError("normals must have one row per vertex")
84
+ if (
85
+ uvs is not None
86
+ and _check_array("uvs", uvs, np.float32, 2) != n_verts
87
+ ):
88
+ raise ValueError("uvs must have one row per vertex")
89
+
90
+
91
+ @dataclasses.dataclass(frozen=True)
92
+ class Texture:
93
+ """An encoded 2D image.
94
+
95
+ Attributes:
96
+ data: The encoded file bytes.
97
+ media_type: ``"image/png"`` or ``"image/jpeg"``.
98
+ width: Width in pixels.
99
+ height: Height in pixels.
100
+ """
101
+
102
+ data: bytes
103
+ media_type: Literal["image/png", "image/jpeg"]
104
+ width: int
105
+ height: int
106
+
107
+
108
+ @dataclasses.dataclass(frozen=True)
109
+ class Material:
110
+ """Surface appearance of a geom.
111
+
112
+ Attributes:
113
+ rgba: Base color and opacity, each in ``[0, 1]``.
114
+ metallic: PBR metalness in ``[0, 1]``.
115
+ roughness: PBR roughness in ``[0, 1]``.
116
+ texture: Index into ``Scene.textures``, or ``None``.
117
+ texrepeat: Texture repeats along u and v.
118
+ """
119
+
120
+ rgba: Rgba = (0.8, 0.8, 0.8, 1.0)
121
+ metallic: float = 0.0
122
+ roughness: float = 1.0
123
+ texture: int | None = None
124
+ texrepeat: tuple[float, float] = (1.0, 1.0)
125
+
126
+
127
+ @dataclasses.dataclass(frozen=True)
128
+ class Body:
129
+ """A rigid body that owns geoms and has one pose per frame.
130
+
131
+ Attributes:
132
+ name: Body name or USD path. Not required to be unique.
133
+ parent: Index of the parent body, or ``-1`` for a root.
134
+ mass: Mass in kilograms, or ``0.0`` if unknown (the world body, or
135
+ a simulator that does not say). Highlights weight the centre of
136
+ mass by it.
137
+
138
+ Raises:
139
+ ValueError: If ``mass`` is negative or not finite.
140
+ """
141
+
142
+ name: str
143
+ parent: int = -1
144
+ mass: float = 0.0
145
+
146
+ def __post_init__(self) -> None:
147
+ """Checks the mass."""
148
+ if not (math.isfinite(self.mass) and self.mass >= 0):
149
+ raise ValueError(f"body mass must be finite and >= 0: {self.mass}")
150
+
151
+
152
+ @dataclasses.dataclass(frozen=True)
153
+ class Geom:
154
+ """A visual or collision shape attached to a body.
155
+
156
+ ``kind`` and ``size`` follow MuJoCo conventions.
157
+
158
+ Attributes:
159
+ body: Index into ``Scene.bodies``.
160
+ kind: Shape kind.
161
+ size: Kind-specific size parameters in metres.
162
+ pos: Position in the body frame.
163
+ quat: Orientation in the body frame, xyzw.
164
+ scale: Component-wise scale applied to local vertices.
165
+ material: Index into ``Scene.materials``.
166
+ mesh: Index into ``Scene.meshes`` when ``kind == "mesh"``.
167
+ role: Whether viewers show the geom by default.
168
+ name: Optional geom name.
169
+ """
170
+
171
+ body: int
172
+ kind: GeomKind
173
+ size: Vec3 = (0.0, 0.0, 0.0)
174
+ pos: Vec3 = (0.0, 0.0, 0.0)
175
+ quat: Quat = IDENTITY_QUAT
176
+ scale: Vec3 = UNIT_SCALE
177
+ material: int = 0
178
+ mesh: int | None = None
179
+ role: GeomRole = "visual"
180
+ name: str = ""
181
+
182
+
183
+ @dataclasses.dataclass(frozen=True, eq=False)
184
+ class Scene:
185
+ """The static part of a rollout, shared by every frame and env.
186
+
187
+ Attributes:
188
+ bodies: Bodies, in the order of the pose stream.
189
+ geoms: Geoms attached to bodies.
190
+ materials: Materials referenced by geoms. Must not be empty when
191
+ there are geoms.
192
+ meshes: Meshes referenced by mesh geoms.
193
+ textures: Textures referenced by materials.
194
+ """
195
+
196
+ bodies: tuple[Body, ...]
197
+ geoms: tuple[Geom, ...] = ()
198
+ materials: tuple[Material, ...] = (Material(),)
199
+ meshes: tuple[Mesh, ...] = ()
200
+ textures: tuple[Texture, ...] = ()
201
+
202
+ def __post_init__(self) -> None:
203
+ """Validates cross-references between scene elements.
204
+
205
+ Raises:
206
+ ValueError: If an index points outside its table, or a mesh geom
207
+ has no mesh.
208
+ """
209
+ n_bodies = len(self.bodies)
210
+ for i, body in enumerate(self.bodies):
211
+ if not -1 <= body.parent < n_bodies or body.parent == i:
212
+ raise ValueError(f"body {i} has invalid parent {body.parent}")
213
+ for j, geom in enumerate(self.geoms):
214
+ if not 0 <= geom.body < n_bodies:
215
+ raise ValueError(f"geom {j} has invalid body {geom.body}")
216
+ if not 0 <= geom.material < len(self.materials):
217
+ raise ValueError(
218
+ f"geom {j} has invalid material {geom.material}"
219
+ )
220
+ if geom.kind == "mesh":
221
+ if geom.mesh is None or not 0 <= geom.mesh < len(self.meshes):
222
+ raise ValueError(f"mesh geom {j} has invalid mesh")
223
+ elif geom.mesh is not None:
224
+ raise ValueError(f"geom {j} of kind {geom.kind} has a mesh")
225
+ for k, material in enumerate(self.materials):
226
+ texture = material.texture
227
+ if texture is not None and not 0 <= texture < len(self.textures):
228
+ raise ValueError(f"material {k} has invalid texture {texture}")
229
+
230
+ @property
231
+ def n_bodies(self) -> int:
232
+ """Number of bodies, which is the pose count per frame."""
233
+ return len(self.bodies)
234
+
235
+
236
+ def _check_array(
237
+ name: str, array: npt.NDArray, dtype: type[np.generic], width: int
238
+ ) -> int:
239
+ """Checks that an array is 2D with a given width and dtype.
240
+
241
+ Args:
242
+ name: Field name for error messages.
243
+ array: Array to check.
244
+ dtype: Required dtype.
245
+ width: Required size of the second axis.
246
+
247
+ Returns:
248
+ The size of the first axis.
249
+
250
+ Raises:
251
+ ValueError: If the shape or dtype does not match.
252
+ """
253
+ if array.dtype != dtype:
254
+ raise ValueError(f"{name} must have dtype {np.dtype(dtype)}")
255
+ if array.ndim != 2 or array.shape[1] != width:
256
+ raise ValueError(f"{name} must have shape [N, {width}]")
257
+ return array.shape[0]