diff --git a/.github/workflows/premerge-ci.yml b/.github/workflows/premerge-ci.yml index 757bfb684..94178c2e0 100644 --- a/.github/workflows/premerge-ci.yml +++ b/.github/workflows/premerge-ci.yml @@ -4,6 +4,12 @@ on: workflow_dispatch: merge_group: types: [checks_requested] + pull_request_target: + types: + - auto_merge_enabled + branches: + - main + - develop env: REGION: us-west-2 @@ -17,349 +23,419 @@ env: ECR_REPOSITORY: "roboverse-dev" jobs: - pre-merge-tests: + pre-merge-tests-impl: + if: github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' + permissions: + contents: read + pull-requests: write + issues: write runs-on: codebuild-EC2_Launcher2-${{ github.run_id }}-${{ github.run_attempt }} timeout-minutes: 720 steps: - # change to the source code directory - - name: Checkout code - uses: actions/checkout@v4 - - run: aws --version - ############# Prebuild ############ - - name: pre_build - env: - SSH_KEY: ${{ secrets.EC2_SSH_KEY }} - run: | - # Get AWS account ID - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) - echo "AWS_ACCOUNT_ID=$AWS_ACCOUNT_ID" >> $GITHUB_ENV - if [ -z "$AWS_ACCOUNT_ID" ]; then - echo "Error: Failed to get AWS account ID" - exit 1 - fi - - echo "Preparing S3 bucket..." - CACHE_BUCKET="${CACHE_BUCKET_PREFIX}-${AWS_ACCOUNT_ID}" - aws s3api head-bucket --bucket $CACHE_BUCKET || \ - aws s3 mb s3://$CACHE_BUCKET --region $REGION - - # Configure S3 bucket lifecycle rule for cache expiration - aws s3api put-bucket-lifecycle-configuration \ - --bucket $CACHE_BUCKET \ - --lifecycle-configuration '{ - "Rules": [ - { - "ID": "ExpireBuildKitCache", - "Status": "Enabled", - "Filter": { - "Prefix": "" - }, - "Expiration": { - "Days": 14 + - name: Checkout code + uses: actions/checkout@v4 + - run: aws --version + ############# Prebuild ############ + - name: pre_build + env: + SSH_KEY: ${{ secrets.EC2_SSH_KEY }} + run: | + # Get AWS account ID + AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) + echo "AWS_ACCOUNT_ID=$AWS_ACCOUNT_ID" >> $GITHUB_ENV + if [ -z "$AWS_ACCOUNT_ID" ]; then + echo "Error: Failed to get AWS account ID" + exit 1 + fi + + echo "Preparing S3 bucket..." + CACHE_BUCKET="${CACHE_BUCKET_PREFIX}-${AWS_ACCOUNT_ID}" + aws s3api head-bucket --bucket $CACHE_BUCKET || \ + aws s3 mb s3://$CACHE_BUCKET --region $REGION + + # Configure S3 bucket lifecycle rule for cache expiration + aws s3api put-bucket-lifecycle-configuration \ + --bucket $CACHE_BUCKET \ + --lifecycle-configuration '{ + "Rules": [ + { + "ID": "ExpireBuildKitCache", + "Status": "Enabled", + "Filter": { + "Prefix": "" + }, + "Expiration": { + "Days": 14 + } } - } - ] - }' - echo "CACHE_BUCKET=$CACHE_BUCKET" >> $GITHUB_ENV - - - echo "Launching EC2 instance to run tests..." - INSTANCE_ID=$(aws ec2 run-instances \ - --image-id ami-0b7f5f52689b2c0d0 \ - --instance-type $INSTANCE_TYPE \ - --region $REGION \ - --key-name $KEY_NAME \ - --security-group-ids sg-03f9110d8d39282ad \ - --subnet-id subnet-0c56793ce29caa78b \ - --iam-instance-profile Name="RoboverseCi" \ - --block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":500}}]' \ - --output text \ - --query 'Instances[0].InstanceId') - echo "INSTANCE_ID=$INSTANCE_ID" >> $GITHUB_ENV - - - # Create ECR repository if it doesn't exist - aws ecr describe-repositories --repository-names $ECR_REPOSITORY || \ - aws ecr create-repository --repository-name $ECR_REPOSITORY + ] + }' + echo "CACHE_BUCKET=$CACHE_BUCKET" >> $GITHUB_ENV + + + echo "Launching EC2 instance to run tests..." + INSTANCE_ID=$(aws ec2 run-instances \ + --image-id ami-0b7f5f52689b2c0d0 \ + --instance-type $INSTANCE_TYPE \ + --region $REGION \ + --key-name $KEY_NAME \ + --security-group-ids sg-03f9110d8d39282ad \ + --subnet-id subnet-0c56793ce29caa78b \ + --iam-instance-profile Name="RoboverseCi" \ + --block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":500}}]' \ + --output text \ + --query 'Instances[0].InstanceId') + echo "INSTANCE_ID=$INSTANCE_ID" >> $GITHUB_ENV + + + # Create ECR repository if it doesn't exist + aws ecr describe-repositories --repository-names $ECR_REPOSITORY || \ + aws ecr create-repository --repository-name $ECR_REPOSITORY echo "Waiting for instance $INSTANCE_ID to be running..." aws ec2 wait instance-running \ --instance-ids $INSTANCE_ID \ --region $REGION - echo "Getting instance IP address..." - EC2_INSTANCE_IP=$(aws ec2 describe-instances \ - --region $REGION \ - --filters "Name=instance-state-name,Values=running" "Name=instance-id,Values=$INSTANCE_ID" \ - --query 'Reservations[*].Instances[*].[PrivateIpAddress]' \ - --output text) - echo "EC2_INSTANCE_IP=$EC2_INSTANCE_IP" >> $GITHUB_ENV - - echo "Setting up SSH configuration..." - mkdir -p ~/.ssh - aws ec2 describe-key-pairs \ - --include-public-key \ - --key-name $KEY_NAME \ - --query 'KeyPairs[0].PublicKey' \ - --output text > ~/.ssh/id_rsa.pub - echo "$SSH_KEY" > ~/.ssh/id_rsa - chmod 400 ~/.ssh/id_* - echo "Host $EC2_INSTANCE_IP\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile=/dev/null\n" >> ~/.ssh/config - - echo "Sending SSH public key to instance..." - aws ec2-instance-connect send-ssh-public-key \ - --instance-id $INSTANCE_ID \ - --availability-zone $AZ \ - --ssh-public-key file://~/.ssh/id_rsa.pub \ - --instance-os-user $EC2_USER_NAME - - ############# Build ############# - - name: build - run: | - echo "====Copying source code...====" - wait_time=$RETRY_WAIT_TIME - SRC_DIR=$(basename $GITHUB_WORKSPACE) - echo ""====Check environment variables..."====" - echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" - echo "CODEBUILD_SRC_DIR=$CODEBUILD_SRC_DIR" - echo "EC2_USER_NAME=$EC2_USER_NAME" - echo "SRC_DIR=$SRC_DIR" - echo "RETRY_WAIT_TIME=$RETRY_WAIT_TIME" - echo "MAX_RETRIES=$MAX_RETRIES" - echo "====Repo file check...====" - ls ./ - - # ==== before buildx build ==== - DOCKERFILE_HASH=$(sha256sum Dockerfile | cut -c1-16) - IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPOSITORY:df-$DOCKERFILE_HASH" - echo "IMAGE_URI=$IMAGE_URI" - - retry_count=0 - - # change to parent directory t - cd .. - - while [ $retry_count -lt $MAX_RETRIES ]; do - if [ $retry_count -gt 0 ]; then - wait_time=$((wait_time * 2)) - echo "Retry attempt $((retry_count + 1))/$MAX_RETRIES. Waiting $wait_time seconds..." - sleep $wait_time + echo "Getting instance IP address..." + EC2_INSTANCE_IP=$(aws ec2 describe-instances \ + --region $REGION \ + --filters "Name=instance-state-name,Values=running" "Name=instance-id,Values=$INSTANCE_ID" \ + --query 'Reservations[*].Instances[*].[PrivateIpAddress]' \ + --output text) + echo "EC2_INSTANCE_IP=$EC2_INSTANCE_IP" >> $GITHUB_ENV + + echo "Setting up SSH configuration..." + mkdir -p ~/.ssh + aws ec2 describe-key-pairs \ + --include-public-key \ + --key-name $KEY_NAME \ + --query 'KeyPairs[0].PublicKey' \ + --output text > ~/.ssh/id_rsa.pub + echo "$SSH_KEY" > ~/.ssh/id_rsa + chmod 400 ~/.ssh/id_* + printf "Host %s\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile=/dev/null\n" "$EC2_INSTANCE_IP" >> ~/.ssh/config + + echo "Sending SSH public key to instance..." + aws ec2-instance-connect send-ssh-public-key \ + --instance-id $INSTANCE_ID \ + --availability-zone $AZ \ + --ssh-public-key file://~/.ssh/id_rsa.pub \ + --instance-os-user $EC2_USER_NAME + + ############# Build ############# + - name: build + run: | + echo "====Copying source code...====" + wait_time=$RETRY_WAIT_TIME + SRC_DIR=$(basename $GITHUB_WORKSPACE) + echo "====Check environment variables...====" + echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" + echo "CODEBUILD_SRC_DIR=$CODEBUILD_SRC_DIR" + echo "EC2_USER_NAME=$EC2_USER_NAME" + echo "SRC_DIR=$SRC_DIR" + echo "RETRY_WAIT_TIME=$RETRY_WAIT_TIME" + echo "MAX_RETRIES=$MAX_RETRIES" + echo "====Repo file check...====" + ls ./ + + # ==== before buildx build ==== + DOCKERFILE_HASH=$(sha256sum Dockerfile | cut -c1-16) + IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPOSITORY:df-$DOCKERFILE_HASH" + echo "IMAGE_URI=$IMAGE_URI" + + retry_count=0 + + # change to parent directory to copy files + cd .. + + while [ $retry_count -lt $MAX_RETRIES ]; do + if [ $retry_count -gt 0 ]; then + wait_time=$((wait_time * 2)) + echo "Retry attempt $((retry_count + 1))/$MAX_RETRIES. Waiting $wait_time seconds..." + sleep $wait_time + fi + + if scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no -r $SRC_DIR $EC2_USER_NAME@$EC2_INSTANCE_IP:~; then + echo "SCP command succeeded" + break + fi + + retry_count=$((retry_count + 1)) + done + + if [ $retry_count -eq $MAX_RETRIES ]; then + echo "SCP command failed after $MAX_RETRIES attempts" + exit 1 fi - if scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no -r $SRC_DIR $EC2_USER_NAME@$EC2_INSTANCE_IP:~; then - echo "SCP command succeeded" - break + # login + ECR_LOGIN_TOKEN=$(aws ecr get-login-password --region $REGION) + + echo "====Running tests on EC2 instance...====" + ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no $EC2_USER_NAME@$EC2_INSTANCE_IP " + + set -euo pipefail + + # Login to ECR using token from CodeBuild + echo \"$ECR_LOGIN_TOKEN\" | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com + + # Configure BuildKit environment + export DOCKER_BUILDKIT=1 + export BUILDKIT_INLINE_CACHE=1 + + docker buildx create --name metasim-builder --driver docker-container \ + --driver-opt env.AWS_REGION=$REGION \ + --driver-opt env.AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ + --driver-opt env.AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ + --bootstrap + docker buildx use metasim-builder + + cd \"\$HOME/${SRC_DIR}\" + + # docker build + if docker pull "$IMAGE_URI" 2>/dev/null ; then + echo "Image $IMAGE_URI already exists. Skipping build." + else + echo "===Starting docker build.===" + docker buildx build --progress=plain --platform linux/amd64 \ + -t "$IMAGE_URI" \ + --cache-from type=registry,ref=$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPOSITORY:cache,mode=max \ + --cache-to type=registry,ref=$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPOSITORY:cache,mode=max \ + --build-arg DOCKER_UID=1000 \ + --build-arg DOCKER_GID=1000 \ + --build-arg DOCKER_USER=$EC2_USER_NAME \ + -f Dockerfile \ + --load . + + docker push "$IMAGE_URI" + fi + + # begin run test + GENERAL_TEST_EXIT_CODE=0 + MUJOCO_TEST_EXIT_CODE=0 + SAPIEN3_TEST_EXIT_CODE=0 + ISAACSIM_TEST_EXIT_CODE=0 + ISAACGYM_TEST_EXIT_CODE=0 + # run all test + # Run general tests (no simulator required) + docker run --rm --entrypoint bash --runtime=nvidia --network=host \ + --name metasim-autotest \ + --user 1000:1000 --privileged \ + -e LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu \ + -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y -e OMNI_KIT_ACCEPT_EULA=YES \ + -v /usr/local/cuda:/usr/local/cuda \ + -v "$(pwd)":/home/$EC2_USER_NAME/RoboVerse \ + "$IMAGE_URI" \ + -c "bash -lc 'set -o pipefail; \ + /home/$EC2_USER_NAME/conda/envs/metasim/bin/python3 -m pytest -k general -vv \ + | tee /home/$EC2_USER_NAME/${SRC_DIR}/pytest-general.log'" \ + || GENERAL_TEST_EXIT_CODE=$? + + docker run --rm --entrypoint bash --runtime=nvidia --network=host \ + --name metasim-autotest \ + --user 1000:1000 --privileged \ + -e LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu \ + -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y -e OMNI_KIT_ACCEPT_EULA=YES \ + -v /usr/local/cuda:/usr/local/cuda \ + -v "$(pwd)":/home/$EC2_USER_NAME/RoboVerse \ + "$IMAGE_URI" \ + -c "bash -lc 'set -o pipefail; \ + /home/$EC2_USER_NAME/conda/envs/metasim/bin/python3 -m pytest -k mujoco -vv \ + | tee /home/$EC2_USER_NAME/${SRC_DIR}/pytest-mujoco.log'" \ + || MUJOCO_TEST_EXIT_CODE=$? + + docker run --rm --entrypoint bash --runtime=nvidia --network=host \ + --name metasim-autotest \ + --user 1000:1000 --privileged \ + -e LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu \ + -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y -e OMNI_KIT_ACCEPT_EULA=YES \ + -v /usr/local/cuda:/usr/local/cuda \ + -v "$(pwd)":/home/$EC2_USER_NAME/RoboVerse \ + "$IMAGE_URI" \ + -c "bash -lc 'set -o pipefail; \ + /home/$EC2_USER_NAME/conda/envs/metasim/bin/python3 -m pytest -k sapien3 -vv \ + | tee /home/$EC2_USER_NAME/${SRC_DIR}/pytest-sapien3.log'" \ + || SAPIEN3_TEST_EXIT_CODE=$? + + docker run --rm --entrypoint bash --runtime=nvidia --network=host \ + --name metasim-autotest \ + --user 1000:1000 --privileged \ + -e LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu \ + -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y -e OMNI_KIT_ACCEPT_EULA=YES \ + -v /usr/local/cuda:/usr/local/cuda \ + -v "$(pwd)":/home/$EC2_USER_NAME/RoboVerse \ + "$IMAGE_URI" \ + -c "bash -lc 'set -o pipefail; \ + /home/$EC2_USER_NAME/conda/envs/metasim/bin/python3 -m pytest -k isaacsim -vv \ + | tee /home/$EC2_USER_NAME/${SRC_DIR}/pytest-isaacsim.log'" \ + || ISAACSIM_TEST_EXIT_CODE=$? + + docker run --rm --entrypoint bash --runtime=nvidia --network=host \ + --name metasim-autotest \ + --user 1000:1000 --privileged \ + -e LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu \ + -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y -e OMNI_KIT_ACCEPT_EULA=YES \ + -v /usr/local/cuda:/usr/local/cuda \ + -v "$(pwd)":/home/$EC2_USER_NAME/RoboVerse \ + "$IMAGE_URI" \ + -c "bash -lc 'set -o pipefail; \ + /home/$EC2_USER_NAME/conda/envs/metasim_isaacgym/bin/python3 /home/$EC2_USER_NAME/RoboVerse/metasim/test/isaacgym_entry.py -k isaacgym -vv \ + | tee /home/$EC2_USER_NAME/${SRC_DIR}/pytest-isaacgym.log'" \ + || ISAACGYM_TEST_EXIT_CODE=$? + + # TODO check if test_exit_code necessary + touch ~/$SRC_DIR/test_exit_codes.txt + { + echo \"GENERAL_TEST_EXIT_CODE=\$GENERAL_TEST_EXIT_CODE\" + echo \"MUJOCO_TEST_EXIT_CODE=\$MUJOCO_TEST_EXIT_CODE\" + echo \"SAPIEN3_TEST_EXIT_CODE=\$SAPIEN3_TEST_EXIT_CODE\" + echo \"ISAACSIM_TEST_EXIT_CODE=\$ISAACSIM_TEST_EXIT_CODE\" + echo \"ISAACGYM_TEST_EXIT_CODE=\$ISAACGYM_TEST_EXIT_CODE\" + } > ~/${SRC_DIR}/test_exit_codes.txt + " || { echo "Test execution failed"; exit 1; } + + echo "===Copying test reports...===" + scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/test_exit_codes.txt $CODEBUILD_SRC_DIR/ + + source $CODEBUILD_SRC_DIR/test_exit_codes.txt + + echo "General test exit code: ${GENERAL_TEST_EXIT_CODE}" + echo "Mujoco test exit code: ${MUJOCO_TEST_EXIT_CODE}" + echo "Sapien3 test exit code: ${SAPIEN3_TEST_EXIT_CODE}" + echo "IsaacSim test exit code: ${ISAACSIM_TEST_EXIT_CODE}" + echo "IsaacGym test exit code: ${ISAACGYM_TEST_EXIT_CODE}" + + EXIT_CODE=0 + + if [ "${GENERAL_TEST_EXIT_CODE:-0}" -ne 0 ]; then + echo "=== General tests failed. Fetching logs... ===" + scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ + $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/pytest-general.log \ + $CODEBUILD_SRC_DIR/ || true + echo "===== General pytest log =====" + cat $CODEBUILD_SRC_DIR/pytest-general.log || true + EXIT_CODE=1 fi - retry_count=$((retry_count + 1)) - done - - if [ $retry_count -eq $MAX_RETRIES ]; then - echo "SCP command failed after $MAX_RETRIES attempts" - exit 1 - fi + if [ "${MUJOCO_TEST_EXIT_CODE:-0}" -ne 0 ]; then + echo "=== Mujoco tests failed. Fetching logs... ===" + scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ + $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/pytest-mujoco.log \ + $CODEBUILD_SRC_DIR/ || true + echo "===== Mujoco pytest log =====" + cat $CODEBUILD_SRC_DIR/pytest-mujoco.log || true + EXIT_CODE=1 + fi - # login - ECR_LOGIN_TOKEN=$(aws ecr get-login-password --region $REGION) + if [ "${SAPIEN3_TEST_EXIT_CODE:-0}" -ne 0 ]; then + echo "=== Sapien3 tests failed. Fetching logs... ===" + scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ + $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/pytest-sapien3.log \ + $CODEBUILD_SRC_DIR/ || true + echo "===== Sapien3 pytest log =====" + cat $CODEBUILD_SRC_DIR/pytest-sapien3.log || true + EXIT_CODE=1 + fi - echo "====Running tests on EC2 instance...====" - ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no $EC2_USER_NAME@$EC2_INSTANCE_IP " + if [ "${ISAACSIM_TEST_EXIT_CODE:-0}" -ne 0 ]; then + echo "=== IsaacSim tests failed. Fetching logs... ===" + scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ + $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/pytest-isaacsim.log \ + $CODEBUILD_SRC_DIR/ || true + echo "===== IsaacSim pytest log =====" + cat $CODEBUILD_SRC_DIR/pytest-isaacsim.log || true + EXIT_CODE=1 + fi - set -euo pipefail + if [ "${ISAACGYM_TEST_EXIT_CODE:-0}" -ne 0 ]; then + echo "=== IsaacGym tests failed. Fetching logs... ===" + scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ + $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/pytest-isaacgym.log \ + $CODEBUILD_SRC_DIR/ || true + echo "===== IsaacGym pytest log =====" + cat $CODEBUILD_SRC_DIR/pytest-isaacgym.log || true + EXIT_CODE=1 + fi - # Login to ECR using token from CodeBuild - echo \"$ECR_LOGIN_TOKEN\" | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com + if [ "$EXIT_CODE" -ne 0 ]; then + echo "Tests failed with exit code $EXIT_CODE" + exit 1 + else + echo "===All tests passed!===" + fi - # Configure BuildKit environment - export DOCKER_BUILDKIT=1 - export BUILDKIT_INLINE_CACHE=1 + ########### Postbuild ######### + - name: post_build + if: always() # always try to terminate the instance + run: | + echo "Cleaning up resources..." + if [ -n "$INSTANCE_ID" ]; then + echo "Terminating EC2 instance $INSTANCE_ID..." + aws ec2 terminate-instances --instance-ids $INSTANCE_ID --region $REGION || true + fi - docker buildx create --name metasim-builder --driver docker-container \ - --driver-opt env.AWS_REGION=$REGION \ - --driver-opt env.AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ - --driver-opt env.AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ - --bootstrap - docker buildx use metasim-builder + - name: Prepare test logs for upload + if: always() + run: | + # Copy test logs from CODEBUILD_SRC_DIR to workspace root for artifact upload + if [ -d "$CODEBUILD_SRC_DIR" ]; then + cp -v $CODEBUILD_SRC_DIR/pytest-*.log . 2>/dev/null || echo "No pytest logs found" + cp -v $CODEBUILD_SRC_DIR/test_exit_codes.txt . 2>/dev/null || echo "No exit codes file found" + else + echo "CODEBUILD_SRC_DIR not set, files should already be in workspace" + fi - cd \"\$HOME/${SRC_DIR}\" + - name: Upload test logs as artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-logs + path: | + pytest-*.log + test_exit_codes.txt + if-no-files-found: warn + retention-days: 7 - # docker build - if docker pull "$IMAGE_URI" 2>/dev/null ; then - echo "Image $IMAGE_URI already exists. Skipping build." - else - echo "===Starting docker build.===" - docker buildx build --progress=plain --platform linux/amd64 \ - -t "$IMAGE_URI" \ - --cache-from type=registry,ref=$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPOSITORY:cache,mode=max \ - --cache-to type=registry,ref=$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPOSITORY:cache,mode=max \ - --build-arg DOCKER_UID=1000 \ - --build-arg DOCKER_GID=1000 \ - --build-arg DOCKER_USER=$EC2_USER_NAME \ - -f Dockerfile \ - --load . - - docker push "$IMAGE_URI" + pre-merge-tests: + if: always() + needs: [workflow-integrity-check, pre-merge-tests-impl] + runs-on: ubuntu-latest + steps: + - run: | + if [[ "${{ github.event_name }}" == "pull_request_target" ]]; then + if [[ "${{ needs.workflow-integrity-check.result }}" != "success" ]]; then + echo "❌ Workflow integrity check failed." + exit 1 + fi + echo "✅ Workflow integrity verified. Ready for merge queue." + elif [[ "${{ github.event_name }}" == "merge_group" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then + if [[ "${{ needs.pre-merge-tests-impl.result }}" != "success" ]]; then + echo "❌ Tests failed." + exit 1 + fi + echo "✅ Tests passed." fi - # begin run test - GENERAL_TEST_EXIT_CODE=0 - MUJOCO_TEST_EXIT_CODE=0 - SAPIEN3_TEST_EXIT_CODE=0 - ISAACSIM_TEST_EXIT_CODE=0 - ISAACGYM_TEST_EXIT_CODE=0 - # run all test - # Run general tests (no simulator required) - docker run --rm --entrypoint bash --runtime=nvidia --network=host \ - --name metasim-autotest \ - --user 1000:1000 --privileged \ - -e LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu \ - -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y -e OMNI_KIT_ACCEPT_EULA=YES \ - -v /usr/local/cuda:/usr/local/cuda \ - -v "$(pwd)":/home/$EC2_USER_NAME/RoboVerse \ - "$IMAGE_URI" \ - -c "bash -lc 'set -o pipefail; \ - /home/$EC2_USER_NAME/conda/envs/metasim/bin/python3 -m pytest -k general -vv \ - | tee /home/$EC2_USER_NAME/${SRC_DIR}/pytest-general.log'" \ - || GENERAL_TEST_EXIT_CODE=$? - - docker run --rm --entrypoint bash --runtime=nvidia --network=host \ - --name metasim-autotest \ - --user 1000:1000 --privileged \ - -e LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu \ - -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y -e OMNI_KIT_ACCEPT_EULA=YES \ - -v /usr/local/cuda:/usr/local/cuda \ - -v "$(pwd)":/home/$EC2_USER_NAME/RoboVerse \ - "$IMAGE_URI" \ - -c "bash -lc 'set -o pipefail; \ - /home/$EC2_USER_NAME/conda/envs/metasim/bin/python3 -m pytest -k mujoco -vv \ - | tee /home/$EC2_USER_NAME/${SRC_DIR}/pytest-mujoco.log'" \ - || MUJOCO_TEST_EXIT_CODE=$? - - docker run --rm --entrypoint bash --runtime=nvidia --network=host \ - --name metasim-autotest \ - --user 1000:1000 --privileged \ - -e LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu \ - -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y -e OMNI_KIT_ACCEPT_EULA=YES \ - -v /usr/local/cuda:/usr/local/cuda \ - -v "$(pwd)":/home/$EC2_USER_NAME/RoboVerse \ - "$IMAGE_URI" \ - -c "bash -lc 'set -o pipefail; \ - /home/$EC2_USER_NAME/conda/envs/metasim/bin/python3 -m pytest -k sapien3 -vv \ - | tee /home/$EC2_USER_NAME/${SRC_DIR}/pytest-sapien3.log'" \ - || SAPIEN3_TEST_EXIT_CODE=$? - - docker run --rm --entrypoint bash --runtime=nvidia --network=host \ - --name metasim-autotest \ - --user 1000:1000 --privileged \ - -e LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu \ - -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y -e OMNI_KIT_ACCEPT_EULA=YES \ - -v /usr/local/cuda:/usr/local/cuda \ - -v "$(pwd)":/home/$EC2_USER_NAME/RoboVerse \ - "$IMAGE_URI" \ - -c "bash -lc 'set -o pipefail; \ - /home/$EC2_USER_NAME/conda/envs/metasim/bin/python3 -m pytest -k isaacsim -vv \ - | tee /home/$EC2_USER_NAME/${SRC_DIR}/pytest-isaacsim.log'" \ - || ISAACSIM_TEST_EXIT_CODE=$? - - docker run --rm --entrypoint bash --runtime=nvidia --network=host \ - --name metasim-autotest \ - --user 1000:1000 --privileged \ - -e LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu \ - -e ACCEPT_EULA=Y -e PRIVACY_CONSENT=Y -e OMNI_KIT_ACCEPT_EULA=YES \ - -v /usr/local/cuda:/usr/local/cuda \ - -v "$(pwd)":/home/$EC2_USER_NAME/RoboVerse \ - "$IMAGE_URI" \ - -c "bash -lc 'set -o pipefail; \ - /home/$EC2_USER_NAME/conda/envs/metasim_isaacgym/bin/python3 /home/$EC2_USER_NAME/RoboVerse/metasim/test/isaacgym_entry.py -k isaacgym -vv \ - | tee /home/$EC2_USER_NAME/${SRC_DIR}/pytest-isaacgym.log'" \ - || ISAACGYM_TEST_EXIT_CODE=$? - - # TODO check if test_exit_code necessary - touch ~/$SRC_DIR/test_exit_codes.txt - { - echo \"GENERAL_TEST_EXIT_CODE=\$GENERAL_TEST_EXIT_CODE\" - echo \"MUJOCO_TEST_EXIT_CODE=\$MUJOCO_TEST_EXIT_CODE\" - echo \"SAPIEN3_TEST_EXIT_CODE=\$SAPIEN3_TEST_EXIT_CODE\" - echo \"ISAACSIM_TEST_EXIT_CODE=\$ISAACSIM_TEST_EXIT_CODE\" - echo \"ISAACGYM_TEST_EXIT_CODE=\$ISAACGYM_TEST_EXIT_CODE\" - } > ~/${SRC_DIR}/test_exit_codes.txt - " || { echo "Test execution failed"; exit 1; } - - echo "===Copying test reports...===" - scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/test_exit_codes.txt $CODEBUILD_SRC_DIR/ - - source $CODEBUILD_SRC_DIR/test_exit_codes.txt - - echo "General test exit code: ${GENERAL_TEST_EXIT_CODE}" - echo "Mujoco test exit code: ${MUJOCO_TEST_EXIT_CODE}" - echo "Sapien3 test exit code: ${SAPIEN3_TEST_EXIT_CODE}" - echo "IsaacSim test exit code: ${ISAACSIM_TEST_EXIT_CODE}" - echo "IsaacGym test exit code: ${ISAACGYM_TEST_EXIT_CODE}" - - EXIT_CODE=0 - - if [ "${GENERAL_TEST_EXIT_CODE:-0}" -ne 0 ]; then - echo "=== General tests failed. Fetching logs... ===" - scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ - $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/pytest-general.log \ - $CODEBUILD_SRC_DIR/ || true - echo "===== General pytest log =====" - cat $CODEBUILD_SRC_DIR/pytest-general.log || true - EXIT_CODE=1 - fi - - if [ "${MUJOCO_TEST_EXIT_CODE:-0}" -ne 0 ]; then - echo "=== Mujoco tests failed. Fetching logs... ===" - scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ - $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/pytest-mujoco.log \ - $CODEBUILD_SRC_DIR/ || true - echo "===== Mujoco pytest log =====" - cat $CODEBUILD_SRC_DIR/pytest-mujoco.log || true - EXIT_CODE=1 - fi - - if [ "${SAPIEN3_TEST_EXIT_CODE:-0}" -ne 0 ]; then - echo "=== Sapien3 tests failed. Fetching logs... ===" - scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ - $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/pytest-sapien3.log \ - $CODEBUILD_SRC_DIR/ || true - echo "===== Sapien3 pytest log =====" - cat $CODEBUILD_SRC_DIR/pytest-sapien3.log || true - EXIT_CODE=1 - fi - - if [ "${ISAACSIM_TEST_EXIT_CODE:-0}" -ne 0 ]; then - echo "=== IsaacSim tests failed. Fetching logs... ===" - scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ - $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/pytest-isaacsim.log \ - $CODEBUILD_SRC_DIR/ || true - echo "===== IsaacSim pytest log =====" - cat $CODEBUILD_SRC_DIR/pytest-isaacsim.log || true - EXIT_CODE=1 - fi - - if [ "${ISAACGYM_TEST_EXIT_CODE:-0}" -ne 0 ]; then - echo "=== IsaacGym tests failed. Fetching logs... ===" - scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no \ - $EC2_USER_NAME@$EC2_INSTANCE_IP:~/$SRC_DIR/pytest-isaacgym.log \ - $CODEBUILD_SRC_DIR/ || true - echo "===== IsaacGym pytest log =====" - cat $CODEBUILD_SRC_DIR/pytest-isaacgym.log || true - EXIT_CODE=1 - fi - - if [ "$EXIT_CODE" -ne 0 ]; then - echo "Tests failed with exit code $EXIT_CODE" - exit 1 - else - echo "===All tests passed!===" - fi - - ########### Postbuild ######### - - name: post_build - if: always() # always try to terminate the instance - run: | - echo "Cleaning up resources..." - if [ ! -z "$INSTANCE_ID" ]; then - echo "Terminating EC2 instance $INSTANCE_ID..." - aws ec2 terminate-instances --instance-ids $INSTANCE_ID --region $REGION || true - fi + workflow-integrity-check: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request_target' + permissions: + pull-requests: read + steps: + - name: Check for workflow changes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + set -euo pipefail + echo "Checking if .github/workflows/premerge-ci.yml is modified in PR #$PR_NUMBER..." + CHANGES=$(gh pr diff "$PR_NUMBER" --name-only -R ${{ github.repository }}) + + if echo "$CHANGES" | grep -q "^.github/workflows/premerge-ci.yml$"; then + echo "❌ Critical workflow modification detected!" + echo "For security reasons, this workflow file cannot be modified via Pull Request." + echo "Please revert changes to .github/workflows/premerge-ci.yml to pass this check." + exit 1 + fi + + echo "✅ Workflow integrity verified (file not modified)." diff --git a/.gitignore b/.gitignore index a6e1e0bf5..fc9a76a6c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ **/.vscode/ # Don't ignore the top-level .vscode directory as it is # used to configure VS Code settings +*.rrd +# rerun recording files !.vscode/ .vscode/.history .hydra/ @@ -32,6 +34,7 @@ checkpoints slurm.sh data_policy/ outputs/ +il_outputs/ eval_outputs/ tmp/ results/ @@ -104,3 +107,11 @@ id_rsa* # for test cache .pytest_cache/ + +*.pkl +*.pt +output + +.cursor/ +artifacts/ +nohup/ diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index fc6f553bb..4549a9b8c 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -21,11 +21,14 @@ Guidelines for modifications: ## Contributors * Bangjun Wang +* Bei Wang +* Boqi Zhao * Chaoyi Xu * Chengyang Zhao * Dechen Gao * Di Fan * Dylan Goetting +* Hanchu Zhou * Haoran Lu * Haozhe Chen * Haozhe Lou @@ -39,6 +42,7 @@ Guidelines for modifications: * Songlin Wei * Xinjie Wang * Xinying Guo +* Yi Zhang * Yikai Tang * Yongce Liu * Yu Hong diff --git a/docs/source/_static/standard_output/0_static_scene_isaaclab.png b/docs/source/_static/standard_output/0_static_scene_isaacsim.png similarity index 100% rename from docs/source/_static/standard_output/0_static_scene_isaaclab.png rename to docs/source/_static/standard_output/0_static_scene_isaacsim.png diff --git a/docs/source/_static/standard_output/0_static_scene_newton.png b/docs/source/_static/standard_output/0_static_scene_newton.png new file mode 100644 index 000000000..2b1134d45 Binary files /dev/null and b/docs/source/_static/standard_output/0_static_scene_newton.png differ diff --git a/docs/source/_static/standard_output/1_control_robot_newton.gif b/docs/source/_static/standard_output/1_control_robot_newton.gif new file mode 100644 index 000000000..2238cb26e Binary files /dev/null and b/docs/source/_static/standard_output/1_control_robot_newton.gif differ diff --git a/docs/source/_static/standard_output/1_move_robot_newton.mp4 b/docs/source/_static/standard_output/1_move_robot_newton.mp4 new file mode 100644 index 000000000..e92ef4cc3 Binary files /dev/null and b/docs/source/_static/standard_output/1_move_robot_newton.mp4 differ diff --git a/docs/source/_static/standard_output/2_add_new_robot_newton.gif b/docs/source/_static/standard_output/2_add_new_robot_newton.gif new file mode 100644 index 000000000..620eb0bbe Binary files /dev/null and b/docs/source/_static/standard_output/2_add_new_robot_newton.gif differ diff --git a/docs/source/_static/standard_output/2_add_new_robot_newton.mp4 b/docs/source/_static/standard_output/2_add_new_robot_newton.mp4 new file mode 100644 index 000000000..bbf2ed886 Binary files /dev/null and b/docs/source/_static/standard_output/2_add_new_robot_newton.mp4 differ diff --git a/docs/source/_static/standard_output/3_parallel_envs_newton.gif b/docs/source/_static/standard_output/3_parallel_envs_newton.gif new file mode 100644 index 000000000..94b1f8fdc Binary files /dev/null and b/docs/source/_static/standard_output/3_parallel_envs_newton.gif differ diff --git a/docs/source/_static/standard_output/3_parallel_envs_newton.mp4 b/docs/source/_static/standard_output/3_parallel_envs_newton.mp4 new file mode 100644 index 000000000..14d74bac6 Binary files /dev/null and b/docs/source/_static/standard_output/3_parallel_envs_newton.mp4 differ diff --git a/docs/source/dataset_benchmark/dataset/robots.md b/docs/source/dataset_benchmark/dataset/robots.md index 90063e9df..0a4ef323d 100644 --- a/docs/source/dataset_benchmark/dataset/robots.md +++ b/docs/source/dataset_benchmark/dataset/robots.md @@ -19,3 +19,18 @@ RoboVerse currently includes some robots. | UR5e | Arm | 6 | ur5e_2f85 | | Walker | Bipedal | 6 | walker | | Ant | Quadreuped | 12 | ant | + +## Dexterous Hands + +RoboVerse includes support for various dexterous hands for manipulation tasks. + +| Robot Name | Number of DoFs | Config Name | Notes | +| ------ | ---------------- | ------------ | ----- | +| Allegro Hand | 16 | allegrohand | 4-finger anthropomorphic hand | +| BrainCo Hand (Left) | 11 | brainco_hand_left | 6 actuated + 5 mimic/passive joints, prosthetic hand | +| BrainCo Hand (Right) | 11 | brainco_hand_right | 6 actuated + 5 mimic/passive joints, prosthetic hand | +| Inspire Hand (Left) | 12 | inspire_hand_left | 6 actuated + 6 mimic/passive (coupled) joints | +| Inspire Hand (Right) | 12 | inspire_hand_right | 6 actuated + 6 mimic/passive (coupled) joints | +| PSIHand (Left) | 21 | psihand_left | All 21 joints actuated. Known issues with IsaacGym, use MuJoCo/Genesis | +| PSIHand (Right) | 21 | psihand_right | All 21 joints actuated. Known issues with IsaacGym, use MuJoCo/Genesis | +| XHand | 12 | xhand | Compact dexterous hand | diff --git a/docs/source/dataset_benchmark/tasks/generate_task_docs.py b/docs/source/dataset_benchmark/tasks/generate_task_docs.py index 25de29b6e..1653f3cda 100644 --- a/docs/source/dataset_benchmark/tasks/generate_task_docs.py +++ b/docs/source/dataset_benchmark/tasks/generate_task_docs.py @@ -24,7 +24,7 @@ "Metaworld", "Rlafford", ] -PLATFORMS = ["isaaclab", "mujoco", "isaacgym", "sapien3", "genesis"] +PLATFORMS = ["isaacsim", "mujoco", "isaacgym", "sapien3", "genesis", "newton"] def parse_docstring_metadata(docstring: str): diff --git a/docs/source/metasim/concept/config.md b/docs/source/metasim/concept/config.md index e76c91eb7..16dd8f44a 100644 --- a/docs/source/metasim/concept/config.md +++ b/docs/source/metasim/concept/config.md @@ -141,6 +141,6 @@ RobotCfg( enabled_gravity=True, control_type={"joint1": "position", "joint2": "effort"}, actuators={"joint1": BaseActuatorCfg(stiffness=500, damping=10), - "joint2": BaseActuatorCfg(torque_limit=50)} + "joint2": BaseActuatorCfg(effort_limit_sim=50)} ) ``` diff --git a/docs/source/metasim/developer_guide/autotest.md b/docs/source/metasim/developer_guide/autotest.md index 077410f4d..78b921756 100644 --- a/docs/source/metasim/developer_guide/autotest.md +++ b/docs/source/metasim/developer_guide/autotest.md @@ -2,7 +2,7 @@ ## Overview -RoboVerse provides a comprehensive testing infrastructure built on pytest that enables efficient, cross-backend testing of simulation functionality. The system is designed around handler reuse and scenario sharing, dramatically reducing test execution time while maintaining full coverage across all supported simulator backends (MuJoCo, MJX, IsaacGym, IsaacSim). +RoboVerse provides a comprehensive testing infrastructure built on pytest that enables efficient, cross-backend testing of simulation functionality. The system is designed around handler reuse and scenario sharing, dramatically reducing test execution time while maintaining full coverage across all supported simulator backends (MuJoCo, MJX, IsaacGym, IsaacSim, Newton). Key features of the testing system include: @@ -38,6 +38,7 @@ Markers declare which simulator backends a test requires: - `@pytest.mark.mjx`: Test runs on MuJoCo MJX backend - `@pytest.mark.isaacgym`: Test runs on IsaacGym backend - `@pytest.mark.isaacsim`: Test runs on IsaacSim backend +- `@pytest.mark.newton`: Test runs on Newton backend - `@pytest.mark.sim("sim1", "sim2")`: Test runs on multiple specified backends - `@pytest.mark.general`: Test requires no simulator/handler (pure unit test) @@ -321,6 +322,9 @@ python metasim/test/isaacgym_entry.py metasim/test/ -k isaacgym # IsaacSim only pytest metasim/test/ -k isaacsim +# Newton only +pytest metasim/test/ -k newton + # General tests (no simulator) pytest metasim/test/ -k general ``` diff --git a/docs/source/metasim/features/cross_sim.md b/docs/source/metasim/features/cross_sim.md index 50b7d0d9c..8068af87a 100644 --- a/docs/source/metasim/features/cross_sim.md +++ b/docs/source/metasim/features/cross_sim.md @@ -1,7 +1,7 @@ # Cross Simulator ## Basic usage -By default, the simulator is set to `isaaclab`. You can change it to other simulators by setting the `sim` argument. Currently, we support: -- `isaaclab` +By default, the simulator is set to `isaacsim`. You can change it to other simulators by setting the `sim` argument. Currently, we support: +- `isaacsim` - `isaacgym` - `pyrep` diff --git a/docs/source/metasim/features/support_matrix.rst b/docs/source/metasim/features/support_matrix.rst index 2a7d5c0e6..fa0f668ac 100644 --- a/docs/source/metasim/features/support_matrix.rst +++ b/docs/source/metasim/features/support_matrix.rst @@ -9,7 +9,7 @@ Supported Simulators There are 3 levels of supportance for each simulator: -- **Actively supported**: ``isaaclab``, ``isaacgym``, ``mujoco``, ``sapien2``, ``sapien3``, ``genesis``, ``pybullet`` . These simulators should always be guaranteed to work on the main branch. +- **Actively supported**: ``isaacsim``, ``isaacgym``, ``mujoco``, ``sapien2``, ``sapien3``, ``genesis``, ``pybullet``, ``newton`` . These simulators should always be guaranteed to work on the main branch. - **Inactively supported**: ``pyrep``. These simulators won't be actively supported. They will only be guaranteed to work when a major version is released. - **Experimental**: ``mjx``, ``blender``. These simulators (renderers) are still in experimental stage and will be added to "actively supported" list in the future. @@ -26,15 +26,16 @@ Simulation Configuration .. list-table:: :header-rows: 1 - :widths: 20 20 20 20 20 20 20 + :widths: 15 15 15 15 15 15 15 15 * - Parameter - - IsaacLab + - IsaacSim - IsaacGym - MuJoCo - Genesis - SAPIEN3 - PyBullet + - Newton * - ``dt`` - `1/60 `_ - `1/60 `_ @@ -42,6 +43,7 @@ Simulation Configuration - `1/100 `_ - 1/100 - `1/240 `_ + - 1/60 * - ``solver_type`` - `✓ `_ - `✓ `_ @@ -49,6 +51,7 @@ Simulation Configuration - - - + - * - ``env_spacing`` - - ✓ @@ -56,6 +59,7 @@ Simulation Configuration - ✓ - - + - ✓ @@ -64,15 +68,16 @@ Robot Configuration .. list-table:: :header-rows: 1 - :widths: 20 20 20 20 20 20 20 + :widths: 15 15 15 15 15 15 15 15 * - Parameter - - IsaacLab + - IsaacSim - IsaacGym - MuJoCo - Genesis - SAPIEN3 - PyBullet + - Newton * - ``stiffness`` - `✓ `_ - ✓ @@ -80,6 +85,7 @@ Robot Configuration - - ✓ - + - * - ``damping`` - `✓ `_ - ✓ @@ -87,6 +93,7 @@ Robot Configuration - - ✓ - + - * - ``velocity_limit`` - `✓ `_ - @@ -94,13 +101,15 @@ Robot Configuration - - - - * - ``torque_limit`` + - + * - ``effort_limit_sim`` - `✓ `_ - - - - - + - * - ``fully_actuated`` - ✓ - ✓ @@ -108,6 +117,7 @@ Robot Configuration - - - + - Physics Engine Configuration @@ -115,15 +125,16 @@ Physics Engine Configuration .. list-table:: :header-rows: 1 - :widths: 20 20 20 20 20 20 20 + :widths: 15 15 15 15 15 15 15 15 * - Parameter - - IsaacLab + - IsaacSim - IsaacGym - MuJoCo - Genesis - SAPIEN3 - PyBullet + - Newton * - ``bounce_threshold_velocity`` - `✓ `_ - `✓ `_ @@ -131,6 +142,7 @@ Physics Engine Configuration - - - + - * - ``contact_offset`` - - `✓ `_ @@ -138,6 +150,7 @@ Physics Engine Configuration - - - + - * - ``friction_correlation_distance`` - `✓ `_ - `✓ `_ @@ -145,6 +158,7 @@ Physics Engine Configuration - - - + - * - ``friction_offset_threshold`` - `✓ `_ - `✓ `_ @@ -152,6 +166,7 @@ Physics Engine Configuration - - - + - * - ``num_position_iterations`` - - `✓ `_ @@ -159,6 +174,7 @@ Physics Engine Configuration - - - + - * - ``num_velocity_iterations`` - - `✓ `_ @@ -166,6 +182,7 @@ Physics Engine Configuration - - - + - * - ``rest_offset`` - - `✓ `_ @@ -173,6 +190,7 @@ Physics Engine Configuration - - - + - * - ``max_depenetration_velocity`` - - `✓ `_ @@ -180,6 +198,7 @@ Physics Engine Configuration - - - + - * - ``default_buffer_size_multiplier`` - - `✓ `_ @@ -187,21 +206,23 @@ Physics Engine Configuration - - - + - Resource Management Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 - :widths: 20 20 20 20 20 20 20 + :widths: 15 15 15 15 15 15 15 15 * - Parameter - - IsaacLab + - IsaacSim - IsaacGym - MuJoCo - Genesis - SAPIEN3 - PyBullet + - Newton * - ``num_threads`` - - `✓ `_ @@ -209,21 +230,23 @@ Resource Management Configuration - - - + - Misc Configuration ~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 - :widths: 20 20 20 20 20 20 20 + :widths: 15 15 15 15 15 15 15 15 * - Parameter - - IsaacLab + - IsaacSim - IsaacGym - MuJoCo - Genesis - SAPIEN3 - PyBullet + - Newton * - ``replace_cylinder_with_capsule`` - - `✓ `_ @@ -231,3 +254,4 @@ Misc Configuration - - - + - diff --git a/docs/source/metasim/get_started/advanced/rl_example/infrastructure.md b/docs/source/metasim/get_started/advanced/rl_example/infrastructure.md index f1a2fbfcc..d3c0c0441 100644 --- a/docs/source/metasim/get_started/advanced/rl_example/infrastructure.md +++ b/docs/source/metasim/get_started/advanced/rl_example/infrastructure.md @@ -26,7 +26,7 @@ python get_started/rl/0_ppo.py --num-envs 256 --headless # Different simulators python get_started/rl/0_ppo.py --sim mujoco python get_started/rl/0_ppo.py --sim genesis -python get_started/rl/0_ppo.py --sim isaaclab +python get_started/rl/0_ppo.py --sim isaacsim ``` **Arguments:** @@ -34,7 +34,7 @@ python get_started/rl/0_ppo.py --sim isaaclab - `--task`: Task name (default: `reach_origin`) - `--robot`: Robot type (default: `franka`) - `--num-envs`: Number of parallel environments (default: `128`) -- `--sim`: Simulator backend (`isaacgym`, `isaaclab`, `mujoco`, `genesis`, `mjx`) +- `--sim`: Simulator backend (`isaacgym`, `isaacsim`, `mujoco`, `genesis`, `mjx`, `newton`) - `--headless`: Run without GUI (flag) **Outputs:** @@ -63,7 +63,7 @@ python get_started/rl/0_ppo_gym.py --task reach_origin --robot franka --num-envs - `--task`: Task name (default: `reach_origin`) - `--robot`: Robot type (default: `franka`) - `--num-envs`: Number of environments (default: `128`) -- `--sim`: Simulator (`isaaclab`, `isaacgym`, `mujoco`, `genesis`, `mjx`) +- `--sim`: Simulator (`isaacsim`, `isaacgym`, `mujoco`, `genesis`, `mjx`, `newton`) - `--headless`: Headless mode (flag) - `--device`: Device (`cuda`, `cpu`) @@ -114,10 +114,11 @@ CONFIG = { ### Simulator Backends 1. **Isaac Gym**: NVIDIA's physics simulation -2. **Isaac Lab**: Next-generation Isaac simulation +2. **Isaac Sim**: Next-generation Isaac simulation 3. **MuJoCo**: Fast physics simulation 4. **Genesis**: Multi-physics simulation 5. **MJX**: JAX-based MuJoCo implementation +6. **Newton**: GPU-accelerated physics simulation ## Dependencies diff --git a/docs/source/metasim/get_started/advanced_installation/pyroki.md b/docs/source/metasim/get_started/advanced_installation/pyroki.md index 18ed193d5..e600ad013 100644 --- a/docs/source/metasim/get_started/advanced_installation/pyroki.md +++ b/docs/source/metasim/get_started/advanced_installation/pyroki.md @@ -9,16 +9,20 @@ PyRoki requires Python 3.10 or higher. Python 3.12+ is recommended for best comp ## Installation ```bash +cd third_part git clone https://github.com/chungmin99/pyroki.git cd pyroki pip install -e . +cd ../../ +pip install jax==0.4.30 jaxlib==0.4.30 ``` -For Isaacsim, also need the following commands: +### For Isaacsim +If you encounter a NumPy version mismatch between lsaacSim 5.0.0 and PyRoki, for example, an error +`TypeError: asarray() got an unexpected keyword argument 'copy'`, try running the following commands: ```bash -pip install numpy==1.26.0 # For Isaacsim -pip install jax==0.6.0 # For Isaacsim +pip install numpy==1.26.0 +pip install sentry-sdk==1.43.0 typing-extensions==4.12.2 websockets==12.0 +pip install --upgrade websockets ``` - - diff --git a/docs/source/metasim/get_started/installation.rst b/docs/source/metasim/get_started/installation.rst index d16635976..af6009f6f 100644 --- a/docs/source/metasim/get_started/installation.rst +++ b/docs/source/metasim/get_started/installation.rst @@ -46,6 +46,10 @@ MuJoCo, SAPIEN2, SAPIEN3, Genesis, and PyBullet can be installed directly via `` - ``uv pip install -e ".[pybullet]"`` - 3.6-3.11 - 3.10 + * - Newton + - ``uv pip install -e ".[newton]"`` + - 3.10-3.12 + - 3.10 * - IsaacSim v4.5.0 - See below - 3.10 diff --git a/docs/source/metasim/get_started/prerequisite.md b/docs/source/metasim/get_started/prerequisite.md index fa57849c0..8d776fccf 100644 --- a/docs/source/metasim/get_started/prerequisite.md +++ b/docs/source/metasim/get_started/prerequisite.md @@ -8,11 +8,11 @@ You may choose one or more of the following simulators, according to your operat For beginners, we recommend choosing one specific simulator for each environment, which makes environment configuration simpler and provides better isolation. -| OS | IsaacSim | IsaacGym | MuJoCo | SAPIEN2 | SAPIEN3 | Genesis | PyBullet | -|---------|----------|----------|--------|---------|---------|---------|----------| -| MacOS | | | ✓ | | ✓ | ✓ | | -| Windows | ✓ | | ✓ | | ✓ | ✓ | | -| Ubuntu | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| OS | IsaacSim | IsaacGym | MuJoCo | SAPIEN2 | SAPIEN3 | Genesis | PyBullet | Newton | +|---------|----------|----------|--------|---------|---------|---------|----------|--------| +| MacOS | | | ✓ | | ✓ | ✓ | | | +| Windows | ✓ | | ✓ | | ✓ | ✓ | | | +| Ubuntu | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ```{note} RoboVerse team hasn't got the chance to fully test MetaSim on MacOS and Windows. Please let us know if you have any issues. diff --git a/docs/source/metasim/get_started/quick_start/0_static_scene.md b/docs/source/metasim/get_started/quick_start/0_static_scene.md index f522280cd..266464d49 100644 --- a/docs/source/metasim/get_started/quick_start/0_static_scene.md +++ b/docs/source/metasim/get_started/quick_start/0_static_scene.md @@ -43,18 +43,27 @@ python get_started/0_static_scene.py --sim sapien3 python get_started/0_static_scene.py --sim pybullet ``` +#### Newton +```bash +python get_started/0_static_scene.py --sim newton +``` + You will get the following image: --- -| Isaac Lab | Isaac Gym | Mujoco | +| Isaac Sim | Isaac Gym | Mujoco | |:---:|:---:|:---:| -| ![Isaac Lab](../../../_static/standard_output/0_static_scene_isaaclab.png) | ![Isaac Gym](../../../_static/standard_output/0_static_scene_isaacgym.png) | ![Mujoco](../../../_static/standard_output/0_static_scene_mujoco.png) | +| ![Isaac Sim](../../../_static/standard_output/0_static_scene_isaacsim.png) | ![Isaac Gym](../../../_static/standard_output/0_static_scene_isaacgym.png) | ![Mujoco](../../../_static/standard_output/0_static_scene_mujoco.png) | | Genesis | Sapien | PyBullet | |:---:|:---:|:---:| | ![Genesis](../../../_static/standard_output/0_static_scene_genesis.png) | ![Sapien](../../../_static/standard_output/0_static_scene_sapien3.png) | ![Pybullet](../../../_static/standard_output/0_static_scene_pybullet.png) | +| Newton | +|:---:| +| ![Newton](../../../_static/standard_output/0_static_scene_newton.png) | + ## Code Highlights **Object Configuration**: Objects are added to `scenario.objects` with different types: diff --git a/docs/source/metasim/get_started/quick_start/17_rerun_visualization.md b/docs/source/metasim/get_started/quick_start/17_rerun_visualization.md new file mode 100644 index 000000000..019508e0a --- /dev/null +++ b/docs/source/metasim/get_started/quick_start/17_rerun_visualization.md @@ -0,0 +1,306 @@ +# 17. Rerun Visualization + +This tutorial shows you how to use [Rerun](https://rerun.io/) to visualize RoboVerse simulations with timeline-based exploration, recording, and replay capabilities. + +## What is Rerun? + +Rerun is an open-source SDK for logging, storing, querying, and visualizing multimodal data. Unlike traditional simulation viewers, Rerun provides: + +- **Timeline-based exploration**: Scrub through simulation history like a video +- **Recording & Replay**: Save sessions as `.rrd` files for offline viewing +- **Multi-modal support**: Visualize robots, objects, images, point clouds together +- **Cross-platform**: Works on Linux, macOS (including Apple Silicon), and Windows + +## Installation + +Install the Rerun SDK and dependencies: + +```bash +pip install rerun-sdk trimesh yourdfpy +``` + +Verify installation: + +```bash +rerun --version +``` + +## Quick Start + +### Step 1: Replay an Existing Task Demo + +The easiest way to get started is to replay a pre-recorded task trajectory. This doesn't require GPU or IK solvers. + +```bash +# Replay the stack_cube task +python get_started/rerun/replay_task_demo.py --task stack_cube --sim mujoco --output stack_cube.rrd +``` + +This will: +1. Load the `stack_cube` task configuration +2. Download required assets (URDF files, meshes) +3. Replay the trajectory step by step +4. Save the visualization as `stack_cube.rrd` + +### Step 2: View the Recording + +Open the saved recording in the Rerun viewer: + +```bash +rerun stack_cube.rrd +``` + +You'll see: +- 🤖 **Franka robot** with moving joints +- 📦 **Colored cubes** being stacked +- ⏱️ **Timeline** at the bottom for scrubbing through the simulation + +### Step 3: Live Viewer During Recording + +To see the visualization in real-time while recording: + +```bash +python get_started/rerun/replay_task_demo.py --task stack_cube --sim mujoco --output stack_cube.rrd --spawn-viewer +``` + +## Available Demo Scripts + +### 1. Replay Task Demo (Recommended for Beginners) + +Replays pre-recorded task trajectories. **No GPU or IK solver needed!** + +```bash +# Available tasks: stack_cube, close_box, pick_cube, etc. +python get_started/rerun/replay_task_demo.py --task stack_cube --sim mujoco --output stack_cube.rrd + +# Try different tasks +python get_started/rerun/replay_task_demo.py --task close_box --sim mujoco --output close_box.rrd +python get_started/rerun/replay_task_demo.py --task pick_cube --sim mujoco --output pick_cube.rrd +``` + +**Command Line Arguments:** + +| Argument | Type | Default | Description | +|----------|------|---------|-------------| +| `--task` | str | "stack_cube" | Task name to replay | +| `--robot` | str | "franka" | Robot to use | +| `--sim` | str | "mujoco" | Simulator backend | +| `--output` | str | "task_replay.rrd" | Output recording file | +| `--spawn-viewer` | bool | False | Open viewer during recording | +| `--max-steps` | int | None | Maximum steps to record | + +### 2. Simple Trajectory Recording (CPU-Only) + +Generates sinusoidal or random joint motions directly. **Works on Mac without GPU!** + +```bash +# Sinusoidal motion (smooth, periodic) +python get_started/rerun/save_trajectory_simple.py --sim mujoco --output trajectory.rrd + +# Random motion +python get_started/rerun/save_trajectory_simple.py --sim mujoco --motion-type random --output trajectory.rrd + +# More simulation steps +python get_started/rerun/save_trajectory_simple.py --sim mujoco --num-steps 500 --output trajectory.rrd +``` + +**Command Line Arguments:** + +| Argument | Type | Default | Description | +|----------|------|---------|-------------| +| `--robot` | str | "franka" | Robot model | +| `--sim` | str | "mujoco" | Simulator backend | +| `--output` | str | "trajectory.rrd" | Output recording file | +| `--num-steps` | int | 200 | Number of simulation steps | +| `--motion-type` | str | "sinusoidal" | Motion type: "sinusoidal" or "random" | +| `--spawn-viewer` | bool | False | Open viewer during recording | + +### 3. Full Demo with IK Solver (Requires GPU) + +For users with GPU and IK solver (PyRoKi or cuRobo): + +```bash +# With PyRoKi IK solver +python get_started/rerun/rerun_demo.py --sim mujoco --dynamic --solver pyroki + +# With cuRobo IK solver +python get_started/rerun/rerun_demo.py --sim mujoco --dynamic --solver curobo + +# Save recording +python get_started/rerun/rerun_demo.py --sim mujoco --dynamic --save-recording demo.rrd +``` + +## Step-by-Step Tutorial + +### Understanding the Rerun Viewer + +When you open a `.rrd` file, the Rerun viewer shows: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ [Entity Tree] │ [3D Viewport] │ +│ │ │ +│ ▼ world │ 🤖 Robot + 📦 Objects │ +│ ▼ franka │ │ +│ panda_link0│ │ +│ panda_link1│ │ +│ ... │ │ +│ ▼ cube │ │ +│ ▼ base │ │ +│ │ │ +├─────────────────┴───────────────────────────────────────────┤ +│ [Timeline] ◀ ▶ ━━━━━●━━━━━━━━━━━━━━━━━━ Step: 42/200 │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Navigation Controls:** +- **Rotate**: Left mouse drag +- **Pan**: Middle mouse drag or Shift+Left drag +- **Zoom**: Scroll wheel +- **Timeline**: Drag the playhead or use play button + +### Creating Your Own Visualization + +Here's how to add Rerun visualization to your own simulation: + +```python +from metasim.utils.rerun.rerun_util import RerunVisualizer + +# 1. Initialize visualizer +visualizer = RerunVisualizer( + app_name="My Simulation", + spawn=True, # Auto-open viewer + save_path="my_sim.rrd" # Optional: save recording +) + +# 2. Add coordinate frame (optional) +visualizer.add_frame("world/origin") + +# 3. Initial visualization of objects and robots +visualizer.visualize_scenario_items(scenario.objects, object_states) +visualizer.visualize_scenario_items(scenario.robots, robot_states) + +# 4. Simulation loop +for step in range(num_steps): + # Set timeline position + visualizer.set_time(step) + + # Run simulation + handler.simulate() + obs = handler.get_states(mode="tensor") + + # Extract and update states + for name, state in robot_states.items(): + visualizer.update_item_pose(name, state) + for name, state in object_states.items(): + visualizer.update_item_pose(name, state) + +# 5. Cleanup +visualizer.close() +``` + +### State Format + +The state dictionary format expected by `update_item_pose`: + +```python +state = { + "pos": [x, y, z], # Position in world frame + "rot": [w, x, y, z], # Quaternion (wxyz format) + "dof_pos": { # Joint positions (for articulated objects) + "joint_name": value, + ... + } +} +``` + +## Comparison with Other Visualizers + +| Feature | Rerun | Native Viewer | Viser | +|---------|-------|---------------|-------| +| Timeline scrubbing | ✅ | ❌ | ❌ | +| Recording/Replay | ✅ `.rrd` | ❌ | ❌ | +| Works on Mac | ✅ | ⚠️ Limited | ✅ | +| No GPU required | ✅ | ⚠️ | ✅ | +| Interactive controls | ❌ | ✅ | ✅ | +| Web-based | ❌ | ❌ | ✅ | + +**Use Rerun when:** +- You need to record and replay simulations +- You want timeline-based exploration +- You're debugging complex trajectories +- You're on macOS without GPU + +**Use Native Viewer when:** +- You need real-time interactive simulation +- You want built-in physics visualization + +**Use Viser when:** +- You need web-based access +- You want interactive joint/IK sliders + +## Troubleshooting + +### Viewer doesn't open automatically + +```bash +# Launch viewer manually +rerun + +# Then run your script with connect mode +python your_script.py +``` + +### URDF/mesh loading issues + +Ensure dependencies are installed: +```bash +pip install trimesh yourdfpy +``` + +### Recording file too large + +Reduce the number of steps or recording frequency: +```bash +python get_started/rerun/replay_task_demo.py --task stack_cube --max-steps 100 --output small.rrd +``` + +### Performance issues on macOS + +Use headless mode for the simulator: +```bash +python get_started/rerun/replay_task_demo.py --task stack_cube --sim mujoco --output stack_cube.rrd +# The simulator runs headless, only Rerun viewer shows +``` + +## Example Output + +After running the stack_cube demo, you'll see: + +| Rerun Viewer | +|:---:| +| ![Rerun Viewer](../../../_static/standard_output/rerun_stack_cube.png) | + +The recording shows: +- Franka robot arm with all links and joints +- Red cube being picked up +- Blue base cube as the target +- Full timeline for scrubbing through the trajectory + +## Files Reference + +| File | Description | +|------|-------------| +| `get_started/rerun/replay_task_demo.py` | Replay pre-recorded task trajectories | +| `get_started/rerun/save_trajectory_simple.py` | CPU-only trajectory recording | +| `get_started/rerun/rerun_demo.py` | Full demo with IK solver | +| `metasim/utils/rerun/rerun_util.py` | Core RerunVisualizer class | +| `metasim/utils/rerun/rerun_env_wrapper.py` | RL environment wrapper | + +## Next Steps + +- Try different tasks: `close_box`, `pick_cube`, `poke_cube` +- Add Rerun visualization to your own training loop +- Explore the [Rerun documentation](https://rerun.io/docs) for advanced features +- Check out the [Viser integration](../advanced/viser/usage.md) for web-based visualization + diff --git a/docs/source/metasim/get_started/quick_start/1_control_robot.md b/docs/source/metasim/get_started/quick_start/1_control_robot.md index c752bd83c..853cfd086 100644 --- a/docs/source/metasim/get_started/quick_start/1_control_robot.md +++ b/docs/source/metasim/get_started/quick_start/1_control_robot.md @@ -46,6 +46,11 @@ python get_started/1_control_robot.py --sim sapien3 python get_started/1_control_robot.py --sim pybullet ``` +#### Newton +```bash +python get_started/1_control_robot.py --sim newton +``` + You will get the following videos: @@ -53,9 +58,9 @@ You will get the following videos:
-

IsaacSim

+

Isaac Sim

+
+
+ +

Newton

+
+
## Code Highlights diff --git a/docs/source/metasim/get_started/quick_start/2_add_new_robot.md b/docs/source/metasim/get_started/quick_start/2_add_new_robot.md index f23e7a9c3..ad5d529cf 100644 --- a/docs/source/metasim/get_started/quick_start/2_add_new_robot.md +++ b/docs/source/metasim/get_started/quick_start/2_add_new_robot.md @@ -46,6 +46,11 @@ python get_started/2_add_new_robot.py --sim sapien3 python get_started/2_add_new_robot.py --sim pybullet ``` +#### Newton +```bash +python get_started/2_add_new_robot.py --sim newton +``` + You will get the following videos: @@ -53,9 +58,9 @@ You will get the following videos:
-

Isaac Lab

+

Isaac Sim

+
+
+ +

Newton

+
+
## Code Highlights diff --git a/docs/source/metasim/get_started/quick_start/3_parallel_envs.md b/docs/source/metasim/get_started/quick_start/3_parallel_envs.md index b25ed0d9f..e31db4e78 100644 --- a/docs/source/metasim/get_started/quick_start/3_parallel_envs.md +++ b/docs/source/metasim/get_started/quick_start/3_parallel_envs.md @@ -36,6 +36,11 @@ python get_started/3_parallel_envs.py --sim mujoco --num_envs 4 --headless ``` **If you are on mac**, please avoid running this task without the `headless` tag. +#### Newton +```bash +python get_started/3_parallel_envs.py --sim newton --num_envs 4 +``` + We can open multiple environments at the same time.