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"