diff --git a/docs/source/index.rst b/docs/source/index.rst index b784adce..c3a47f2f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -48,6 +48,7 @@ Table of Contents :caption: Resources :glob: + resources/assets.md resources/robot/index* resources/task/index* resources/roadmap.md diff --git a/docs/source/resources/assets.md b/docs/source/resources/assets.md new file mode 100644 index 00000000..c2ac95fd --- /dev/null +++ b/docs/source/resources/assets.md @@ -0,0 +1,118 @@ + +# Data Assets + +EmbodiChain provides a comprehensive set of pre-built data assets hosted on HuggingFace, covering robots, end-effectors, objects, scenes, materials, and more. Assets are automatically downloaded on first use, but you can also pre-download them using the built-in CLI tool. + +## Data Root Directory + +By default, assets are stored in `~/.cache/embodichain_data`. You can override this by setting the `EMBODICHAIN_DATA_ROOT` environment variable: + +```bash +export EMBODICHAIN_DATA_ROOT=/mnt/shared/embodichain_data +``` + +Similarly, the dataset recording root (used by `LeRobotRecorder`) defaults to `~/.cache/embodichain_datasets` and can be overridden with: + +```bash +export EMBODICHAIN_DATASET_ROOT=/mnt/shared/embodichain_datasets +``` + +## Download CLI + +The `embodichain.data` module provides a command-line interface for managing assets. + +### List Available Assets + +```bash +# List all assets across every category +python -m embodichain.data list + +# List assets in a specific category +python -m embodichain.data list --category robot +``` + +The output shows each asset name and whether it has already been downloaded (`✓`): + +```text +[robot] (18 assets) + [✓] ABB + [ ] ARX5 + [ ] Agile + [✓] Aubo + ... + +Data root: /home/user/.cache/embodichain_data +``` + +### Download Assets + +```bash +# Download a single asset by name +python -m embodichain.data download --name CobotMagicArm + +# Download all assets in a category +python -m embodichain.data download --category robot + +# Download everything +python -m embodichain.data download --all +``` + +Downloaded files are saved to `/download/` and automatically extracted to `/extract/`. Non-zip assets (e.g. `.glb` files) are copied into the extract directory. + +## Asset Categories + +| Category | Description | Examples | +|-------------|------------------------------------------------|-------------------------------------------------| +| `robot` | Robot URDF models | CobotMagicArm, Franka, UniversalRobots, UnitreeH1 | +| `eef` | End-effector / gripper models | DH_PGC_140_50, Robotiq2F85, InspireHand | +| `obj` | Manipulable objects and furniture | ShopTableSimple, CoffeeCup, TableWare | +| `scene` | Full scene environments | SceneData, EmptyRoom | +| `materials` | Rendering materials, IBL, and backgrounds | SimResources, CocoBackground | +| `w1` | DexForce W1 humanoid robot and components | DexforceW1V021, DexforceW1ChassisV021 | +| `demo` | Demo scene data | ScoopIceNewEnv, MultiW1Data | + +## Using Assets in Code + +Use `get_data_path` to resolve asset paths in your configuration. It accepts a relative path in the format `/`: + +```python +from embodichain.data import get_data_path + +# Resolves to the URDF file, downloading if necessary +urdf_path = get_data_path("CobotMagicArm/CobotMagicWithGripperV100.urdf") +``` + +`get_data_path` resolves paths in the following order: + +1. **Absolute path** — returned as-is. +2. **Local data root** — if the file exists under `EMBODICHAIN_DATA_ROOT`, it is returned immediately without triggering a download. +3. **Data-class download** — falls back to the registered asset class, which downloads and extracts the asset from HuggingFace. + +You can also instantiate asset classes directly: + +```python +from embodichain.data.assets import CobotMagicArm + +dataset = CobotMagicArm() +print(dataset.extract_dir) # Path to extracted files +``` + +## Adding Custom Assets + +To add a new asset: + +1. **Create a class** in the appropriate file under `embodichain/data/assets/` (e.g., `robot_assets.py` for a robot): + +```python +class MyRobot(EmbodiChainDataset): + def __init__(self, data_root: str = None): + data_descriptor = o3d.data.DataDescriptor( + os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "MyRobot.zip"), + "", + ) + prefix = type(self).__name__ + path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root + super().__init__(prefix, data_descriptor, path) +``` + +2. The class is automatically discovered by the download CLI and `get_data_path` — no additional registration is needed. diff --git a/embodichain/data/__main__.py b/embodichain/data/__main__.py new file mode 100644 index 00000000..b9232282 --- /dev/null +++ b/embodichain/data/__main__.py @@ -0,0 +1,21 @@ +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +"""Allow ``python -m embodichain.data`` to invoke the download CLI.""" + +from embodichain.data.download import main + +main() diff --git a/embodichain/data/assets/demo_assets.py b/embodichain/data/assets/demo_assets.py index 83c5da91..9b00a654 100644 --- a/embodichain/data/assets/demo_assets.py +++ b/embodichain/data/assets/demo_assets.py @@ -34,7 +34,7 @@ def __init__(self, data_root: str = None): ), "e92734a9de0f64be33a11fbda0fbd3b6", ) - prefix = "ScoopIceNewEnv" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -46,6 +46,6 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, demo_assets, "multi_w1_demo.zip"), "984e8fa3aa05cb36a1fd973a475183ed", ) - prefix = "MultiW1Data" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) diff --git a/embodichain/data/assets/eef_assets.py b/embodichain/data/assets/eef_assets.py index 60ee9289..b2644712 100644 --- a/embodichain/data/assets/eef_assets.py +++ b/embodichain/data/assets/eef_assets.py @@ -50,7 +50,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, eef_assets, "DH_PGC_140_50.zip"), "c2a642308a76e99b1b8b7cb3a11c5df3", ) - prefix = "DH_PGC_140_50" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -79,7 +79,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, eef_assets, "DH_PGI_140_80.zip"), "05a1a08b13c6250cc12affeeda3a08ba", ) - prefix = "DH_PGI_140_80" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -111,7 +111,7 @@ def __init__(self, data_root: str = None): ), "3a9ab5f32639e03afb38dc033b44bb62", ) - prefix = "DH_PGC_140_50_M" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -141,7 +141,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, eef_assets, "DH_AG95.zip"), "34b6f3c2f649697ea7f12814b6a50529", ) - prefix = "DH_AG95" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -170,7 +170,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, eef_assets, "ZH_CTM2F110.zip"), "0e7c3310425609797fe010b2a76fe465", ) - prefix = "ZH_CTM2F110" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -203,7 +203,7 @@ def __init__(self, data_root: str = None): ), "ff9ac77e7e1493fd32d40c87fecbee6c", ) - prefix = "BrainCoHandRevo1" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -238,7 +238,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, eef_assets, "InspireHand.zip"), "c60132a6f03866fb021cca5b6d72845e", ) - prefix = "InspireHand" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -271,7 +271,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, eef_assets, "Robotiq.zip"), "9cc84f3a2bfc3a80f428b8ed6864fbeb", ) - prefix = "Robotiq" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -300,7 +300,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, eef_assets, "Robotiq2F85.zip"), "53ecbf2c953f43f1134aa7223e592292", ) - prefix = "Robotiq2F85" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -329,7 +329,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, eef_assets, "WheelTecFA2F.zip"), "feaf13f25b1c6ce58d011b1f2fa72f58", ) - prefix = "WheelTecFA2F" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) diff --git a/embodichain/data/assets/materials.py b/embodichain/data/assets/materials.py index 9b04b60d..8243cb8a 100644 --- a/embodichain/data/assets/materials.py +++ b/embodichain/data/assets/materials.py @@ -39,7 +39,7 @@ def __init__(self, data_root: str = None): ), "53c054b3ae0857416dc52632eb562c12", ) - prefix = "SimResources" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -109,7 +109,7 @@ def __init__(self, data_root: str = None): ), "fda82404a317281263bd5849e9eb31a1", ) - prefix = "CocoBackground" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) diff --git a/embodichain/data/assets/obj_assets.py b/embodichain/data/assets/obj_assets.py index 4790a26b..e81fd252 100644 --- a/embodichain/data/assets/obj_assets.py +++ b/embodichain/data/assets/obj_assets.py @@ -35,7 +35,7 @@ def __init__(self, data_root: str = None): ), "e3061ee024de7840f773b70140dcd43f", ) - prefix = "ShopTableSimple" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -49,7 +49,7 @@ def __init__(self, data_root: str = None): ), "42ad2be8cd0caddcf9bfbf106b7783f3", ) - prefix = "CircleTableSimple" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -61,7 +61,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "plastic_bin.zip"), "21e00083689a4a3c4e4ae3fd89c61e55", ) - prefix = "PlasticBin" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -73,7 +73,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "chair.zip"), "2a971a92e0956e72f262308a1054dc73", ) - prefix = "Chair" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -87,7 +87,7 @@ def __init__(self, data_root: str = None): ), "ceafb87f8177609f87aaa6779fcbb9a3", ) - prefix = "ContainerMetal" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -101,7 +101,7 @@ def __init__(self, data_root: str = None): ), "966b648bca16823ee91525847c183973", ) - prefix = "SimpleBoxDrawer" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -113,7 +113,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "adriano_table.zip"), "8453583a9a1a9d04d50268f8a3da554f", ) - prefix = "AdrianoTable" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -125,7 +125,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "CoffeeCup.zip"), "f05fce385826414c15e19df3b75dc886", ) - prefix = "CoffeeCup" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -139,7 +139,7 @@ def __init__(self, data_root: str = None): ), "b03d9006503d27b75ddeb06d31b2c7a5", ) - prefix = "SlidingBoxDrawer" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -151,7 +151,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "AluminumTable.glb"), "02991d36ca9b70f019ed330a61143aa9", ) - prefix = "AluminumTable" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -163,7 +163,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "ToyDuck.zip"), "2f5c00ba487edf34ad668f7257c0264e", ) - prefix = "ToyDuck" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -175,7 +175,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "PaperCup.zip"), "359d13af8c5f31ad3226d8994a1a7198", ) - prefix = "PaperCup" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -187,7 +187,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "lianguijie.zip"), "2387589040a4d3f2676b622362452242", ) - prefix = "ChainRainSec" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -199,7 +199,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "tableware.zip"), "403e340fc0e4996c002ee774f89cd236", ) - prefix = "TableWare" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -211,7 +211,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "ScannedBottle.zip"), "d2b2d4deb7b463a734af099f7624b4af", ) - prefix = "ScannedBottle" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -223,7 +223,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, obj_assets, "sugar_box_usd.zip"), "a1bc5075512cedecd08af4f9c3e8f636", ) - prefix = "SugarBox" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) diff --git a/embodichain/data/assets/robot_assets.py b/embodichain/data/assets/robot_assets.py index 821f78ca..55cd17a7 100644 --- a/embodichain/data/assets/robot_assets.py +++ b/embodichain/data/assets/robot_assets.py @@ -58,7 +58,7 @@ def __init__(self, data_root: str = None): ), "14af3e84b74193680899a59fc74e8337", ) - prefix = "CobotMagicArm" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -87,7 +87,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "RidgeBack.zip"), "f03e1a6f4c781ad8957a88bdb010e9b6", ) - prefix = "RidgeBack" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -118,7 +118,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "UnitreeH1.zip"), "339417cef5051a912693f3c64d29dddc", ) - prefix = "UnitreeH1" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -146,7 +146,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "H1_usd.zip"), "9fc19f8c8b4a49398ec661e6ea9877ee", ) - prefix = "UnitreeH1Usd" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -177,7 +177,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "ABB.zip"), "ea6df4983982606c43387783e5fb8c05", ) - prefix = "ABB" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -208,7 +208,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "Motoman.zip"), "ee5f16cfce34d8e2cb996fcff8a25986", ) - prefix = "Motoman" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -239,7 +239,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "KUKA.zip"), "da7a2dfd0db3f486e407f038d25c7537", ) - prefix = "KUKA" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -270,7 +270,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "Fanuc.zip"), "0a1c562f4719f7cdc1b24545fec4a301", ) - prefix = "Fanuc" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -311,7 +311,7 @@ def __init__(self, data_root: str = None): ), "4dd4ee5a86125364a9e8cbcd4aafc31a", ) - prefix = "UniversalRobots" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -342,7 +342,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "Rokae.zip"), "fbfb852d6139e94b7c422771542f988f", ) - prefix = "Rokae" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -378,7 +378,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "FrankaV2.zip"), "f0675b9da98126bc3d4e18c98ef5e06c", ) - prefix = "Franka" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -407,7 +407,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "Agile.zip"), "fd47d7ab8a4d13960fd76e59544ba836", ) - prefix = "Agile" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -438,7 +438,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "Hans.zip"), "c867c406e3dffd6982fd0a15e7dc7e29", ) - prefix = "Hans" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -467,7 +467,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "Aubo.zip"), "2574649cd199c11267cc0f4aeac65557", ) - prefix = "Aubo" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -496,7 +496,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "RainbowY1.zip"), "5979a3aaadb5de6488b13765d523564f", ) - prefix = "RainbowY1" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -528,7 +528,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "cart_pole.zip"), "9d185eb18b19f9c95153e01943c5b0a2", ) - prefix = "cart_pole" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -557,7 +557,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, robot_assets, "ARX5_2.zip"), "6fc1d6a5a35b7926ba6325c0d20f5bc3", ) - prefix = "ARX5" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) diff --git a/embodichain/data/assets/scene_assets.py b/embodichain/data/assets/scene_assets.py index 0bb3b1c1..5b7b90bb 100644 --- a/embodichain/data/assets/scene_assets.py +++ b/embodichain/data/assets/scene_assets.py @@ -50,7 +50,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, scene_assets, "SceneData.zip"), "fb46e4694cc88886fc785704e891a68a", ) - prefix = "SceneData" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -61,7 +61,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, scene_assets, "empty_room.zip"), "612ffead4fac95114bec2e3812469f96", ) - prefix = "EmptyRoom" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) diff --git a/embodichain/data/assets/w1_assets.py b/embodichain/data/assets/w1_assets.py index 2b1eee93..d6ec94ce 100644 --- a/embodichain/data/assets/w1_assets.py +++ b/embodichain/data/assets/w1_assets.py @@ -68,7 +68,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, w1_assets, "DexforceW1V021.zip"), "3cc3a0bfd1c50ebed5bee9dadeee6756", ) - prefix = "DexforceW1V021" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -94,7 +94,7 @@ def __init__(self, data_root: str = None): ), "06ec5dfa76dc69160d7ff9bc537a6a7b", ) - prefix = "DexforceW1V021_INDUSTRIAL_DH_PGC_GRIPPER_M" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -120,7 +120,7 @@ def __init__(self, data_root: str = None): ), "ef19d247799e79233863b558c47b32cd", ) - prefix = "DexforceW1V021_ANTHROPOMORPHIC_BRAINCO_HAND_REVO1" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -132,7 +132,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, w1_assets, "W1_Chassis_v021.zip"), "6b0517a4d92a572988641d46269d063f", ) - prefix = "DexforceW1ChassisV021" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -144,7 +144,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, w1_assets, "W1_Torso_v021.zip"), "4f762a3ae6ef2acbe484c915cf80da7b", ) - prefix = "DexforceW1TorsoV021" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -156,7 +156,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, w1_assets, "W1_Eyes_v021.zip"), "80e0b86ef2e934f439c99b79074f6f3c", ) - prefix = "DexforceW1EyesV021" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -168,7 +168,7 @@ def __init__(self, data_root: str = None): os.path.join(EMBODICHAIN_DOWNLOAD_PREFIX, w1_assets, "W1_Head_v021.zip"), "ba72805828c5fd62ad55d6a1458893d0", ) - prefix = "DexforceW1HeadV021" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -182,7 +182,7 @@ def __init__(self, data_root: str = None): ), "c3cacda7bd36389ed98620047bff6216", ) - prefix = "DexforceW1LeftArm1V021" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -196,7 +196,7 @@ def __init__(self, data_root: str = None): ), "456c9495748171003246a3f6626bb0db", ) - prefix = "DexforceW1RightArm2V021" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -210,7 +210,7 @@ def __init__(self, data_root: str = None): ), "b99bd0587cc9a36fed3cdaa4f9fd62e7", ) - prefix = "DexforceW1LeftArm2V021" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) @@ -224,7 +224,7 @@ def __init__(self, data_root: str = None): ), "d9f25b2d5244ca5a859040327273a99e", ) - prefix = "DexforceW1RightArm1V021" + prefix = type(self).__name__ path = EMBODICHAIN_DEFAULT_DATA_ROOT if data_root is None else data_root super().__init__(prefix, data_descriptor, path) diff --git a/embodichain/data/constants.py b/embodichain/data/constants.py index d56817e8..692a6fd3 100644 --- a/embodichain/data/constants.py +++ b/embodichain/data/constants.py @@ -15,13 +15,18 @@ # ---------------------------------------------------------------------------- +import os from pathlib import Path EMBODICHAIN_DOWNLOAD_PREFIX = ( "https://hf-mirror.com/datasets/dexforce/embodichain_data/resolve/main/" ) -EMBODICHAIN_DEFAULT_DATA_ROOT = str(Path.home() / ".cache" / "embodichain_data") -EMBODICHAIN_DEFAULT_DATASET_ROOT = str(Path.home() / ".cache" / "embodichain_datasets") +EMBODICHAIN_DEFAULT_DATA_ROOT = os.environ.get( + "EMBODICHAIN_DATA_ROOT", str(Path.home() / ".cache" / "embodichain_data") +) +EMBODICHAIN_DEFAULT_DATASET_ROOT = os.environ.get( + "EMBODICHAIN_DATASET_ROOT", str(Path.home() / ".cache" / "embodichain_datasets") +) EMBODICHAIN_DEFAULT_DATABASE_ROOT = str( Path.home() / ".cache" / "embodichain" / "database" ) diff --git a/embodichain/data/dataset.py b/embodichain/data/dataset.py index 97be04d8..694ca5c4 100644 --- a/embodichain/data/dataset.py +++ b/embodichain/data/dataset.py @@ -153,8 +153,16 @@ def get_data_class(dataset_name: str, extra_modules: list[str] | None = None): def get_data_path(data_path_in_config: str) -> str: """Get the absolute path of the data file. + Resolution order: + 1. If ``data_path_in_config`` is an absolute path, return it directly. + 2. If a matching file/directory exists under ``EMBODICHAIN_DEFAULT_DATA_ROOT`` + (which can be overridden via the ``EMBODICHAIN_DATA_ROOT`` environment + variable), return that path. + 3. Otherwise, resolve via the registered data-class download mechanism. + Args: - data_path_in_config (str): The dataset path in the format "${dataset_name}/subpath". + data_path_in_config (str): The dataset path in the format + ``"dataset_name/subpath"``. Returns: str: The absolute path of the data file. @@ -162,11 +170,18 @@ def get_data_path(data_path_in_config: str) -> str: if os.path.isabs(data_path_in_config): return data_path_in_config + # Try resolving under the user-configurable data root first + from embodichain.data.constants import EMBODICHAIN_DEFAULT_DATA_ROOT + + local_path = os.path.join(EMBODICHAIN_DEFAULT_DATA_ROOT, data_path_in_config) + if os.path.exists(local_path): + return local_path + + # Fall back to the data-class download mechanism split_str = data_path_in_config.split("/") dataset_name = split_str[0] sub_path = os.path.join(*split_str[1:]) - # Use the optimized get_data_class function data_class = get_data_class(dataset_name) data_obj = data_class() data_dir = data_obj.extract_dir diff --git a/embodichain/data/download.py b/embodichain/data/download.py new file mode 100644 index 00000000..f0d5d4b5 --- /dev/null +++ b/embodichain/data/download.py @@ -0,0 +1,252 @@ +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +"""CLI tool for pre-downloading EmbodiChain data assets. + +Usage:: + + # List all available assets + python -m embodichain.data.download list + + # List assets in a specific category + python -m embodichain.data.download list --category robot + + # Download a specific asset by name + python -m embodichain.data.download download --name CobotMagicArm + + # Download all assets in a category + python -m embodichain.data.download download --category robot + + # Download everything + python -m embodichain.data.download download --all +""" + +from __future__ import annotations + +import argparse +import importlib +import inspect +import os +import shutil +import sys + +import open3d as o3d + +from embodichain.data.constants import EMBODICHAIN_DEFAULT_DATA_ROOT + +# Mapping from category name to the module path that defines the asset classes. +CATEGORY_MODULES: dict[str, str] = { + "demo": "embodichain.data.assets.demo_assets", + "eef": "embodichain.data.assets.eef_assets", + "materials": "embodichain.data.assets.materials", + "obj": "embodichain.data.assets.obj_assets", + "robot": "embodichain.data.assets.robot_assets", + "scene": "embodichain.data.assets.scene_assets", + "w1": "embodichain.data.assets.w1_assets", +} + + +def _get_asset_classes(module_path: str) -> list[tuple[str, type]]: + """Return (name, cls) pairs for all DownloadDataset subclasses in *module_path*.""" + module = importlib.import_module(module_path) + results: list[tuple[str, type]] = [] + for name, obj in inspect.getmembers(module, inspect.isclass): + if ( + issubclass(obj, o3d.data.DownloadDataset) + and obj is not o3d.data.DownloadDataset + and obj.__module__ == module.__name__ + ): + results.append((name, obj)) + results.sort(key=lambda x: x[0]) + return results + + +def get_registry() -> dict[str, list[tuple[str, type]]]: + """Build ``{category: [(class_name, class), ...]}`` for every category.""" + registry: dict[str, list[tuple[str, type]]] = {} + for category, module_path in CATEGORY_MODULES.items(): + registry[category] = _get_asset_classes(module_path) + return registry + + +def find_asset_class( + name: str, registry: dict[str, list[tuple[str, type]]] +) -> tuple[str, type] | None: + """Find an asset class by name (case-insensitive) across all categories.""" + name_lower = name.lower() + for category, assets in registry.items(): + for cls_name, cls in assets: + if cls_name.lower() == name_lower: + return category, cls + return None + + +# --------------------------------------------------------------------------- +# Download helpers +# --------------------------------------------------------------------------- + + +def _ensure_extract(data_obj: o3d.data.DownloadDataset, prefix: str) -> None: + """For non-zip assets, copy the downloaded file into the extract directory. + + ``o3d.data.DownloadDataset`` extracts zip archives automatically but + leaves single-file downloads (e.g. ``.glb``) only in the download + directory. This helper copies them to the extract tree so that + ``get_data_path`` can find them under ``/extract//``. + """ + extract_dir = os.path.join(EMBODICHAIN_DEFAULT_DATA_ROOT, "extract", prefix) + if os.path.exists(extract_dir) and os.listdir(extract_dir): + return # already extracted + + download_dir = os.path.join(EMBODICHAIN_DEFAULT_DATA_ROOT, "download", prefix) + if not os.path.isdir(download_dir): + return + + os.makedirs(extract_dir, exist_ok=True) + for item in os.listdir(download_dir): + src = os.path.join(download_dir, item) + dst = os.path.join(extract_dir, item) + if os.path.isdir(src): + shutil.copytree(src, dst, dirs_exist_ok=True) + else: + shutil.copy2(src, dst) + + print(f" Copied non-zip asset to extract dir: {extract_dir}") + + +def download_asset(cls_name: str, cls: type) -> None: + """Instantiate an asset class to trigger download, then ensure extraction.""" + print(f" Downloading {cls_name} ...") + try: + data_obj = cls() + _ensure_extract(data_obj, cls_name) + print(f" ✓ {cls_name} ready") + except Exception as exc: + print(f" ✗ {cls_name} failed: {exc}", file=sys.stderr) + + +# --------------------------------------------------------------------------- +# CLI commands +# --------------------------------------------------------------------------- + + +def cmd_list(args: argparse.Namespace) -> None: + """List available assets.""" + registry = get_registry() + categories = [args.category] if args.category else sorted(registry) + + for category in categories: + assets = registry.get(category) + if assets is None: + print(f"Unknown category: {category}", file=sys.stderr) + print( + f"Available categories: {', '.join(sorted(registry))}", file=sys.stderr + ) + sys.exit(1) + + print(f"\n[{category}] ({len(assets)} assets)") + for cls_name, _ in assets: + # Show whether it is already downloaded + extract_dir = os.path.join( + EMBODICHAIN_DEFAULT_DATA_ROOT, "extract", cls_name + ) + status = ( + "✓" if os.path.isdir(extract_dir) and os.listdir(extract_dir) else " " + ) + print(f" [{status}] {cls_name}") + + print(f"\nData root: {EMBODICHAIN_DEFAULT_DATA_ROOT}") + + +def cmd_download(args: argparse.Namespace) -> None: + """Download assets by name, category, or everything.""" + registry = get_registry() + + targets: list[tuple[str, type]] = [] + + if args.all: + for assets in registry.values(): + targets.extend(assets) + elif args.category: + assets = registry.get(args.category) + if assets is None: + print(f"Unknown category: {args.category}", file=sys.stderr) + print( + f"Available categories: {', '.join(sorted(registry))}", file=sys.stderr + ) + sys.exit(1) + targets.extend(assets) + elif args.name: + result = find_asset_class(args.name, registry) + if result is None: + print(f"Asset '{args.name}' not found.", file=sys.stderr) + print("Use 'list' to see available assets.", file=sys.stderr) + sys.exit(1) + _category, cls = result + targets.append((args.name, cls)) + else: + print("Specify --name, --category, or --all.", file=sys.stderr) + sys.exit(1) + + print(f"Data root: {EMBODICHAIN_DEFAULT_DATA_ROOT}") + print(f"Downloading {len(targets)} asset(s) ...\n") + + for cls_name, cls in targets: + download_asset(cls_name, cls) + + print(f"\nDone. {len(targets)} asset(s) processed.") + + +def main() -> None: + parser = argparse.ArgumentParser( + prog="embodichain.data.download", + description="Pre-download EmbodiChain data assets from HuggingFace.", + ) + subparsers = parser.add_subparsers(dest="command") + + # --- list --- + list_parser = subparsers.add_parser("list", help="List available assets.") + list_parser.add_argument( + "--category", + choices=sorted(CATEGORY_MODULES), + help="Show only assets in this category.", + ) + + # --- download --- + dl_parser = subparsers.add_parser("download", help="Download assets.") + dl_group = dl_parser.add_mutually_exclusive_group(required=True) + dl_group.add_argument("--name", help="Download a single asset by class name.") + dl_group.add_argument( + "--category", + choices=sorted(CATEGORY_MODULES), + help="Download all assets in a category.", + ) + dl_group.add_argument( + "--all", action="store_true", help="Download every registered asset." + ) + + args = parser.parse_args() + if args.command == "list": + cmd_list(args) + elif args.command == "download": + cmd_download(args) + else: + parser.print_help() + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/embodichain/lab/sim/robots/dexforce_w1/utils.py b/embodichain/lab/sim/robots/dexforce_w1/utils.py index 93278825..c5ebbd0d 100644 --- a/embodichain/lab/sim/robots/dexforce_w1/utils.py +++ b/embodichain/lab/sim/robots/dexforce_w1/utils.py @@ -43,12 +43,12 @@ class ChassisManager: def __init__(self): - self.urdf_paths = { - DexforceW1Version.V021: get_data_path("DexforceW1ChassisV021/chassis.urdf"), + self._urdf_rel_paths = { + DexforceW1Version.V021: "DexforceW1ChassisV021/chassis.urdf", } def get_urdf(self, version=DexforceW1Version.V021): - return self.urdf_paths[version] + return get_data_path(self._urdf_rel_paths[version]) def get_config(self, version=DexforceW1Version.V021): return { @@ -61,13 +61,13 @@ def get_config(self, version=DexforceW1Version.V021): class TorsoManager: def __init__(self): - self.urdf_paths = { - DexforceW1Version.V021: get_data_path("DexforceW1TorsoV021/torso.urdf"), + self._urdf_rel_paths = { + DexforceW1Version.V021: "DexforceW1TorsoV021/torso.urdf", } self.joint_names = ["ANKLE", "KNEE", "BUTTOCK", "WAIST"] def get_urdf(self, version=DexforceW1Version.V021): - return self.urdf_paths[version] + return get_data_path(self._urdf_rel_paths[version]) def get_config(self, version=DexforceW1Version.V021): return { @@ -80,13 +80,13 @@ def get_config(self, version=DexforceW1Version.V021): class HeadManager: def __init__(self): - self.urdf_paths = { - DexforceW1Version.V021: get_data_path("DexforceW1HeadV021/head.urdf"), + self._urdf_rel_paths = { + DexforceW1Version.V021: "DexforceW1HeadV021/head.urdf", } self.joint_names = ["NECK1", "NECK2"] def get_urdf(self, version=DexforceW1Version.V021): - return self.urdf_paths[version] + return get_data_path(self._urdf_rel_paths[version]) def get_config(self, version=DexforceW1Version.V021): return { @@ -99,12 +99,12 @@ def get_config(self, version=DexforceW1Version.V021): class EyesManager: def __init__(self): - self.urdf_paths = { - DexforceW1Version.V021: get_data_path("DexforceW1EyesV021/eyes.urdf"), + self._urdf_rel_paths = { + DexforceW1Version.V021: "DexforceW1EyesV021/eyes.urdf", } def get_urdf(self, version=DexforceW1Version.V021): - return self.urdf_paths[version] + return get_data_path(self._urdf_rel_paths[version]) def get_config(self, version=DexforceW1Version.V021): return { @@ -117,31 +117,31 @@ def get_config(self, version=DexforceW1Version.V021): class ArmManager: def __init__(self): - self.urdf_paths = { + self._urdf_rel_paths = { ( DexforceW1ArmKind.ANTHROPOMORPHIC, DexforceW1ArmSide.LEFT, DexforceW1Version.V021, - ): get_data_path("DexforceW1LeftArm1V021/left_arm.urdf"), + ): "DexforceW1LeftArm1V021/left_arm.urdf", ( DexforceW1ArmKind.ANTHROPOMORPHIC, DexforceW1ArmSide.RIGHT, DexforceW1Version.V021, - ): get_data_path("DexforceW1RightArm1V021/right_arm.urdf"), + ): "DexforceW1RightArm1V021/right_arm.urdf", ( DexforceW1ArmKind.INDUSTRIAL, DexforceW1ArmSide.LEFT, DexforceW1Version.V021, - ): get_data_path("DexforceW1LeftArm2V021/left_arm.urdf"), + ): "DexforceW1LeftArm2V021/left_arm.urdf", ( DexforceW1ArmKind.INDUSTRIAL, DexforceW1ArmSide.RIGHT, DexforceW1Version.V021, - ): get_data_path("DexforceW1RightArm2V021/right_arm.urdf"), + ): "DexforceW1RightArm2V021/right_arm.urdf", } def get_urdf(self, kind, side, version=DexforceW1Version.V021): - return self.urdf_paths[(kind, side, version)] + return get_data_path(self._urdf_rel_paths[(kind, side, version)]) def get_config(self, kind, side, version=DexforceW1Version.V021): prefix = "LEFT" if side == DexforceW1ArmSide.LEFT else "RIGHT" @@ -155,37 +155,37 @@ def get_config(self, kind, side, version=DexforceW1Version.V021): class HandManager: def __init__(self): - self.urdf_paths = { + self._urdf_rel_paths = { ( DexforceW1HandBrand.BRAINCO_HAND, DexforceW1ArmSide.LEFT, DexforceW1Version.V021, - ): get_data_path("BrainCoHandRevo1/BrainCoLeftHand/BrainCoLeftHand.urdf"), + ): "BrainCoHandRevo1/BrainCoLeftHand/BrainCoLeftHand.urdf", ( DexforceW1HandBrand.BRAINCO_HAND, DexforceW1ArmSide.RIGHT, DexforceW1Version.V021, - ): get_data_path("BrainCoHandRevo1/BrainCoRightHand/BrainCoRightHand.urdf"), + ): "BrainCoHandRevo1/BrainCoRightHand/BrainCoRightHand.urdf", ( DexforceW1HandBrand.DH_PGC_GRIPPER, DexforceW1ArmSide.LEFT, DexforceW1Version.V021, - ): get_data_path("DH_PGC_140_50/DH_PGC_140_50.urdf"), + ): "DH_PGC_140_50/DH_PGC_140_50.urdf", ( DexforceW1HandBrand.DH_PGC_GRIPPER, DexforceW1ArmSide.RIGHT, DexforceW1Version.V021, - ): get_data_path("DH_PGC_140_50/DH_PGC_140_50.urdf"), + ): "DH_PGC_140_50/DH_PGC_140_50.urdf", ( DexforceW1HandBrand.DH_PGC_GRIPPER_M, DexforceW1ArmSide.LEFT, DexforceW1Version.V021, - ): get_data_path("DH_PGC_140_50_M/DH_PGC_140_50_M.urdf"), + ): "DH_PGC_140_50_M/DH_PGC_140_50_M.urdf", ( DexforceW1HandBrand.DH_PGC_GRIPPER_M, DexforceW1ArmSide.RIGHT, DexforceW1Version.V021, - ): get_data_path("DH_PGC_140_50_M/DH_PGC_140_50_M.urdf"), + ): "DH_PGC_140_50_M/DH_PGC_140_50_M.urdf", } def get_config( @@ -242,7 +242,7 @@ def get_urdf( side: DexforceW1ArmSide, version: DexforceW1Version = DexforceW1Version.V021, ): - return self.urdf_paths[(brand, side, version)] + return get_data_path(self._urdf_rel_paths[(brand, side, version)]) def get_attach_xpos( self,