Skip to content
10 changes: 6 additions & 4 deletions agent_context/MAP.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ topics:
keywords: [BaseEnv, EmbodiedEnv, EpisodeContext, EnvCfg, register_env, list-task, run-task, run-example, target_control_frequency,
sim_steps_per_control, step_dt, environment.component, embodiment.component, file-owned physics backend,
physics_config, ControllerAction, EnvProfiler, startup_summary, dexsim_startup_info,
ExpertTrajectoryCfg, joint_command_mode, position_velocity, qpos qvel, expert action schema,
FluidReplayEnv, fluid Gym, 流体 Gym, ExpertTrajectoryCfg, joint_command_mode, position_velocity, qpos qvel, expert action schema,
task_instruction, resolve_demo_instruction,
show-task, task catalog, task gallery, 任务画廊, physical_objective, 物理目标]
paths: [topics/env-framework/env-framework.md]
Expand All @@ -72,11 +72,13 @@ topics:
- embodichain/lab/gym/envs/embodied_env.py
- embodichain/lab/gym/envs/episode_context.py
- embodichain/lab/gym/envs/expert_trajectory.py
- embodichain/lab/gym/envs/fluid_replay.py
- embodichain/lab/scripts/run_fluid_task_program.py
- embodichain/lab/gym/envs/demo.py
- embodichain/lab/gym/utils/profiler.py
watch_paths: [embodichain/lab/gym/envs/episode_context.py, embodichain/lab/gym/envs/base_env.py, embodichain/lab/gym/envs/embodied_env.py, embodichain/lab/gym/envs/expert_trajectory.py,
embodichain/lab/gym/envs/demo.py,
embodichain/lab/gym/utils/, embodichain/lab/gym/envs/task_program/, embodichain/cli/, embodichain/lab/scripts/run_task.py,
embodichain/lab/gym/envs/demo.py, embodichain/lab/gym/envs/fluid_replay.py,
embodichain/lab/scripts/run_fluid_task_program.py, embodichain/lab/gym/utils/, embodichain/lab/gym/envs/task_program/, embodichain/cli/, embodichain/lab/scripts/run_task.py,
extensions/embodichain_tasks/, tests/gym/envs/, embodichain/lab/gym/envs/objectives/,
embodichain/lab/scripts/evaluate_task_objective.py, tests/lab/scripts/test_evaluate_task_objective.py]
related_topics: [simulation-system, manager-functor, sim-visualization, task-programs, data-pipeline]
Expand Down Expand Up @@ -445,7 +447,7 @@ topics:
title: Data Assets
aliases: [data assets, data path, asset download, 数据资产, 资产下载, 数据路径]
keywords: [get_data_path, get_data_class, EmbodiChainDataset, TabletopFourBases, demo assets, EMBODICHAIN_DATA_ROOT, EMBODICHAIN_DATASET_ROOT,
CATEGORY_MODULES, embodichain.assets, ROBOT_ASSETS, 资产注册, download lock, get_registry, DefaultMaterials, list_default_materials, get_default_material_info]
CATEGORY_MODULES, embodichain.assets, ROBOT_ASSETS, 资产注册, download lock, get_registry, DefaultMaterials, list_default_materials, get_default_material_info, task_assets, PourCupAssets, PourCupDemoData]
paths: [topics/data-assets/data-assets.md]
source_of_truth:
- embodichain/data/dataset.py
Expand Down
4 changes: 4 additions & 0 deletions agent_context/topics/data-assets/data-assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ Keep the lock outside the directory repair may delete. POSIX uses `flock`;
Windows uses a polled byte lock. Direct Open3D subclasses do not automatically
receive the project's locking/repair behavior.

The `task` category registers `PourCupAssets` runtime meshes and
`PourCupDemoData` historical recordings through `assets/task_assets.py`.
Both use the shared download cache and class-prefixed paths.

The CLI honors an asset class's optional `cache_prefix` when checking readiness
and ensuring extraction; otherwise the class name is the cache entry.
The shared registry merges core category modules and `embodichain.assets`
Expand Down
2 changes: 2 additions & 0 deletions agent_context/topics/env-framework/env-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ Lightweight RL environments have a separate owner in

