simhub 0.1.0__tar.gz
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.
- simhub-0.1.0/PKG-INFO +514 -0
- simhub-0.1.0/README.sdk.md +477 -0
- simhub-0.1.0/pyproject.toml +163 -0
- simhub-0.1.0/src/simhub/__init__.py +239 -0
- simhub-0.1.0/src/simhub/asset_cli.py +58 -0
- simhub-0.1.0/src/simhub/base.py +564 -0
- simhub-0.1.0/src/simhub/batch.py +334 -0
- simhub-0.1.0/src/simhub/blender.py +860 -0
- simhub-0.1.0/src/simhub/blender_replay.py +144 -0
- simhub-0.1.0/src/simhub/checks.py +415 -0
- simhub-0.1.0/src/simhub/cleaning.py +92 -0
- simhub-0.1.0/src/simhub/client.py +569 -0
- simhub-0.1.0/src/simhub/datasets.py +304 -0
- simhub-0.1.0/src/simhub/emit.py +283 -0
- simhub-0.1.0/src/simhub/env.py +264 -0
- simhub-0.1.0/src/simhub/errors.py +116 -0
- simhub-0.1.0/src/simhub/evaluation.py +117 -0
- simhub-0.1.0/src/simhub/hf.py +130 -0
- simhub-0.1.0/src/simhub/ids.py +29 -0
- simhub-0.1.0/src/simhub/kinematics.py +205 -0
- simhub-0.1.0/src/simhub/mjcf.py +665 -0
- simhub-0.1.0/src/simhub/model_cli.py +83 -0
- simhub-0.1.0/src/simhub/models.py +233 -0
- simhub-0.1.0/src/simhub/named_assets.py +365 -0
- simhub-0.1.0/src/simhub/notebook.py +237 -0
- simhub-0.1.0/src/simhub/objects.py +180 -0
- simhub-0.1.0/src/simhub/openfoam.py +231 -0
- simhub-0.1.0/src/simhub/particulate.py +508 -0
- simhub-0.1.0/src/simhub/policies.py +152 -0
- simhub-0.1.0/src/simhub/project.py +427 -0
- simhub-0.1.0/src/simhub/recording.py +686 -0
- simhub-0.1.0/src/simhub/registry.py +101 -0
- simhub-0.1.0/src/simhub/replay.py +116 -0
- simhub-0.1.0/src/simhub/report.py +403 -0
- simhub-0.1.0/src/simhub/robots.py +349 -0
- simhub-0.1.0/src/simhub/rollout.py +613 -0
- simhub-0.1.0/src/simhub/run_cli.py +229 -0
- simhub-0.1.0/src/simhub/runner.py +535 -0
- simhub-0.1.0/src/simhub/schema.py +93 -0
- simhub-0.1.0/src/simhub/scorer.py +481 -0
- simhub-0.1.0/src/simhub/session.py +507 -0
- simhub-0.1.0/src/simhub/single.py +205 -0
- simhub-0.1.0/src/simhub/spec.py +352 -0
- simhub-0.1.0/src/simhub/studio.py +312 -0
- simhub-0.1.0/src/simhub/suite.py +237 -0
- simhub-0.1.0/src/simhub/task.py +628 -0
- simhub-0.1.0/src/simhub/types.py +518 -0
- simhub-0.1.0/src/simhub/viewer.py +444 -0
simhub-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: simhub
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Define an environment, a policy and a task in Python; run them on hosted GPUs
|
|
5
|
+
Project-URL: Homepage, https://github.com/General-Deployment/sim
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Keywords: evaluation,mujoco,reinforcement-learning,robotics,simulation
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: numpy>=1.24
|
|
11
|
+
Requires-Dist: pydantic>=2.7
|
|
12
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
13
|
+
Provides-Extra: all
|
|
14
|
+
Requires-Dist: huggingface-hub>=0.26; extra == 'all'
|
|
15
|
+
Requires-Dist: imageio-ffmpeg>=0.5; extra == 'all'
|
|
16
|
+
Requires-Dist: ipython>=8.0; extra == 'all'
|
|
17
|
+
Requires-Dist: matplotlib>=3.7; extra == 'all'
|
|
18
|
+
Requires-Dist: mujoco==3.11.*; (python_version >= '3.10') and extra == 'all'
|
|
19
|
+
Requires-Dist: pandas>=2.0; extra == 'all'
|
|
20
|
+
Requires-Dist: pillow>=10.0; extra == 'all'
|
|
21
|
+
Requires-Dist: pyarrow>=15.0; extra == 'all'
|
|
22
|
+
Provides-Extra: dataset
|
|
23
|
+
Requires-Dist: pyarrow>=15.0; extra == 'dataset'
|
|
24
|
+
Provides-Extra: mujoco
|
|
25
|
+
Requires-Dist: mujoco==3.11.*; (python_version >= '3.10') and extra == 'mujoco'
|
|
26
|
+
Provides-Extra: notebook
|
|
27
|
+
Requires-Dist: ipython>=8.0; extra == 'notebook'
|
|
28
|
+
Requires-Dist: matplotlib>=3.7; extra == 'notebook'
|
|
29
|
+
Requires-Dist: pandas>=2.0; extra == 'notebook'
|
|
30
|
+
Requires-Dist: pillow>=10.0; extra == 'notebook'
|
|
31
|
+
Provides-Extra: video
|
|
32
|
+
Requires-Dist: imageio-ffmpeg>=0.5; extra == 'video'
|
|
33
|
+
Requires-Dist: pillow>=10.0; extra == 'video'
|
|
34
|
+
Provides-Extra: weights
|
|
35
|
+
Requires-Dist: huggingface-hub>=0.26; extra == 'weights'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# simhub
|
|
39
|
+
|
|
40
|
+
Upload reusable robots with `simhub asset upload robot.urdf --env robots --name arm`,
|
|
41
|
+
then load them in hosted projects with `simhub.asset("arm").path`.
|
|
42
|
+
See [named URDF and mesh assets](docs/named-assets.md) for dependency packaging,
|
|
43
|
+
project aliases, and version pinning.
|
|
44
|
+
|
|
45
|
+
The [single-file house-ceiling example](../examples/asbestos_ceiling.py) defines a
|
|
46
|
+
photo-derived tracked robot base, two articulated tool arms, a domestic scene, a registered
|
|
47
|
+
policy, a frozen evaluation and the Cycles scene in one Python file:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
PYTHONPATH=simhub/src:. python examples/asbestos_ceiling.py evaluate
|
|
51
|
+
PYTHONPATH=simhub/src:. python examples/asbestos_ceiling.py render
|
|
52
|
+
PYTHONPATH=simhub/src:. python examples/asbestos_ceiling.py export-model
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The example now uses `simhub.particulate`: two-layer moisture penetration,
|
|
56
|
+
contact-work-driven detachment, finite shroud capture, three fibre size bins and
|
|
57
|
+
conservative 3D advection/diffusion/settling. All coating, head, particle and
|
|
58
|
+
ventilation assumptions remain in the single file. Mass is tracked in kg of the
|
|
59
|
+
constituent, including constituent still bound in fragments. Hosted delivery solves the room velocity with OpenFOAM; local material-only runs
|
|
60
|
+
use a prescribed velocity. Neither mode resolves shroud-scale flow or predicts
|
|
61
|
+
calibrated asbestos clearance.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
PYTHONPATH=simhub/src:. python examples/asbestos_ceiling.py material-checks
|
|
65
|
+
PYTHONPATH=simhub/src:. python examples/asbestos_ceiling.py diagnostics
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`material-checks` runs controlled parameter interventions and analytic/grid/time
|
|
69
|
+
checks; `diagnostics` renders the recorded state as a scientific animation and
|
|
70
|
+
requires Matplotlib plus ffmpeg. New outputs default to
|
|
71
|
+
`results/asbestos_ceiling_v3`; earlier outputs remain preserved.
|
|
72
|
+
Evaluation retains the original design targets and reports failures without
|
|
73
|
+
changing the physical parameters to obtain a pass.
|
|
74
|
+
|
|
75
|
+
For other scenes, `simhub.render_cycles(project_directory, trajectory_directory,
|
|
76
|
+
output_directory, program="scene.py")` invokes Blender locally. A scene program
|
|
77
|
+
defines `build_scene(recorded, config)` and returns a mapping of recorded body
|
|
78
|
+
names to unparented Blender objects plus an `update(index, fraction)` callback.
|
|
79
|
+
The callback can animate recorded fields and cameras; it must not rerun a policy.
|
|
80
|
+
Blender uses the best available Cycles GPU backend, with a CPU fallback.
|
|
81
|
+
|
|
82
|
+
`Recorder.channel(name, sampler)` records fixed-shape finite numeric fields
|
|
83
|
+
alongside body poses. `simhub.SurfaceContamination` and `simhub.CleaningTool`
|
|
84
|
+
provide a mass-conserving surface-transfer surrogate. The ceiling example records
|
|
85
|
+
remaining coating, surface and bond moisture, size-resolved airborne concentration
|
|
86
|
+
and seven mass inventories through this channel API, so the render and evaluation
|
|
87
|
+
read the same state. `AirTransport`, `CoatingParameters`, `ExtractionContact`,
|
|
88
|
+
`ParticleClass` and `WetCoating` are also exported directly from `simhub`. The hosted Cycles worker uses the same replay
|
|
89
|
+
entry point when supplied a Python bundle and `params.program` (use
|
|
90
|
+
`"__init__.py"` for a bundled single-file project). The worker must have Blender
|
|
91
|
+
and ffmpeg available; no cloud deployment is performed by the local commands.
|
|
92
|
+
|
|
93
|
+
Define an environment, a policy and a task in Python. Run one episode on your
|
|
94
|
+
laptop; run a hundred on GPUs from the same notebook, and get the videos and
|
|
95
|
+
the success rates back inline.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install simhub # the library and a client, two dependencies
|
|
99
|
+
pip install 'simhub[all]' # + MuJoCo, video, plots, dataset export
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Two environment variables reach the hosted half, and the console prints both:
|
|
103
|
+
sign in, open **API keys**, create one, and paste the two `export` lines it
|
|
104
|
+
gives you. `SIMHUB_API_URL` is in there because a key on its own is half a
|
|
105
|
+
credential -- the console and the API are different origins behind Modal, so it
|
|
106
|
+
is not a thing to guess.
|
|
107
|
+
|
|
108
|
+
## The whole authoring surface
|
|
109
|
+
|
|
110
|
+
Three things, and none of them is a base class you have to inherit from.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# warehouse_pick/__init__.py
|
|
114
|
+
import simhub
|
|
115
|
+
|
|
116
|
+
@simhub.environment("warehouse-pick")
|
|
117
|
+
def build(cell_seed: int = 7) -> simhub.Env:
|
|
118
|
+
from .env import PickEnv # heavy imports inside, always
|
|
119
|
+
return PickEnv(cell_seed)
|
|
120
|
+
|
|
121
|
+
@simhub.policy("scripted")
|
|
122
|
+
def scripted(env):
|
|
123
|
+
from .policies import ScriptedPick
|
|
124
|
+
return ScriptedPick(env)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
An **environment** is your own class with five members — `spec`, `reset`,
|
|
128
|
+
`step`, `observe`, `score`, and a `done` property. A **policy** has `spec`,
|
|
129
|
+
`reset` and `act`. A **task** is data: `simhub.Task(placed="eq:1",
|
|
130
|
+
collisions="eq:0")`, or a staged one that gives partial credit.
|
|
131
|
+
|
|
132
|
+
Beside the package, four keys:
|
|
133
|
+
|
|
134
|
+
```toml
|
|
135
|
+
# simhub.toml
|
|
136
|
+
name = "warehouse-pick"
|
|
137
|
+
image = "python-gpu" # python-cpu | python-gpu
|
|
138
|
+
requirements = ["mujoco==3.11.*", "gr00t"] # installed at container start
|
|
139
|
+
include = ["*.py", "assets/**"]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
No entrypoint key and no inventory key. The package directory *is* the
|
|
143
|
+
entrypoint, and what the project offers is generated by importing it — in your
|
|
144
|
+
interpreter, so a syntax error or a missing dependency fails on your machine
|
|
145
|
+
rather than ninety seconds into a container.
|
|
146
|
+
|
|
147
|
+
## One episode, here
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
import simhub
|
|
151
|
+
|
|
152
|
+
simhub.run("warehouse-pick", "scripted", seed=3, out="out/one",
|
|
153
|
+
record=simhub.Record(video="hero"))
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
This is the debug loop, not the product: same `rollout`, same output files, one
|
|
157
|
+
at a time and without a GPU. Use it to find out your scene works before you
|
|
158
|
+
spend money running a hundred of them.
|
|
159
|
+
|
|
160
|
+
## A hundred episodes, hosted
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
sh = simhub.connect() # SIMHUB_API_URL + SIMHUB_API_KEY
|
|
164
|
+
project = sh.project("./warehouse_pick") # imports locally, builds the inventory
|
|
165
|
+
|
|
166
|
+
batch = project.launch(
|
|
167
|
+
env="warehouse-pick",
|
|
168
|
+
policies=["scripted", "groot-ft"],
|
|
169
|
+
seeds=range(32),
|
|
170
|
+
task=simhub.Stages(target_lifted="gte:1", target_placed="gte:1"),
|
|
171
|
+
record=simhub.Record(cameras=["policy_ext", "wrist"], video="hero"),
|
|
172
|
+
render="cycles", # photoreal replay, in the background
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
batch # a live table, refreshing
|
|
176
|
+
batch.wait() # follows the server-sent events, per run; returns the batch
|
|
177
|
+
batch.dataframe() # one row per run
|
|
178
|
+
batch.videos("hero") # an inline grid of players, captioned with the verdict
|
|
179
|
+
batch.plot.success() # rates with intervals, checked and claimed apart
|
|
180
|
+
batch.cancel() # one call stops every child
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The upload is invisible: your project is zipped deterministically and stored by
|
|
184
|
+
content hash, so re-launching unchanged code uploads nothing. **Nobody builds
|
|
185
|
+
or deploys a container image to add a new simulation.**
|
|
186
|
+
|
|
187
|
+
## Why hosted at all
|
|
188
|
+
|
|
189
|
+
Anybody can step MuJoCo in a loop for free. The reason to reach for this is
|
|
190
|
+
everything that is not the loop:
|
|
191
|
+
|
|
192
|
+
| what you want | why a laptop cannot |
|
|
193
|
+
| --- | --- |
|
|
194
|
+
| evaluate a VLA | GR00T N1.7 is 3B parameters on CUDA; pi0.5's checkpoint is gigabytes |
|
|
195
|
+
| a batch, not an episode | 192 episodes one after another against 16–64 at once |
|
|
196
|
+
| any video at all | 3.0 ms a frame on an A10G against 355 ms on CPU OSMesa |
|
|
197
|
+
| photoreal video | Cycles at 1080p is "under an hour" locally, minutes fanned over GPUs |
|
|
198
|
+
| fine-tune on what you recorded | never a laptop |
|
|
199
|
+
| a number worth citing next month | a result from somebody's machine has no digest, no row, and nothing to compare against |
|
|
200
|
+
|
|
201
|
+
## Evaluations
|
|
202
|
+
|
|
203
|
+
An eval is a **frozen** set of episodes, so two policies are scored on the same
|
|
204
|
+
problems rather than on different random draws. The suite's content digest *is*
|
|
205
|
+
its version.
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
suite = simhub.Suite.grid("warehouse-pick/v1", cell_seed=range(8), target_seed=range(8))
|
|
209
|
+
ev = simhub.Eval(suite=suite, task=simhub.Task(target_placed="eq:1"),
|
|
210
|
+
primary="success", truncated_is="failure")
|
|
211
|
+
|
|
212
|
+
report = project.evaluate(ev, env="warehouse-pick",
|
|
213
|
+
policies=["scripted", "groot-ft"]).wait().report(ev)
|
|
214
|
+
|
|
215
|
+
report.table() # n, coverage, rate with its interval
|
|
216
|
+
report.compare("groot-ft", "scripted") # paired: effect, interval, p
|
|
217
|
+
report.failures(policy="groot-ft") # runs grouped by failure mode; join
|
|
218
|
+
# to batch.videos() on run_id for clips
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Five rules the report enforces, because each is a mistake that is easy to make
|
|
222
|
+
and expensive to publish:
|
|
223
|
+
|
|
224
|
+
1. **Checked and unchecked verdicts are never averaged.** A verdict a stored
|
|
225
|
+
predicate computed and one a runner asserted are different kinds of number.
|
|
226
|
+
2. **Coverage is reported before the score.** A crashed run is a missing
|
|
227
|
+
observation, not a policy failure. An eval at 80% coverage is not an eval.
|
|
228
|
+
3. **Intervals, not bare fractions.** 12/20 is 0.60 with a 95% Wilson interval
|
|
229
|
+
of [0.39, 0.78]; "8/20 to 12/20" is inside that noise.
|
|
230
|
+
4. **Comparisons are paired and come back as an effect, not a ranking.** Two
|
|
231
|
+
policies that ran different suites raise rather than being compared.
|
|
232
|
+
5. **Determinism is checked, not assumed** — `report.check_determinism()`
|
|
233
|
+
re-runs one episode and asserts the metrics match.
|
|
234
|
+
|
|
235
|
+
## What every run produces
|
|
236
|
+
|
|
237
|
+
The same layout whether it finished or died halfway:
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
trajectory/000000.npz ... every body's pose and quaternion (wxyz), per step
|
|
241
|
+
trajectory/meta.json body names, units, the quaternion convention, in words
|
|
242
|
+
video/hero.mp4 captioned with the instruction and the exact buffers
|
|
243
|
+
renders/<camera>/*.png when you ask for frames
|
|
244
|
+
metrics.json what score() reported
|
|
245
|
+
result.json the verdict, on both axes
|
|
246
|
+
checkpoints/000200.npz resume points; one is written on SIGTERM
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
One recording, three readers: the video, the evaluation and
|
|
250
|
+
`simhub.datasets.lerobot(batch, ...)` — which exports the same episodes in the
|
|
251
|
+
format GR00T and pi0.5 fine-tunes read.
|
|
252
|
+
|
|
253
|
+
## Costs, and the one line that controls them
|
|
254
|
+
|
|
255
|
+
Rendering is the entire cost of a batch: a physics step is 0.08 ms and a GPU
|
|
256
|
+
frame is 1.7–8.4 ms. So a policy declares what it reads:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
simhub.PolicySpec(action_dim=7, control_hz=15.0, expects=("observation/state",))
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
and the rollout renders exactly that. A scripted demonstrator that never looks
|
|
263
|
+
at an image renders **no frames at all**.
|
|
264
|
+
|
|
265
|
+
## Checking a scene you just generated
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
report = simhub.check("./savant_lobby", env="savant-lobby")
|
|
269
|
+
report.ok, report.images # links to pictures, not numbers
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Five assertions: it imports and resolves; every policy camera actually sees
|
|
273
|
+
something; everything rests under gravity; `score()` reports every metric the
|
|
274
|
+
spec declares; one episode completes. Non-zero exit when it does not — which is
|
|
275
|
+
what an agent generating scenes can branch on.
|
|
276
|
+
|
|
277
|
+
## Modelling the scene in Blender
|
|
278
|
+
|
|
279
|
+
A project whose scene is easier to model than to write can build it with `bpy`
|
|
280
|
+
and hand simhub the result. Nothing about it is a special case: the contract is
|
|
281
|
+
still `write_mjcf(out, seed, **kwargs)`, and the same
|
|
282
|
+
`python_bundle -> mjcf_bundle` conversion turns it into a scene.
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
from simhub import blender
|
|
286
|
+
|
|
287
|
+
def write_mjcf(out, seed=0, **kwargs):
|
|
288
|
+
import bpy # heavy import, inside
|
|
289
|
+
|
|
290
|
+
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
291
|
+
build_the_dock(seed) # your code, your bpy
|
|
292
|
+
|
|
293
|
+
blender.static(bpy.data.objects["plinth"]) # welded to the world
|
|
294
|
+
blender.body(crate, density=450, friction=0.9) # falls, collides
|
|
295
|
+
blender.body(ball, mass=0.35, collision=proxy) # cheap collision stand-in
|
|
296
|
+
blender.exclude(backdrop) # not in the simulation
|
|
297
|
+
|
|
298
|
+
return blender.export(out, name="depot")
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Cameras and lights come across on their own — a Blender camera and a MuJoCo
|
|
302
|
+
camera share a convention, which is the one thing here needing no conversion.
|
|
303
|
+
Marks are stored as custom properties, so a scene keeps its meaning through a
|
|
304
|
+
`.blend` round-trip.
|
|
305
|
+
|
|
306
|
+
Two things shape how you model. **Collision is convex**, because MuJoCo's is:
|
|
307
|
+
each object collides as the hull of each of its loose parts, so a room wants to
|
|
308
|
+
be several objects rather than one welded mesh, and `collision=` takes a
|
|
309
|
+
low-poly proxy when a hull is wrong. **Mass comes from the hull**, not the
|
|
310
|
+
visual mesh — a hollow shell and its hull weigh different amounts and only one
|
|
311
|
+
of them is the physics.
|
|
312
|
+
|
|
313
|
+
Articulation is not Blender's job. A robot's joints, actuators and limits
|
|
314
|
+
already have a good representation in MJCF, so the Blender program builds the
|
|
315
|
+
*set* and `write_mjcf` attaches the arm before returning the path.
|
|
316
|
+
|
|
317
|
+
### Or ship the `.blend` itself
|
|
318
|
+
|
|
319
|
+
A set modelled in Blender's UI rather than written as a program is a complete
|
|
320
|
+
input, because the marks live on the objects: `blender.body` and friends write
|
|
321
|
+
custom properties, so they survive a save.
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
from pathlib import Path
|
|
325
|
+
from simhub import blender
|
|
326
|
+
|
|
327
|
+
def write_mjcf(out, seed=0, **kwargs):
|
|
328
|
+
blender.open_file(Path(__file__).parent / "dock.blend")
|
|
329
|
+
return blender.export(out, name="dock")
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```toml
|
|
333
|
+
include = ["*.py", "*.blend"] # the default list is text formats only
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Nothing new happens to it. A `.blend` is a file in the project directory, so it
|
|
337
|
+
travels inside the ordinary `python_bundle` -- content-addressed, deduped, and
|
|
338
|
+
re-uploading nothing when it has not changed -- and the compile is the same
|
|
339
|
+
`python_bundle -> mjcf_bundle` conversion. There is no `blend` artifact format
|
|
340
|
+
and no second upload, deliberately: a fifth format would be a fifth thing to
|
|
341
|
+
keep in step with the other four, in exchange for behaviour that is already
|
|
342
|
+
here. [`examples/blend_file_dock`](../examples/blend_file_dock) is the worked
|
|
343
|
+
example.
|
|
344
|
+
|
|
345
|
+
One thing it is not: a `.blend` is one fixed layout, so it is a set rather than
|
|
346
|
+
a suite. Sixty-four seeds against it are sixty-four copies of one episode, and
|
|
347
|
+
a batch that says `randomised` is refused server-side for exactly that. Vary in
|
|
348
|
+
`reset`, or author the set as a program the way `blender_depot` does.
|
|
349
|
+
|
|
350
|
+
### Without Blender on your machine
|
|
351
|
+
|
|
352
|
+
You do not need `bpy` locally. Listing what a project offers never imports it --
|
|
353
|
+
every Blender call lives inside the scene program's body, the same rule every
|
|
354
|
+
project follows for MuJoCo and torch -- so `Project.load` works on any
|
|
355
|
+
interpreter and the wheel is only ever installed in the image that compiles:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
export SIMHUB_API_KEY=sk_...
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
sh = simhub.connect()
|
|
363
|
+
batch = sh.project("./my_scene").launch(
|
|
364
|
+
env="depot-settle", policies=["sweep", "simhub:hold"], seeds=range(8),
|
|
365
|
+
task=simhub.Conditions(settled="gte:4"),
|
|
366
|
+
record=simhub.Record(video="hero"),
|
|
367
|
+
)
|
|
368
|
+
batch.wait(); batch.report().table(); batch.videos()
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
The deployment registers `python-blender` as its `python_bundle -> mjcf_bundle`
|
|
372
|
+
compiler, the compile runs in an image that has Blender, and the scene lands on
|
|
373
|
+
the version as an ordinary `mjcf_bundle`. Every simulate job after that gets it
|
|
374
|
+
on disk; an environment that wants it declares a `scene` parameter and the
|
|
375
|
+
runner fills it in.
|
|
376
|
+
|
|
377
|
+
That indirection is not fastidiousness: `bpy` publishes one wheel per CPython
|
|
378
|
+
release and skips 3.12 entirely, so "put Blender in the worker" is not something
|
|
379
|
+
a 3.12 deployment can do at any price. Behind the compiler protocol the image
|
|
380
|
+
picks its own interpreter and nothing else has to agree.
|
|
381
|
+
|
|
382
|
+
[`examples/blender_depot`](../examples/blender_depot) is the worked example, end
|
|
383
|
+
to end.
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
### Coupled room CFD and recorded-video artifacts
|
|
387
|
+
|
|
388
|
+
The `python-multiphysics` runtime adds distro OpenFOAM and official Blender to
|
|
389
|
+
MuJoCo on an A10G runner. Like other runtimes, it is declared by `runner.toml`
|
|
390
|
+
and registered by `.github/scripts/render_runners.py` for `deploy.yml`.
|
|
391
|
+
`simhub.manifest(image="python-multiphysics", ...)` selects it for a single-file project.
|
|
392
|
+
|
|
393
|
+
`simhub.openfoam.solve_room(...)` writes a reproducible room case, executes
|
|
394
|
+
`blockMesh`, `checkMesh` and transient `pimpleFoam`, and validates discrete
|
|
395
|
+
continuity. `apply_flow(transport, "velocity.npz")` transfers the solver's
|
|
396
|
+
oriented face fluxes to `AirTransport` without interpolating a nonconservative
|
|
397
|
+
cell-centred velocity. The current adapter supports a rectangular room with
|
|
398
|
+
no-slip walls, clean makeup air at y- and an outlet at y+. It does not resolve
|
|
399
|
+
the robot or the moving shroud; release, capture and aerosol mixing laws still
|
|
400
|
+
require calibration.
|
|
401
|
+
|
|
402
|
+
An environment can expose `finalize_run(out, result)` to produce additional
|
|
403
|
+
artifacts after its recorded rollout. Return file paths contained within `out`.
|
|
404
|
+
The hosted Python runner announces these using the normal artifact protocol
|
|
405
|
+
and remains running until the hook completes. A failed hook fails the run.
|
|
406
|
+
This supports evaluations and Cycles replay from the very trajectory that was
|
|
407
|
+
scored, without a separate manually uploaded demonstration video.
|
|
408
|
+
|
|
409
|
+
`examples/asbestos_ceiling.py` declares the scene, photo-derived robot,
|
|
410
|
+
controller, material assumptions, CFD inputs, evaluation suite and rendering
|
|
411
|
+
in one file. Dimensions and lift configuration are estimates from photographs;
|
|
412
|
+
the policy is scripted, and the material model is not a clearance prediction.
|
|
413
|
+
|
|
414
|
+
Public viewer links can open `/api/shared/view?token=...&run_id=...`. The page
|
|
415
|
+
shows the scoped run's execution status, task outcome, metrics and stored
|
|
416
|
+
video/artifacts without an account. The token is revocable and expires; never
|
|
417
|
+
substitute an organization API key in this URL.
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
## Run pages and evaluation output
|
|
421
|
+
|
|
422
|
+
Every run has an organization-scoped console URL:
|
|
423
|
+
`https://simhub-console.pages.dev/runs/<run_id>?org=<organization-slug>`.
|
|
424
|
+
Open a run card to watch its recording and inspect its metrics, timing, status,
|
|
425
|
+
and task evaluation. Copy link preserves this address through sign-in and
|
|
426
|
+
refresh. The recipient needs access to that organization; no API key is included.
|
|
427
|
+
|
|
428
|
+
`rollout()` writes and announces `evaluation.json` (`schema: simhub.evaluation/1`)
|
|
429
|
+
as an ordinary artifact. It adds display metadata and the local task verdict to
|
|
430
|
+
the existing flat `metrics.json` and `result.json` outputs. Custom metrics need
|
|
431
|
+
no console changes:
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
spec = simhub.EnvSpec(
|
|
435
|
+
id="pick-place",
|
|
436
|
+
action_dim=7,
|
|
437
|
+
metrics={
|
|
438
|
+
"placement_error": simhub.MetricSpec(
|
|
439
|
+
unit="m", better="lower", required=True,
|
|
440
|
+
label="Placement error", description="Distance from object to target",
|
|
441
|
+
),
|
|
442
|
+
"objects_placed": simhub.MetricSpec(label="Objects placed", unit="objects"),
|
|
443
|
+
},
|
|
444
|
+
)
|
|
445
|
+
# Env.score() returns {"placement_error": 0.003, "objects_placed": 2}.
|
|
446
|
+
# simhub.rollout(...) records those values and their metadata automatically.
|
|
447
|
+
|
|
448
|
+
with simhub.connect() as sh:
|
|
449
|
+
evaluation = sh.run_evaluation("run_...")
|
|
450
|
+
print(evaluation["metrics"], evaluation["metric_specs"])
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Reports include metrics for interrupted and failed episodes too. Nonfinite
|
|
454
|
+
numbers become JSON null and display as “Not reported”; a required metric that
|
|
455
|
+
is missing or null refuses a clean exit. Arrays and nested JSON statistics are
|
|
456
|
+
preserved. Task definitions and `local_verdict` describe evaluation inside the
|
|
457
|
+
runner. The hosted run's metrics, outcome, outcome source and checked flag remain
|
|
458
|
+
authoritative, including when a server task overrules the runner.
|
|
459
|
+
|
|
460
|
+
Older runs without this artifact still expose their recorded metrics. The SDK
|
|
461
|
+
raises a clear error for unsupported sidecar versions; the console reports that
|
|
462
|
+
error and keeps the server's metrics visible. Custom runner loops can build a
|
|
463
|
+
report with `simhub.evaluation.evaluation_report()` and publish it with
|
|
464
|
+
`Emitter.evaluation(report)` alongside their existing metric/result events.
|
|
465
|
+
|
|
466
|
+
## Hosted model credentials
|
|
467
|
+
|
|
468
|
+
`simhub model credentials set NAME` securely uploads an HF token via a hidden
|
|
469
|
+
prompt (or `--token-file` / `--stdin`). `simhub model register` creates a pinned
|
|
470
|
+
HF policy revision bound to that credential, and
|
|
471
|
+
`project.launch(policy_version_id=...)` records it on the runs. This requires
|
|
472
|
+
the matching hosted API/worker/runner deployment. See
|
|
473
|
+
[MODEL_CREDENTIALS.md](MODEL_CREDENTIALS.md) for the contract and deployment steps.
|
|
474
|
+
|
|
475
|
+
## Inspect a generated scene in the console
|
|
476
|
+
|
|
477
|
+
Publish a read-only, orbitable initial scene without running a policy:
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
import simhub
|
|
481
|
+
|
|
482
|
+
with simhub.connect() as session:
|
|
483
|
+
project = session.project("./warehouse_pick_cell")
|
|
484
|
+
preview = project.preview(env="warehouse-pick", seed=7, env_kwargs={"cell_seed": 7})
|
|
485
|
+
print(preview.get("console_url") or preview["artifact_id"])
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
The equivalent CLI entry point is `simhub launch ./warehouse_pick_cell --preview
|
|
489
|
+
--env warehouse-pick --seed 7`. Preview generation is an explicit hosted compile
|
|
490
|
+
job and can incur compute charges. The deployment must advertise a
|
|
491
|
+
`python_bundle -> glb` compiler with the project's dependencies. The operation
|
|
492
|
+
constructs the selected environment and calls `reset(seed)`; it never steps a
|
|
493
|
+
policy. Environments that declare a `scene` argument get their project's
|
|
494
|
+
`write_mjcf()` output before construction. `env_kwargs` go to the environment
|
|
495
|
+
factory, while `seed` controls reset and any required scene build.
|
|
496
|
+
|
|
497
|
+
The console's **Scenes** tab lists initial previews and compiled templates for
|
|
498
|
+
the active environment. New SDK runs with MuJoCo recorder bindings also publish
|
|
499
|
+
the scene immediately after reset: **Runs → View scene** opens that run's exact
|
|
500
|
+
snapshot, including its seed and attempt. An older run without a snapshot says
|
|
501
|
+
so. Merely opening a scene reads stored artifacts; it does not start compute.
|
|
502
|
+
|
|
503
|
+
Operators set `SIMHUB_CONSOLE_URL` to return a shareable organization-scoped
|
|
504
|
+
console link. The link contains no API key; viewers still need access to that
|
|
505
|
+
organization. Existing MJCF bundles have a limited browser fallback and an
|
|
506
|
+
explicit **Generate full preview** action when the compiler supports it.
|
|
507
|
+
|
|
508
|
+
The first version displays rigid MuJoCo geometry, authored colors, mesh normals,
|
|
509
|
+
UV-mapped 2D textures, body selection, bounds and named perspective cameras.
|
|
510
|
+
Procedural texture projections, heightfields, skins and deformables report
|
|
511
|
+
coverage warnings. It displays the physics scene rather than a separately
|
|
512
|
+
constructed Cycles set, and does not replay trajectories. GLB content is limited
|
|
513
|
+
to 64 MiB and manifests to 2 MiB. Preview failures do not change simulation
|
|
514
|
+
outcomes; recordings remain available from the run page.
|