Introduce Lab Simulation for Visualization and Robotics - #2
Merged
Conversation
The event vocabulary, the lab.sim-trace.v0 document, its summary derivation, and the facility description are documents, not runtime behavior, so they live with the other formats. lab-runtime re-exports them unchanged and keeps only the ports: EventSink, the sinks, and the clock-coupled TraceSink. lab-scene can now read traces and facilities without a lab-runtime edge.
A scene node's geometry can now be a real asset: AssetCatalog resolves station kinds, labware ids, carrier ids, and 'room' against a facility's assets directory, falling back to the dimensioned box that every consumer can draw. bundle_assets copies referenced files beside the scene and relativizes their paths, so a scene bundle is self-contained. The USD export composes assets with prim references; glTF keeps the fallback box and records the asset path in extras, since core glTF cannot reference external meshes. Nodes gain rotation_z_deg for placed stations.
facility.toml gains a [room] section (kit floor and walls, or an environment asset key) and per-station position_mm/rotation_deg, with placements validated against the room bounds. lab scene --facility checks the plan's stations, resolves real meshes from the facility's assets directory, lays stations out on the floor plan, and bundles referenced assets beside the scene. Without a facility the schematic row layout renders exactly as before.
lab scene --animated writes time samples over the same emission the static layer uses: one timecode per simulated second, labware holding home until each confirmed handoff and arriving on the destination's top two seconds later, and a synthesized pipetting head per liquid handler that follows the frames' deck coordinates and hides between programs. Every geometry prim now binds a UsdPreviewSurface material by semantic, so Omniverse, Blender, and usdview shade the bench without extra work. Animated labware under a rotated ancestor is refused, not mis-posed.
ACES tone mapping, image-based lighting from a procedural room environment, and a shadow-mapped key light replace the flat shading. Mesh geometry loads its glTF asset and swaps out the fallback box, keeping the box on failure. The facility's room shell renders inside-out so the camera sees in from anywhere, nodes honor rotation_z_deg, and the camera frames whatever was built: a bare bench or a whole room.
The player script ships inside the binary and reads the same two documents the web player and USD stage consume: it builds Blender objects from scene.json, keyframes labware and pipetting heads from sim-trace.json, lights with the built-in sky or a supplied HDRI, and renders through camera presets with EEVEE (preview) or path-traced Cycles (final). ffmpeg assembles a movie when present; frames stay either way. Blender is found, never bundled, and a missing install is an error naming the fix. The end-to-end check is gated on LAB_BLENDER, the same pattern as the Opentrons simulator tests.
Every renderer interprets the same scene and trace documents: assets are facility-owned references with box fallbacks, the facility authors the room, USD carries the animation, and the schematic tier stays first-class. Scanned environments compose in later as the room asset without changing any format.
Stations now carry cosmetic part assemblies built from the same primitives every renderer draws: the STAR gets its towers, gantry beam, back panel, front glass, and status light; the cycler its base, lid seam, vents, and light; and every station stands on a bench. Parts are children of the station node with a material vocabulary (frame, panel, glass, accent) honored by the web player, glTF, USD, and Blender; the station's own geometry stays the body the seat math measures. Running the harness against a real Blender 5.2 fixed what only a real Blender could: keyframe interpolation rides the new-keyframe preference (the fcurve API changed shape across 4/5), the EEVEE identifier is probed across generations, camera clip ranges suit millimeter scenes, script failures now fail the process (--python-exit-code), and the room shell renders without driving the camera framing. Facility and run-document errors no longer print their cause twice.
From a package directory, the three commands take no arguments: lab build compiles the manifest's default target into .lab/build, and lab simulate, lab scene, and lab render walk that target's waves in order. The facility resolves by convention: facility.toml at the package root, then the manifest's [build] facility pointer under facilities/, with --facility always winning. lab render is the whole flow in one command — it regenerates the simulation and the animated scene idempotently before Blender runs, so a fresh checkout needs only build and render. golden-gate now defaults to the workcell-star target and keeps its facility at the root, so its README flow is three bare commands. A named run directory keeps its exact previous behavior on every command.
A preview render is dominated by per-frame overhead, so --jobs splits the frame range across several Blender processes writing distinct frames into the shared directory; the timeline is identical in every chunk, so frames line up exactly. Previews default to half the cores (capped at four) and path-traced finals to one process, which already saturates the GPU. The player only touches use_nodes on datablocks that still lack a node tree, so current Blenders stop warning about its removal.
A preview render is dominated by per-frame overhead, so --jobs splits the frame range across several Blender processes writing distinct frames into the shared directory; the timeline is identical in every chunk, so frames line up exactly. Previews default to half the cores (capped at four) and path-traced finals to one process, which already saturates the GPU. The player only touches use_nodes on datablocks that still lack a node tree, so current Blenders stop warning about its removal.
Each step fingerprints its inputs (file identity, size, mtime) and the settings that shape its output, and stamps the fingerprint beside the result: simulate over the run documents and facility, scene over those plus the trace and assets, render over the scene, trace, and camera settings. Rerunning with nothing changed reports up to date and does no work; touching any input regenerates exactly the stale steps. Preview renders now default to every core instead of four.
Uniform speedup wastes most of a movie on motionless holds: a Golden Gate wave is nine hours of thermocycling around nine minutes of pipetting. lab render now warps time instead: motion plays at --speedup (default 60, slow enough to watch the head work), and every hold between motions compresses to --hold-seconds of footage, however long it really is. The warped trace lands beside the renders and the player runs it at speedup one; --uniform keeps real proportions. An animation render also owns its frames directory outright now, so stale frames from an earlier, longer cut cannot ride into the movie.
lab render only renders: it consumes the outputs of lab simulate and lab scene and names whichever is missing, so iterating on a simulation never costs frames. Two freshness bugs made the pipeline look coupled anyway: fingerprints hashed path strings as typed, so the same files went stale whenever the working directory changed, and a re-run step rewrote byte-identical output with a fresh mtime, cascading staleness downstream. Fingerprints now canonicalize their paths and every step writes only when its bytes actually changed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.