Read [configuration and registration](configuration.md) for task-first layout,
component ownership, path resolution, config-owned IDs, and task listing.
Read [fluid Gym integration](fluid-replay.md) for online Task Program execution
with rigid/SPH dynamics, fluid configuration and coordinate conventions.
Read [execution](execution.md) for hooks, bridge acceptance, reset ordering,
wrappers, and replay. Read [profiling](profiling.md) only for instrumentation.

Expand Down
70 changes: 70 additions & 0 deletions agent_context/topics/env-framework/fluid-replay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Fluid Gym integration

`EmbodiedEnvCfg.fluid_object` owns fixed-count fluid declarations. Physical
components own these alongside rigid entities; `config_to_cfg()` resolves
fluid point files and explicit relative rigid/visual mesh paths against the
owning component. Spawn rigid references are resolved before fluid binding.
The default pour deployment generates its fill from a local revolved profile;
no particle asset file is required. Pouring meshes use `PourCupAssets/...`
references resolved through the [data asset cache](../data-assets/data-assets.md),
so installed task packages carry configuration without mesh payloads. The runner's spacing override resamples the
same volume recipe and rejects fixed point imports. It scales explicit render
radius, mesh cell size and reconstruction kernel radius by the spacing ratio,
keeping the surface reconstruction consistent with the new particle density. Fill definitions and
in-memory template ownership are documented under
[simulation fluids](../simulation-system/fluids.md).

`managers/observations.py` provides particle state, summary and volume-in-region
observations. Particle arrays are world-frame; rigid `get_local_pose()` is
arena-local. Region measurement and `events.reset_fluid_in_container()` add
arena offsets before comparing or placing particles. Region volume is particle
count times nominal spacing cubed, not a reconstructed-surface measurement.

## Online Task Program workflow

`lab/scripts/run_fluid_task_program.py` runs the configured pour-cup deployment
through ordinary `EmbodiedEnv` and `execute_demo_episode()`. The environment
component selects DexUni with DFSPH settings nested under `sph_cfg`. Robot
drive targets, dynamic grasp contacts and fluid state
advance in one World, with no recorded trajectory input. SPH reads live rigid
boundaries; fluid reaction forces are not fed back into the rigid solver.
The fluid program measures cup lift and alignment, transports the held cup
to a receiver-relative pour pose through the existing MoveHeldObject lowerer,
then pours and returns it. The standalone Sim tutorial uses the same physical
component and relative pour target with freshly planned Atomic Action targets.

Program completion and physical qualification are separate: the runner measures
cup lift, maximum tilt, final return error and received volume. Projected effect
assurance alone does not prove grasp or pouring success. Optional trajectory
recording stores measured online state for inspection. `--seconds` qualifies
only a bounded fragment, not completion of the task.

`FluidReplayEnv` remains an explicit diagnostic utility for accepted historical
recordings; it is not the default deployment or task runner. It requires a
separate kinematic configuration, one Newton substep and fixed-root source
states, and rejects action managers and policy dataset/trajectory recording.

## Rendering and recording

