Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
- name: Build docs
shell: bash
run: |
pip install -e . --extra-index-url http://pyp.open3dv.site:2345/simple/ --trusted-host pyp.open3dv.site
pip install -e . --extra-index-url http://pyp.open3dv.site:2345/simple/ --trusted-host pyp.open3dv.site
pip install -r docs/requirements.txt
python3 docs/scripts/sync_readme.py
cd ${GITHUB_WORKSPACE}/docs
Expand Down
4 changes: 1 addition & 3 deletions embodichain/lab/gym/envs/base_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,7 @@ def __init__(

self._setup_scene(**kwargs)

# TODO: To be removed.
if self.device.type == "cuda":
self.sim.init_gpu_physics()
self.sim.prepare_physics()

if not self.sim_cfg.headless:
self.sim.open_window()
Expand Down
91 changes: 90 additions & 1 deletion embodichain/lab/sim/cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def to_dexsim_flags(self):


@configclass
class PhysicsCfg:
class DefaultPhysicsCfg:
gravity: np.ndarray = field(default_factory=lambda: np.array([0, 0, -9.81]))
"""Gravity vector for the simulation environment."""

Expand Down Expand Up @@ -124,6 +124,95 @@ def to_dexsim_args(self) -> Dict[str, Any]:
return args


# Backwards-compatible alias for existing task configs.
PhysicsCfg = DefaultPhysicsCfg


@configclass
class NewtonPhysicsCfg:
"""Configuration for DexSim Newton physics backend."""

num_substeps: int = 10
"""Number of Newton solver substeps per EmbodiChain physics step."""

device: str | None = None
"""Newton device. If None, derived from ``SimulationManagerCfg.sim_device`` and ``gpu_id``."""

require_grad: bool = False
"""Whether to finalize the Newton model for differentiable simulation."""

use_cuda_graph: bool = True
"""Whether to use CUDA graph capture for Newton stepping when supported."""

debug_mode: bool = False
"""Whether to enable Newton debug mode."""

solver_type: Literal["mjwarp", "xpbd", "semi_implicit", "featherstone", "vbd"] = (
"semi_implicit"
)
"""Newton solver preset."""

broad_phase: Literal["nxn", "sap", "explicit"] | None = None
"""Newton collision broad-phase implementation. If None, DexSim chooses its default."""

visualizer_enabled: bool = False
"""Whether to enable the Newton visualizer."""

def to_dexsim_cfg(
self,
physics_dt: float,
sim_device: str | torch.device,
gpu_id: int,
):
"""Convert this config to ``dexsim.engine.newton_physics.NewtonCfg``."""
from dexsim.engine.newton_physics import (
FeatherstoneSolverCfg,
MJWarpSolverCfg,
NewtonCfg,
NewtonCollisionPipelineCfg,
SemiImplicitSolverCfg,
VBDSolverCfg,
XPBDSolverCfg,
)

torch_device = (
torch.device(sim_device) if isinstance(sim_device, str) else sim_device
)
device = self.device
if device is None:
device = f"cuda:{gpu_id}" if torch_device.type == "cuda" else "cpu"

solver_cfg_map = {
"mjwarp": MJWarpSolverCfg,
"xpbd": XPBDSolverCfg,
"semi_implicit": SemiImplicitSolverCfg,
"featherstone": FeatherstoneSolverCfg,
"vbd": VBDSolverCfg,
}
solver_cfg = solver_cfg_map[self.solver_type]()

if self.require_grad and self.solver_type != "semi_implicit":
logger.log_error(
"Newton gradient mode requires solver_type='semi_implicit'."
)

cfg = NewtonCfg(
dt=physics_dt,
num_substeps=self.num_substeps,
device=device,
debug_mode=self.debug_mode,
require_grad=self.require_grad,
solver_cfg=solver_cfg,
collision_pipeline_cfg=NewtonCollisionPipelineCfg(
broad_phase=self.broad_phase,
requires_grad=self.require_grad,
),
)
cfg.use_cuda_graph = self.use_cuda_graph and not self.require_grad
cfg._visualizer_enabled = self.visualizer_enabled
return cfg


@configclass
class MarkerCfg:
"""Configuration for visual markers in the simulation.
Expand Down
26 changes: 26 additions & 0 deletions embodichain/lab/sim/objects/backends/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# ----------------------------------------------------------------------------
# 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.
# ----------------------------------------------------------------------------

from .base import RigidBodyViewBase
from .default import DefaultRigidBodyView
from .newton import NewtonRigidBodyView, is_newton_scene

__all__ = [
"RigidBodyViewBase",
"DefaultRigidBodyView",
"NewtonRigidBodyView",
"is_newton_scene",
]
128 changes: 128 additions & 0 deletions embodichain/lab/sim/objects/backends/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# ----------------------------------------------------------------------------
# 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.
# ----------------------------------------------------------------------------
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Sequence

import torch

__all__ = ["RigidBodyViewBase"]


class RigidBodyViewBase(ABC):
"""Abstract interface for physics-backend rigid body data access.

All pose/velocity/acceleration data uses EmbodiChain convention:
``(x, y, z, qx, qy, qz, qw)``.
"""

# -- Lifecycle & State --------------------------------------------------

@property
@abstractmethod
def is_ready(self) -> bool:
"""Whether the backend simulation is finalized and data can be accessed."""
...

# -- Body ID Management -------------------------------------------------

@property
@abstractmethod
def body_ids(self) -> list[int]:
"""Backend body IDs for all managed entities."""
...

@property
@abstractmethod
def body_ids_tensor(self) -> torch.Tensor:
"""Body IDs as an int32 tensor on ``device``."""
...

@abstractmethod
def select_body_ids(self, indices: Sequence[int] | torch.Tensor) -> list[int]:
"""Return body IDs for the given entity indices."""
...

# -- Pose ---------------------------------------------------------------

@abstractmethod
def fetch_pose(self, body_ids: Sequence[int] | None = None) -> torch.Tensor:
"""Fetch poses as ``(N, 7)`` tensor in ``(x, y, z, qx, qy, qz, qw)``."""
...

@abstractmethod
def apply_pose(self, pose: torch.Tensor, body_ids: Sequence[int]) -> None:
"""Apply poses from ``(N, 7)`` tensor in ``(x, y, z, qx, qy, qz, qw)``."""
...

# -- Velocity -----------------------------------------------------------

@abstractmethod
def fetch_linear_velocity(
self, body_ids: Sequence[int] | None = None
) -> torch.Tensor:
"""Fetch linear velocities as ``(N, 3)`` tensor."""
...

@abstractmethod
def fetch_angular_velocity(
self, body_ids: Sequence[int] | None = None
) -> torch.Tensor:
"""Fetch angular velocities as ``(N, 3)`` tensor."""
...

@abstractmethod
def apply_linear_velocity(
self, data: torch.Tensor, body_ids: Sequence[int]
) -> None:
"""Set linear velocities from ``(N, 3)`` tensor."""
...

@abstractmethod
def apply_angular_velocity(
self, data: torch.Tensor, body_ids: Sequence[int]
) -> None:
"""Set angular velocities from ``(N, 3)`` tensor."""
...

# -- Acceleration -------------------------------------------------------

@abstractmethod
def fetch_linear_acceleration(
self, body_ids: Sequence[int] | None = None
) -> torch.Tensor:
"""Fetch linear accelerations as ``(N, 3)`` tensor."""
...

@abstractmethod
def fetch_angular_acceleration(
self, body_ids: Sequence[int] | None = None
) -> torch.Tensor:
"""Fetch angular accelerations as ``(N, 3)`` tensor."""
...

# -- Force & Torque -----------------------------------------------------

@abstractmethod
def apply_force(self, data: torch.Tensor, body_ids: Sequence[int]) -> None:
"""Apply external forces ``(N, 3)``. One-shot — consumed on next step."""
...

@abstractmethod
def apply_torque(self, data: torch.Tensor, body_ids: Sequence[int]) -> None:
"""Apply external torques ``(N, 3)``. One-shot — consumed on next step."""
...
Loading