From f3b68995616af1848eff43ea91993a8772722d8b Mon Sep 17 00:00:00 2001 From: "-T.K.-" Date: Thu, 20 Aug 2026 11:41:44 -0700 Subject: [PATCH] robot_assets: put the CAD toolchain behind a `cad` extra The package ships two unrelated halves: `robot_assets.loader`, a stdlib-only GitHub fetch/cache helper that runtime consumers use to pull descriptions, and `robot_assets.workflow`, the CAD -> URDF/MJCF/xacro generator. Both were covered by one hard dependency list, so anyone installing this for the loader also got mujoco, onshape-to-robot and pymeshlab -- none of which the loader imports. Move the three to a `cad` extra and leave the base install dependency-free. Deployment code that only calls `load()` now pulls in no simulator and no CAD toolchain; asset generation asks for `robot-assets[cad]`. MuJoCo here is the MJCF compiler rather than a simulator (urdf_to_mjcf.py goes through MjModel.from_xml_path / mj_saveLastXML), and pymeshlab is really onshape-to-robot's own extra, needed because every cad/config.json sets "simplify_stls": true -- both noted inline so the extra stays self-explaining. Guard the two entry points that need the toolchain so a base install fails with a message naming the extra instead of a bare ModuleNotFoundError, or an obscure subprocess failure for the onshape-to-robot CLI. The `test` group depends on `robot-assets[cad]`, so CI needs no change. Note for downstream: prime_description declares a plain `robot-assets` git dependency and relies on these three arriving transitively. It needs to move to `robot-assets[cad]` once this lands. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FwK47CB1Cdu2PEYtqt87Js --- README.md | 2 +- pyproject.toml | 6 +++++- robot_assets/workflow/onshape_to_urdf.py | 14 ++++++++++++-- robot_assets/workflow/urdf_to_mjcf.py | 8 +++++++- uv.lock | 17 ++++++++++++----- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8576d11..c6a8d94 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ the finalize stage idempotent and the MJCF rooted at the CAD root link. ## Re-generating from CAD ```bash -uv sync +uv sync --extra cad sudo apt install openscad # for collider editing (onshape-to-robot) ``` diff --git a/pyproject.toml b/pyproject.toml index 9eae228..b593a49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,10 @@ version = "0.3.1" description = "Robot asset tool: generate URDF / MJCF / xacro from Onshape CAD, and load robot descriptions" readme = "README.md" requires-python = ">=3.10" -dependencies = [ +dependencies = [] + +[project.optional-dependencies] +cad = [ "mujoco>=3.4.0", "onshape-to-robot>=1.8.0", "pymeshlab>=2025.7.post1", @@ -26,6 +29,7 @@ robot-assets-urdf-to-xacro = "robot_assets.workflow.urdf_to_xacro:main" [dependency-groups] test = [ "pytest>=8.0", + "robot-assets[cad]", ] [tool.pytest.ini_options] diff --git a/robot_assets/workflow/onshape_to_urdf.py b/robot_assets/workflow/onshape_to_urdf.py index 176f231..f7b8c84 100644 --- a/robot_assets/workflow/onshape_to_urdf.py +++ b/robot_assets/workflow/onshape_to_urdf.py @@ -26,8 +26,18 @@ def _onshape_to_robot_bin() -> str: - """Locate the onshape-to-robot CLI (PATH, else alongside the running interpreter).""" - return shutil.which("onshape-to-robot") or str(Path(sys.executable).parent / "onshape-to-robot") + """Locate the onshape-to-robot CLI (PATH, else alongside the running interpreter). + + Raises: + FileNotFoundError: If the CLI is missing, i.e. the `cad` extra is not installed. + """ + binary = shutil.which("onshape-to-robot") or str(Path(sys.executable).parent / "onshape-to-robot") + if not Path(binary).exists(): + raise FileNotFoundError( + "The onshape-to-robot CLI was not found. It ships with the CAD toolchain rather than " + "the base install; install it with `pip install robot-assets[cad]`." + ) + return binary def export(robot_dir: Path, *, keep_assets: bool = False, convert: bool = False) -> Path: diff --git a/robot_assets/workflow/urdf_to_mjcf.py b/robot_assets/workflow/urdf_to_mjcf.py index ab02e5a..59a0b2a 100644 --- a/robot_assets/workflow/urdf_to_mjcf.py +++ b/robot_assets/workflow/urdf_to_mjcf.py @@ -20,7 +20,13 @@ import tempfile import xml.etree.ElementTree as ET -import mujoco +try: + import mujoco +except ModuleNotFoundError as error: + raise ModuleNotFoundError( + "MuJoCo is needed to compile MJCF, and ships with the CAD toolchain rather than the " + "base install. Install it with `pip install robot-assets[cad]`." + ) from error from . import robot_model diff --git a/uv.lock b/uv.lock index 07b0d01..82f8833 100644 --- a/uv.lock +++ b/uv.lock @@ -616,7 +616,9 @@ wheels = [ name = "robot-assets" version = "0.3.1" source = { editable = "." } -dependencies = [ + +[package.optional-dependencies] +cad = [ { name = "mujoco" }, { name = "onshape-to-robot" }, { name = "pymeshlab" }, @@ -625,17 +627,22 @@ dependencies = [ [package.dev-dependencies] test = [ { name = "pytest" }, + { name = "robot-assets", extra = ["cad"] }, ] [package.metadata] requires-dist = [ - { name = "mujoco", specifier = ">=3.4.0" }, - { name = "onshape-to-robot", specifier = ">=1.8.0" }, - { name = "pymeshlab", specifier = ">=2025.7.post1" }, + { name = "mujoco", marker = "extra == 'cad'", specifier = ">=3.4.0" }, + { name = "onshape-to-robot", marker = "extra == 'cad'", specifier = ">=1.8.0" }, + { name = "pymeshlab", marker = "extra == 'cad'", specifier = ">=2025.7.post1" }, ] +provides-extras = ["cad"] [package.metadata.requires-dev] -test = [{ name = "pytest", specifier = ">=8.0" }] +test = [ + { name = "pytest", specifier = ">=8.0" }, + { name = "robot-assets", extras = ["cad"] }, +] [[package]] name = "tomli"