The pouring environment and runner select `fast-rt`. Hybrid is rejected for
native fluid rendering: OptiX-only liquid geometry is absent from its Vulkan
primary surface. Particle-state and video-decode checks alone do not qualify
liquid visibility; use the image-based fluid regression linked below. The
default record view focuses on the cup and receiver; the mesh kernel radius
is 1.5 times the particle spacing to retain a connected surface after settling.
Its interval `record_camera` event uses the existing `record_camera_data`
functor with `fps=None`, recording independent environment views at the
control frequency. The runner only redirects the output directory and sets
the recording limit to the resolved environment count. It explicitly commits
before close, which otherwise discards pending frames. Incomplete or rejected
runs use the recorder's diagnostic purpose rather than qualifying the output.
Selected-row recording and camera consumption are owned by the
[recording functor](../manager-functor/manager-functor.md#camera-recording).
NoRender qualification removes the recorder before constructing the scene.

Production configuration/measurement checks are in
`tests/gym/envs/test_fluid_integration.py`; live isolation checks are in
`tests/sim/objects/test_fluid_batch_runtime.py` and
`tests/sim/objects/test_fluid_online_runtime.py`; actual primary-surface visibility
is checked by `tests/sim/objects/test_fluid_visibility.py`. Generated trajectories/videos
are local qualification evidence, not packaged task assets.
17 changes: 17 additions & 0 deletions agent_context/topics/manager-functor/manager-functor.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
| Observation manager + built-in functors | `managers/observation_manager.py`, `managers/observations.py` |
| Reward manager + built-in functors | `managers/reward_manager.py`, `managers/rewards.py` |
| Event manager + built-in functors | `managers/event_manager.py`, `managers/events.py` |
| Episode camera recording events | `managers/record.py` |
| Action manager + built-in terms | `managers/action_manager.py`, `managers/actions.py` |
| Dataset manager + built-in recorders | `managers/dataset_manager.py`, `managers/datasets.py`, `managers/async_datasets.py` |
| Randomization functors (event sub-type) | `managers/randomization/` (spatial, visual, physics, geometry) |
Expand Down Expand Up @@ -112,6 +113,22 @@ Event and observation calls have per-functor profiling through
`ManagerBase._call_functor()`; reward/action/dataset calls do not yet use that
helper. See [profiling](../env-framework/profiling.md).

### Camera recording

`record_camera_data` is an interval event, independent of policy dataset
recording. It renders its camera group before reading the buffers because
interval events precede observation rendering. Views use arena-relative camera
extrinsics. It retains separate owned CPU frames and episode counters per row.
It captures only the event's selected `env_ids` within `max_env_num`, preserving
their original arena IDs; `None` captures all views within that limit. Selected
save/discard only touches those rows. `fps=None` follows control frequency for
an every-step recording event; an explicit fps fixes the output cadence.

Reset commits or discards frames via `save_and_clear` / `discard_and_clear`.
Close discards uncommitted frames, so a host ending without reset must explicitly
commit its last episode. Source and regression tests live in `record.py` and
`tests/gym/envs/managers/test_record.py`.

## Change sites and validation

Change the selected manager's dispatch for scheduling changes; change its
Expand Down
28 changes: 28 additions & 0 deletions agent_context/topics/simulation-system/fluids.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,31 @@ Focused checks are `tests/compute/test_particles.py`,
`tests/sim/objects/test_fluid_runtime.py`; `test_fluid_visibility.py` checks
camera pixels after stepping and direct state writes. Tutorial checks remain
syntax/CLI smoke checks rather than tutorial import tests.

## Online task and replay layer

The pouring task uses DexUni with nested DFSPH, a shared simulation clock and
one-way rigid-to-fluid coupling. When no joint ownership was explicitly authored,
the fluid setup assigns every finalized joint to MuJoCo before solver construction,
which covers a robot and a free cup across replicated worlds. Explicit ownership
selectors remain caller-owned. The current native prototype requires the pure
MuJoCo rigid path when bodies exist and does not support fluid reaction forces.

`RigidObjectCfg.particle_collision_shape` puts a particle-only SDF boundary on
the same body as its rigid contact hulls. Where DexSim exposes
`NewtonCollisionDesc.contributes_to_mass`, the boundary explicitly excludes
itself from mass. Older builds need explicit mass and inertia for dynamic bodies;
the pouring cup authors its COM and body-frame inertia from its contact hulls.
Static/kinematic boundaries retain their respective motion semantics. Independent
`visual_shape` settings preserve the render mesh.

Replay may write `RigidObject.set_kinematic_state()` or register a manager-owned
kinematic rigid trajectory before preparation. These host controls disable graph
replay and rewind selected clocks on object reset; they do not qualify online
grasping. Normal Gym reset restores or refills selected fluid rows without
stepping peer worlds. Online action execution supplies physical settling.

The robot demonstrations, task assets, Gym declarations, observations, recording,
Task Program runner and replay belong to the pouring task layer. See
[fluid Gym integration](../env-framework/fluid-replay.md) and the task's
`extensions/embodichain_tasks/configs/tasks/manipulation/tableware/pour_cup/README.md`.
23 changes: 23 additions & 0 deletions docs/source/api_reference/public_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2875,3 +2875,26 @@ Asset generalization contracts
:maxdepth: 2

asset_generalization

Fluid task interfaces
=====================

The pouring task supplies downloadable scene assets and a diagnostic fluid
replay environment. Its runner executes the configured Task Program against
live one-way rigid-to-fluid simulation.

.. autosummary::

embodichain.data.assets.task_assets.PourCupAssets
embodichain.data.assets.task_assets.PourCupDemoData
embodichain.lab.gym.envs.fluid_replay.FluidReplayEnv
embodichain.lab.scripts.run_fluid_task_program.main

.. autoclass:: embodichain.data.assets.task_assets.PourCupAssets

.. autoclass:: embodichain.data.assets.task_assets.PourCupDemoData

.. autoclass:: embodichain.lab.gym.envs.fluid_replay.FluidReplayEnv
:members:

.. autofunction:: embodichain.lab.scripts.run_fluid_task_program.main
1 change: 1 addition & 0 deletions embodichain/data/_asset_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"embodichain.data.assets.so101_assets",
),
"scene": ("embodichain.data.assets.scene_assets",),
"task": ("embodichain.data.assets.task_assets",),
"w1": ("embodichain.data.assets.w1_assets",),
}

