diff --git a/Makefile b/Makefile index ec68abec..d82903ac 100755 --- a/Makefile +++ b/Makefile @@ -75,11 +75,9 @@ clean: # Static Checks # # ------------------------ # -py-files := $(shell find . -name '*.py') - format: - @black $(py-files) - @ruff format $(py-files) + @black sim + @ruff format sim .PHONY: format format-cpp: @@ -88,15 +86,11 @@ format-cpp: .PHONY: format-cpp static-checks: - @black --diff --check $(py-files) - @ruff check $(py-files) - @mypy --install-types --non-interactive $(py-files) + @black --diff --check sim + @ruff check sim + @mypy --install-types --non-interactive sim .PHONY: lint -mypy-daemon: - @dmypy run -- $(py-files) -.PHONY: mypy-daemon - # ------------------------ # # Unit tests # # ------------------------ # diff --git a/README.md b/README.md index 7c0cd362..613fbae9 100755 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ [![Discord](https://img.shields.io/discord/1224056091017478166)](https://discord.gg/k5mSvCkYQh) [![Wiki](https://img.shields.io/badge/wiki-humanoids-black)](https://humanoids.wiki)
+[![python](https://img.shields.io/badge/-Python_3.8-blue?logo=python&logoColor=white)](https://github.com/pre-commit/pre-commit) +[![black](https://img.shields.io/badge/Code%20Style-Black-black.svg?labelColor=gray)](https://black.readthedocs.io/en/stable/) +[![ruff](https://img.shields.io/badge/Linter-Ruff-red.svg?labelColor=gray)](https://github.com/charliermarsh/ruff) +
+[![Python Checks](https://github.com/kscalelabs/sim/actions/workflows/test.yml/badge.svg)](https://github.com/kscalelabs/sim/actions/workflows/test.yml) [![Update Stompy S3 Model](https://github.com/kscalelabs/sim/actions/workflows/update_stompy_s3.yml/badge.svg)](https://github.com/kscalelabs/sim/actions/workflows/update_stompy_s3.yml) @@ -29,6 +34,8 @@ The getting up task is still an open challenge! ## Getting Started +This repository requires Python 3.8 due to compatibility issues with underlying libraries. We hope to support more recent Python versions in the future. + 1. Clone this repository: ```bash git clone https://github.com/kscalelabs/sim.git diff --git a/pyproject.toml b/pyproject.toml index d863eeb5..f5317fea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,12 +30,15 @@ warn_redundant_casts = true incremental = true namespace_packages = false +exclude = "sim/humanoid_gym/" + [[tool.mypy.overrides]] module = [ - "isaacgym.*", "humanoid.*", + "isaacgym.*", "IsaacGymEnvs.*", + "mujoco.*", ] ignore_missing_imports = true @@ -49,6 +52,8 @@ profile = "black" line-length = 120 target-version = "py310" +exclude = ["sim/humanoid_gym"] + [tool.ruff.lint] select = ["ANN", "D", "E", "F", "I", "N", "PGH", "PLC", "PLE", "PLR", "PLW", "W"] diff --git a/sim/requirements.txt b/sim/requirements.txt index 4a0b4cf3..a22a9673 100644 --- a/sim/requirements.txt +++ b/sim/requirements.txt @@ -1,3 +1,4 @@ # requirements.txt +mediapy torch diff --git a/sim/scripts/create_mjcf.py b/sim/scripts/create_mjcf.py index 5cdc70ef..8c67557e 100644 --- a/sim/scripts/create_mjcf.py +++ b/sim/scripts/create_mjcf.py @@ -10,19 +10,23 @@ 2. Condim 3 and 4 and difference in results """ +import logging +import xml.dom.minidom import xml.etree.ElementTree as ET from pathlib import Path -import xml.dom.minidom +from typing import List, Union from kol.formats import mjcf + from sim.stompy.joints import StompyFixed from sim.env import stompy_mjcf_path +logger = logging.getLogger(__name__) STOMPY_HEIGHT = 1.0 -def _pretty_print_xml(xml_string): +def _pretty_print_xml(xml_string: str) -> str: """Formats the provided XML string into a pretty-printed version.""" parsed_xml = xml.dom.minidom.parseString(xml_string) return parsed_xml.toprettyxml(indent=" ") @@ -32,7 +36,8 @@ class Sim2SimRobot(mjcf.Robot): """A class to adapt the world in a Mujoco XML file.""" def adapt_world(self) -> None: - root = self.tree.getroot() + root: ET.Element = self.tree.getroot() + asset = root.find("asset") asset.append( ET.Element( @@ -116,8 +121,10 @@ def adapt_world(self) -> None: ).to_xml(), ) - motors = [] - sensors = [] + motors: List[mjcf.Motor] = [] + sensor_pos: List[mjcf.Actuatorpos] = [] + sensor_vel: List[mjcf.Actuatorvel] = [] + sensor_frc: List[mjcf.Actuatorfrc] = [] for joint, limits in StompyFixed.default_limits().items(): if joint in StompyFixed.default_standing().keys(): motors.append( @@ -125,17 +132,13 @@ def adapt_world(self) -> None: name=joint, joint=joint, gear=1, ctrlrange=(limits["lower"], limits["upper"]), ctrllimited=True ) ) - sensors.extend( - [ - mjcf.Actuatorpos(name=joint + "_p", actuator=joint, user="13"), - mjcf.Actuatorvel(name=joint + "_v", actuator=joint, user="13"), - mjcf.Actuatorfrc(name=joint + "_f", actuator=joint, user="13", noise=0.001), - ] - ) + sensor_pos.append(mjcf.Actuatorpos(name=joint + "_p", actuator=joint, user="13")) + sensor_vel.append(mjcf.Actuatorvel(name=joint + "_v", actuator=joint, user="13")) + sensor_frc.append(mjcf.Actuatorfrc(name=joint + "_f", actuator=joint, user="13", noise=0.001)) # Add motors and sensors root.append(mjcf.Actuator(motors).to_xml()) - root.append(mjcf.Sensor(sensors).to_xml()) + root.append(mjcf.Sensor(sensor_pos, sensor_vel, sensor_frc).to_xml()) # Add imus sensors = root.find("sensor") @@ -182,11 +185,11 @@ def adapt_world(self) -> None: ) self.tree = ET.ElementTree(root) - def save(self, path: str | Path) -> None: + def save(self, path: Union[str, Path]) -> None: rough_string = ET.tostring(self.tree.getroot(), "utf-8") # Pretty print the XML formatted_xml = _pretty_print_xml(rough_string) - + logger.info("XML:\n%s", formatted_xml) with open(path, "w") as f: f.write(formatted_xml) diff --git a/sim/scripts/simulate_mjcf.py b/sim/scripts/simulate_mjcf.py index 056ec51d..921e6d1d 100644 --- a/sim/scripts/simulate_mjcf.py +++ b/sim/scripts/simulate_mjcf.py @@ -5,17 +5,25 @@ mjpython sim/scripts/simulate_mjcf.py --record """ +import argparse +import logging import time +from pathlib import Path +from typing import List, Union + +import mediapy as media import mujoco import mujoco.viewer -import mediapy as media -import argparse +import numpy as np from sim.env import stompy_mjcf_path +from sim.logging import configure_logging + +logger = logging.getLogger(__name__) -def simulate(model_path, duration, framerate, record_video): - frames = [] +def simulate(model_path: Union[str, Path], duration: float, framerate: float, record_video: bool) -> None: + frames: List[np.ndarray] = [] model = mujoco.MjModel.from_xml_path(model_path) data = mujoco.MjData(model) renderer = mujoco.Renderer(model) @@ -37,18 +45,18 @@ def simulate(model_path, duration, framerate, record_video): if record_video: video_path = "mjcf_simulation.mp4" media.write_video(video_path, frames, fps=framerate) - print(f"Video saved to {video_path}") + # print(f"Video saved to {video_path}") + logger.info("Video saved to %s", video_path) if __name__ == "__main__": + configure_logging() + parser = argparse.ArgumentParser(description="MuJoCo Simulation") - parser.add_argument( - "--model_path", type=str, default=str(stompy_mjcf_path()), help="Path to the MuJoCo XML model file" - ) + parser.add_argument("--model_path", type=str, default=str(stompy_mjcf_path()), help="Path to the MuJoCo XML file") parser.add_argument("--duration", type=int, default=3, help="Duration of the simulation in seconds") parser.add_argument("--framerate", type=int, default=30, help="Frame rate for video recording") parser.add_argument("--record", action="store_true", help="Flag to record video") - args = parser.parse_args() simulate(args.model_path, args.duration, args.framerate, args.record) diff --git a/sim/scripts/simulate_urdf.py b/sim/scripts/simulate_urdf.py index 4b73dd4f..068e6810 100644 --- a/sim/scripts/simulate_urdf.py +++ b/sim/scripts/simulate_urdf.py @@ -8,9 +8,8 @@ """ import logging -import time from dataclasses import dataclass -from typing import Any, Dict, List, Literal, NewType +from typing import Any, Dict, Literal, NewType from isaacgym import gymapi, gymtorch, gymutil @@ -174,14 +173,14 @@ def load_gym() -> GymParams: def run_gym(gym: GymParams, mode: Literal["one_at_a_time", "all_at_once"] = "all_at_once") -> None: - joints = Stompy.all_joints() - last_time = time.time() + # joints = Stompy.all_joints() + # last_time = time.time() - dof_ids: Dict[str, int] = gym.gym.get_actor_dof_dict(gym.env, gym.robot) - body_ids: List[str] = gym.gym.get_actor_rigid_body_names(gym.env, gym.robot) + # dof_ids: Dict[str, int] = gym.gym.get_actor_dof_dict(gym.env, gym.robot) + # body_ids: List[str] = gym.gym.get_actor_rigid_body_names(gym.env, gym.robot) - joint_id = 0 - effort = 5.0 + # joint_id = 0 + # effort = 5.0 while not gym.gym.query_viewer_has_closed(gym.viewer): gym.gym.simulate(gym.sim) diff --git a/sim/scripts/update_stompy_s3.py b/sim/scripts/update_stompy_s3.py index c19f5f49..b214010d 100644 --- a/sim/scripts/update_stompy_s3.py +++ b/sim/scripts/update_stompy_s3.py @@ -1,11 +1,9 @@ +# mypy: disable-error-code="import-not-found" """Updates the Stompy model.""" import tarfile from pathlib import Path -from kol.logging import configure_logging -from kol.onshape.converter import Converter - STOMPY_MODEL = ( "https://cad.onshape.com/documents/71f793a23ab7562fb9dec82d/" "w/6160a4f44eb6113d3fa116cd/e/1a95e260677a2d2d5a3b1eb3" @@ -23,6 +21,12 @@ def main() -> None: + try: + from kol.logging import configure_logging + from kol.onshape.converter import Converter + except ImportError: + raise ImportError("Please install the `kscale-onshape-library` package to run this script.") + configure_logging() output_dir = Path("stompy") diff --git a/sim/stompy/joints.py b/sim/stompy/joints.py index 901bfa7f..6f4a45a6 100755 --- a/sim/stompy/joints.py +++ b/sim/stompy/joints.py @@ -7,7 +7,7 @@ import textwrap from abc import ABC -from typing import Dict, List, Union +from typing import Dict, List, Tuple, Union import numpy as np @@ -30,8 +30,8 @@ def joints(cls) -> List[str]: ] @classmethod - def joints_motors(cls) -> List[str]: - joint_names = [] + def joints_motors(cls) -> List[Tuple[str, str]]: + joint_names: List[Tuple[str, str]] = [] for attr in dir(cls): if not attr.startswith("__"): attr2 = getattr(cls, attr) diff --git a/tests/conftest.py b/tests/conftest.py index fdc62bec..26b37caa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ """Defines PyTest configuration for the project.""" import random +from typing import List import pytest from _pytest.python import Function @@ -11,5 +12,5 @@ def set_random_seed() -> None: random.seed(1337) -def pytest_collection_modifyitems(items: list[Function]) -> None: +def pytest_collection_modifyitems(items: List[Function]) -> None: items.sort(key=lambda x: x.get_closest_marker("slow") is not None)