Expand Down
3 changes: 2 additions & 1 deletion embodichain/data/assets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# limitations under the License.
# ----------------------------------------------------------------------------

"""Preset configuration objects for scene assets (robots, end-effectors, objects, materials, sensors, planners, solvers, demo scenes) ready to reference from task configs."""
"""Preset configuration objects for scene assets (robots, end-effectors, objects, materials, sensors, planners, solvers, demo scenes and task bundles) ready to reference from task configs."""

from __future__ import annotations

Expand All @@ -29,4 +29,5 @@
from .planner_assets import *

from .locomotion_assets import *
from .task_assets import *
from .so101_assets import *
78 changes: 78 additions & 0 deletions embodichain/data/assets/task_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------

"""Downloadable task meshes and reference demonstration data."""

from __future__ import annotations

import open3d as o3d

from embodichain.data.constants import (
EMBODICHAIN_DEFAULT_DATA_ROOT,
EMBODICHAIN_DOWNLOAD_PREFIX,
)
from embodichain.data.dataset import EmbodiChainDataset

__all__ = ["PourCupAssets", "PourCupDemoData"]

_HUB_PREFIX = (
"https://huggingface.co/datasets/DexForceAI/embodichain_data/resolve/main/"
)


class _TaskAsset(EmbodiChainDataset):
"""Download one task archive through the shared locked dataset cache."""

archive_md5: str

def __init__(self, data_root: str | None = None) -> None:
"""Initialize and extract the task bundle.

Args:
data_root: Optional cache root overriding the EmbodiChain default.
"""
prefix = type(self).__name__
filename = f"task_assets/{prefix}.zip"
urls = [f"{EMBODICHAIN_DOWNLOAD_PREFIX}{filename}"]
if EMBODICHAIN_DOWNLOAD_PREFIX != _HUB_PREFIX:
urls.append(f"{_HUB_PREFIX}{filename}")
descriptor = o3d.data.DataDescriptor(urls, self.archive_md5)
super().__init__(
prefix,
descriptor,
EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root,
)


class PourCupAssets(_TaskAsset):
"""Cup, fluid boundary, receiver, table and floor meshes for cup pouring.

Resolve individual meshes with ``get_data_path("PourCupAssets/cup.glb")``.
The bundle contains no initial-fluid particle file.
"""

archive_md5 = "f97857b639189be4daf717a873ab84cf"


class PourCupDemoData(_TaskAsset):
"""Recorded pouring trajectory, videos and original validation evidence.

``trajectory.pt`` is a historical MuJoCo recording for diagnostic replay.
Online pouring tutorials and tasks do not load it. ``particles_30.npz`` is a final reference snapshot and is not used
to initialize fluids. Resolve files through ``get_data_path()``.
"""

archive_md5 = "8eb96cb756ac08efe403417f50ad2bf2"
8 changes: 8 additions & 0 deletions embodichain/lab/gym/envs/embodied_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import numpy as np
import gymnasium as gym

from embodichain.lab.sim.cfg.fluid import FluidObjectCfg

from dataclasses import MISSING, fields
from typing import (
TYPE_CHECKING,
Expand Down Expand Up @@ -214,6 +216,9 @@ class EnvLightCfg:

articulation: List[ArticulationCfg] = []

fluid_object: list[FluidObjectCfg] = []
"""Fixed-count fluids owned by the physical environment component."""

events: Union[object, None] = None
"""Event settings. Defaults to None, in which case no events are applied through the event manager.

Expand Down Expand Up @@ -2789,6 +2794,9 @@ def _setup_interactive_objects(self) -> None:
for cfg in self.cfg.rigid_object_group:
self.sim.add_rigid_object_group(cfg=cfg)

for cfg in self.cfg.fluid_object:
self.sim.add_fluid_object(cfg)

def preview_sensor_data(
self,
name: str,
Expand Down
Loading