From 862d2dafd49883ea6449312538d2b391bbc839de Mon Sep 17 00:00:00 2001 From: Alexis DUBURCQ Date: Thu, 8 Apr 2021 06:56:54 +0200 Subject: [PATCH] [python/viewer] Initial support of 'panda3d-qt' rendering backend. (#313) * [python/viewer] Improve video recording efficiency using panda3d backend. (#309) * [misc] Avoid copying irrelevant files when build wheels. * [misc] To need to install wheels twice anymore thanks to new pip dependency resolver. * [python/dynamics] Add XYZQuat to XYZRPY position and velocity converters. * [python/viewer] Use 'av' package instead of 'cv2' to export video. It is faster and support web-compatible 'h264' codec. * [python/viewer] Improve video recording speed even further by disabling panda3d framerate limit during recording. * [python/viewer] Restore default window size. * [python/viewer] Update collision geometry placement only once and only if necessary. * [python/viewer] Raise warning if 'enable_clock' is true and 'panda3d' backend is not used. * [python/dynamics] Minor performance improvement of newly introduced 'velocityXYZQuatToXYZRPY' converter. * [python/viewer] Reduce video quality: ~15mo for 1min. Raise helpful exception if 'replay' called with wrong urdf color. * [python/viewer] Reorder and remove some keyword arguments of 'Viewer.__init__' and 'play_trajectories'. * [python/viewer] Rename 'urdf_rgba' to 'robot_color' for clarity. Add 's' suffix to list arguments. * [python/viewer] Replace 'window_name' argument by static 'Viewer' attribute. * [misc] Update dependencies to latest version available. * [misc] Fix support of Ninja Cmake generator. * [misc] Fix support of Cmake 3.20 on windows 10 CI. * [python/viewer] Initial support of 'panda3d-qt' rendering backend. * [python/viewer] Select panda3d Qt backend automatically. Only refresh panda3d scene when needed. * [python/viewer] Add method to update robot color on-the-fly (only supported by panda3d). * [python/viewer] Fix panda3d screenshot wrongly removing alpha channel for '.png'. * [python/viewer] Fix panda3d scene sometimes not updated before capturing frame. * [gym/common/envs] Fix opening onscreen window at env reset if offscreen window is already available. * [misc] Update hpp-fcl to 1.7.1 to fix Ubuntu CI. * [misc] Update panda3d units tests screenshots. * [misc] Clearer Cmake warnings for Ubuntu 18 legacy mode. * [python/viewer] Add support of 'capture_frame' as raw data for panda3d. * [misc] Update panda3d units tests screenshots. * [misc] Fix Window scripts cmake targets. * [misc] Update release patch tag. Co-authored-by: Alexis Duburcq --- .github/workflows/win.yml | 16 +- CMakeLists.txt | 2 +- INSTALL.md | 16 +- build_tools/build_install_deps_linux.sh | 4 +- build_tools/build_install_deps_windows.ps1 | 34 +- build_tools/cmake/base.cmake | 13 +- build_tools/cmake/buildPythonWheel.cmake | 12 +- build_tools/cmake/docs.cmake | 4 +- build_tools/cmake/setupPython.cmake | 7 +- build_tools/easy_install_deps_ubuntu.sh | 4 +- build_tools/patch_deps_linux/eigenpy.patch | 2 +- build_tools/patch_deps_linux/hppfcl.patch | 67 ++-- build_tools/patch_deps_windows/eigenpy.patch | 2 +- build_tools/patch_deps_windows/hppfcl.patch | 71 ++-- core/CMakeLists.txt | 4 - core/src/solver/LCPSolvers.cc | 6 +- python/CMakeLists.txt | 2 +- .../gym_jiminy/common/envs/env_generic.py | 6 +- .../examples/rllib/tools/utilities.py | 3 +- .../unit_py/data/atlas_standing_panda3d_0.png | Bin 28841 -> 29142 bytes .../unit_py/data/atlas_standing_panda3d_1.png | Bin 27014 -> 26389 bytes .../unit_py/data/atlas_standing_panda3d_2.png | Bin 0 -> 27041 bytes .../unit_py/data/atlas_standing_panda3d_3.png | Bin 0 -> 27826 bytes .../data/cassie_standing_panda3d_0.png | Bin 7236 -> 8843 bytes .../data/cassie_standing_panda3d_1.png | Bin 8849 -> 7199 bytes .../data/cassie_standing_panda3d_2.png | Bin 0 -> 7227 bytes python/jiminy_py/setup.py | 4 +- python/jiminy_py/src/jiminy_py/dynamics.py | 34 +- python/jiminy_py/src/jiminy_py/simulator.py | 15 +- .../viewer/panda3d/panda3d_visualizer.py | 296 ++++++++++++----- .../viewer/panda3d/panda3d_widget.py | 115 +++++++ .../jiminy_py/src/jiminy_py/viewer/replay.py | 305 ++++++++++-------- .../jiminy_py/src/jiminy_py/viewer/viewer.py | 263 ++++++++------- soup/CMakeLists.txt | 8 +- soup/gtest/CMakeLists.txt | 61 ++-- soup/hdf5/CMakeLists.txt | 45 +-- soup/jsoncpp/CMakeLists.txt | 20 +- 37 files changed, 925 insertions(+), 516 deletions(-) create mode 100644 python/gym_jiminy/unit_py/data/atlas_standing_panda3d_2.png create mode 100644 python/gym_jiminy/unit_py/data/atlas_standing_panda3d_3.png create mode 100644 python/gym_jiminy/unit_py/data/cassie_standing_panda3d_2.png create mode 100644 python/jiminy_py/src/jiminy_py/viewer/panda3d/panda3d_widget.py diff --git a/.github/workflows/win.yml b/.github/workflows/win.yml index 4b370b94c..1f767892d 100644 --- a/.github/workflows/win.yml +++ b/.github/workflows/win.yml @@ -57,11 +57,11 @@ jobs: $ErrorActionPreference = "Stop" Set-PSDebug -Trace 1 - $RootDir = "${Env:GITHUB_WORKSPACE}" -replace '\\', '/' + $RootDir = "${env:GITHUB_WORKSPACE}" -replace '\\', '/' $InstallDir = "$RootDir/install" - if (Test-Path Env:/Boost_ROOT) { - Remove-Item Env:/Boost_ROOT + if (Test-Path env:Boost_ROOT) { + Remove-Item env:Boost_ROOT } if (-not (Test-Path -PathType Container $RootDir/build)) { @@ -77,7 +77,7 @@ jobs: -DBoost_USE_STATIC_LIBS=ON -DPYTHON_REQUIRED_VERSION="${{ matrix.python-version }}" ` -DBUILD_TESTING=ON -DBUILD_EXAMPLES=ON -DBUILD_PYTHON_INTERFACE=ON ` -DCMAKE_CXX_FLAGS="/EHsc /bigobj /Zc:__cplusplus -D_USE_MATH_DEFINES -DBOOST_ALL_NO_LIB -DBOOST_LIB_DIAGNOSTIC -DEIGENPY_STATIC -DURDFDOM_STATIC -DHPP_FCL_STATIC -DPINOCCHIO_STATIC" - cmake --build . --config "${Env:BUILD_TYPE}" --parallel 2 + cmake --build . --target ALL_BUILD --config "${env:BUILD_TYPE}" --parallel 2 if (-not (Test-Path -PathType Container "$RootDir/build/pypi/jiminy_py/src/jiminy_py")) { New-Item -ItemType "directory" -Force -Path "$RootDir/build/pypi/jiminy_py/src/jiminy_py" @@ -86,14 +86,14 @@ jobs: -Destination "$RootDir/build/pypi/jiminy_py/src/jiminy_py" $env:Path += ";$InstallDir/lib" - cmake --build . --target INSTALL --config "${Env:BUILD_TYPE}" --parallel 2 + cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" ##################################################################################### - name: Generating the Python Pip wheels if: success() && github.repository == 'duburcqa/jiminy' run: | - $RootDir = "${Env:GITHUB_WORKSPACE}" -replace '\\', '/' + $RootDir = "${env:GITHUB_WORKSPACE}" -replace '\\', '/' $InstallDir = "$RootDir/install" $env:Path += ";$InstallDir/lib" @@ -124,9 +124,9 @@ jobs: - name: Running unit tests run: | - $RootDir = "${Env:GITHUB_WORKSPACE}" -replace '\\', '/' + $RootDir = "${env:GITHUB_WORKSPACE}" -replace '\\', '/' - & "$RootDir/build/unit\${Env:BUILD_TYPE}/unit.exe" + & "$RootDir/build/unit\${env:BUILD_TYPE}/unit.exe" Set-Location -Path "$RootDir/unit_py" python -m unittest discover -v diff --git a/CMakeLists.txt b/CMakeLists.txt index 887f4e4b8..d010a4edb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.10) # Set the build version -set(BUILD_VERSION 1.6.7) +set(BUILD_VERSION 1.6.8) # Add definition of Jiminy version for C++ headers add_definitions("-DJIMINY_VERSION=\"${BUILD_VERSION}\"") diff --git a/INSTALL.md b/INSTALL.md index e92c38f24..f999a59c8 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -70,9 +70,9 @@ Installing the Python packages `ray==1.0.0` are required to run some of the prov python -m pip install gym-jiminy ``` -## Build Jiminy from source on Ubuntu 18 (excluding dependencies) +## Build Jiminy from source on Ubuntu 18/20 (excluding dependencies) -First, one must install the pre-compiled libraries of the dependencies. Most of them are available on `robotpkg` APT repository. Just run the bash script to install them automatically for Ubuntu 18. It should be straightforward to adapt it to any other distribution for which `robotpkg` is available. +First, one must install the pre-compiled libraries of the dependencies. Most of them are available on `robotpkg` APT repository. Just run the bash script to install them automatically for Ubuntu 18/20. It should be straightforward to adapt it to any other distribution for which `robotpkg` is available. ```bash sudo ./build_tools/easy_install_deps_ubuntu18.sh @@ -211,7 +211,7 @@ python -m pip install wheel "numpy<1.20" Now you can simply run the powershell script already available. ``` -$Env:BUILD_TYPE = "Release" +$env:BUILD_TYPE = "Release" & './build_tools/build_install_deps_windows.ps1' ``` @@ -222,12 +222,12 @@ You are finally ready to build Jiminy itself. ``` $RootDir = ".... The location of jiminy repository ...." -$Env:BUILD_TYPE = "Release" +$env:BUILD_TYPE = "Release" $RootDir = $RootDir -replace '\\', '/' $InstallDir = "$RootDir/install" -if (Test-Path Env:/Boost_ROOT) { - Remove-Item Env:/Boost_ROOT +if (Test-Path env:Boost_ROOT) { + Remove-Item env:Boost_ROOT } if (-not (Test-Path -PathType Container $RootDir/build)) { @@ -241,7 +241,7 @@ cmake "$RootDir" -G "Visual Studio 16 2019" -T "v142" -DCMAKE_GENERATOR_PLATFORM -DBoost_USE_STATIC_LIBS=OFF ` -DBUILD_TESTING=ON -DBUILD_EXAMPLES=ON -DBUILD_PYTHON_INTERFACE=ON ` -DCMAKE_CXX_FLAGS="/EHsc /bigobj -D_USE_MATH_DEFINES -DBOOST_ALL_NO_LIB -DBOOST_LIB_DIAGNOSTIC -DURDFDOM_STATIC" -cmake --build . --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target all --config "${env:BUILD_TYPE}" --parallel 8 if (-not (Test-Path -PathType Container "$RootDir/build/PyPi/jiminy_py/src/jiminy_py/core")) { New-Item -ItemType "directory" -Force -Path "$RootDir/build/PyPi/jiminy_py/src/jiminy_py/core" @@ -253,5 +253,5 @@ Copy-Item -Path "$InstallDir/lib/boost_python*.dll" ` Copy-Item -Path "$InstallDir/lib/site-packages/*" ` -Destination "$RootDir/build/PyPi/jiminy_py/src/jiminy_py" -Recurse -cmake --build . --target INSTALL --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target install --config "${env:BUILD_TYPE}" ``` diff --git a/build_tools/build_install_deps_linux.sh b/build_tools/build_install_deps_linux.sh index 70e353146..96fae7cd9 100755 --- a/build_tools/build_install_deps_linux.sh +++ b/build_tools/build_install_deps_linux.sh @@ -54,7 +54,7 @@ if [ ! -d "$RootDir/eigenpy" ]; then fi cd "$RootDir/eigenpy" git reset --hard -git checkout --force "v2.6.0" +git checkout --force "v2.6.2" git submodule --quiet foreach --recursive git reset --quiet --hard git submodule --quiet update --init --recursive --jobs 8 git apply --reject --whitespace=fix "$RootDir/build_tools/patch_deps_linux/eigenpy.patch" @@ -108,7 +108,7 @@ if [ ! -d "$RootDir/hpp-fcl" ]; then fi cd "$RootDir/hpp-fcl" git reset --hard -git checkout --force "v1.6.0" +git checkout --force "v1.7.1" git submodule --quiet foreach --recursive git reset --quiet --hard git submodule --quiet update --init --recursive --jobs 8 git apply --reject --whitespace=fix "$RootDir/build_tools/patch_deps_linux/hppfcl.patch" diff --git a/build_tools/build_install_deps_windows.ps1 b/build_tools/build_install_deps_windows.ps1 index 9cbec4c08..6f24ec466 100644 --- a/build_tools/build_install_deps_windows.ps1 +++ b/build_tools/build_install_deps_windows.ps1 @@ -5,8 +5,8 @@ $ErrorActionPreference = "Continue" Set-PSDebug -Trace 1 ### Set the build type to "Release" if undefined -if (-not (Test-Path Env:BUILD_TYPE)) { - $Env:BUILD_TYPE = "Release" +if (-not (Test-Path env:BUILD_TYPE)) { + $env:BUILD_TYPE = "Release" } ### Get the fullpath of Jiminy project @@ -24,8 +24,8 @@ if (-not (Test-Path -PathType Container "$InstallDir")) { $PYTHON_EXECUTABLE = ( python -c "import sys; sys.stdout.write(sys.executable)" ) ### Remove the preinstalled boost library from search path -if (Test-Path Env:/Boost_ROOT) { - Remove-Item Env:/Boost_ROOT +if (Test-Path env:Boost_ROOT) { + Remove-Item env:Boost_ROOT } ################################## Checkout the dependencies ########################################### @@ -54,7 +54,7 @@ if (-not (Test-Path -PathType Container "$RootDir/eigenpy")) { } Set-Location -Path "$RootDir/eigenpy" git reset --hard -git checkout --force "v2.6.0" +git checkout --force "v2.6.2" git submodule --quiet foreach --recursive git reset --quiet --hard git submodule --quiet update --init --recursive --jobs 8 dos2unix "$RootDir/build_tools/patch_deps_windows/eigenpy.patch" @@ -110,7 +110,7 @@ if (-not (Test-Path -PathType Container "$RootDir/hpp-fcl")) { } Set-Location -Path "$RootDir/hpp-fcl" git reset --hard -git checkout --force "v1.6.0" +git checkout --force "v1.7.1" git submodule --quiet foreach --recursive git reset --quiet --hard git submodule --quiet update --init --recursive --jobs 8 dos2unix "$RootDir/build_tools/patch_deps_windows/hppfcl.patch" @@ -154,7 +154,7 @@ Set-Location -Path "$RootDir/boost" # C++ runtime library Windows (aka (U)CRT) ships as part of Windows 10. # Note that static linkage is still possible on windows but Jamroot must be edited # to remove line "@handle-static-runtime". -$BuildTypeB2 = ${Env:BUILD_TYPE}.ToLower() +$BuildTypeB2 = ${env:BUILD_TYPE}.ToLower() if (-not (Test-Path -PathType Container "$RootDir/boost/build")) { New-Item -ItemType "directory" -Force -Path "$RootDir/boost/build" } @@ -180,7 +180,7 @@ cmake "$RootDir/eigen3" -Wno-dev -G "Visual Studio 16 2019" -T "v142" -DCMAKE_GE -DCMAKE_CXX_STANDARD=17 -DCMAKE_INSTALL_PREFIX="$InstallDir" ` -DBUILD_TESTING=OFF -DEIGEN_BUILD_PKGCONFIG=OFF ` -DCMAKE_CXX_FLAGS="/EHsc /bigobj -DNDEBUG /O2 /Zc:__cplusplus" -cmake --build . --target install --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" --parallel 2 ################################### Build and install eigenpy ########################################## @@ -198,7 +198,7 @@ cmake "$RootDir/eigenpy" -Wno-dev -G "Visual Studio 16 2019" -T "v142" -DCMAKE_G -DBUILD_TESTING=OFF -DINSTALL_DOCUMENTATION=OFF -DCMAKE_DISABLE_FIND_PACKAGE_Doxygen=ON ` -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS="/EHsc /bigobj -DNDEBUG /O2 /Zc:__cplusplus $( ) -DBOOST_ALL_NO_LIB -DEIGENPY_STATIC" -cmake --build . --target install --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" --parallel 2 ################################## Build and install tinyxml ########################################### @@ -211,7 +211,7 @@ cmake "$RootDir/tinyxml" -Wno-dev -G "Visual Studio 16 2019" -T "v142" -DCMAKE_G -DCMAKE_CXX_STANDARD=17 -DCMAKE_INSTALL_PREFIX="$InstallDir" ` -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS="/EHsc /bigobj -DNDEBUG /O2 /Zc:__cplusplus $( ) -DTIXML_USE_STL" -cmake --build . --target install --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" --parallel 2 ############################## Build and install console_bridge ######################################## @@ -224,7 +224,7 @@ cmake "$RootDir/console_bridge" -Wno-dev -G "Visual Studio 16 2019" -T "v142" -D -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY="MultiThreaded$<$:Debug>DLL" ` -DCMAKE_CXX_STANDARD=17 -DCMAKE_INSTALL_PREFIX="$InstallDir" ` -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS="/EHsc /bigobj -DNDEBUG /O2 /Zc:__cplusplus" -cmake --build . --target install --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" --parallel 2 ############################### Build and install urdfdom_headers ###################################### @@ -236,7 +236,7 @@ Set-Location -Path "$RootDir/urdfdom_headers/build" cmake "$RootDir/urdfdom_headers" -Wno-dev -G "Visual Studio 16 2019" -T "v142" -DCMAKE_GENERATOR_PLATFORM=x64 ` -DCMAKE_CXX_STANDARD=17 -DCMAKE_INSTALL_PREFIX="$InstallDir" ` -DCMAKE_CXX_FLAGS="/EHsc /bigobj -DNDEBUG /O2 /Zc:__cplusplus" -cmake --build . --target install --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" --parallel 2 ################################## Build and install urdfdom ########################################### @@ -251,7 +251,7 @@ cmake "$RootDir/urdfdom" -Wno-dev -G "Visual Studio 16 2019" -T "v142" -DCMAKE_G -DBUILD_TESTING=OFF ` -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS="/EHsc /bigobj -DNDEBUG /O2 /Zc:__cplusplus $( ) -D_USE_MATH_DEFINES -DURDFDOM_STATIC" -cmake --build . --target install --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" --parallel 2 ###################################### Build and install assimp ######################################## @@ -267,7 +267,7 @@ cmake "$RootDir/assimp" -Wno-dev -G "Visual Studio 16 2019" -T "v142" -DCMAKE_GE -DASSIMP_BUILD_SAMPLES=OFF -DBUILD_DOCS=OFF -DASSIMP_INSTALL_PDB=OFF ` -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS="/EHsc /bigobj -DNDEBUG /O2 /Zc:__cplusplus $( ) -D_USE_MATH_DEFINES" -cmake --build . --target install --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" --parallel 2 ############################# Build and install qhull and hpp-fcl ###################################### @@ -279,7 +279,7 @@ Set-Location -Path "$RootDir/hpp-fcl/third-parties/qhull/build" cmake "$RootDir/hpp-fcl/third-parties/qhull" -Wno-dev -G "Visual Studio 16 2019" -T "v142" -DCMAKE_GENERATOR_PLATFORM=x64 ` -DCMAKE_CXX_STANDARD=17 -DCMAKE_INSTALL_PREFIX="$InstallDir" ` -DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON -DCMAKE_CXX_FLAGS="/EHsc /bigobj /Zc:__cplusplus" -DCMAKE_C_FLAGS="/EHsc /bigobj" -cmake --build . --target install --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" --parallel 2 ### Build hpp-fcl if (-not (Test-Path -PathType Container "$RootDir/hpp-fcl/build")) { @@ -296,7 +296,7 @@ cmake "$RootDir/hpp-fcl" -Wno-dev -G "Visual Studio 16 2019" -T "v142" -DCMAKE_G -DINSTALL_DOCUMENTATION=OFF -DENABLE_PYTHON_DOXYGEN_AUTODOC=OFF -DCMAKE_DISABLE_FIND_PACKAGE_Doxygen=ON ` -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS="/EHsc /bigobj /wd4068 /wd4267 /permissive- /Zc:__cplusplus $( ) -DBOOST_ALL_NO_LIB -D_USE_MATH_DEFINES -DEIGENPY_STATIC -DHPP_FCL_STATIC" -cmake --build . --target install --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" --parallel 2 ################################ Build and install Pinocchio ########################################## @@ -315,4 +315,4 @@ cmake "$RootDir/pinocchio" -Wno-dev -G "Visual Studio 16 2019" -T "v142" -DCMAKE -DBUILD_TESTING=OFF -DINSTALL_DOCUMENTATION=OFF -DCMAKE_DISABLE_FIND_PACKAGE_Doxygen=ON ` -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_FLAGS="/EHsc /bigobj /wd4068 /wd4715 /wd4834 /permissive- /Zc:__cplusplus $( ) -DBOOST_ALL_NO_LIB -D_USE_MATH_DEFINES -DNOMINMAX -DEIGENPY_STATIC -DURDFDOM_STATIC -DHPP_FCL_STATIC -DPINOCCHIO_STATIC" -cmake --build . --target install --config "${Env:BUILD_TYPE}" --parallel 2 +cmake --build . --target INSTALL --config "${env:BUILD_TYPE}" --parallel 2 diff --git a/build_tools/cmake/base.cmake b/build_tools/cmake/base.cmake index 31993ede3..8acbdf8d0 100644 --- a/build_tools/cmake/base.cmake +++ b/build_tools/cmake/base.cmake @@ -1,8 +1,11 @@ # Minimum version required cmake_minimum_required (VERSION 3.10) -# Check if network is available for compiling gtest -if(NOT WIN32) +# Check if network is available before compiling external projects +if(WIN32) + find_program(HAS_PING "ping") +endif() +if(HAS_PING) unset(BUILD_OFFLINE) unset(BUILD_OFFLINE CACHE) execute_process(COMMAND bash -c @@ -13,7 +16,7 @@ else() set(BUILD_OFFLINE 0) endif() if(${BUILD_OFFLINE}) - message(STATUS "No internet connection. Not building external projects.") + message(WARNING "No internet connection. Not building external projects.") endif() # Set default warning flags @@ -70,7 +73,9 @@ if(LEGACY_MODE) if(WIN32) message(FATAL_ERROR "Boost >= 1.71.0 required.") else() - message(STATUS "Old boost version detected. Fallback to Ubuntu 18 legacy mode. Make sure depedencies have been installed using apt-get.") + message(WARNING "Boost version < 1.71.0 detected. Falling back to Ubuntu 18 legacy mode. " + "Make sure depedencies have been installed via `apt-get`, and building " + "against Pinocchio >= 2.5.2, and Hpp-Fcl >= 1.5.4.") endif() endif() diff --git a/build_tools/cmake/buildPythonWheel.cmake b/build_tools/cmake/buildPythonWheel.cmake index 1a66c494c..1b57d7ddb 100644 --- a/build_tools/cmake/buildPythonWheel.cmake +++ b/build_tools/cmake/buildPythonWheel.cmake @@ -27,10 +27,12 @@ function(buildPythonWheel) ) list(FILTER src_file_list EXCLUDE REGEX \".*\.egg-info\") list(FILTER src_file_list EXCLUDE REGEX \"unit\") + list(FILTER src_file_list EXCLUDE REGEX \"__pycache__\") + list(FILTER src_file_list EXCLUDE REGEX \"mypy_cache\") foreach(src_file \${src_file_list}) get_filename_component(src_file_real \"\${src_file}\" REALPATH BASE_DIR \"${CMAKE_SOURCE_DIR}/${TARGET_DIR}\") - if(src_file_real MATCHES \".*\\.(txt|py|md|in)\$\") + if(src_file_real MATCHES \".*\\.(txt|py|md|in|js|html|toml|json|urdf|xacro)\$\") configure_file(\"\${src_file_real}\" \"${CMAKE_BINARY_DIR}/pypi/\${src_file}\" @ONLY) else() @@ -41,10 +43,10 @@ function(buildPythonWheel) ) # TODO: Use add_custom_command instead of install to enable auto-cleanup of copied files - # add_custom_command( - # OUTPUT ${CMAKE_BINARY_DIR}/pypi - # COMMAND ${CMAKE_COMMAND} -E copy_directory \"${CMAKE_SOURCE_DIR}/${TARGET_PATH}\" \"${CMAKE_BINARY_DIR}/pypi\" - # ) + # add_custom_command( + # OUTPUT ${CMAKE_BINARY_DIR}/pypi + # COMMAND ${CMAKE_COMMAND} -E copy_directory \"${CMAKE_SOURCE_DIR}/${TARGET_PATH}\" \"${CMAKE_BINARY_DIR}/pypi\" + # ) install(FILES ${CMAKE_SOURCE_DIR}/README.md DESTINATION "${CMAKE_BINARY_DIR}/pypi/${TARGET_NAME}" diff --git a/build_tools/cmake/docs.cmake b/build_tools/cmake/docs.cmake index cb62d80c0..5b1436fd1 100644 --- a/build_tools/cmake/docs.cmake +++ b/build_tools/cmake/docs.cmake @@ -3,7 +3,7 @@ macro(create_component_docs) find_package(Doxygen COMPONENTS dot) find_package(Sphinx) if (NOT Sphinx_FOUND OR NOT Doxygen_FOUND) - message(STATUS "Doxygen or Sphinx not available. Not creating 'docs' cmake component.") + message(WARNING "Doxygen or Sphinx not available. Not creating 'docs' cmake component.") return() endif() @@ -70,6 +70,6 @@ macro(create_component_docs) COMPONENT docs EXCLUDE_FROM_ALL) else() - message(STATUS "Doxygen with Dot component not found. Documentation generation disabled.") + message(WARNING "Doxygen with Dot component not found. Documentation generation disabled.") endif() ENDMACRO() diff --git a/build_tools/cmake/setupPython.cmake b/build_tools/cmake/setupPython.cmake index 7c326db3c..c4cd4c21f 100644 --- a/build_tools/cmake/setupPython.cmake +++ b/build_tools/cmake/setupPython.cmake @@ -63,7 +63,8 @@ endif() if(${HAS_NO_WRITE_PERMISSION_ON_PYTHON_SYS_SITELIB}) set(PYTHON_INSTALL_FLAGS " --user ") set(PYTHON_SITELIB "${PYTHON_USER_SITELIB}") - message(STATUS "No right on Python system site-packages: ${PYTHON_SYS_SITELIB}. Installing on user site as fallback.") + message(STATUS "No right on Python system site-packages: ${PYTHON_SYS_SITELIB}.\n" + "-- Installing on user site as fallback.") else() set(PYTHON_SITELIB "${PYTHON_SYS_SITELIB}") endif() @@ -125,9 +126,7 @@ message(STATUS "Boost Python Libs: ${BOOST_PYTHON_LIB}") function(deployPythonPackage) # The input arguments are [TARGET_NAME...] foreach(TARGET_NAME IN LISTS ARGN) - install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pip install ${PYTHON_INSTALL_FLAGS} . - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/pypi/${TARGET_NAME}) - execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pip install ${PYTHON_INSTALL_FLAGS} --upgrade --no-deps --force-reinstall . + install(CODE "execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pip install ${PYTHON_INSTALL_FLAGS} --upgrade . WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/pypi/${TARGET_NAME})") endforeach() endfunction() diff --git a/build_tools/easy_install_deps_ubuntu.sh b/build_tools/easy_install_deps_ubuntu.sh index 6c15141bc..d27e5a2f1 100755 --- a/build_tools/easy_install_deps_ubuntu.sh +++ b/build_tools/easy_install_deps_ubuntu.sh @@ -54,9 +54,9 @@ if ! [ -d "/opt/openrobots/lib/${PYTHON_BIN}/site-packages/" ] ; then # apt-get must be used instead of apt to support wildcard in package name on Ubuntu 20 apt-get install -y --allow-downgrades --allow-unauthenticated \ - robotpkg-urdfdom=1.0.3 robotpkg-urdfdom-headers=1.0.4 robotpkg-hpp-fcl=1.6.0 robotpkg-pinocchio=2.5.4 \ + robotpkg-urdfdom=1.0.3 robotpkg-urdfdom-headers=1.0.4 robotpkg-hpp-fcl=1.7.1 robotpkg-pinocchio=2.5.6 \ robotpkg-py3*-qt5-gepetto-viewer=4.10.1r1 robotpkg-py3*-qt5-gepetto-viewer-corba=5.5.1 robotpkg-py3*-omniorbpy=4.2.4 \ - robotpkg-py3*-eigenpy=2.5.0 robotpkg-py3*-hpp-fcl=1.6.0 robotpkg-py3*-pinocchio=2.5.4 + robotpkg-py3*-eigenpy=2.6.2 robotpkg-py3*-hpp-fcl=1.7.1 robotpkg-py3*-pinocchio=2.5.6 sudo -H -u $(id -nu "$SUDO_UID") bash -c " \ echo 'export LD_LIBRARY_PATH=\"/opt/openrobots/lib:\${LD_LIBRARY_PATH}\"' >> \$HOME/.bashrc && \ diff --git a/build_tools/patch_deps_linux/eigenpy.patch b/build_tools/patch_deps_linux/eigenpy.patch index 8dcd2e55a..bfda0e45b 100644 --- a/build_tools/patch_deps_linux/eigenpy.patch +++ b/build_tools/patch_deps_linux/eigenpy.patch @@ -2,7 +2,7 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ddc96b..b978939 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -171,7 +171,7 @@ SET(${PROJECT_NAME}_SOURCES +@@ -172,7 +172,7 @@ SET(${PROJECT_NAME}_SOURCES src/version.cpp ) diff --git a/build_tools/patch_deps_linux/hppfcl.patch b/build_tools/patch_deps_linux/hppfcl.patch index 1a2e63360..cb6613908 100644 --- a/build_tools/patch_deps_linux/hppfcl.patch +++ b/build_tools/patch_deps_linux/hppfcl.patch @@ -2,7 +2,7 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt index d675c2c..c503cf1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -70,6 +70,9 @@ COMPUTE_PROJECT_ARGS(PROJECT_ARGS LANGUAGES CXX) +@@ -70,38 +70,31 @@ COMPUTE_PROJECT_ARGS(PROJECT_ARGS LANGUAGES CXX) PROJECT(${PROJECT_NAME} ${PROJECT_ARGS}) ADD_PROJECT_DEPENDENCY(Eigen3 REQUIRED PKG_CONFIG_REQUIRES "eigen3 >= 3.0.0") @@ -12,28 +12,46 @@ index d675c2c..c503cf1 100644 if(BUILD_PYTHON_INTERFACE) FIND_PACKAGE(eigenpy 2.2 REQUIRED) -@@ -86,18 +89,8 @@ if (BUILD_PYTHON_INTERFACE) + endif() + + # Required dependencies + SET_BOOST_DEFAULT_OPTIONS() + EXPORT_BOOST_DEFAULT_OPTIONS() +-IF(WIN32) +- ADD_PROJECT_DEPENDENCY(Boost REQUIRED COMPONENTS chrono thread date_time serialization) +-ELSE(WIN32) +- ADD_PROJECT_DEPENDENCY(Boost REQUIRED chrono serialization) +-ENDIF(WIN32) + if (BUILD_PYTHON_INTERFACE) + FINDPYTHON() + search_for_boost_python(REQUIRED) + find_package(Boost REQUIRED COMPONENTS system) endif () ++IF(WIN32) ++ ADD_PROJECT_DEPENDENCY(Boost REQUIRED COMPONENTS chrono thread date_time serialization) ++ELSE(WIN32) ++ ADD_PROJECT_DEPENDENCY(Boost REQUIRED chrono serialization) ++ENDIF(WIN32) # Optional dependencies -ADD_PROJECT_DEPENDENCY(octomap PKG_CONFIG_REQUIRES "octomap >= 1.6") -if(octomap_FOUND) -- SET(HPP_FCL_HAVE_OCTOMAP TRUE) +- SET(HPP_FCL_HAS_OCTOMAP TRUE) - string(REPLACE "." ";" VERSION_LIST ${octomap_VERSION}) - list(GET VERSION_LIST 0 OCTOMAP_MAJOR_VERSION) - list(GET VERSION_LIST 1 OCTOMAP_MINOR_VERSION) - list(GET VERSION_LIST 2 OCTOMAP_PATCH_VERSION) - message(STATUS "FCL uses Octomap") -else() -- SET(HPP_FCL_HAVE_OCTOMAP FALSE) +- SET(HPP_FCL_HAS_OCTOMAP FALSE) - message(STATUS "FCL does not use Octomap") -endif() -+SET(HPP_FCL_HAVE_OCTOMAP FALSE) ++SET(HPP_FCL_HAS_OCTOMAP FALSE) +message(STATUS "FCL does not use Octomap") - + option(HPP_FCL_HAS_QHULL "use qhull library to compute convex hulls." FALSE) if(HPP_FCL_HAS_QHULL) -@@ -115,7 +108,7 @@ if(HPP_FCL_HAS_QHULL) +@@ -119,7 +112,7 @@ if(HPP_FCL_HAS_QHULL) PATHS ${Qhull_PREFIX} ) find_library(Qhull_r_LIBRARY @@ -42,8 +60,8 @@ index d675c2c..c503cf1 100644 PATHS ${Qhull_PREFIX} ) endif() -@@ -172,12 +165,10 @@ SET(${PROJECT_NAME}_HEADERS - include/hpp/fcl/internal/traversal.h +@@ -188,12 +181,10 @@ SET(${PROJECT_NAME}_HEADERS + include/hpp/fcl/timings.h ) -add_subdirectory(doc) @@ -54,12 +72,12 @@ index d675c2c..c503cf1 100644 -add_subdirectory(test) pkg_config_append_libs("hpp-fcl") - IF(HPP_FCL_HAVE_OCTOMAP) + IF(HPP_FCL_HAS_OCTOMAP) diff --git a/cmake/boost.cmake b/cmake/boost.cmake index bea38e4..3dcfbf6 100644 --- a/cmake/boost.cmake +++ b/cmake/boost.cmake -@@ -115,7 +115,10 @@ MACRO(SEARCH_FOR_BOOST_PYTHON) +@@ -102,7 +102,10 @@ MACRO(SEARCH_FOR_BOOST_PYTHON) MESSAGE(WARNING "Impossible to check Boost.Python version. Trying with 'python'.") ENDIF(NOT BOOST_PYTHON_FOUND) @@ -70,11 +88,19 @@ index bea38e4..3dcfbf6 100644 STRING(TOUPPER ${BOOST_PYTHON_NAME} UPPERCOMPONENT) LIST(APPEND LOGGING_WATCHED_VARIABLES +@@ -151,7 +154,6 @@ MACRO(SEARCH_FOR_BOOST) + FIND_PACKAGE(Boost ${BOOST_REQUIRED}) + STRING(REPLACE "_" "." Boost_SHORT_VERSION ${Boost_LIB_VERSION}) + IF("${Boost_SHORT_VERSION}" VERSION_GREATER "1.70" OR "${Boost_SHORT_VERSION}" VERSION_EQUAL "1.70") +- SET(BUILD_SHARED_LIBS ON) + SET(Boost_NO_BOOST_CMAKE ON) + ENDIF("${Boost_SHORT_VERSION}" VERSION_GREATER "1.70" OR "${Boost_SHORT_VERSION}" VERSION_EQUAL "1.70") + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 043d18b..dcc79be 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt -@@ -148,7 +148,6 @@ FOREACH(header ${${PROJECT_NAME}_HEADERS}) +@@ -152,7 +152,6 @@ FOREACH(header ${${PROJECT_NAME}_HEADERS}) ENDFOREACH() LIST(APPEND PROJECT_HEADERS_FULL_PATH ${PROJECT_BINARY_DIR}/include/hpp/fcl/config.hh) add_library(${LIBRARY_NAME} @@ -82,7 +108,7 @@ index 043d18b..dcc79be 100644 ${PROJECT_HEADERS_FULL_PATH} ${${LIBRARY_NAME}_SOURCES} ) -@@ -157,6 +156,9 @@ add_library(${LIBRARY_NAME} +@@ -161,6 +160,9 @@ add_library(${LIBRARY_NAME} ADD_SOURCE_GROUP(${LIBRARY_NAME}_SOURCES) ADD_HEADER_GROUP(PROJECT_HEADERS_FULL_PATH) @@ -92,7 +118,7 @@ index 043d18b..dcc79be 100644 TARGET_INCLUDE_DIRECTORIES(${LIBRARY_NAME} SYSTEM PUBLIC ${Boost_INCLUDE_DIRS} -@@ -164,9 +166,14 @@ TARGET_INCLUDE_DIRECTORIES(${LIBRARY_NAME} +@@ -168,8 +170,7 @@ TARGET_INCLUDE_DIRECTORIES(${LIBRARY_NAME} TARGET_LINK_LIBRARIES(${LIBRARY_NAME} PRIVATE @@ -100,20 +126,13 @@ index 043d18b..dcc79be 100644 - # assimp::assimp # Not working + Boost::boost assimp::assimp IrrXML::IrrXML zlib::zlib ) -+if(NOT WIN32) -+ TARGET_LINK_LIBRARIES(${LIBRARY_NAME} -+ PUBLIC -+ rt -+ ) -+endif() - - if(HPP_FCL_HAS_QHULL) - target_compile_definitions(${LIBRARY_NAME} PRIVATE -DHPP_FCL_HAS_QHULL) + + TARGET_LINK_LIBRARIES(${LIBRARY_NAME} diff --git a/cmake/python.cmake b/cmake/python.cmake index 3286da3..a142198 100644 --- a/cmake/python.cmake +++ b/cmake/python.cmake -@@ -89,7 +89,9 @@ MACRO(FINDPYTHON) +@@ -96,7 +96,9 @@ MACRO(FINDPYTHON) SET(Python_EXECUTABLE ${PYTHON_EXECUTABLE}) SET(Python${_PYTHON_VERSION_MAJOR}_EXECUTABLE ${PYTHON_EXECUTABLE}) diff --git a/build_tools/patch_deps_windows/eigenpy.patch b/build_tools/patch_deps_windows/eigenpy.patch index 494b74e15..17525c8eb 100644 --- a/build_tools/patch_deps_windows/eigenpy.patch +++ b/build_tools/patch_deps_windows/eigenpy.patch @@ -2,7 +2,7 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ddc96b..b978939 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -171,7 +171,7 @@ SET(${PROJECT_NAME}_SOURCES +@@ -172,7 +172,7 @@ SET(${PROJECT_NAME}_SOURCES src/version.cpp ) diff --git a/build_tools/patch_deps_windows/hppfcl.patch b/build_tools/patch_deps_windows/hppfcl.patch index d0c9a3cc5..78985e82c 100644 --- a/build_tools/patch_deps_windows/hppfcl.patch +++ b/build_tools/patch_deps_windows/hppfcl.patch @@ -2,7 +2,7 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt index d675c2c..c503cf1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -70,6 +70,9 @@ COMPUTE_PROJECT_ARGS(PROJECT_ARGS LANGUAGES CXX) +@@ -70,38 +70,31 @@ COMPUTE_PROJECT_ARGS(PROJECT_ARGS LANGUAGES CXX) PROJECT(${PROJECT_NAME} ${PROJECT_ARGS}) ADD_PROJECT_DEPENDENCY(Eigen3 REQUIRED PKG_CONFIG_REQUIRES "eigen3 >= 3.0.0") @@ -12,28 +12,46 @@ index d675c2c..c503cf1 100644 if(BUILD_PYTHON_INTERFACE) FIND_PACKAGE(eigenpy 2.2 REQUIRED) -@@ -86,18 +89,8 @@ if (BUILD_PYTHON_INTERFACE) + endif() + + # Required dependencies + SET_BOOST_DEFAULT_OPTIONS() + EXPORT_BOOST_DEFAULT_OPTIONS() +-IF(WIN32) +- ADD_PROJECT_DEPENDENCY(Boost REQUIRED COMPONENTS chrono thread date_time serialization) +-ELSE(WIN32) +- ADD_PROJECT_DEPENDENCY(Boost REQUIRED chrono serialization) +-ENDIF(WIN32) + if (BUILD_PYTHON_INTERFACE) + FINDPYTHON() + search_for_boost_python(REQUIRED) + find_package(Boost REQUIRED COMPONENTS system) endif () ++IF(WIN32) ++ ADD_PROJECT_DEPENDENCY(Boost REQUIRED COMPONENTS chrono thread date_time serialization) ++ELSE(WIN32) ++ ADD_PROJECT_DEPENDENCY(Boost REQUIRED chrono serialization) ++ENDIF(WIN32) # Optional dependencies -ADD_PROJECT_DEPENDENCY(octomap PKG_CONFIG_REQUIRES "octomap >= 1.6") -if(octomap_FOUND) -- SET(HPP_FCL_HAVE_OCTOMAP TRUE) +- SET(HPP_FCL_HAS_OCTOMAP TRUE) - string(REPLACE "." ";" VERSION_LIST ${octomap_VERSION}) - list(GET VERSION_LIST 0 OCTOMAP_MAJOR_VERSION) - list(GET VERSION_LIST 1 OCTOMAP_MINOR_VERSION) - list(GET VERSION_LIST 2 OCTOMAP_PATCH_VERSION) - message(STATUS "FCL uses Octomap") -else() -- SET(HPP_FCL_HAVE_OCTOMAP FALSE) +- SET(HPP_FCL_HAS_OCTOMAP FALSE) - message(STATUS "FCL does not use Octomap") -endif() -+SET(HPP_FCL_HAVE_OCTOMAP FALSE) ++SET(HPP_FCL_HAS_OCTOMAP FALSE) +message(STATUS "FCL does not use Octomap") option(HPP_FCL_HAS_QHULL "use qhull library to compute convex hulls." FALSE) if(HPP_FCL_HAS_QHULL) -@@ -115,7 +108,7 @@ if(HPP_FCL_HAS_QHULL) +@@ -119,7 +112,7 @@ if(HPP_FCL_HAS_QHULL) PATHS ${Qhull_PREFIX} ) find_library(Qhull_r_LIBRARY @@ -42,24 +60,24 @@ index d675c2c..c503cf1 100644 PATHS ${Qhull_PREFIX} ) endif() -@@ -172,12 +165,10 @@ SET(${PROJECT_NAME}_HEADERS - include/hpp/fcl/internal/traversal.h +@@ -188,12 +181,10 @@ SET(${PROJECT_NAME}_HEADERS + include/hpp/fcl/timings.h ) - + -add_subdirectory(doc) add_subdirectory(src) if (BUILD_PYTHON_INTERFACE) add_subdirectory(python) endif () -add_subdirectory(test) - + pkg_config_append_libs("hpp-fcl") - IF(HPP_FCL_HAVE_OCTOMAP) + IF(HPP_FCL_HAS_OCTOMAP) diff --git a/cmake/boost.cmake b/cmake/boost.cmake index bea38e4..3dcfbf6 100644 --- a/cmake/boost.cmake +++ b/cmake/boost.cmake -@@ -115,7 +115,15 @@ MACRO(SEARCH_FOR_BOOST_PYTHON) +@@ -102,7 +102,15 @@ MACRO(SEARCH_FOR_BOOST_PYTHON) MESSAGE(WARNING "Impossible to check Boost.Python version. Trying with 'python'.") ENDIF(NOT BOOST_PYTHON_FOUND) @@ -75,11 +93,19 @@ index bea38e4..3dcfbf6 100644 STRING(TOUPPER ${BOOST_PYTHON_NAME} UPPERCOMPONENT) LIST(APPEND LOGGING_WATCHED_VARIABLES +@@ -151,7 +159,6 @@ MACRO(SEARCH_FOR_BOOST) + FIND_PACKAGE(Boost ${BOOST_REQUIRED}) + STRING(REPLACE "_" "." Boost_SHORT_VERSION ${Boost_LIB_VERSION}) + IF("${Boost_SHORT_VERSION}" VERSION_GREATER "1.70" OR "${Boost_SHORT_VERSION}" VERSION_EQUAL "1.70") +- SET(BUILD_SHARED_LIBS ON) + SET(Boost_NO_BOOST_CMAKE ON) + ENDIF("${Boost_SHORT_VERSION}" VERSION_GREATER "1.70" OR "${Boost_SHORT_VERSION}" VERSION_EQUAL "1.70") + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 043d18b..dcc79be 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt -@@ -148,7 +148,6 @@ FOREACH(header ${${PROJECT_NAME}_HEADERS}) +@@ -152,7 +152,6 @@ FOREACH(header ${${PROJECT_NAME}_HEADERS}) ENDFOREACH() LIST(APPEND PROJECT_HEADERS_FULL_PATH ${PROJECT_BINARY_DIR}/include/hpp/fcl/config.hh) add_library(${LIBRARY_NAME} @@ -87,33 +113,26 @@ index 043d18b..dcc79be 100644 ${PROJECT_HEADERS_FULL_PATH} ${${LIBRARY_NAME}_SOURCES} ) -@@ -157,6 +156,9 @@ add_library(${LIBRARY_NAME} +@@ -161,6 +160,9 @@ add_library(${LIBRARY_NAME} ADD_SOURCE_GROUP(${LIBRARY_NAME}_SOURCES) ADD_HEADER_GROUP(PROJECT_HEADERS_FULL_PATH) - + +find_package(zlib NO_MODULE) +find_package(IrrXML NO_MODULE) +find_package(assimp NO_MODULE) TARGET_INCLUDE_DIRECTORIES(${LIBRARY_NAME} SYSTEM PUBLIC ${Boost_INCLUDE_DIRS} -@@ -164,9 +166,14 @@ TARGET_INCLUDE_DIRECTORIES(${LIBRARY_NAME} - +@@ -168,8 +170,7 @@ TARGET_INCLUDE_DIRECTORIES(${LIBRARY_NAME} + TARGET_LINK_LIBRARIES(${LIBRARY_NAME} PRIVATE - ${assimp_LIBRARIES} - # assimp::assimp # Not working + Boost::boost assimp::assimp IrrXML::IrrXML zlib::zlib ) -+if(NOT WIN32) -+ TARGET_LINK_LIBRARIES(${LIBRARY_NAME} -+ PUBLIC -+ rt -+ ) -+endif() - - if(HPP_FCL_HAS_QHULL) - target_compile_definitions(${LIBRARY_NAME} PRIVATE -DHPP_FCL_HAS_QHULL) + + TARGET_LINK_LIBRARIES(${LIBRARY_NAME} diff --git a/cmake/deprecated.hh.cmake b/cmake/deprecated.hh.cmake index 0726e3e..8fd0928 100644 --- a/cmake/deprecated.hh.cmake diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 3a856526e..f9912eff2 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -28,10 +28,6 @@ if(NOT LEGACY_MODE) else() find_package(hpp-fcl 1.5.4 REQUIRED NO_MODULE NO_CMAKE_SYSTEM_PATH) # hpp-fcl >= 1.5.4 adds support of collision of primitives with halfspace endif() -else() - message("Impossible to find cmake configuration files for some packages. - Falling back to legacy mode. Please make sure that you are - building against Pinocchio >= 2.5.2, and Hpp-Fcl >= 1.5.4.") endif() find_package(Eigen3 3.3.0 REQUIRED NO_MODULE) # It adds the target Eigen3::Eigen diff --git a/core/src/solver/LCPSolvers.cc b/core/src/solver/LCPSolvers.cc index abd3d8f2d..09b3c00d6 100644 --- a/core/src/solver/LCPSolvers.cc +++ b/core/src/solver/LCPSolvers.cc @@ -162,7 +162,11 @@ namespace jiminy // Compute A A.noalias() = data.sDUiJt.transpose() * data.sDUiJt; - // Add regularization term in case A is not inversible + /* Add regularization term in case A is not inversible. + Note that Mujoco defines an impedance function that depends on + the distance instead of a constant value to model soft contacts. + See: - http://mujoco.org/book/modeling.html#CSolver + - http://mujoco.org/book/computation.html#soParameters */ A.diagonal() += clamp( A.diagonal() * inv_damping, PGS_MIN_REGULARIZER, diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 6f5fb8210..a409d9485 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -11,7 +11,7 @@ add_subdirectory(${LIBRARY_NAME}_pywrap) install(CODE "file(GLOB_RECURSE src_file_list FOLLOW_SYMLINKS LIST_DIRECTORIES false \"${CMAKE_CURRENT_BINARY_DIR}/${LIBRARY_NAME}_pywrap/*core*\") - list(FILTER src_file_list INCLUDE REGEX \".*\.(so|dll|pyd)\") + list(FILTER src_file_list INCLUDE REGEX \".*\.(so|dll|pyd)$\") foreach(src_file \${src_file_list}) get_filename_component(src_file_real \"\${src_file}\" REALPATH) get_filename_component(src_file_name \"\${src_file}\" NAME) diff --git a/python/gym_jiminy/common/gym_jiminy/common/envs/env_generic.py b/python/gym_jiminy/common/gym_jiminy/common/envs/env_generic.py index 48849677e..8dfe8b61b 100644 --- a/python/gym_jiminy/common/gym_jiminy/common/envs/env_generic.py +++ b/python/gym_jiminy/common/gym_jiminy/common/envs/env_generic.py @@ -597,7 +597,7 @@ def reset(self, # keep using the old robot model for display, which must be avoided. if self.simulator.is_viewer_available: self.simulator.viewer._setup(self.robot) - self.render() + self.render(mode='rgb_array') return obs @@ -1032,7 +1032,9 @@ def is_done(self, *args: Any, **kwargs: Any) -> bool: This method is called right after calling `refresh_observation`, so that the internal buffer '_observation' is up-to-date. It can be overloaded to implement a custom termination condition for the - simulation. + simulation. Moreover, as it is called before `compute_reward`, it + can be used to update some share intermediary computations to avoid + redundant calculus and thus improve efficiency. :param args: Extra arguments that may be useful for derived environments, for example `Gym.GoalEnv`. diff --git a/python/gym_jiminy/examples/rllib/tools/utilities.py b/python/gym_jiminy/examples/rllib/tools/utilities.py index 2c0ffd500..70aa57362 100644 --- a/python/gym_jiminy/examples/rllib/tools/utilities.py +++ b/python/gym_jiminy/examples/rllib/tools/utilities.py @@ -308,7 +308,8 @@ def train(train_agent: Trainer, if evaluation_period > 0 and iter % evaluation_period == 0: record_video_path = f"{train_agent.logdir}/iter_{iter}.mp4" test(train_agent, explore=False, viewer_kwargs={ - "record_video_path": record_video_path}) + "record_video_path": record_video_path, + "scene_name": f"iter_{iter}"}) # Check terminal conditions if result["timesteps_total"] > max_timesteps: diff --git a/python/gym_jiminy/unit_py/data/atlas_standing_panda3d_0.png b/python/gym_jiminy/unit_py/data/atlas_standing_panda3d_0.png index f52044da41dba8284ed4278f156928618a594f45..33b26645d9c37d7a0cfd1d0194c2dd716b65d644 100644 GIT binary patch literal 29142 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 T0)LS4$dOwLYVrj)%%1!Y@ZWKe literal 28841 zcmeEt_cxqhv^OCLqPM6~f=Kkl2*j863O zKECg|zuiCKW-Tk)GH0H%&pw~FWqv@>sdy%QKGS!n$2AD!vKl|NwTZ+1KkCr1A^Y`=JI`@udA(CgO-xpn7`1cxk zGibiyW3hxQJgNFVQ(RW98w}Fp0=3(JJs3i4CJ$4R8)m&AVky)1lveWmY)E{xqOca% zc65Eae_*zLekMvRi^CWhpreD_A|(Po9X!{3)B$|rN&e|QS0s_F$zzs6JjO^Oee@?= z4?hOI`M)3kua70HMDq>K(Jg9HZg$f*^$)7|pgm~N@RiYR8i}ll9?4`>04XB>d!U6g z0kXMj=2xVpGFiy^4?`_7H&b(#^tVU9ZTd<`e-IwJL=j0V|M%-V{NR>ncrN>V7aOaZ zI{uG-WyIv8WVPPNFfz%?82TY&V~6f>KA?5;DUvC4-QON;n(ytd)YzN++*>~+D($6VRt88o012AOAtaw1?FDJtqS(YfV}_yHmRs@mttO%H|*ob zj}{1_g4TXE8`ExOuwy_VNt2k(%T0mhJ__2>a~UgNoIhY z-_Cw-uS3-iw({U-Wo8zdwdNSlecm|ju${-0={tcQl-1YAXoeoAm1@7lA6VY5D6n&J z!H4@+%{bADc)a)TTBYmaM`_L9;-V!l9kvJ`+Y-rwv4N6n`|vV9;?n`(8l>4Y)G1_Su9 za9UV|IWgm4*;ZFlwYF^RsH&qtK#%VF8YjG=r6eLHg?Q(mnE!$&cC_VTLvqN+zke<3 zdhddKOzrRQZrkc&P(p{d?-d7F@#O^6<>0=$|72a~B!kZm{}P5}YFEjN<*|MlZLl4s znEh%v&=YbUaJ600ez}n|f8BG=d#j0L;`hYzAN6%#t^pb$XGf$rW1WeNkmh#sUHER5 zyVh%pz}*H26*V}B*>SzSz28JuJI(odBeJW1IxkbsBG7>OqwokOV(sc+euzi|3g&N zW3$jpTl@hBSALi;9rPr&IF>zLB=C6JiX=C_+%Z9C*YZ_tD?CV_EXu20jVZ;#&JJsU z$HVjs@Rhzk<&0B$ML}qG!BtfOllc?FL*%f&49-#Bma;tMN_<950dvuh;bENoO|6=l zfYT|ZaJ=B*v=Nr1RgasCC%Pa;d~#ioGkMaKFR3X!+=2HZqFM+eL@39!iy(D`1*q7q z4huv=#yXdco0lKH_8+Z6jBSm@#l`izTx<30 z!C+biEeiM;NEajrVvPSh^wpmnc^=D@9v+U4KVZPOPB321iN?>*FL8Sc>-v}PPonTp zg{dkTokaRPL(P?%`70mGwO+{GXS5cw6Gao#1x2vOSL$*Ahlfu>Vr*>81=W*X-h5UD z(N(~R((7ZOr?<1Qi7ZkV=WS9b6t#!%{+`YKjqDB<7GChcSPacK>|W*TeFQ8!<5(PO z%%@1Xg4?Wj2*<(ra#8}BDt>IYNrxLH0fX_& zYbldOv5<_+DrU4Deq0rxfX_0|*3y*MFvr;0QJHjNybBohHNKo8?81ioiu&)I#X7FW{f@qdSQfpY=4J)*LjY&vr- z(Eb}*|J&i1_S=@6%N6EitGdpl!G8zOF+PvtUGyY7u^-=>1FuAuLA-*y5h$gFnb%I% z{99zAA5(5I4>JXdl<>`$qLcXEM~{8(#@7H&Y~`<#*L?537)XB%JnO{3nn9u zWaOroz$Y%{cceGaU!EJ$(#WM>4X>riGP-e5rXFu>RP2|R;*M)Udyi} zt8W&+QvGR#3{~`3y4}(#kG@)grdf52UQ4ao*-3%)tY~#5M$PjUI)`ixGPQR`l^?2$ z3n!5&j5z9Jb?TXy^zlOkib_f&o8Hrlg@ROY!E~qQw~eZf`g=q4fhvb*6 zIF+$FDW=wFcYT}zGqa%W$1XK8$SYTuo=fI8-gw5%MrAQLmt90Fzg`&b%bNW7HA4;D ze(-J{*?QgT&u?Eyon%3IBqbBAO0}qci`h%7IZ9JsmY0`L3MM)^=HD%0wxD=2Rd{!T zS@20gI#Ysl5KkOXVjrOgZH%^Kc$2-7Ar7gF*^LWM$)}+1rQD~nW5QeMz!k&BCkvdn zjW&93@20ty#jDoKOHrV)XHy6mmt1jGS8LbIY;B_|>T59Ey;8lCR9Vw(AQR4+xw(Ib zwsr6+5-PLs^_pdc(rg=b`%gHws+jOj@VdSo*y7NzU<;9k2?d1q0XKiT)6-A=e6l80 zHcw4~Dx`GOSWWwfFiqm3JD(vkF1<- zt5Daea_O_APrjVl!K}! z;9`@i($gBCWx-FLJ$qJE-rT>Q%Qx`E#DWwUMdm$yrHTRsY`H1l=3|*o~*E`GVwQMkiIJ-)P7A-nN9vn zFkvL>WV%VJ$rkU}{qz-6WE)wQ*Z+T@)@jxslao)rH@&x{(yjOzTl{Hc`*Bik)_{`$ z9waj|Gd!Z)Q6v$o%qZRKlN;!xbN38+*}Ni1>z(e?PCcf0S+lQg_@@-@*KTQdcj%4H z{5E+U5&pXt2TR@4oHWPBfsBLcZ&Ozy+deohI={>dP0Gqn7G{`|gY>05 z=yCSU6OY9yiIqzZN&20oAKU+4JoHMs&rO)HvIb_6v zVxPxTNlL3i`>5}Z${eQ^pIz^wA74tO)rE5uP|BJ#{P#>7^AmxROpU`_gd;(78H>?hpt6AIZbH|;A>>?=de5gQVww9{

s+6ujGppKbdhr#E$MV) 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 diff --git a/python/gym_jiminy/unit_py/data/atlas_standing_panda3d_1.png b/python/gym_jiminy/unit_py/data/atlas_standing_panda3d_1.png index 48c76fed9ee1755cae31597701ea182a378314a5..40b1ec97a9f67a2c36807aa22b6fdf0a31aafebc 100644 GIT binary patch literal 26389 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

-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 O7@RXci#zRl>%Rb;7TE0o literal 27014 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
Q>0Izs28wpR5=S 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 L>Q@V|Sl;_T;67S4 diff --git a/python/gym_jiminy/unit_py/data/atlas_standing_panda3d_2.png b/python/gym_jiminy/unit_py/data/atlas_standing_panda3d_2.png new file mode 100644 index 0000000000000000000000000000000000000000..e5fe66f54d035bc0c722d38800cee9a9c00925c9 GIT binary patch literal 27041 zcmeFZ^;eYd8#Rgw(qYjdAC;2s90Ww95fP-jyBS(Z1wrYME)fT$h6Z(LgaK&=7+`>* zYv_D$zUREZoj>8Y7K;HF%=6sO6?^Y%UwqKgP$DOxC&9zRBUgU;TpJJX#{TsO(QWXZ zzA^Mi@Cn~jURj3-{0k(qiUwa3yS>!+#KWUBzy7%KO}4}y5APwK@^d*IzpooJ(63f+ zpvPCp21A(fXaI75)Fiqz@&TanDo^N5?h$4v2cuuPt66lN895;v!JP# zr)B2lB}}K0^HhZs?u_aLZ?L>yFCX2a0D>;Wf+XT5xTI+}1i5GMMt`PQwPjbFglsw0 z!X&%0yZkvZl=ukb038!k&_8Use)a9XDk20Gf}iMpXrhKBai}V9LpZpxQk?}1%awRZ zwm+BSczID%Q|2bBuG*%be)jA=P4~|xst|b))4S}2Z@C49g`ZR5H(wr9uxDu`_ARZf zt>u@LL~U)kIH8Km$};8iqV4J5rsNy(CaM(GW8$PHYwRCkb3!1-#>Ph8qi^t<=Lq<6 z-*S5_CF|`WSS~-SXNqoOQb<^{xj;5;U}rr8NkT(fHZBU0K|)2^A7F-cH|Lt z-$f-Qd$EwC9Pk9l6z=dyjo*(^1`Z{?t(k79Y}Uq zd~rs>Z|uw#+@JEnJ3cn<4;l(&ZD&r9@M1l*JkaGQ^jj($u)uVFGV-1AR@2bXE*q`i z|EEevM>n4z!<(;N>Nfhu^f8%8q&Zb2{h%FrK|uljJ53|sJu4H1w;U7V&@f2djXK=1BUc9KEdrz8{mWDSs zVHv;Fj2n2A+E?kfYw?GF@d{7tha?1XtF3IiznGLY`zGn*eA3D`m8`@ExNB~q*t&t2VDkuA|a zIgnsb`h4e+m)9aEbqsCf@8e_fu&^-jLV;4I9i)%Ma+>AiI1qZzmIosykQZnvQIC&-XEbK3J|v9`R5iYU7QqkH%e6;AHH-@@wuR=G=)6sjlN ztGH_@h!y&Wlvp~z?;SF*%wW5?xEOH|sfk;j7jv2JUR-?an#R{*6Fz~~H)!=Itwc)( z%!}iYkjz@gakk|7<^IgBW+<*$db&X!LJB7!v{$x2*Vm|!dpvHFfqz-Gl&|`%y`HimIw{K5WS(Abg=P`so zmMS(5K3M_*_|HicieQ-ib^QE;W5vnsy4llp-IS*PvNSMfH_F$|X>Y@wzaV|7B46>% zddrOcI1%ZvcsE+6hxEUEQSkdwy!0Q#**)rDZ_RqKOb~h>N0hd{slKGKkUeO{7ILQc z1s)?ZDyOH;u^xSt*3vs2LrB>?a1Y*`1uEs-Q7e~1VJzbM)oYmK4Ee)gqf4m z{VsPm<5Y{vm(`PxbCBT7cYNb`on%Zp#U?bpDUI~o1oj#VYISjj|+ z={Dmb5D3M}SaYLdiwQ_!GQ@&J84a#XYU@ds25u;o&S~Q3yB7uT)*F*lo>Wb4Y>|FJ zlCmBerZme$Otd{9A;{=ws|dMRKlw!nQKs^1-ihE_>V6cDFU%)veWwmBq*A2a;jRs9 z+-oZsa5DE;GeJr6Fy!! z+h>a&67-)WlT~U(e$39jQ}THQ)2-x=$;->L%7$DzeH%x8?CHivP@VB`azAPh?mcVM z927roDI8vC_DknZ*3MXqFqjk8{MKf)!tfI5CO~|YVopdDj2Q3?pW@Qrw ztOdxxnIYVrK1T4eJ$=IUAk0e=k#5_Xf<_0iG}ZY*=%VRqe7j%0+@+KGc&9o(!vA8N0gz=Qdf0c&i)N4Ko6taN+qf8_ng8Rby@fy5y^k8LMV zQmQHKEB9;O)buo3B|27fss}&yryJuOYE`Ny)y68pgVz*XcpJe$E4UUkntgM(>SiD!0j4`*99Hwq-To3-3WgPy@SMN*S{2j)w zPMma2+}-WYt}de5sJvo|hA8uHkO~b(E-cL?^3gI+Kr`Lw^4XkHbamx3rzLL?%OwBx zkW8U!l3R|-3^hbuVov)ny?3sWw;_Uv~|U&1_lC-+Ak$cKgPPa(!xq$iWekluw3)doweSpE?!L4A(~=_ViY zJNS^+ZBUmT8|EV!>W>%t3~WC@7|0MW%M}tQ2`k=HS!lkbG>*QQ7#yU!PEGv=4+%6V zq5$x;J^YSOv$s+ZzaUNlcYY1ib!Ut+fK5&5t7D)Ut*ybqtv2K>Ef;>lpvm8S2GcDv z_!K;50KSm8*w=I0>tScdLdw0KilGCAn3$Nz$jJqyrO6s?|DVImgY2&>6R(2U2o7FN zZ(e&-kq=VNYgJoPN-AA81QLgFoADms9lC0yQzPYnYe#WosNB9BRap3OM265BwepLw z=ph-V_h;@xKQ+0Q+hl_+C_I0KkXAE|QJmCL-^SO^=P5&exie~r&*N`xNkb1wAINWb zE_2$Np8pOCx+5Pw$wOQRZmI#B9pDE*VLkXPzF9NOub$|8zMlN`bXuXuhBWq*-cPBI zXsicYNTwW!5bG z+cXC%xjybXx78Gagw@Te`1*>0Q{+wErDm?HFulv#j`?g2a-$1Wt82_vrVGn2D~plB zvFDGlX_wx|UX1hLF;X^vKRDku^%5*!-|Ke`PL$6Jwa-ZEv*t=BX}j2Th4!eL$OA+Q zAWnr7?|{F?@O1A7_U!JRJ0Ys*arAG?c$v4KdXeWJak^tCzbS)%g;7Gf`%EuA062!k$sE2NkU266K0~4R0KWFz z`ArxG@fh>$WT?+A_MoY;Z>iBZMJ|%}0pR1SZ{1H|+x!g+m2OYo5V| z--JG^3)%M$Tzj4KUuxfc<85=I5TT!8rx7$BDCfE%6pGYvu^K@2^rYYi7A(iBPbyf6 zGJhC(KZchIBz3eLU3`femuZhc2XH{Mt@S}o!d2&7#H#YOB!QZvHtYnGtpmV3-?Lw@ z$ELb)*d84E5e27zK)|ka{II5M)|6rcF;si?HK)z`1Kx%oMk*BaHZc=!Xx)-$%dgm)U2Kk&)GW?EhAn zeXitMft$l#(INP|ZK`P_hoa`H&LC1T66qcRl6H|19CPQ z54x6tBPb5p420g`$E*YkwKs)d-{K3`l9+8Vb)l04D*PKWGc(}%{NY!N zW4W4!Z~-`e6}No`0Ad79tKLEb)mikDZ5h_XR@b0)$Mey2+b1$;Ka*yY3d;A16b2h^-n}zOy)09kDo-S! z#63CpHo9d!vaXFR)Ds=(BlvaJPMDwUR$o=ZWB51Gru6+ta>PTT_nbFsf`ZRJL{8^$ z93$a8YfiYW%{I-Py7-DsL-gfs+poI@r)Ov4$LfNqyzR65?HDilXo=JwtA^9Hvj5>~ zKr`|TU;s6iuP-$6W@FvQlv+EM#C|NnVItoTNcyM5y~h#0#%u4CMsaW3yrUmFIeUJ; zMW6kGkXO=+Q$+gvGxBIryJXz}i0hqz`bDKCY*~Ia=Lj8tgIDT`Ls1?lwEvvDMWqrsMK{P6!xsH zk-)t|hD<>XmGoPd^-%E9=>U&dLyzlmv=vQa5IRye)M0^NO>J+^1Hp^B+Mxj#b6W7QTes`T=eO6#^<6y9aE;0-*O`cg zj;wnF!kVlS;U&qG>`9}Jr!-8VtV`@(L9lM&3 zRcH)nxH!%PVi-QrG-55wkXV;R6)=Q^96OktF1)@5SC{7}Uf$kc`j!B!3xsHjW`6qE z^Om}lpCs)z#H@fkqKGU|!JPhfoG1}bgeVzX@8%g!$AM__Mg#LrPGq=3+UXVEXk0x^% z;1sUM(^yCs<>e7QcRsU!!O*XxAZa9ECy?`@kdpPkzT3m&`q8ptYZtOi$v`?X(QY_F zyD_q-i*2v37XY;b6eQ8iCppMOB(~C-0i|0%)!z9?i-MXtuS%=8U7Iz}+>(LywrFdf z`+eHm9Gg@buP9CF*i`I)$>*&|?u&Ph&3wq?PRf&G;P7%(^}44sQYo5r+uEY0YUN%d zMQV{QO>I?ofE>*pPe8*tfB5JDKXQKYFRm6vqcY;&!!~)*FTC$^Wei zXJac}W*S@07D#q&HB{P2Drt(eDO5Pe%8mQ(C^5>xbX8T#Qu?g@{rv|GFya*D>ueF$ z3+>f@j>Ct&Zky{v$=bOO^8Rr=c%iVG_wn)#Ns)YwAS;=9WZ|R}o5T&E0u8yi41LM&(0ffZq>%z6uB16>jdw;hFN;*ne^Fxo?QIM=<)}mQ zivl^WWQ(4JCm=BxyVCCtvXa!4uWMI~1EB!$&5}U!YJ$~6c>z?NYe_)?AUj!slXh1( z0Ym=uA`EnSqwyl}j+^4=I|v|OB=s$g?PgD|KM9}k9BXaGW4-*^ZvEy4+-0VBZtmfz zgUIR~*XjB%4z<1WQ7jq-Loa=V94Y?*2K0$R?BDo!7my5c_5(LVDvFUGG^;(a8UTfX z)&!dwc2Jh_eYpBbjGmm0jV*z>alKK8D|sMASYI!OSVRLV`HcS;`ad(i}7gS zSeMG|Pw2OSVi?ffqk5+O;O?^V5420ewMme;m8~s}l-k;HPjqXLR9042^_V&OUc8uZ zwnj;!|K5@Zv^Ow#`kt?GDu5W^<~Wj70Q`hIO@7q=7zl|U%h2I^usJPy5^KWC+WtD1 z_hg9ogoLNv#A~?8^c@xoK)A0*CtZb&nHeO8`amPS1n!)foy`nT{x$ZhsNkEd0GaUE zb9cAgaZ_Oxw9dBvL%*{i0hmaw!6zqCRVsX)vNzJm0w7}XrqVGm6zFgXh>CV@)}h13 zUR8dTXVq+AByXwk8!kCqDB}wM%(pxu%GbE?W9hh6i8nJ(x6A{z+4eBA z=T+I8hRf5>Kx4(9F}O0W7*9`2dwJ8JaT}`lBmXJy(Xq0?v@Tl{I7dasU42_cbBpy>SJ5*sH3}DOp)txTDvE>&mKAOt{z`OB6E--@L~4C zYs#8uk62D#?isjcI8d=K4Mf z&veV)Bx2Kx)4A`FBKTJyU~$lgB)P@*Y>GC;M{pE&0b}`h%rm5!_4sgRcp)3g__u77 z3n&HrWNEVmdMfT`n~rH;zs_|sRFqVNK(3CHo0c~Y?#W9FtmctcruvzL@y7=IVlGEL zB+5@H-F%O2^X}bl-3EmbsK7w518uV3<{tW}WPh;asFkI{7lOh~1+47ylwVR4?*C66 zSe*7mW(|p=kG_q54>x})nMGh$W#oToqQ=@z2F48&s=UWzl7VPTCnvq9pI^vKKP0n} z*30p_^GqPB((!4_nz4wl@)5yKU}wjTlETte1yhLVY65fl``3bgM7F zE#yWvcEBzb=g(PebgVVF`xhfv zLv!WE&T`gwLgkD}c)TW(@oElfRrM+q>Af?gN9MLm>ATgRY7i}JsQvauv~5pYn**oZ z9Oew7nhG=f{l4gjX^8B-rMoyR-ydhu9qIUoL_5tkg09TC9N!yj<2mZV6*^qvaSD=I z8q1TO4bw>~9=rWj+yI5#Z7)HVArBQHCbQJ3KA*%4KOBj>R;*smX8P{*v>A*v+7NJf z(Jq|urmNjXY2;~5z!6=@w?F`8>9@(M3qH%E!?v97LkqR-Gx{U(Ls+wK=)>9Cs|(+5 zu#yFJb}4acCUskRzD-fL)uVI~qL`x}3^zaHug^|!rKW5;Yt0$ajqmF+NJ*Avr*MAt%v%rm02T%jgnyl0G7VsPYh0-wMb5`m=IVA%6asv<@T@ z|AC+Vw#&hgb7Drp^-7F+Dn1MfQW_1!Bo+_8G8Oq zME{mCNuNZ;n3Q|?HvrkZeP2fAmQGpXajO*O7-&!Is1gzZ!b^+@;VX{&0%W8ppP$^B z4z=&KZy)&c7oJ+pvK{!77rnfR4RDwl|cw{X+~f>rlm<`x!YMfXW*csp=`+}NR?+dxLa`@71C*~FLSsUDMJ%d4WdD^^xkXvXFLsPCY#p{$bU^!Dj*IGw;IkmHsIq_&-;f=8NMgz3fO5_LX#8q;*S zcQ1J?;!YmtYHEf$QcevAY`e!dUo`Aa2j*!#6ROqv+vGaYSz^)u{X zfOSFJ0q7stek>`hQ|W3_<|dUjuFI5cL1Zl(DaRW1hKpQk4+WE<;E9$U@m**6U0rF}YBabY%KIGTtWD!pD0tX*zz~&G=uLD? zz`{_iO*M4(A`HE~n@Y{velPKVDZSI8>+ECDHk-y0i?eHus9_amN zwz7v#sz{5?gUN!sRVM$Wa$vU1=s>jQC0xd?s|9zMgK@0inV7ugKX*11t?&)yzrdT7 z)dKrh$J~lFr?vT~(WU&#b;OoYlW2Fpr%Hr1I}dr;&nis+$!&^K$}AuV@Dz=co52OGm%gyJYq3 z)pNhOXF;KCP6w{#MS%ueWwrQMFcx`w`BQ%9;%q z7vdHSBW{%4X=G@|hlQfMN#*1`1=H7wyfei$NMIsCK{z|nOpcMskbTj)kN`C{zDq*k89p(!j zKTH>s>yp6+KvQ28&;!AgF#(A!!QVYxavVy1UpfvniDG;8rz(~=gZGLan%u}ulE3#! zC9PPlTAw{xrSGE%>vFVH4p%$l%Id08t)R9vvZIn|DsfJje%!NCtqjKN-+W+abt-+* z=)E4A9*|lmqz>R&BJ}vfhYybjeSLf^^r->8fc;`1*fW4dn$unn@DVTJZl9{r7;J~a zw!8Y#WXf1A`(%XVbIw~tuN>BUw6pc?QSQor-=@vP7+@LF&&KacUY1 zVuYE>ix_iStjP` zwCp1c8$G1X0I@ohaeAz(y5EuOZTv2NXf4YTR_Vkbug;eEg_{{X3#$fF0X?0kTDLeN z(w#WJ3y?+Jp;t>`FTS5PWNG;c*bZzrpL(tiXACtdy9WyekOAH0K zW$JZhA&yS1?RYnH6g2qSc9jhAtU$kZjepQ>J1FFFwu2UlM*|}UWBE+De-6q^|4F{K z9GTi|^#8uI(oNZiUh-tlxivOkRhU7 zX5)i`f?lt|G--I~R9}~k+DJpBrPWGx{!8o`92)w9A5e53jC=kNvp<%T*6^?9cIie~(-|8N2{+UcfO_xm{?j)hY`F)fNjPYv?ta}oyr4J_xLfJ;l_Z1`UGRJp!jfLVk z!~Ogpb819O02FAg86*{vr%^S*@X7xl1~&S=r}+>#XVPRtOhzmzOK!stNnGODDQboj zmu?dh0{!31%j^1D9`|=F8Da424`qgZQ|-xZV{f;sP85e&HDgZujdmNu3=}Q%?^eiB z{q)580K>^wva@yFKSpze_`%Ph`OlSv!^)5llCnn;qWPbc zr7J#+u|_fZ;3um*Aua^LHtsxVHzGjR(f&OB;f~>N=#cZ-xP0) z5)rKD?s|<~g$J-l-$IWCQLYTvBgT9T{OPuFC zY|pu7p#o7Z&5K?}QdVYP%TX2!C?Ns1ylWn@AJg1Y;#YSvkoW^xiv~CPFTxcX43mbO{v@6qIL6EYhLZ z^uMY#;Ax;nMBi*O=TIA77?5(sCfoMd##4DJ1IJ660Hc&ZEBK6f83W^Fg)ce#sW zzqVnf9NSPSt0qg?0hiD8^uA23i48#Fe<&mg+LF~Gwv5MYNZU7SQ687UPV zAuRZEHEe-X=yru&_qmW}WXwIyK-OEgZ~#=?G5>agygC=oIc~Q-o+IR2{!O@Td$3ke zzw^fzJ~gfEe86C}WfUz@JR3a21~nuWAOy!lM&B=47B8JJI}15IBLv2Y{Oal$i%KE7 z)3`COfaY~$nt5^kXSj|}o57ADdmCjO}lT+E<)FJP>u{Z!< z7lXUpft6|kuFjkKi0CF`S6)+-6qI{5h=<~`FO?6QH7+aZ*rfMEJPkNeAf~wm1;2nB zCtb`n*rU}>td8BanrdM+@wzwz6|o`bH-Ty*vvUm}RM^&p>(h-P?HV1fFpPNfrt|i^ zP|gLVQJsL}2@sjB5ob3ytb(!;8zAA-|g6 z{no9mO)qb|JS~j#Kl67_Ys46Qjinwpk7wE5-p=_VOsECZ1@j{$4xE?rgL?4~ytAL5 zXnc(cIPGE%;Fi6#_U;uaH>#w!{szz3yHT2in3&)I>FXsKW=~)5QeBJ4RD>B=PgW_2 zZzf!QApKOcYNMI0<(k+)3T%FVy zcM=6vQ%KY*-DLlXi37DGddW%D9WP#JbNxUv4#O`Y@nO9Y4DCQZr!;UD0e3@8)pW^j zAfLTwvloxb=NJ?tXe%8SwpCw|I2&dqqTRneU>xZ=*DJ>SBIL?UoBs(}L3cAA1NQu#1yd0$#*^4@Q3WC4@7H2=DoKKsA z3-8KqmFRQIyjGfcQ!Rnd1PF{k@GdKJ?4=4kcWrK20|W7sz-;Z(=P+GI@-HPo2pSrC zrCVX54#@#twmq17i{{j_U6PqYruy*9~IU=2r;*~xo!3)e`Q zv$!oJv_mz;eWtErKFu<&z#uD$0vw3i|3Fwq|Lu1aKb`=VywX?VGdcB-Ms zS*eCzHI?c8n503rXzKWI#_P8E$V`I>%fQyFi|wC&L)wh&BJ9|YsAh9S7`H)9^XRgq z!58kjvcLb9AMOA%#M0L~Ft+kdSOVP}IPla&bMCwEB0{|+Z$-W{&j2%UzFLOb17c&M@>UmgLG8#V{p@<6oEX^pj4OFzIH&ciD(?xEIju*($avlN z=ArKgTb=_FAyMXOfw;_ZFr+DGfDy9S6C(KQ_j;d-Z#T!-iLL{KNWd&kbf9&uT*g7@ zd1i&0MyzFR?W7yta(TaeG&?(Ru>5`ZL{)c=5CQ;FeLoumv%adWEnkz5b4i6OH~GvrYQp*f0$lmw zOf4@Rl#=3`M1`E#*%7UpWK;$A0AZfSrZX)PYF?vAdjny5Z~p+oZYx~ju)Z+;Yr61_ z*_*W&EyuF1QXhtf?odcrJ>d(uqD(j&#a`a%tE%6G8?J8Wbd?(`yiMt6jry@T^4j#T z$avrbbV#g!%L`Xm*>trG;MC#(IJmbU-o@&qN@4JM0dW_jCpY?HtL|je)wRGRo@DrM zrr66ot=zLE8clmla!A`p#TaVbsZK_(c}U;R&SA;SvLAx_VYD`3DTz02lBh86ZOret%Q; z7PpQfI_DsQ>v@sBe@GHnTmMrv>C^1!sEDJhNY;RMshY?9`1q5fAWTjUa?whHB-cC? zHMOV1*%8Nt@*9dV`8U-k{j31!HDeiCQ5*yj@^^~vCni2PAM+MvAeU!K&WDXV2Ck~+ zu;(D%53#=NT^EZ5w!m&jULQc~uWU0N`NXAz$an-%)8&e6@kGp*lo2>{PMUh*QBD-|6m9duW&Hg1FWd`fnMN$li?lw zWWl&hLkUc*-fqkGg=?9w3;a-AEb*fU-qpS)jmPMl9P`Plu!&~qqu@DQ<9JIp@RA8i z=(7>sgq+VGCAM5maj3HRbvzvaZnaGz$x+YI|D9QpDS*U)f(2jr1MUV)i6=Ph>rx#6 zxj~@rt{iLwr7eY9U%p_-tS^N}`Tg6o0QcEuIZ>BDeDqPnTp6XBL>C`qSJG!=qVqig zsl;j_jmP4bvWCS3-}y6U0LK^uHg=C&$0dtZd`yNVAA{pBQxrjUb-q~LW_jk=A2935 zi9Q*qJI=W}k(Bb?0)~9eIhbj{XD+Jk(94r*$}VAb4JHQb4s_5dF-jUBA!ih~QN3F{ ziaXrT=6}?%yNX*EW}rxg|HC*yw!c^@j|xV^!oYxlJA-;uhYpqkn)PEq6RlsN&@l|6 zKiJ7`IiZ$QbIut6S94Zchet(40{Zb=m`Bvb2nqDpe$JBSG@9nQ`(UGh?~8^~W(5%T zb(~_N=Bp>kY7HX3eBpp@y(gXROl|dg!gBm8pprXkex3=~H9SEX<3D{jY-oDhOQ%kT z2yBHI@ro@O*up2(r=)=syQm*DUSOnjsGW)heJ`8te+agkI|hKMByHMAhX;NiL}h!H zJUEH~h*$mu#HNBDIJo6kw2FAC8d|GiG9{i7pG+E*S%4MzDx-*9ohMt?Iz+N=Q6H}dF_GIad=dPS@5kQeNjFkkM4 z$p%h~YFth8s|(pzYXc{PtVt$MU&etrV3nG{N0FxB{@#G`Z;~KC*}XmwFtHgHPW)sD zOtWxjutNWMo7`N1x0iA2HZGs0 z1s^6tDg84=#rkgO?Cj;YDkDc+Qc_|k{9nM=caWC;$UJ_sb?~4iB~>I_uvh@sBrHu$ zL#YT8fNwyXE193HptdZ-g}^65o6RgY=>wKPO-=2m<2WyH{{!b~`UlJ5psRnWm@}aK z-${N&(AlX({GOi^Yfv#hWFX`^D{XSrDE8BPojUla30>v0*%G)D#}&209p(Y*m3|X76{GN+6Q37nyvtP zd&_wOgTC)(#M_kNUcvZQA%;km$aJ2*|NiF{NF+N*H8~GsXA1H8dOx}MaW8*~x)K)OPWCSF1_W$@=r4cV7?BX{j^meXy+6K)QRNZLY z;oygYWX@ucAHuq%+PcqXxu2o1sVSAddf-PMn^gBGw;d99SeXo2}(+mELK5#mp)i&(C1c9^mniDgY6e_jSRpms~pGqfuq2o z_D^qe8Aoy_a0BE{I=LOsmrYh$J`D>a1?G>Z#rAX1p$1S$RbaZ0Qu{6SMa^k}^Y-FF ziK!3b9vtfDCvJ}`h~LuBq-j}8ZyS@3p0VmSj+W2`TRzOp%i;IpuyImbg#`uAAUR#Q z4C!qs($>$976qrHU9mjb3oDTIe}81om23lAEFQOhGb4GDiX~{VU~f!UYNz%j)7Z3ZR{bv;?_BcKohppR*KDU^uAGZQA-? zjV)$7008OdoE`I2e zeF&d9;vN+5Phq~hrC0;Cyay|Vr(W<%a~WkR6$QTq)|WEHDwH^H{ckP{Y$P?y#1*@W z+m{rm;2nsl%k+eUC#wW{*dC|`rPrelE;F~awPjWI?=W=ANi^}946IrjVb~fdi)Y#}=Lf`94O28;K|xc(O#i|U z_uL0N#t-KzyF`V=>qBiEZ>5@E?qPfGHfCqHF1jwP)6&stkjz@(vYz%dU6nt^wg94(hW>Z-HCx2llXR+*hpIqHG|I z-}w+WOUjqu)eYj_Hg;_Sj7cf~q;z9On1=$gLM7@v8*QJ4|Du^qX6;FnOX9l(QE4o}UbO9Mg*W$NTJ>sE{{74PDT; z&}YjOa|J7pWw3#Ud;{3Op`qUyB93qU$L_Hpl#ARL)Qymqxgn5~I0}FdZ+e*?tIFRLE zau4V0(8sS;+TsF)vCjHz>*EzALw08Rx>X^K+8-?{>y@svNoMVwZ-$-K7#&r^ea!B;^=R^0hvxCPfOvk@hyTIQEdrjN9daCDXS-H8f zwzS*=J5DYQOJ`rN@Kq-k){$0%fJsn>f*lt9tHa@v(>-Fan)p9mwcH=~h1(F$t*rC( zKFRBZv6?Z^{XK`HG&Fq-P&M}Vu%z^+#p|Npsz~Yr~1{9hqYeFzWj>G)4>JbDpGFs~@BsezAw}QB?BV!Qw zK;l?Qnmv8v3b}i|1j}D~7YM{~=Ypg^-p1FPzJ9nhtFRP5!p7CjYl2oq0jg?oZKrTQ z=MCOg7Q4-tHZ{&u`8Gd#f>9`Rm;=v^*Dt!w%i`und;>H2nxSiIhfqCqpD!;|0C*gA zxI~tbEQJ*nIbegB2Cv@b-mtKnv6y}Z#b*sAh4};jPoS%K%#I!n_&bHVHeWnvQ^6Ge zvj=k3Bz&`ybG$4mzpYJHIWR_O8uQ7*33dCW|N3r_A>8RpEug@x*A5!Sm@x0ALv_^! zRBWWYPmCdkqwOClrC`qHF)0Cf@bI0RH#hS|9Li(1P5) z-anf0)%1`lwl_x&+=G{xR1^>shvA{Gu24-l@rGVWGzRUdu{jz5OhGS+l%EmZOjW$e zA>`(5HLdteE{kJr<+_*4DtRvbT279u)hAPjd8)=9+NOz_v6J7Z9weA49{JwHt&!5)l`t z-Gb}3C=*8>mc)5mSjeYXM>y+-FylFdvHIqAW(RCZJh@9O(dhPp;s1I8;tHPsmj{Q# zf0nq=MgotbHs{ve*FZmNtwTWl5PGe-WfiBfM&n+avbG^Yvc66UPw zR)VE07sqD?KXZ-wng`j(y%C=bvx9uX?tZA(F0J`j8tc@=F)TbfGSUp>;T~@$iMXdF zUlZx7M`mVfhq0f!FysGMWo2KqY37-v!&T(b@WW5K+@kWEb}3j`ECR2Mf(6fGhf%EuVI|0y zQ;tqf37Die9E+o~l`naCU_vH2FCULgO-YIbX{8&iY;KNDOuRjAEK*JFzvg2T}sMexGg`AA6 z8@hpBgk~EUqP+eNl^u+42kV6R&O!y%6+V6XROM{gU_@1@q@-k}uWS7h{nOkG$<+82%K}`ugR^oeeXRXxSZ>emK^y>UX)P-mT<{EE}FnX>$`(zxr z+d)rD`?-+6EMO>KQBeJL+eyFcpSdk)_UT#O>NyUXL$dZD#PhI0a<6-do|$peSd0eADP1x&UU4j<@V;k!>lPe(Td)>@#QI(_ghFI%ddB+NjBuKy^= z+t=4|^!powYMW0+<9e#9G2DIHuG_fYC6C{kC0Q}-*>s)AdtTp77$uAn`{zli z5$}3w3G+D1CDm&~)XgGo&L@eSwRLrlAgjO=N@gorDZ{dr!$_ALS{L4S7O z?Et~{W?W3)&;r-Z*;h3(J^ka_CWVv*^rP3N+Wh^*zaphxcSdpw3UIQ7nYC&G7roCe?>^pPdznqSaLh4187nyx)ddvO2Eu@T^XDw;H@pL}Ewc_`0xt8s#bCV`qHL_Vn zrX_Q968gM|dt+<7PA|;u?7ia04vVSg25|lXPRIHz&AWGbOq$=qosr!W6|JsrZd!e* z6=`2ia{6tOkytu!LWfuhuiQ@VzJC)FX;V{Ef0#OZm#V<;&{CK=s&zJO3q+4EF4lAR zP1&13y_|Fm>s&@f#*K^Q#0rxZu-SyraS$zJ+_KN8XQZ?z<+JP^Y2IDd1g z8FIGaDJ&sTs$I%~#MRjJ>-+ByMLme=KsdN(W7oi6c^JBEJ~&T^bg6kZ^@Ed^`j21+gks98FVx~d(mOxUNj8$ zyMvsZo>v4PtiRz+wMN0S1Ii799uau!PJ!ir@NnrF8I!gdk{Qy>x@Eq+3~#yO2bePv zQ-6q8`@oWf>01`~Lk?VtJ>5r7hHJQP{Gal^!>g(7TX!Q09;Da-X%=ksDn*)NL#p&% zgdn1H0zzoY5d;(zkRnJqNbg87AOS=`njkG8O={=?LJ#H6=zZt@#=B#@f8aSB8y)QI zJ=a{beDhm#t+aOj`jw=cM_<(KZ~piWT~vw=K3H9z8IN1BQR=9!_EYK9c5(SA)21cE z$#kex`(tDJgSM-oS_aakq2wHn546bdgxe& zJ|Fz0333Y$wNYxb@pX3A^!Km%?od&h^X??*H^^X=6#)d`t+&eH`8!KBcocL zN3klm!mYI*^y>v!*g)$h=z-_>vaOBH>#@kUt=8gloJ{zL=)0-r`76!_352Gc$lTnl zwa?iulkjSbPet>ZPtIvpQVG+G8h`0&Z%+bW?|S2~G3v7yPu24E^;Hc;V6n9H^rB&B zVu?5Aun-$toYlKDC|@o$2R{#T(mbqBCi`!$pgO>kM8d5Oo3LVCe`5d z`-MqQWCS2$-Pw;qsdhpVL9wK1W+r(i6plS}Lumj0Aej^Zt`aIadT@1RCF`S$)5hmn zX;>HsP15f{_U)LC{IH>h(!k?fbvDo?URkr{Blzyq#vE>moLvmHy_WGg7w`tU1>~RF zn2Cb2`Et_<7q!->4P!w$@QzVhiWj(lM^P51Y@o(obnU z!d42oczNoSJGQQK%2}e7JN5ZYV~KeSTh`CbXgQu}1ssVcV2kXo_RgTqwK*8Hc97;G zD}$)frBB~C7kw929AwvCxo2ryYT1bIrr$}5=l3&AE}+*?oOU&wpP#pIHgIk|A!;Tx z@8yz+GpM_N4_5tSm=f12ZH4j|F5@gyOE~;3S25jwI0ETs#>&^qyC<-}>dj~BYa&pw z&V$YyM5P)ArQ6De_L(fDW;}1ZdV10z!M`!{Hih6D`%&)XuJVJ=#L~q_v+8A(@blPV zy{h>z~9$OlJY3p`%W_DI9SwkB; z*R;+6f9985_Qh~`PAM?t8kXEODr3C#=FYbwdR3Y2^HQN&mpR8Wu~-XwkfG{$n&&?|CJc!A z({E2p9E^=gerEZ6o-F{{T|DmydNiJzaF+5&I7i-XYb>=xee81_8b+I(k5KAc>soM% zLd{QrWDJw-Pft0s-&U8~eDWzL$B?%2U9#!?G)<+cR?rP&UfrDb@$nC7%prq1To^rB z5{Ex#qjxMl2WMKucdkV(IxFter;$RITdyUV`HV=<_}tw^9?Qc3A?2d&-~g5I4~}`h z(;cZZb8}t5boDQ$Rg0+_=&CfZlg?u8VF?{U)_rX^WY2@)*)a%nHntKb)q< zm6RB4dYR_t=Kd_O@Q0iN$CZeQi2U;wJXUaf)~oHJH?+JKfgPbBIoDk`E{%lVr{Ai$tl0$_KF_?0VZkWaWq z9CweY%baN=b-AuxSQ^y2Qr`4-|_vb!n6Oy^!jI;mW`iGxp7)QYA1v4?u8iKa+w_$>@ z5d@nr0)g{Y3o{kg#XGZc0{1K}EhUd05papHdM8{tdQN^|TA!+mCe`ezly<9H8DDm| zy}pNm~H^xyU$H2=!T5S zlWZF61}Cf7Yx&;p^U=){i!^EFBx5-@kW~SZbF`oMD&W9OP70Ww3Q{ z&@(cM1OBYIVO^DJvU5Jq-87e-`M7f*uA$V-ebt2Ld}cthzKD;3iD`C|AfXpL#l60PMc7#a2A)b4yUiPNc(42b93Et7 zXjpu|jt*?|Ran@pmi$zlj#0%s!NeineEa32El-K(`R$pN(4cIwSB?^}iM)1hMKhDT z!Abj@`aYF0Jzi`nI^XCllAVu*jTz_^6+hc9C)ufiyEe z4=Gm*NL8rbPHL;i)nUsl4-f08SN>ESc5+yiEtxK_IYqK{V#T9T@3dH_H8o_?w!Bq& z$>mpp?F57Ihx7q%h@Qzzw>39c;KHDEB?hAV*ZpaPc=?Q|CpF{}@f#bTU`S$;k{M7a zYxF(Vop$xqUGFHh%hu-$sK`_L`S4q1x!8B7_{!3GGM$^csH)O6Gvm5wy zkDs4E+;`4?_+!b=tMb*K64Nuy0&kD+Z@gc42{d+Q*|x1*y2ZY~6xfWFh&;ymT()^h zRA1@6zMWjd>sNXU>cny#|B9P6bT+x2K*-WADoGM9QLi(H+q>}ibSRCiL;#4o_G@K& z@5WNa$gU~UPHL!=9=VS94u(Gy?*~+;!*{Zi7%#fEHIzLh|CS}XjZo_+aG;W8tFZ2E z21B6V(i}7GXY7f*D$=TX-m$`YFmnipinTZ!Z^6QI(%)h|Dyr0g!v&JG*D%xFUTae< zetloBZ%-qka$PiVOA)zuB}(QNe`?+$F^wV+{EjYKcA2Y)Pq|QECC#8OwKY1tQCpImYpz%~ih!LN`fPBq za(>puvjth>GFYg-w&>NFoxWqY_Z@4yn0GSsjdtdwb%5+_AALFNt=B7)lA(0ZKSM?f zZ;X^_V~}vX9L)jCOmFYr+*$2okhVOTSb4I6-hBAY1Y^rFtg#w3H8luRx-61rd*(@J#;?a;}$P_-^ z1OX%S)~x{X9naTl>EMYIWE`jO_B{^wBY3)`gdd8uGrffkc~RTufgeZST&uGT5}GvB zRe}2V`61@UbuYBtHql@(ac{Xk1d+UdI7_+X0P0jteyQmm^S z&3V3GV5W(8&maobnzb`DM9{f(d?jH$CxVEf*mB(PXWfg5UIC_?P54w>FY% zr>aCo1tx{P0qp+A&AsIiZ`&&3P#YX9*&@g&|fN6xq^ zQB_fa(@nN=5?&>m%+~JLI0u;wZCe+tEGbvDt1UP@YReUG5E)p*n546WQ;yHo4~c}!lvDo03AjF8vtwptxHvsShkAY%y^ z|rYx9=5ya#WP=`H1OLoE(0J zDJ#4h=>QAi?5*r&Z+Vp`Bwp4zK)}$8ewX`bQBI6;8F#IacpPc7@gi4enyQJ2s&X3a z*1TDFpCO#PQ>^xjIF8ti=I|!PNjz$;!0C{w4`>{lJYy!=;7=Q7BByKi#2+@ho(OIj z3$8nAYis)kI+WszjPW`cOdv3QDxF=M1hWrUiQA1iKYf=Q8(g7%sY#3>HZ3*av8BG0 zKyM@}2?#Dah?QFkdK7tw8yKQRU@fo70)_PL zT=XL=D}9bbS08k*$$O2Qft^5Q(>ugHjRM+oFQu9S+{1Uy9JlN4Uvqd;dtht9-J!G4 zVaxed7^4Dzoflm*g64g%HXC}?Cri+G^A;Qwhk2$jTMjudRr4P32{YO9*@0ZUCBPnw z^^-R11m%=&bClc#6WS~%BwR(ktC5>}e*Ibm*6IC|(MHc{<+dnPERQ1u!FNk*ZSvm? z=(;k=RX#xw1+&18fu=-%m&$Md@|@!o%aop$Rs_`HdU%>#V~=9+RMVYS`N1pMYZtky zl-guS#cHzXSGxIfrtk(F5x{~?&9moQGkWUzAe*S@|M+zLXMBi%< z;+>*)xA0XNRaIv4+ataQ+3v?n= z{%4+;nQe6t#yxQK=XwtYJUXB;EUEld`eax`_s~!>IH#2ekPGAF1oj5q3&*TJ5J*yW z_k)_QTD!}wuRB1VkLE^wWB@Dj8jg5)3aa!-yZ&%`h=EG2#=L%>CoK)>Gadxc*J=nz zTyE6J)Q;qq3eA4==1hrLStHuSAnF94QQXAl_DZL^ZRI8OZY_EwU>P6)r zs2#hODk*wNPPCcSCq$px$*L3pX;=r&9w6gj404HjE?ObLjb3VSI!-X8>JsmvWMT$~ zPYPgX(I#sa9tp>EU-Cvb!-*J`V;saNJLc7T3De@{E4cMG!d&SN1o~H}*!*|8()B)o z%SNFdl7IPujm%64?p6HcV)uLZ54dZCr)NTE)7#s#Ll|NQD%vC&IIsTF3CYkr?Bqwo zs$DtR)vjZPA175*RbKL9<47Lv1P|aWfAdW11A0?HufoReh=+zioNRTa%K>Q1P>m~h zZRX$08XH=9i7e`Ypdrp{Vj*I{o%ETg%ywn_rtdC^3y4-hsOSZ%w&@^HL7ze`FczLfj36E|uk|&0 z4Z@vZ>KT2J*qq0qqXFcEiag#nmkEyq08I4fV)jDotbL`~0=QXcrlz`}mgLsi&5ePZ zLFMN+A)jR?_P)isuG}K0yB3*o#kJ3V?nIsLh1}N3fy|^>UzVMTqc-57jRNYRS`IvO0%9p_KDG4n6X)LTq1kBb@QG~K$=C<8^=wt# z!|Nct++qzcXgnYwplh&FNl-2p9JcM!4qaCvP=QnBvw7V{!{xe6GLBW><@bP%7?(s1 z`tEsy%`k6$yDgU~zq_<<`|x4Eok{GVtQk+=vW&X>z>+28&8feSsf81j!H(g0OjWzS zj`U^$^6T>QD^pMMPAHgI-lxy73?2Jcstg~g+J#LZvqpOtI>jPYE`Swy(|HbE8#OM} z?+sgP4*xI~iYWx3vjR;PfT5&9QvfNTmmAZyOSSlW7Bi5R0~CxiH>$p;DPOg87H&|H zPH7zYTc!P$>Y)_lDl;`mI`SaFart2zOTDk{Dkw>uYD@uXvYY}tzg67Q7!1>{5y64-R zA1&5aWE-3!Rno-7icMlwba7mfQK}chmv_t?+31$a;XpR_{j`lU22)TMT`vNmn`k&n>aN-%~} zlknnU4p*gXzd{?1g+JrOpoihmF{^hMI9%Z+z^oBEri3GKgrU4iNINXWi%_6(k3H}1~YAVRf_0ux7u zO9J7cmzOr)WL+Gamx9A_qS3P1>z@hUpe_@Wwyfl2B{JI&f6e@;yvj9Cpe$-@6}O;M zCgo8cR+mZc6E6I81A1SEJH;pMYo58mrhmJEjU2DxPIccc#Y)(T(hgk(P(xkmPXh9F z=ba)EyC-^zgX72e_}}Ff5|q`ai}IUKhn7O!AKBPw2zvf-D&L&Uw`~khoLMRJwzOwF zte{%p&7(mG2%uT@=khAkJR*<_Yl21&JHQ{t19+6-)GE~PY9#+yTU>k%ccIDi^5F!J zKSXu;YLAijrQ=^@I}&|&rQVy8&QZ_Y6!R?2fiPq&uB58TVs+@b7b;E*b|KwkHt}fl zNyRcT+D9MfdnKx>wdti6aHN=(Pfrv|zO`N&Mzif(UCb13$$RHy|4vuGJ2%S^v34LV zE#UTo(R;~UzDqiyKeHfe<+1UFRV+4PK0U!7msjoLgj(!9<^Qhhub6?lZxU0joc0~$ zSK3-)Hse?uVA4MOMjQKh_L=&wjMu_Ne3c>atC#L;39_6=+(OQ{n{!?|z6=pn-(^8P z_o~Cghn-VXi$m0drnp442igMtS1Sb~>|a})+OG-5wZdCK>+^maK=9k4Psxd1)8fk$ zys;{PBB!6J4^=j-FU(5{Y*@9AwOO-`H#7AE3(%;oN!T7MW7=FI1(!r5SFM{uq`%u_HQ4yyrZxz(ped<-)hDw@O`ZGuZ>{pXGmHokUFFh$T?s>^`dhqw=N`11AHWenEhiF6&>6}aJG%g zUt3dSU}U%yd#5Sb0E$}A#fAK7)%-rpjXf=OrOeP1hS+5mSVaYL2<;}&jmDd#pox1g z#@T`m0$v7OFc4OT<LTHdtYjE`GA4Y3FSbB7IVz$8LC6 zuX1(N{qmbT8Nx$Ju9%ejY6gnO&fRU$JjKqbi6LKyMZe6Cq!%UvtiwxKlDu2=Le+#_ z(R{d?^t70G8k_hP;eXV9M?Y$c@QZRrOT^;azh?MsjQQek=Wi5WxaiDtf=+tR(!G$6 zBSTRe(9Ih`y9FQJHrYjGey3*7`Q z0jypdcjXQ?UiVIu_Rlq|Z*K&fh0u|x8NI}v58KfR{1y6Ht8eyY906)nIZds#cXB1YVuv@se;QlL#{J0II9!Y`(?qkd zBkiJD8X$(1$gyda2iJGEFyqbmdEWtSt~YwOfjB?}Hcb@RP-(Ht598AAO1UWqE1%LU zNC{6he`0sN;~3pDM@>z&UA3j{?XaQq-j4|io)xC~FF%BH>vdQka8PY*FYq!5H{_8x z7^dVrk*(OVSz&XiI{Crpr4;w09aKmRp*Q{j;&;&8EkQR)b!2*KZp!$79 zWL+u0kotY)bEo|K3|wbwr~)j*M-fEwhArze%dhZt6^unv2XT=;2awbI08GLaQXitw z%CY_HZq!u-Iim3UdiZJ=oM7R%S)X4!_Kz53Sm|b%rgFYy_*qbtdMd+WLWw~P6yct4FeLU z+{*a-102#6!BZtEkNqNo9KQJy$LYr6ObZ{IN~W%sxm`tnw7CZ{=N#J?dii)1)xVDu z3S>mkP3aBK4b1-t^+(j%cFM3J)xbcx>!_-06o`Mhyrj&^Z?U^rcDUK;{!GOGhy~L0 zo%(kc{#%VOs|TH~ece7`iIGE)NqH!?C1rj@U;7I6{4b#|2bg}`v|vTv)L-Xh7x?oi z5b5;wspMaalwbTi5r1stU+KEe3#eAlQIzrft$vDOJ_j7D|BM)}j*d=^RFnJvH7u7G zoYam58ZaS#2O&X$kk#zce$>?m_Gd5)tKFdp<~fC|{S^&0f^Z2BvO^ zKMoje3vYY@;zmJ-Lr%f_dTCdF-v{ez<$j4#X%PQ&Uj7XEaEd=8QoViq zKU@VS=?1%d4#LP3>2q^sk(c3@5Uh$*)bsl4q(sz1#E+Y<**syi>?C3h#poZsO;Y(N zQJFhj;-mg=djy^S)(FLz6!sS|a?qYw4?~0`F6{(k8}eC@7O4PmpchEMz&)dZ)BCE= z93dH$(IC~IA8^hv!I#l6LO)T=c^?ZTiId_p|7VK8o8788IFXjy!ar`(BIZLWfj%r_ zx{Sy_=jw0n2z||{%jy9RClG2|VSljrg6S zI38s%K0;2!{YcC?;3HhN{iqO^FmS4WqrqhRqiRClLB#Pzp*Ii3-v0!QGwa?pmuQfr z?@%v*8rl_ykE}~T=y~zmXsay-6$!dD$l(+J+ihM9uuHFBMEq)x{HNcM9KO9jasWZx zzWkmMjbT~W1#txFy@)PHnVG!@rGo-|ujvKnX7grznb{qUh~(66G!Xe}{2DiVFV!yLnfBiuR(R?iJII QVAhDzO|=_&*Ug{(7xmYUE&u=k literal 0 HcmV?d00001 diff --git a/python/gym_jiminy/unit_py/data/atlas_standing_panda3d_3.png b/python/gym_jiminy/unit_py/data/atlas_standing_panda3d_3.png new file mode 100644 index 0000000000000000000000000000000000000000..6e24492f13e16810fabac74341c9ce99e7139e80 GIT binary patch literal 27826 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 literal 0 HcmV?d00001 diff --git a/python/gym_jiminy/unit_py/data/cassie_standing_panda3d_0.png b/python/gym_jiminy/unit_py/data/cassie_standing_panda3d_0.png index 4b6f8a715f6386457185680e28c1ec1a1902dbdb..a1c912413207c14c1e88818af1e8e1451b4baf2a 100644 GIT binary patch literal 8843 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 diff --git a/python/gym_jiminy/unit_py/data/cassie_standing_panda3d_1.png b/python/gym_jiminy/unit_py/data/cassie_standing_panda3d_1.png index cd8509712a48c9945d79af5b78a34db4125d0162..330a88d8c25c8ce91d40385ca85446f8251fb745 100644 GIT binary patch literal 7199 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 Jp{muB{{f<8M^OL( literal 0 HcmV?d00001 diff --git a/python/jiminy_py/setup.py b/python/jiminy_py/setup.py index 0f9c08b09..9dbaa485b 100644 --- a/python/jiminy_py/setup.py +++ b/python/jiminy_py/setup.py @@ -99,8 +99,8 @@ def finalize_options(self) -> None: "panda3d_viewer", # Used internally by Viewer to record video programmatically when # Meshcat is not used as rendering backend. - # Python3.9 is supported since version 4.4.0.46. - "opencv-python-headless", + # Cross-platform precompiled binary wheels are provided since 8.0.0. + "av", # Used internally by Viewer to detect running Meshcat servers and avoid # orphan child processes. "psutil", diff --git a/python/jiminy_py/src/jiminy_py/dynamics.py b/python/jiminy_py/src/jiminy_py/dynamics.py index 8aa7e2508..fd2313e2b 100644 --- a/python/jiminy_py/src/jiminy_py/dynamics.py +++ b/python/jiminy_py/src/jiminy_py/dynamics.py @@ -4,7 +4,10 @@ import hppfcl import pinocchio as pin -from pinocchio.rpy import rpyToMatrix, matrixToRpy, computeRpyJacobian +from pinocchio.rpy import (rpyToMatrix, + matrixToRpy, + computeRpyJacobian, + computeRpyJacobianInverse) from . import core as jiminy from .viewer import TrajectoryDataType @@ -35,14 +38,39 @@ def XYZRPYToXYZQuat(xyzrpy): return pin.SE3ToXYZQUAT(XYZRPYToSE3(xyzrpy)) +def XYZQuatToXYZRPY(xyzquat): + """Convert [X,Y,Z,Qx,Qy,Qz,Qw] to [X,Y,Z,Roll,Pitch,Yaw]. + """ + return np.concatenate(( + xyzquat[:3], matrixToRpy(pin.Quaternion(xyzquat[3:]).matrix()))) + + def velocityXYZRPYToXYZQuat(xyzrpy: np.ndarray, dxyzrpy: np.ndarray) -> np.ndarray: """Convert the derivative of [X,Y,Z,Roll,Pitch,Yaw] to [X,Y,Z,Qx,Qy,Qz,Qw]. + + .. warning:: + Linear velocity in XYZRPY must be local-world-aligned frame, while + returned linear velocity in XYZQuat is in local frame. """ - rpy = xyzrpy[-3:] + rpy = xyzrpy[3:] R = rpyToMatrix(rpy) J_rpy = computeRpyJacobian(rpy) - return np.concatenate((R.T @ dxyzrpy[:3], J_rpy @ dxyzrpy[-3:])) + return np.concatenate((R.T @ dxyzrpy[:3], J_rpy @ dxyzrpy[3:])) + + +def velocityXYZQuatToXYZRPY(xyzquat: np.ndarray, + v: np.ndarray) -> np.ndarray: + """Convert the derivative of [X,Y,Z,Roll,Pitch,Yaw] to [X,Y,Z,Qx,Qy,Qz,Qw]. + + .. warning:: + Linear velocity in XYZRPY must be local-world-aligned frame, while + returned linear velocity in XYZQuat is in local frame. + """ + quat = pin.Quaternion(xyzquat[3:]) + rpy = matrixToRpy(quat.matrix()) + J_rpy_inv = computeRpyJacobianInverse(rpy) + return np.concatenate((quat * v[:3], J_rpy_inv @ v[3:])) # ##################################################################### diff --git a/python/jiminy_py/src/jiminy_py/simulator.py b/python/jiminy_py/src/jiminy_py/simulator.py index 47adcd02a..e7a3f1801 100644 --- a/python/jiminy_py/src/jiminy_py/simulator.py +++ b/python/jiminy_py/src/jiminy_py/simulator.py @@ -467,23 +467,20 @@ def render(self, uniq_id = next(tempfile._get_candidate_names()) robot_name = f"{uniq_id}_robot" scene_name = f"{uniq_id}_scene" - window_name = f"{uniq_id}_window" else: robot_name = self.viewer.robot_name scene_name = self.viewer.scene_name - window_name = self.viewer.window_name # Create a new viewer client self.viewer = Viewer(self.robot, use_theoretical_model=False, - backend=self.viewer_backend, open_gui_if_parent=(not return_rgb_array), - delete_robot_on_close=True, - robot_name=robot_name, - scene_name=scene_name, - window_name=window_name, - **kwargs) - self.viewer_backend = Viewer.backend + **{'scene_name': scene_name, + 'robot_name': robot_name, + 'backend': self.viewer_backend, + 'delete_robot_on_close': True, + **kwargs}) + self.viewer_backend = Viewer.backend # Just in case it was `None` if self.viewer.is_backend_parent and camera_xyzrpy is None: camera_xyzrpy = [(9.0, 0.0, 2e-5), (np.pi/2, 0.0, np.pi/2)] self.viewer.wait(require_client=False) # Wait to finish loading diff --git a/python/jiminy_py/src/jiminy_py/viewer/panda3d/panda3d_visualizer.py b/python/jiminy_py/src/jiminy_py/viewer/panda3d/panda3d_visualizer.py index d8d33569e..7ab041dcb 100644 --- a/python/jiminy_py/src/jiminy_py/viewer/panda3d/panda3d_visualizer.py +++ b/python/jiminy_py/src/jiminy_py/viewer/panda3d/panda3d_visualizer.py @@ -8,7 +8,7 @@ import xml.etree.ElementTree as ET from datetime import datetime from pathlib import PureWindowsPath -from typing import Optional, Dict, Tuple, Union, Sequence +from typing import Optional, Dict, Tuple, Union, Sequence, Any import numpy as np import matplotlib.pyplot as plt @@ -16,12 +16,14 @@ from matplotlib.patches import Patch from panda3d.core import ( - NodePath, Point3, Vec3, Mat4, LQuaternion, Geom, GeomEnums, GeomNode, + NodePath, Point3, Vec3, Vec4, Mat4, LQuaternion, Geom, GeomEnums, GeomNode, GeomVertexData, GeomTriangles, GeomVertexArrayFormat, GeomVertexFormat, GeomVertexWriter, CullFaceAttrib, GraphicsWindow, PNMImage, InternalName, OmniBoundingVolume, CompassEffect, BillboardEffect, Filename, TextNode, Texture, TextureStage, PNMImageHeader, PGTop, Camera, PerspectiveLens, - TransparencyAttrib, OrthographicLens, ClockObject) + TransparencyAttrib, OrthographicLens, ClockObject, GraphicsPipe, + WindowProperties, FrameBufferProperties, loadPrcFileData, AntialiasAttrib) +from direct.showbase.ShowBase import ShowBase from direct.gui.OnscreenImage import OnscreenImage from direct.gui.OnscreenText import OnscreenText @@ -29,7 +31,8 @@ import panda3d_viewer.viewer_app import panda3d_viewer.viewer_proxy from panda3d_viewer import geometry -from panda3d_viewer import Viewer as Panda3dViewer +from panda3d_viewer import (Viewer as Panda3dViewer, + ViewerConfig as Panda3dViewerConfig) from panda3d_viewer.viewer_errors import ViewerClosedError import hppfcl @@ -38,12 +41,15 @@ from pinocchio.visualize import BaseVisualizer +WINDOW_SIZE_DEFAULT = (500, 500) +CAMERA_POS_DEFAULT = [(4.0, -4.0, 1.5), (0, 0, 0.5)] + LEGEND_DPI = 400 LEGEND_SCALE = 0.3 CLOCK_SCALE = 0.1 WIDGET_MARGIN_REL = 0.05 -PANDA3D_MAX_FRAMERATE = 30 +PANDA3D_FRAMERATE_MAX = 30 def create_gradient(sky_color, ground_color, offset=0.0, subdiv=2): @@ -127,29 +133,70 @@ def create_gradient(sky_color, ground_color, offset=0.0, subdiv=2): class Panda3dApp(panda3d_viewer.viewer_app.ViewerApp): - def __init__(self, config) -> None: - # Force creating offscreen display + def __init__(self, *args: Any, **kwargs: Any) -> None: + # Enforce viewer configuration + config = Panda3dViewerConfig() + config.set_window_size(*WINDOW_SIZE_DEFAULT) + config.set_window_fixed(False) + config.enable_antialiasing(True, multisamples=4) + config.set_value('framebuffer-software', '0') + config.set_value('framebuffer-hardware', '0') + config.set_value('load-display', 'pandagl') + config.set_value('aux-display', + 'p3headlessgl' + '\naux-display pandadx9' + '\naux-display pandadx8' + '\naux-display p3tinydisplay') config.set_value('window-type', 'offscreen') - super().__init__(config) - - # Limit framerate of Panda3d to avoid consuming too much ressources - clock = ClockObject.getGlobalClock() - clock.setMode(ClockObject.MLimited) - clock.setFrameRate(PANDA3D_MAX_FRAMERATE) - - # Enable only one directional light - for i in range(2, len(self._lights_mask)): - self._lights_mask[i] = False - self._lights[0].getNode(0).set_color((0.5, 0.5, 0.5, 1)) - self._lights[1].getNode(0).set_color((0.5, 0.5, 0.5, 1)) - self.enable_lights(self._lights_enabled) - - # Set background color + config.set_value('model-cache-textures', True) + loadPrcFileData('', str(config)) + + # Define offscreen buffer + self.buff = None + + # Initialize base implementation. + # Note that the original constructor is by-passed on purpose. + ShowBase.__init__(self) + + # Configure rendering + self.render.set_shader_auto() + self.render.set_antialias(AntialiasAttrib.MAuto) + + # Define default camera pos + self._camera_defaults = CAMERA_POS_DEFAULT + self.reset_camera(*self._camera_defaults) + + # Define clock. It will be used later to limit framerate. + self.clock = ClockObject.getGlobalClock() + self.framerate = None + + # Configure lighting and shadows + self._spotlight = self.config.GetBool('enable-spotlight', False) + self._shadow_size = self.config.GetInt('shadow-buffer-size', 1024) + self._lights = [self._make_light_ambient((0.5, 0.5, 0.5)), + self._make_light_direct( + 1, (0.5, 0.5, 0.5), pos=(8.0, 8.0, 10.0))] + self._lights_mask = [True, True] + self.enable_lights(True) + + # Create default scene objects + self._fog = self._make_fog() + self._axes = self._make_axes() + self._grid = self._make_grid() + self._floor = self._make_floor() + + # Create scene tree + self._scene_root = self.render.attach_new_node('scene_root') + self._scene_scale = self.config.GetFloat('scene-scale', 1.0) + self._scene_root.set_scale(self._scene_scale) + self._groups = {} + + # Create background sky # define the colors at the top ("sky"), bottom ("ground") and center # ("horizon") of the background gradient sky_color = (0.53, 0.8, 0.98, 1.0) ground_color = (0.1, 0.1, 0.43, 1.0) - self.background_gradient = create_gradient( + self.background_sky = create_gradient( sky_color, ground_color, 0.7) # looks like the background needs to be parented to an intermediary # node to which a compass effect is applied to keep it at the same @@ -157,7 +204,7 @@ def __init__(self, config) -> None: pivot = self.render.attach_new_node("pivot") effect = CompassEffect.make(self.camera, CompassEffect.P_pos) pivot.set_effect(effect) - self.background_gradient.reparent_to(pivot) + self.background_sky.reparent_to(pivot) # now the background model just needs to keep facing the camera (only # its heading should correspond to that of the camera; its pitch and # roll need to remain unaffected) @@ -165,7 +212,7 @@ def __init__(self, config) -> None: Vec3.up(), False, True, 0., NodePath(), # make the background model face a point behind the camera Point3(0., -10., 0.), False) - self.background_gradient.set_effect(effect) + self.background_sky.set_effect(effect) # Create shared 2D renderer to allow display selectively gui elements # on offscreen and onscreen window used for capturing frames. @@ -174,7 +221,7 @@ def __init__(self, config) -> None: self.sharedRender2d.setDepthWrite(False) # Create dedicated camera 2D for offscreen rendering - self.offCamera2d = NodePath(Camera(f'Camera2d_{i}')) + self.offCamera2d = NodePath(Camera('off_camera2d')) lens = OrthographicLens() lens.setFilmSize(2, 2) lens.setNearFar(-1000, 1000) @@ -203,16 +250,43 @@ def __init__(self, config) -> None: self._legend = None self._clock = None self.offDisplayRegion = None - self.zoom_rate = 0.98 + self.zoom_rate = 1.03 self.camera_lookat = np.zeros(3) self.key_map = {"mouse1": 0, "mouse2": 0, "mouse3": 0} self.longitudeDeg = 0.0 self.latitudeDeg = 0.0 + # Create resizeable offscreen buffer + self._openOffscreenWindow(WINDOW_SIZE_DEFAULT) + + # Set default options + self.enable_lights(True) + self.enable_shadow(True) + self.enable_hdr(False) + self.enable_fog(False) + self.show_axes(True) + self.show_grid(False) + self.show_floor(True) + + def _make_light_ambient(self, color): + """Must be patched to fix wrong color alpha. + """ + node = super()._make_light_ambient(color) + node.getNode(0).set_color(Vec4(*color, 1)) + return node + + def _make_light_direct(self, index, color, pos, target=(0, 0, 0)): + """Must be patched to fix wrong color alpha. + """ + node = super()._make_light_direct(index, color, pos, target) + node.getNode(0).set_color(Vec4(*color, 1)) + return node + def set_camera_transform(self, pos, quat): self.camera.set_pos(Vec3(*pos)) self.camera.setQuat(LQuaternion(quat[-1], *quat[:-1])) self.camera_lookat = np.zeros(3) + self.step() # Update frame on-the-spot def open_window(self) -> None: # Make sure a graphical window is not already open @@ -235,13 +309,16 @@ def open_window(self) -> None: self.taskMgr.add( self.moveOrbitalCameraTask, "moveOrbitalCameraTask", sort=2) - # Create secondary offscreen buffer - self._openSecondaryOffscreenWindow(size) + # Create resizeable offscreen buffer + self._openOffscreenWindow(size) + + # Limit framerate to reduce computation cost + self.set_framerate(PANDA3D_FRAMERATE_MAX) - def _openSecondaryOffscreenWindow(self, - size: Optional[Tuple[int, int]] = None - ) -> None: - """Create new completely independent offscreen buffer rendering the + def _openOffscreenWindow(self, + size: Optional[Tuple[int, int]] = None + ) -> None: + """Create new completely independent offscreen buffer, rendering the same scene than the main window. """ # Handling of default size @@ -251,21 +328,52 @@ def _openSecondaryOffscreenWindow(self, # Close existing offscreen display if any. # Note that one must remove display region associated with shared 2D # renderer, otherwise it will be altered when closing current window. - if len(self.winList) > 1: - self.winList[-1].removeDisplayRegion(self.offDisplayRegion) - self.closeWindow(self.winList[-1], keepCamera=False) - - # Open new window and create 2D display region for widgets - win = self.openWindow( - type='offscreen', size=size, keepCamera=False, makeCamera=False) - aspectRatio = self.getAspectRatio(win) + if self.buff is not None: + self.buff.removeDisplayRegion(self.offDisplayRegion) + self.closeWindow(self.buff, keepCamera=False) + + # Set offscreen buffer frame properties + # Note that accumalator bits and back buffers is not supported by + # resizeable buffers. + fprops = FrameBufferProperties(self.win.getFbProperties()) + fprops.set_accum_bits(0) + fprops.set_back_buffers(0) + + # Set offscreen buffer windows properties + winprops = WindowProperties() + winprops.set_size(*size) + + # Set offscreen buffer flags to enforce resizeable `GaphicsBuffer` + flags = GraphicsPipe.BFRefuseWindow | GraphicsPipe.BFRefuseParasite + flags |= GraphicsPipe.BFResizeable + + # Create new offscreen buffer. + # Note that it is impossible to create resizeable buffer without an + # already existing host for some reason... + self.buff = self.graphicsEngine.make_output( + self.pipe, "off_buffer", 0, fprops, winprops, flags, + self.win.get_gsg(), self.win) + + # Append buffer to the list of windows managed by the ShowBase + self.winList.append(self.buff) + + # Create 2D display region for widgets self.graphicsLens = PerspectiveLens() - self.graphicsLens.setAspectRatio(aspectRatio) - self.makeCamera(win, camName='offCam', lens=self.graphicsLens) - self.offDisplayRegion = self.winList[-1].makeMonoDisplayRegion() + self.makeCamera(self.buff, camName='off_cam', lens=self.graphicsLens) + self.offDisplayRegion = self.buff.makeMonoDisplayRegion() self.offDisplayRegion.setSort(5) self.offDisplayRegion.setCamera(self.offCamera2d) + # # Adjust aspect ratio + self._adjustOffscreenWindowAspectRatio() + + def _adjustOffscreenWindowAspectRatio(self): + # Get aspect ratio + aspectRatio = self.getAspectRatio(self.buff) + + # Adjust 3D rendering aspect ratio + self.graphicsLens.setAspectRatio(aspectRatio) + # Adjust existing anchors for offscreen 2D rendering if aspectRatio < 1: # If the window is TALL, lets expand the top and bottom @@ -291,29 +399,29 @@ def getSize(self, win=None): """Must be patched to return the size of the window used for capturing frame by default, instead of main window. """ - if win is None and self.winList: - win = self.winList[-1] + if win is None: + win = self.buff return super().getSize(win) + def getMousePos(self) -> Tuple[int, int]: + md = self.win.getPointer(0) + return md.getX(), md.getY() + def handleKey(self, key, value): if key in ["mouse1", "mouse2", "mouse3"]: - md = self.win.getPointer(0) - self.lastMouseX = md.getX() - self.lastMouseY = md.getY() + self.lastMouseX, self.lastMouseY = self.getMousePos() self.key_map[key] = value elif key in ["wheelup", "wheeldown"]: cam_dir = self.camera_lookat - np.asarray(self.camera.getPos()) if key == "wheelup": - cam_pos = self.camera_lookat - cam_dir * self.zoom_rate - else: cam_pos = self.camera_lookat - cam_dir / self.zoom_rate + else: + cam_pos = self.camera_lookat - cam_dir * self.zoom_rate self.camera.set_pos(Vec3(*cam_pos.tolist())) def moveOrbitalCameraTask(self, task): - # Get mouse - md = self.win.getPointer(0) - x = md.getX() - y = md.getY() + # Get mouse position + x, y = self.getMousePos() # Ensure consistent camera pose and lookat self.longitudeDeg, self.latitudeDeg, _ = self.camera.getHpr() @@ -415,8 +523,7 @@ def set_watermark(self, height = height or float(image_header.getYSize()) # Compute relative image size - width_win = self.winList[-1].getXSize() - height_win = self.winList[-1].getYSize() + width_win, height_win = self.getSize() width_rel, height_rel = width / width_win, height / height_win # Make sure it does not take too much space of window @@ -437,6 +544,9 @@ def set_watermark(self, self._watermark.setPos( WIDGET_MARGIN_REL + width_rel, 0, WIDGET_MARGIN_REL + height_rel) + # Refresh frame + self.step() + def set_legend(self, items: Optional[Dict[str, Optional[Sequence[int]]]] = None ) -> None: @@ -490,8 +600,7 @@ def set_legend(self, tex.setRamImage(img_raw) # Compute relative image size - width_win = self.winList[-1].getXSize() - height_win = self.winList[-1].getYSize() + width_win, height_win = self.getSize() width_rel = LEGEND_SCALE * width / width_win height_rel = LEGEND_SCALE * height / height_win @@ -511,6 +620,9 @@ def set_legend(self, self._legend.setTransparency(TransparencyAttrib.MAlpha) self._legend.setTexScale(TextureStage.getDefault(), 1, -1) + # Refresh frame + self.step() + def set_clock(self, time: Optional[float] = None) -> None: # Remove existing watermark, if any if time is None: @@ -553,6 +665,9 @@ def set_clock(self, time: Optional[float] = None) -> None: self._clock.setText(f"{hours:02.0f}:{minutes:02.0f}:{seconds:02.0f}" f".{milliseconds:03.0f}") + # Refresh frame + self.step() + def append_mesh(self, root_path: str, name: str, @@ -612,40 +727,69 @@ def enable_shadow(self, enable: bool) -> None: self._shadow_enabled = enable def set_window_size(self, width: int, height: int) -> None: - if self.windowType == 'offscreen': - self.camLens = None - self.openMainWindow(size=(width, height)) - self.adjustWindowAspectRatio(self.getAspectRatio()) + self.buff.setSize(width, height) + self._adjustOffscreenWindowAspectRatio() + self.step() # Update frame on-the-spot + + def set_framerate(self, + framerate: Optional[float] = None) -> None: + """Limit framerate of Panda3d to avoid consuming too much ressources. + + :param framerate: Desired framerate limit. None to disable. + Optional: Disable framerate limit by default. + """ + if framerate is not None: + self.clock.setMode(ClockObject.MLimited) + self.clock.setFrameRate(PANDA3D_FRAMERATE_MAX) else: - self._openSecondaryOffscreenWindow((width, height)) + self.clock.setMode(ClockObject.MNormal) + self.framerate = framerate - def save_screenshot(self, filename: str = None) -> bool: + def get_framerate(self) -> Optional[float]: + """Get current framerate limit. + """ + return self.framerate + + def save_screenshot(self, filename: Optional[str] = None) -> bool: if filename is None: template = 'screenshot-%Y-%m-%d-%H-%M-%S.png' filename = datetime.now().strftime(template) image = PNMImage() - if not self.winList[-1].get_screenshot(image): + if not self.buff.get_screenshot(image): return False - if filename.lower().endswith('.png'): + if not filename.lower().endswith('.png'): image.remove_alpha() if not image.write(filename): return False return True - def get_screenshot(self, requested_format='BGRA', raw=False): + def get_screenshot(self, + requested_format: str = 'RGBA', + raw: bool = False) -> np.ndarray: """Must be patched to take screenshot of the last window available - instead of the main one, and to add raw data return mode for - efficient multiprocessing. + instead of the main one, and to add raw data return mode for efficient + multiprocessing. + + .. warning:: + Note that the speed of this method is limited by the global + framerate, as any other method relaying on low-level panda3d task + scheduler. The framerate limit must be disable manually to avoid + such limitation. """ - texture = self.winList[-1].get_screenshot() - if texture is None: - return None - xsize = texture.get_x_size() - ysize = texture.get_y_size() - dsize = len(requested_format) + # Capture frame as raw texture + texture = self.buff.get_screenshot() + + # Extract raw array buffer from texture image = texture.get_ram_image_as(requested_format) + + # Return raw texture if requested if raw: return image.get_data() + + # Convert raw texture to numpy array if requested + xsize = texture.get_x_size() + ysize = texture.get_y_size() + dsize = len(requested_format) array = np.frombuffer(image, np.uint8).reshape((ysize, xsize, dsize)) return np.flipud(array) diff --git a/python/jiminy_py/src/jiminy_py/viewer/panda3d/panda3d_widget.py b/python/jiminy_py/src/jiminy_py/viewer/panda3d/panda3d_widget.py new file mode 100644 index 000000000..316fe7062 --- /dev/null +++ b/python/jiminy_py/src/jiminy_py/viewer/panda3d/panda3d_widget.py @@ -0,0 +1,115 @@ +""" TODO: Write documentation. +""" +from typing import Optional, List, Tuple, Any + +from matplotlib.backends.qt_compat import QtCore, QtWidgets, QtGui + +from .panda3d_visualizer import Panda3dApp + + +FRAMERATE = 30 + + +class Panda3dQWidget(QtWidgets.QWidget): + """An interactive panda3D QWidget. + """ + def __init__(self, parent: Optional[Any] = None) -> None: + """ TODO: Write documentation. + """ + # Call base constructor + super().__init__(parent) + + # Only accept focus by clicking on widget + self.setFocusPolicy(QtCore.Qt.ClickFocus) + + # Instantiate Panda3D app + self._app = Panda3dApp(window_type='offscreen') + + # Enable mouse control + self.setMouseTracking(True) + self._app.getMousePos = self.getMousePos + self._app.taskMgr.add(self._app.moveOrbitalCameraTask, + "moveOrbitalCameraTask", + sort=2) + + # Create painter to render "screenshot" from panda3d + self.paint_surface = QtGui.QPainter() + + # Start event loop + self.clock = QtCore.QTimer() + self.clock.setInterval(1000.0 / FRAMERATE) + self.clock.timeout.connect(self.update) + self.clock.start() + + def __getattr__(self, name: str) -> Any: + """Fallback attribute getter. + + It enables to get access to the attribute and methods of the low-level + Panda3d app directly, without having to do it through `_app`. + + .. note:: + This method is not meant to be called manually. + """ + return getattr(super().__getattribute__('_app'), name) + + def __dir__(self) -> List[str]: + """Attribute lookup. + + It is mainly used by autocomplete feature of Ipython. It is overloaded + to get consistent autocompletion wrt `getattr`. + """ + return super().__dir__() + self._app.__dir__() + + def paintEvent(self, event: Any) -> None: + """Pull the contents of the panda texture to the widget. + """ + # Render the scene + self._app.step() + + # Get raw image and convert it to Qt format. + # Note that `QImage` apparently does not manage the lifetime of the + # input data buffer, so it is necessary to keep it is local scope. + data = self._app.get_screenshot('RGBA', raw=True) + img = QtGui.QImage(data, + *self._app.buff.getSize(), + QtGui.QImage.Format_RGBA8888).mirrored() + + # Render image on Qt widget + self.paint_surface.begin(self) + self.paint_surface.drawImage(0, 0, img) + self.paint_surface.end() + + def resizeEvent(self, event: Any) -> None: + """ TODO: Write documentation. + """ + self._app.set_window_size( + event.size().width(), event.size().height()) + + def getMousePos(self) -> Tuple[int, int]: + """ TODO: Write documentation. + """ + pos = self.mapFromGlobal(QtGui.QCursor().pos()) + return pos.x(), pos.y() + + def mousePressEvent(self, event: Any) -> None: + """ TODO: Write documentation. + """ + self._app.handleKey("mouse1", event.buttons() & QtCore.Qt.LeftButton) + self._app.handleKey("mouse2", event.buttons() & QtCore.Qt.MiddleButton) + self._app.handleKey("mouse3", event.buttons() & QtCore.Qt.RightButton) + + def mouseReleaseEvent(self, event: Any) -> None: + """ TODO: Write documentation. + """ + self._app.handleKey("mouse1", event.buttons() & QtCore.Qt.LeftButton) + self._app.handleKey("mouse2", event.buttons() & QtCore.Qt.MiddleButton) + self._app.handleKey("mouse3", event.buttons() & QtCore.Qt.RightButton) + + def wheelEvent(self, event: Any) -> None: + """ TODO: Write documentation. + """ + delta = event.angleDelta().y() + if delta > 0.0: + self._app.handleKey("wheelup", True) + else: + self._app.handleKey("wheeldown", True) diff --git a/python/jiminy_py/src/jiminy_py/viewer/replay.py b/python/jiminy_py/src/jiminy_py/viewer/replay.py index e22149e4c..83840f8c5 100644 --- a/python/jiminy_py/src/jiminy_py/viewer/replay.py +++ b/python/jiminy_py/src/jiminy_py/viewer/replay.py @@ -1,6 +1,4 @@ -import io -import os -import sys +import logging import pathlib import asyncio import tempfile @@ -9,7 +7,7 @@ from itertools import cycle, islice from typing import Optional, Union, Sequence, Tuple, Dict, Any -import cv2 +import av import numpy as np from tqdm import tqdm from typing_extensions import TypedDict @@ -22,7 +20,8 @@ VIDEO_FRAMERATE = 30 -VIDEO_SIZE = (1000, 1000) +VIDEO_SIZE = (800, 800) +VIDEO_QUALITY = 0.3 # [Mbytes/s] DEFAULT_URDF_COLORS = { 'green': (0.4, 0.7, 0.3, 1.0), @@ -35,6 +34,9 @@ } +logger = logging.getLogger(__name__) + + class TrajectoryDataType(TypedDict, total=False): # List of State objects of increasing time. evolution_robot: Sequence[State] @@ -101,25 +103,23 @@ def play_trajectories(trajectory_data: Union[ time_interval: Optional[Union[ np.ndarray, Tuple[float, float]]] = (0.0, np.inf), speed_ratio: float = 1.0, - record_video_path: Optional[str] = None, - viewers: Sequence[Viewer] = None, - start_paused: bool = False, - wait_for_client: bool = True, + xyz_offsets: Optional[Union[ + Tuple3FType, Sequence[Tuple3FType]]] = None, + robots_colors: Optional[Union[ + ColorType, Sequence[ColorType]]] = None, travelling_frame: Optional[str] = None, camera_xyzrpy: Optional[CameraPoseType] = None, camera_motion: Optional[CameraMotionType] = None, - xyz_offset: Optional[Union[ - Tuple3FType, Sequence[Tuple3FType]]] = None, - urdf_rgba: Optional[Union[ - ColorType, Sequence[ColorType]]] = None, - backend: Optional[str] = None, - window_name: str = 'jiminy', - scene_name: str = 'world', - close_backend: Optional[bool] = None, - delete_robot_on_close: Optional[bool] = None, - legend: Optional[Union[str, Sequence[str]]] = None, watermark_fullpath: Optional[str] = None, + legend: Optional[Union[str, Sequence[str]]] = None, enable_clock: bool = False, + scene_name: str = 'world', + record_video_path: Optional[str] = None, + start_paused: bool = False, + backend: Optional[str] = None, + delete_robot_on_close: Optional[bool] = None, + close_backend: Optional[bool] = None, + viewers: Sequence[Viewer] = None, verbose: bool = True, **kwargs: Any) -> Sequence[Viewer]: """Replay one or several robot trajectories in a viewer. @@ -138,22 +138,13 @@ def play_trajectories(trajectory_data: Union[ Optional: [0, inf] by default. :param speed_ratio: Speed ratio of the simulation. Optional: 1.0 by default. - :param record_video_path: Fullpath location where to save generated video. - It must be specified to enable video recording. - Meshcat only support 'webm' format, while the - other renderer only supports 'mp4'. 'mp4' video - are very fast to record but not web-compatible - because encoded using codec 'mp4v'. - Optional: None by default. - :param viewers: List of already instantiated viewers, associated one by one - in order to each trajectory data. None to disable. - Optional: None by default. - :param start_paused: Start the simulation is pause, waiting for keyboard - input before starting to play the trajectories. - Optional: False by default. - :param wait_for_client: Wait for the client to finish loading the meshes - before starting. - Optional: True by default. + :param xyz_offsets: List of constant position of the root joint for each + robot in world frame. None to disable. + Optional: None by default. + :param robots_colors: List of RGBA code defining the color for each robot. + It will apply to every link. None to disable. + Optional: Original color if single robot, default + color cycle otherwise. :param travelling_frame: Name of the frame of the robot associated with the first trajectory_data. The camera will automatically follow it. None to disable. @@ -167,42 +158,44 @@ def play_trajectories(trajectory_data: Union[ :param camera_motion: Camera breakpoint poses over time, as a list of `CameraMotionBreakpointType` dict. None to disable. Optional: None by default. - :param xyz_offset: List of constant position of the root joint for each - robot in world frame. None to disable. - Optional: None by default. - :param urdf_rgba: List of RGBA code defining the color for each robot. It - will apply to every link. None to disable. - Optional: Original color if single robot, default color - cycle otherwise. - :param backend: Backend, one of 'meshcat' or 'gepetto-gui'. If None, - 'meshcat' is used in notebook environment and 'gepetto-gui' - otherwise. - Optional: None by default. - :param window_name: Name of viewer's graphical window in which to display - the robot. - Optional: Common default name if omitted. - :param scene_name: Name of viewer's scene in which to display the robot. - Optional: Common default name if omitted. - :param close_backend: Close backend automatically at exit. - Optional: Enable by default if not (presumably) - available beforehand. - :param delete_robot_on_close: Whether or not to delete the robot from the - viewer when closing it. - Optional: True by default. - :param legend: List of text defining the legend for each robot. `urdf_rgba` - must be specified to enable this option. It is not - persistent but disabled after replay. This option is only - supported by meshcat backend. None to disable. - Optional: No legend if no color by default, the robots names - otherwise. :param watermark_fullpath: Add watermark to the viewer. It is not persistent but disabled after replay. This option is only supported by meshcat backend. None to disable. Optional: No watermark by default. + :param legend: List of text defining the legend for each robot. It is not + persistent but disabled after replay. This option is only + supported by meshcat backend. None to disable. + Optional: No legend if no color by default, the robots names + otherwise. :param enable_clock: Add clock on bottom right corner of the viewer. Only available with panda3d rendering backend. Optional: Disable by default. + :param scene_name: Name of viewer's scene in which to display the robot. + Optional: Common default name if omitted. + :param record_video_path: Fullpath location where to save generated video. + It must be specified to enable video recording. + Meshcat only support 'webm' format, while the + other renderer only supports 'mp4' format encoded + with web-compatible 'h264' codec. + Optional: None by default. + :param start_paused: Start the simulation is pause, waiting for keyboard + input before starting to play the trajectories. + Only available if `record_video_path` is None. + Optional: False by default. + :param backend: Backend, one of 'meshcat' or 'gepetto-gui'. If None, + 'meshcat' is used in notebook environment and 'gepetto-gui' + otherwise. + Optional: None by default. + :param delete_robot_on_close: Whether or not to delete the robot from the + viewer when closing it. + Optional: True by default. + :param close_backend: Close backend automatically before returning. + Optional: Enable by default if not (presumably) + available beforehand. + :param viewers: List of already instantiated viewers, associated one by one + in order to each trajectory data. None to disable. + Optional: None by default. :param verbose: Add information to keep track of the process. Optional: True by default. :param kwargs: Used argument to allow chaining renderining methods. @@ -232,34 +225,41 @@ def play_trajectories(trajectory_data: Union[ close_backend = False # Sanitize user-specified robot offsets - if xyz_offset is None: - xyz_offset = len(trajectory_data) * [None] - elif len(xyz_offset) != len(trajectory_data): - xyz_offset = np.tile(xyz_offset, (len(trajectory_data), 1)) + if xyz_offsets is None: + xyz_offsets = len(trajectory_data) * [None] + elif len(xyz_offsets) != len(trajectory_data): + xyz_offsets = np.tile(xyz_offsets, (len(trajectory_data), 1)) # Sanitize user-specified robot colors - if urdf_rgba is None: + if robots_colors is None: if len(trajectory_data) == 1: - urdf_rgba = [None] + robots_colors = [None] else: - urdf_rgba = list(islice( + robots_colors = list(islice( cycle(DEFAULT_URDF_COLORS.values()), len(trajectory_data))) - elif not isinstance(urdf_rgba, (list, tuple)) or \ - isinstance(urdf_rgba[0], float): - urdf_rgba = [urdf_rgba] - elif isinstance(urdf_rgba, tuple): - urdf_rgba = list(urdf_rgba) - for i, color in enumerate(urdf_rgba): + elif not isinstance(robots_colors, (list, tuple)) or \ + isinstance(robots_colors[0], float): + robots_colors = [robots_colors] + elif isinstance(robots_colors, tuple): + robots_colors = list(robots_colors) + for i, color in enumerate(robots_colors): if isinstance(color, str): - urdf_rgba[i] = DEFAULT_URDF_COLORS[color] - assert len(urdf_rgba) == len(trajectory_data) + if color in DEFAULT_URDF_COLORS.keys(): + robots_colors[i] = DEFAULT_URDF_COLORS[color] + else: + colors_str = ', '.join( + f"'{e}'" for e in DEFAULT_URDF_COLORS.keys()) + raise ValueError( + f"Color '{color}' not available. Use custom (R,G,B,A) " + f"codes, or predefined color names: {colors_str}.") + assert len(robots_colors) == len(trajectory_data) # Sanitize user-specified legend if legend is not None and not isinstance(legend, (list, tuple)): legend = [legend] # Add default legend with robots names if replaying multiple trajectories - if all(color is not None for color in urdf_rgba) and legend is None: + if all(color is not None for color in robots_colors) and legend is None: legend = [viewer.robot_name for viewer in viewers] # Instantiate or refresh viewers if necessary @@ -272,7 +272,7 @@ def play_trajectories(trajectory_data: Union[ viewers = [] lock = Lock() uniq_id = next(tempfile._get_candidate_names()) - for i, (traj, color) in enumerate(zip(trajectory_data, urdf_rgba)): + for i, (traj, color) in enumerate(zip(trajectory_data, robots_colors)): # Create a new viewer instance, and load the robot in it robot = traj['robot'] robot_name = f"{uniq_id}_robot_{i}" @@ -280,11 +280,10 @@ def play_trajectories(trajectory_data: Union[ viewer = Viewer( robot, use_theoretical_model=use_theoretical_model, - urdf_rgba=color, + robot_color=color, robot_name=robot_name, lock=lock, backend=backend, - window_name=window_name, scene_name=scene_name, delete_robot_on_close=delete_robot_on_close, open_gui_if_parent=(record_video_path is None)) @@ -295,20 +294,31 @@ def play_trajectories(trajectory_data: Union[ close_backend = True else: # Reset robot model in viewer if requested color has changed - for viewer, traj, color in zip(viewers, trajectory_data, urdf_rgba): - if color != viewer.urdf_rgba: + for viewer, traj, color in zip( + viewers, trajectory_data, robots_colors): + if color != viewer.robot_color: viewer._setup(traj['robot'], color) assert len(viewers) == len(trajectory_data) - # # Early return if nothing to replay + # Use first viewers as main viewer to call static methods conveniently + viewer = viewers[0] + + # Make sure clock is only enabled for panda3d backend + if enable_clock: + if Viewer.backend != 'panda3d': + logger.warn( + "`enable_clock` is only available with 'panda3d' backend.") + enable_clock = False + + # Early return if nothing to replay if all(not len(traj['evolution_robot']) for traj in trajectory_data): return viewers # Set camera pose or activate camera travelling if requested if travelling_frame is not None: - viewers[0].attach_camera(travelling_frame, camera_xyzrpy) + viewer.attach_camera(travelling_frame, camera_xyzrpy) elif camera_xyzrpy is not None: - viewers[0].set_camera_transform(*camera_xyzrpy) + viewer.set_camera_transform(*camera_xyzrpy) # Enable camera motion if requested if camera_motion is not None: @@ -322,25 +332,25 @@ def play_trajectories(trajectory_data: Union[ if watermark_fullpath is not None: Viewer.set_watermark(watermark_fullpath) - # Load robots in gepetto viewer - for viewer, traj, offset in zip(viewers, trajectory_data, xyz_offset): + # Initialize robot configuration is viewer before any further processing + for viewer_i, traj, offset in zip(viewers, trajectory_data, xyz_offsets): evolution_robot = traj['evolution_robot'] if len(evolution_robot): i = bisect_right([s.t for s in evolution_robot], time_interval[0]) - viewer.display(evolution_robot[i].q, offset) + viewer_i.display(evolution_robot[i].q, offset) - # Wait for the meshes to finish loading if non video recording mode - if wait_for_client and record_video_path is None: - if Viewer.backend.startswith('meshcat'): + # Wait for the meshes to finish loading if video recording is disable + if record_video_path is None: + if Viewer.backend == 'meshcat': if verbose and not interactive_mode(): print("Waiting for meshcat client in browser to connect: " f"{Viewer._backend_obj.gui.url()}") Viewer.wait(require_client=True) if verbose and not interactive_mode(): - print("Browser connected! Starting to replay the simulation.") + print("Browser connected! Replaying simulation...") # Handle start-in-pause mode - if start_paused and not interactive_mode(): + if start_paused and record_video_path is None and not interactive_mode(): input("Press Enter to continue...") # Replay the trajectory @@ -369,59 +379,71 @@ def play_trajectories(trajectory_data: Union[ else: position_evolutions.append(None) - # Play trajectories without multithreading and record_video - is_initialized = False + # Disable framerate limit of Panda3d for efficiency + if Viewer.backend.startswith('panda3d'): + framerate = viewer._backend_obj._app.get_framerate() + viewer._backend_obj._app.set_framerate(None) + + # Initialize video recording + if Viewer.backend == 'meshcat': + # Sanitize the recording path to enforce '.webm' extension + record_video_path = str( + pathlib.Path(record_video_path).with_suffix('.webm')) + + # Start backend recording thread + viewer._backend_obj.start_recording( + VIDEO_FRAMERATE, *VIDEO_SIZE) + else: + # Sanitize the recording path to enforce '.mp4' extension + record_video_path = str( + pathlib.Path(record_video_path).with_suffix('.mp4')) + + # Create ffmpeg video writer + out = av.open(record_video_path, mode='w') + out.metadata['title'] = scene_name + stream = out.add_stream('libx264', rate=VIDEO_FRAMERATE) + stream.width, stream.height = VIDEO_SIZE + stream.pix_fmt = 'yuv420p' + stream.bit_rate = VIDEO_QUALITY * (8 * 1024 ** 2) + + # Add frames to video sequentially for i, t_cur in enumerate(tqdm( time_global, desc="Rendering frames", disable=(not verbose))): + # Update the configurations of the robots for viewer, positions, offset in zip( - viewers, position_evolutions, xyz_offset): + viewers, position_evolutions, xyz_offsets): if positions is not None: - viewer.display( - positions[i], xyz_offset=offset) + viewer.display(positions[i], xyz_offset=offset) + + # Update clock if enabled + if enable_clock: + Viewer.set_clock(t_cur) + + # Add frame to video if Viewer.backend == 'meshcat': - if not is_initialized: - viewers[0]._backend_obj.start_recording( - VIDEO_FRAMERATE, *VIDEO_SIZE) - viewers[0]._backend_obj.add_frame() + viewer._backend_obj.add_frame() else: - frame = viewers[0].capture_frame(VIDEO_SIZE[1], VIDEO_SIZE[0]) - if not is_initialized: - # Determine the right video container and codec to use - if pathlib.Path(record_video_path).suffix == ".webm": - codec = cv2.VideoWriter_fourcc(*'VP80') - else: # fallback to mp4 container in any other case - codec = cv2.VideoWriter_fourcc(*'mp4v') - record_video_path = str(pathlib.Path( - record_video_path).with_suffix('.mp4')) - - # Redirect opencv warnings - original_stderr_fd = sys.stderr.fileno() - saved_stderr_fd = os.dup(original_stderr_fd) - with open(os.devnull, 'w') as tfile: - try: - sys.stderr.close() - os.dup2(tfile.fileno(), original_stderr_fd) - out = cv2.VideoWriter( - record_video_path, codec, fps=VIDEO_FRAMERATE, - frameSize=frame.shape[1::-1]) - os.dup2(saved_stderr_fd, original_stderr_fd) - sys.stderr = io.TextIOWrapper( - os.fdopen(original_stderr_fd, 'wb')) - finally: - os.close(saved_stderr_fd) - - if enable_clock and Viewer.backend == 'panda3d': - Viewer.set_clock(t_cur) + # Capture frame + frame = viewer.capture_frame(*VIDEO_SIZE) # Write frame - out.write(cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)) - is_initialized = True + frame = av.VideoFrame.from_ndarray(frame, format='rgb24') + for packet in stream.encode(frame): + out.mux(packet) + + # Finalize video recording if Viewer.backend == 'meshcat': - record_video_path = str( - pathlib.Path(record_video_path).with_suffix('.webm')) - viewers[0]._backend_obj.stop_recording(record_video_path) + # Stop backend recording thread + viewer._backend_obj.stop_recording(record_video_path) else: - out.release() + # Flush and close recording file + for packet in stream.encode(None): + out.mux(packet) + out.close() + + # Restore framerate limit of Panda3d + if Viewer.backend.startswith('panda3d'): + viewer._backend_obj._app.set_framerate(framerate) else: def replay_thread(viewer, *args): loop = asyncio.new_event_loop() @@ -430,7 +452,7 @@ def replay_thread(viewer, *args): # Play trajectories with multithreading threads = [] - for viewer, traj, offset in zip(viewers, trajectory_data, xyz_offset): + for viewer, traj, offset in zip(viewers, trajectory_data, xyz_offsets): threads.append(Thread( target=replay_thread, args=(viewer, @@ -438,8 +460,7 @@ def replay_thread(viewer, *args): time_interval, speed_ratio, offset, - enable_clock, - wait_for_client))) + enable_clock))) for thread in threads: thread.daemon = True thread.start() @@ -461,7 +482,7 @@ def replay_thread(viewer, *args): if watermark_fullpath is not None: Viewer.set_watermark() - if enable_clock and Viewer.backend == 'panda3d': + if enable_clock: Viewer.set_clock() # Close backend if needed diff --git a/python/jiminy_py/src/jiminy_py/viewer/viewer.py b/python/jiminy_py/src/jiminy_py/viewer/viewer.py index beba30310..e41e22e8a 100644 --- a/python/jiminy_py/src/jiminy_py/viewer/viewer.py +++ b/python/jiminy_py/src/jiminy_py/viewer/viewer.py @@ -27,9 +27,6 @@ import zmq import meshcat.transformations as mtf -from panda3d_viewer import ( - Viewer as Panda3dViewer, ViewerConfig as Panda3dViewerConfig) -from panda3d_viewer.viewer_app import ViewerApp as Panda3dApp from panda3d_viewer.viewer_errors import ViewerClosedError import pinocchio as pin @@ -42,7 +39,9 @@ from .meshcat.utilities import interactive_mode from .meshcat.wrapper import MeshcatWrapper from .meshcat.meshcat_visualizer import MeshcatVisualizer -from .panda3d.panda3d_visualizer import Panda3dVisualizer +from .panda3d.panda3d_visualizer import (Panda3dViewer, + Panda3dApp, + Panda3dVisualizer) CAMERA_INV_TRANSFORM_PANDA3D = rpyToMatrix(np.array([-np.pi / 2, 0.0, 0.0])) @@ -50,18 +49,22 @@ DEFAULT_CAMERA_XYZRPY_ABS = [[7.5, 0.0, 1.4], [1.4, 0.0, np.pi / 2]] DEFAULT_CAMERA_XYZRPY_REL = [[4.5, -4.5, 1.5], [1.3, 0.0, 0.8]] -DEFAULT_CAPTURE_SIZE = 500 DEFAULT_WATERMARK_MAXSIZE = (150, 150) # Determine set the of available backends -backends_available = { - 'meshcat': MeshcatVisualizer, 'panda3d': Panda3dVisualizer} +backends_available = {'meshcat': MeshcatVisualizer, + 'panda3d': Panda3dVisualizer} if __import__('platform').system() == 'Linux': import importlib if (importlib.util.find_spec("gepetto") is not None and importlib.util.find_spec("omniORB") is not None): backends_available['gepetto-gui'] = GepettoVisualizer +try: + from .panda3d.panda3d_widget import Panda3dQWidget + backends_available['panda3d-qt'] = Panda3dVisualizer +except ImportError: + pass def default_backend() -> str: @@ -98,7 +101,7 @@ def _get_backend_exceptions( return (omniORB.CORBA.COMM_FAILURE, omniORB.CORBA.TRANSIENT, gepetto.corbaserver.gepetto.Error) - elif backend == 'panda3d': + elif backend.startswith('panda3d'): return (ViewerClosedError,) else: return (zmq.error.Again, zmq.error.ZMQError) @@ -227,12 +230,13 @@ class Viewer: used to force disabling interactive display. """ backend = default_backend() + window_name = 'jiminy' + _has_gui = False _backend_obj = None _backend_exceptions = _get_backend_exceptions() _backend_proc = None _backend_robot_names = set() _backend_robot_colors = {} - _has_gui = False _camera_motion = None _camera_travelling = None _camera_xyzrpy = deepcopy(DEFAULT_CAMERA_XYZRPY_ABS) @@ -241,42 +245,53 @@ class Viewer: def __init__(self, robot: jiminy.Robot, use_theoretical_model: bool = False, - urdf_rgba: Optional[Tuple4FType] = None, + robot_color: Optional[Tuple4FType] = None, lock: Optional[Lock] = None, backend: Optional[str] = None, open_gui_if_parent: Optional[bool] = None, delete_robot_on_close: bool = False, robot_name: Optional[str] = None, - window_name: str = 'jiminy', scene_name: str = 'world', **kwargs): """ :param robot: Jiminy.Robot to display. :param use_theoretical_model: Whether to use the theoretical (rigid) model or the actual (flexible) model of - this robot. - :param urdf_rgba: RGBA color to use to display this robot, as a list - of 4 floating-point values between 0.0 and 1.0. - Optional: It will override the original color of the - meshes if specified. + this robot. Note that using the actual + model is more efficient since update of + the frames placements can be skipped. + Optional: Actual model by default. + :param robot_color: RGBA color to use to display this robot, as a list + of 4 floating-point values between 0.0 and 1.0. It + will override the original color of the meshes if + specified. `None` to disable. + Optional: Disable by default. :param lock: Custom threading.Lock. Required for parallel rendering. It is required since some backends does not support multiple simultaneous connections (e.g. corbasever). - Optional: Unique lock of the current thread by default. - :param backend: The name of the desired backend to use for rendering. - It can be either 'gepetto-gui' or 'meshcat'. - Optional: 'gepetto-gui' by default if available and not - running from a notebook, 'meshcat' otherwise. + `None` to use the unique lock of the current thread. + Optional: `None` by default. + :param backend: Name of the rendering backend to use. It can be either + 'panda3d', 'panda3d-qt', 'meshcat' or 'gepetto-gui'. + None to keep using to one already running if any, or + the default one otherwise. Note that the default is + hardware and environment dependent. + See `viewer.default_backend` method for details. + Optional: `None` by default. :param open_gui_if_parent: Open GUI if new viewer's backend server is - started. + started. `None` to fallback to default. + Optional: Do not open gui for 'meshcat' + backend in interactive mode with already one + display cell already opened, open gui in + any other case by default. :param delete_robot_on_close: Enable automatic deletion of the robot when closing. + Optional: False by default. :param robot_name: Unique robot name, to identify each robot. Optional: Randomly generated identifier by default. - :param window_name: Window name, used only when gepetto-gui is used - as backend. Note that it is not allowed to be equal - to the window name. - :param scene_name: Scene name, used only with gepetto-gui backend. + :param scene_name: Scene name, used only with 'gepetto-gui' backend. It + must differ from the scene name. + Optional: 'world' by default. :param kwargs: Unused extra keyword arguments to enable forwarding. """ # Handling of default arguments @@ -285,10 +300,9 @@ def __init__(self, robot_name = "_".join(("robot", uniq_id)) # Backup some user arguments - self.urdf_rgba = urdf_rgba + self.robot_color = robot_color self.robot_name = robot_name self.scene_name = scene_name - self.window_name = window_name self.use_theoretical_model = use_theoretical_model self._lock = lock if lock is not None else Viewer._lock self.delete_robot_on_close = delete_robot_on_close @@ -339,7 +353,7 @@ def __init__(self, Viewer.detach_camera() # Make sure that the windows, scene and robot names are valid - if scene_name == window_name: + if scene_name == Viewer.window_name: raise ValueError( "The name of the scene and window must be different.") @@ -350,7 +364,7 @@ def __init__(self, # Create a unique temporary directory, specific to this viewer instance self._tempdir = tempfile.mkdtemp( - prefix="_".join((window_name, scene_name, robot_name, ""))) + prefix="_".join((Viewer.window_name, scene_name, robot_name, ""))) # Access the current backend or create one if none is available self.__is_open = False @@ -359,18 +373,22 @@ def __init__(self, # Connect viewer backend if not Viewer.is_alive(): # Handling of default argument(s) - open_gui = open_gui_if_parent - if open_gui is None: - # Opening a new display cell automatically if there is no - # other display cell already opened. The user is probably - # expecting a display cell to open in such cases, but there - # is no fixed rule. - open_gui = interactive_mode() and \ - not Viewer._backend_obj.comm_manager.n_comm + if open_gui_if_parent is None: + if Viewer.backend == 'meshcat': + # Opening a new display cell automatically if there is + # no other display cell already opened. The user is + # probably expecting a display cell to open in such + # cases, but there is no fixed rule. + open_gui_if_parent = interactive_mode() and \ + not Viewer._backend_obj.comm_manager.n_comm + elif Viewer.backend.startswith('panda3d'): + open_gui_if_parent = not interactive_mode() + else: + open_gui_if_parent = True # Start viewer backend Viewer.__connect_backend( - start_if_needed=True, open_gui=open_gui) + start_if_needed=True, open_gui=open_gui_if_parent) # Update some flags self.is_backend_parent = True @@ -383,10 +401,10 @@ def __init__(self, self._backend_proc = Viewer._backend_proc # Load the robot - self._setup(robot, self.urdf_rgba) + self._setup(robot, self.robot_color) Viewer._backend_robot_names.add(self.robot_name) Viewer._backend_robot_colors.update({ - self.robot_name: self.urdf_rgba}) + self.robot_name: self.robot_color}) except Exception as e: raise RuntimeError( "Impossible to create backend or connect to it.") from e @@ -424,7 +442,7 @@ def fct_safe(*args, **kwargs): @__must_be_open def _setup(self, robot: jiminy.Robot, - urdf_rgba: Optional[Tuple4FType] = None) -> None: + robot_color: Optional[Tuple4FType] = None) -> None: """Load (or reload) robot in viewer. .. note:: @@ -436,23 +454,24 @@ def _setup(self, `simulator.Simulator` instead of `jiminy_py.core.Engine` directly. :param robot: Jiminy.Robot to display. - :param urdf_rgba: RGBA color to use to display this robot, as a list - of 4 floating-point values between 0.0 and 1.0. - Optional: It will override the original color of the - meshes if specified. + :param robot_color: RGBA color to use to display this robot, as a list + of 4 floating-point values between 0.0 and 1.0. + It will override the original color of the meshes + if specified. None to disable. + Optional: Disable by default. """ # Backup desired color - self.urdf_rgba = urdf_rgba + self.robot_color = robot_color # Generate colorized URDF file if using gepetto-gui backend, since it # is not supported by default, because of memory optimizations. self.urdf_path = os.path.realpath(robot.urdf_path) if Viewer.backend == 'gepetto-gui': - if self.urdf_rgba is not None: - assert len(self.urdf_rgba) == 4 - alpha = self.urdf_rgba[3] + if self.robot_color is not None: + assert len(self.robot_color) == 4 + alpha = self.robot_color[3] self.urdf_path = Viewer._get_colorized_urdf( - robot, self.urdf_rgba[:3], self._tempdir) + robot, self.robot_color[:3], self._tempdir) else: alpha = 1.0 @@ -482,9 +501,10 @@ def _setup(self, # Create the scene and load robot if Viewer.backend == 'gepetto-gui': # Initialize the viewer - self._client.initViewer( - viewer=Viewer._backend_obj, windowName=self.window_name, - sceneName=self.scene_name, loadModel=False) + self._client.initViewer(viewer=Viewer._backend_obj, + windowName=Viewer.window_name, + sceneName=self.scene_name, + loadModel=False) # Add missing scene elements self._gui.addFloor('/'.join((self.scene_name, "floor"))) @@ -508,7 +528,7 @@ def _setup(self, # Load the robot robot_node_path = '/'.join((self.scene_name, self.robot_name)) self._client.loadViewerModel( - rootNodeName=robot_node_path, color=urdf_rgba) + rootNodeName=robot_node_path, color=robot_color) @staticmethod def open_gui(start_if_needed: bool = False) -> bool: @@ -529,7 +549,7 @@ def open_gui(start_if_needed: bool = False) -> bool: if Viewer._has_gui: return - if Viewer.backend == 'gepetto-gui': + if Viewer.backend in ['gepetto-gui', 'panda3d-qt']: # No instance is considered manager of the unique window pass elif Viewer.backend == 'panda3d': @@ -602,14 +622,17 @@ def open_gui(start_if_needed: bool = False) -> bool: def wait(require_client: bool = False) -> None: """Wait for all the meshes to finish loading in every clients. + .. note:: + It is a non-op for every backend except `meshcat` since synchronous + mode is enabled for the other ones. + :param require_client: Wait for at least one client to be available before checking for mesh loading. """ if Viewer.backend == 'meshcat': - # Only Meshcat is asynchronous. Note that Gepetto-gui can be - # updated asynchronously, but it is more difficult to manage for - # no real advantage. Viewer._backend_obj.wait(require_client) + elif Viewer.backend.startswith('panda3d'): + Viewer._backend_obj.gui._app.step() @staticmethod def is_alive() -> bool: @@ -864,7 +887,7 @@ def _gepetto_client_connect(get_proc_info=False): raise RuntimeError( "No backend server to connect to but " "'start_if_needed' is set to False") - elif Viewer.backend == 'panda3d': + elif Viewer.backend.startswith('panda3d'): # handle default argument(s) if open_gui is None: open_gui = True @@ -875,29 +898,13 @@ def _gepetto_client_connect(get_proc_info=False): "'panda3d' backend does not support connecting to already " "running client.") - # Instantiate new client with onscreen rendering enabled. + # Instantiate client with onscreen rendering capability enabled. # Note that it fallbacks to software rendering if necessary. - config = Panda3dViewerConfig() - config.set_window_size(DEFAULT_CAPTURE_SIZE, DEFAULT_CAPTURE_SIZE) - config.set_window_fixed(False) - config.enable_antialiasing(True, multisamples=4) - config.enable_shadow(True) - config.enable_lights(True) - config.enable_hdr(False) - config.enable_fog(False) - config.show_axes(True) - config.show_grid(False) - config.show_floor(True) - config.set_value('framebuffer-software', '0') - config.set_value('framebuffer-hardware', '0') - config.set_value('load-display', 'pandagl') - config.set_value('aux-display', - 'p3headlessgl' - '\naux-display pandadx9' - '\naux-display pandadx8' - '\naux-display p3tinydisplay') - client = Panda3dViewer( - window_type='onscreen', window_title='jiminy', config=config) + if Viewer.backend == 'panda3d-qt': + client = Panda3dQWidget() + else: + client = Panda3dViewer(window_type='onscreen', + window_title=Viewer.window_name) client.gui = client # The gui is the client itself for now proc = _ProcessWrapper(client._app, close_at_exit) @@ -992,7 +999,7 @@ def _delete_nodes_viewer(nodes_path: Sequence[str]) -> None: if Viewer.backend == 'gepetto-gui': for node_path in nodes_path: Viewer._backend_obj.gui.deleteNode(node_path, True) - elif Viewer.backend == 'panda3d': + elif Viewer.backend.startswith('panda3d'): for node_path in nodes_path: try: Viewer._backend_obj.gui.remove_group(node_path) @@ -1031,7 +1038,7 @@ def set_watermark(img_fullpath: Optional[str] = None, if Viewer.backend == 'gepetto-gui': logger.warning( "Adding watermark is not available for Gepetto-gui.") - elif Viewer.backend == 'panda3d': + elif Viewer.backend.startswith('panda3d'): Viewer._backend_obj._app.set_watermark(img_fullpath, width, height) else: width = width or DEFAULT_WATERMARK_MAXSIZE[0] @@ -1061,7 +1068,7 @@ def set_legend(labels: Optional[Sequence[str]] = None) -> None: if Viewer.backend == 'gepetto-gui': logger.warning("Adding legend is not available for Gepetto-gui.") - elif Viewer.backend == 'panda3d': + elif Viewer.backend.startswith('panda3d'): if labels is None: items = None else: @@ -1091,7 +1098,7 @@ def set_clock(time: Optional[float] = None) -> None: :param time: Current time is seconds. None to disable. Optional: None by default. """ - if Viewer.backend == 'panda3d': + if Viewer.backend.startswith('panda3d'): Viewer._backend_obj._app.set_clock(time) else: logger.warning("Adding clock is only available for Panda3d.") @@ -1154,7 +1161,7 @@ def set_camera_transform(self, H_abs = SE3(rotation_mat, position) self._gui.setCameraTransform( self._client.windowID, SE3ToXYZQUAT(H_abs).tolist()) - elif Viewer.backend == 'panda3d': + elif Viewer.backend.startswith('panda3d'): rotation_panda3d = pin.Quaternion( rotation_mat @ CAMERA_INV_TRANSFORM_PANDA3D).coeffs() self._gui._app.set_camera_transform(position, rotation_panda3d) @@ -1249,6 +1256,27 @@ def detach_camera() -> None: """ Viewer._camera_travelling = None + @__must_be_open + def set_color(self, robot_color: Tuple4FType) -> None: + """Override the color of the robot on-the-fly. + + .. note:: + Only Panda3d is not supported by this method for now. + + :param robot_color: RGBA color to use to display this robot, as a list + of 4 floating-point values between 0.0 and 1.0. It + will override the original color of the meshes if + specified. `None` to disable. + Optional: Disable by default. + """ + if Viewer.backend.startswith('panda3d'): + for visual in self._client.visual_model.geometryObjects: + node_name = self._client.getViewerNodeName( + visual, pin.GeometryType.VISUAL) + self._client.viewer.set_material(*node_name, robot_color) + else: + logger.warning("This method is aonly supported by Panda3d.") + @__must_be_open def capture_frame(self, width: int = None, @@ -1256,12 +1284,18 @@ def capture_frame(self, raw_data: bool = False) -> Union[np.ndarray, str]: """Take a snapshot and return associated data. + .. warning:: + By default, panda3d framerate of onscreen window is limited to + reduce computational burden, thereby limiting the speed of this + method. One is responsible to disable it manually by calling + `Viewer._backend_obj._app.set_frame(None)`. + :param width: Width for the image in pixels (not available with Gepetto-gui for now). None to keep unchanged. - Optional: DEFAULT_CAPTURE_SIZE by default. + Optional: Kept unchanged by default. :param height: Height for the image in pixels (not available with Gepetto-gui for now). None to keep unchanged. - Optional: DEFAULT_CAPTURE_SIZE by default. + Optional: Kept unchanged by default. :param raw_data: Whether to return a 2D numpy array, or the raw output from the backend (the actual type may vary). """ @@ -1272,8 +1306,8 @@ def capture_frame(self, "Specifying window size is not available for Gepetto-gui.") if raw_data: - raise ValueError( - "Raw data mode is only available for Meshcat.") + raise NotImplementedError( + "Raw data mode is not available for Gepetto-gui.") if Viewer.backend == 'gepetto-gui': # It is not possible to capture frame directly using gepetto-gui, @@ -1283,7 +1317,8 @@ def capture_frame(self, self.save_frame(f.name) img_obj = Image.open(f.name) rgba_array = np.array(img_obj) - elif Viewer.backend == 'panda3d': + elif Viewer.backend.startswith('panda3d'): + # Resize window if size has changed _width, _height = self._gui._app.getSize() if width is None: width = _width @@ -1291,12 +1326,18 @@ def capture_frame(self, height = _height if _width != width or _height != height: self._gui._app.set_window_size(width, height) + # Call low-level `get_screenshot` directly to get raw buffer - self._gui._app.step() # Render the current scene buffer = self._gui._app.get_screenshot( requested_format='RGB', raw=True) - array = np.frombuffer(buffer, np.uint8).reshape((height, width, 3)) - return np.flipud(array) + + # Return raw data if requested + if raw_data: + return buffer + + # Return numpy array RGB + rgb_array = np.frombuffer(buffer, np.uint8) + return np.flipud(rgb_array.reshape((height, width, 3))) else: # Send capture frame request to the background recorder process img_html = Viewer._backend_obj.capture_frame(width, height) @@ -1304,12 +1345,15 @@ def capture_frame(self, # Parse the output to remove the html header, and convert it into # the desired output format. img_data = str.encode(img_html.split(",", 1)[-1]) - img_raw = base64.decodebytes(img_data) + buffer = base64.decodebytes(img_data) + + # Return raw data if requested if raw_data: - return img_raw - else: - img_obj = Image.open(io.BytesIO(img_raw)) - rgba_array = np.array(img_obj) + return buffer + + # Return numpy array RGB + img_obj = Image.open(io.BytesIO(buffer)) + rgba_array = np.array(img_obj) return rgba_array[:, :, :-1] @__must_be_open @@ -1322,15 +1366,15 @@ def save_frame(self, :param image_path: Fullpath of the image (.png extension is mandatory) :param width: Width for the image in pixels (not available with Gepetto-gui for now). None to keep unchanged. - Optional: DEFAULT_CAPTURE_SIZE by default. + Optional: Kept unchanged by default. :param height: Height for the image in pixels (not available with Gepetto-gui for now). None to keep unchanged. - Optional: DEFAULT_CAPTURE_SIZE by default. + Optional: Kept unchanged by default. """ image_path = str(pathlib.Path(image_path).with_suffix('.png')) if Viewer.backend == 'gepetto-gui': self._gui.captureFrame(self._client.windowID, image_path) - elif Viewer.backend == 'panda3d': + elif Viewer.backend.startswith('panda3d'): _width, _height = self._gui._app.getSize() if width is None: width = _width @@ -1384,7 +1428,7 @@ def refresh(self, :param wait: Whether or not to wait for rendering to finish. """ with self._lock: - # Render both visual and collision geometries + # Extract pinocchio model and data pairs to update model_list, data_list, model_type_list = [], [], [] if self._client.display_collisions or force_update_collision: model_list.append(self._client.collision_model) @@ -1395,10 +1439,12 @@ def refresh(self, data_list.append(self._client.visual_data) model_type_list.append(pin.GeometryType.VISUAL) + # Update geometries placements for model, data, in zip(model_list, data_list): pin.updateGeometryPlacements( self._client.model, self._client.data, model, data) + # Render new geometries placements if Viewer.backend == 'gepetto-gui': for model, data, model_type in zip( model_list, data_list, model_type_list): @@ -1407,7 +1453,7 @@ def refresh(self, for geom in model.geometryObjects], [tuple(SE3ToXYZQUAT(data.oMg[i])) for i, geom in enumerate(model.geometryObjects)]) - elif Viewer.backend == 'panda3d': + elif Viewer.backend.startswith('panda3d'): for model, data, model_type in zip( model_list, data_list, model_type_list): name_pose_dict = {} @@ -1442,6 +1488,8 @@ def refresh(self, # Refreshing viewer backend manually is necessary for gepetto-gui if Viewer.backend == 'gepetto-gui': self._gui.refresh() + elif Viewer.backend.startswith('panda3d'): + self._gui._app.step() # Wait for the backend viewer to finish rendering if requested if wait: @@ -1472,12 +1520,9 @@ def display(self, q = q.copy() # Make a copy to avoid altering the original data q[:3] += xyz_offset - # Update pinocchio and collision data + # Update pinocchio data pin.forwardKinematics(self._client.model, self._client.data, q) pin.framesForwardKinematics(self._client.model, self._client.data, q) - pin.updateGeometryPlacements( - self._client.model, self._client.data, - self._client.collision_model, self._client.collision_data) # Refresh the viewer self.refresh(wait) diff --git a/soup/CMakeLists.txt b/soup/CMakeLists.txt index a33165867..3658226dd 100644 --- a/soup/CMakeLists.txt +++ b/soup/CMakeLists.txt @@ -11,11 +11,11 @@ endif(NOT WIN32) # Set fake credential set(GIT_CREDENTIAL_EXTERNAL user.name=cmake;user.email=external.project@cmake.com) -# Propagate the global build type +# Propagate the global build type. +# Note that it is already done automatically for multi-config generators, +# so it is only necessary for the single-config ones. get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if (isMultiConfig) - set(EXTERNALPROJECT_BUILD_TYPE_CMD --config $) -else() +if (NOT isMultiConfig) set(EXTERNALPROJECT_BUILD_TYPE_CMD -DCMAKE_BUILD_TYPE=$) endif() diff --git a/soup/gtest/CMakeLists.txt b/soup/gtest/CMakeLists.txt index 4a5948e87..ac77f1b79 100644 --- a/soup/gtest/CMakeLists.txt +++ b/soup/gtest/CMakeLists.txt @@ -4,6 +4,31 @@ cmake_minimum_required(VERSION 3.10) # Project and library name project(gtest_external) +# Get the paths of the generated libraries +if(NOT WIN32) + set(gtest_PATH_Release "/lib/libgtest.a") + set(gtest_PATH_Debug "/lib/libgtestd.a") + set(gtest_NINJA BUILD_BYPRODUCTS "${gtest_PATH_${CMAKE_BUILD_TYPE}}") + set(gtest_main_PATH_Release "/lib/libgtest_main.a") + set(gtest_main_PATH_Debug "/lib/libgtest_maind.a") + set(gtest_main_NINJA BUILD_BYPRODUCTS "${gtest_main_PATH_${CMAKE_BUILD_TYPE}}") + set(gmock_PATH_Release "/lib/libgmock.a") + set(gmock_PATH_Debug "/lib/libgmockd.a") + set(gmock_NINJA BUILD_BYPRODUCTS "${gmock_PATH_${CMAKE_BUILD_TYPE}}") + set(gmock_main_PATH_Release "/lib/libgmock_main.a") + set(gmock_main_PATH_Debug "/lib/libgmock_maind.a") + set(gmock_main_NINJA BUILD_BYPRODUCTS "${gmock_main_PATH_${CMAKE_BUILD_TYPE}}") +else() + set(gtest_PATH_Release "/lib/Release/gtest.lib") + set(gtest_PATH_Debug "/lib/Debug/gtest.lib") + set(gtest_main_PATH_Release "/lib/Release/gtest_main.lib") + set(gtest_main_PATH_Debug "/lib/Debug/gtest_main.lib") + set(gmock_PATH_Release "/lib/Release/gmock.lib") + set(gmock_PATH_Debug "/lib/Debug/gmock.lib") + set(gmock_main_PATH_Release "/lib/Release/gmock_main.lib") + set(gmock_main_PATH_Debug "/lib/Debug/gmock_main.lib") +endif() + # Download and build gtest. externalproject_add(${PROJECT_NAME} GIT_REPOSITORY https://github.com/google/googletest.git @@ -15,8 +40,8 @@ externalproject_add(${PROJECT_NAME} -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_COMPILE_FLAGS_EXTERNAL} -Dgtest_force_shared_crt=ON # This option is required on Github Windows CI for some reasons.. - ${EXTERNALPROJECT_BUILD_TYPE_CMD} -Wno-dev # Silent Cmake warnings about deprecated support of Cmake < 2.8.12 + ${EXTERNALPROJECT_BUILD_TYPE_CMD} ${gtest_NINJA} ${gtest_main_NINJA} @@ -28,32 +53,16 @@ externalproject_add(${PROJECT_NAME} UPDATE_DISCONNECTED ${BUILD_OFFLINE} ) -# Get the path of the generated libraries +# Replace generator expression by actual build directory in the paths of the generated libraries externalproject_get_property(${PROJECT_NAME} BINARY_DIR) -set(${PROJECT_NAME}_LIB_DIR "${BINARY_DIR}/lib") -if(NOT WIN32) - set(gtest_PATH_Release "${${PROJECT_NAME}_LIB_DIR}/libgtest.a") - set(gtest_PATH_Debug "${${PROJECT_NAME}_LIB_DIR}/libgtestd.a") - set(gtest_NINJA BUILD_BYPRODUCTS "${gtest_PATH_${CMAKE_BUILD_TYPE}}") - set(gtest_main_PATH_Release "${${PROJECT_NAME}_LIB_DIR}/libgtest_main.a") - set(gtest_main_PATH_Debug "${${PROJECT_NAME}_LIB_DIR}/libgtest_maind.a") - set(gtest_main_NINJA BUILD_BYPRODUCTS "${gtest_main_PATH_${CMAKE_BUILD_TYPE}}") - set(gmock_PATH_Release "${${PROJECT_NAME}_LIB_DIR}/libgmock.a") - set(gmock_PATH_Debug "${${PROJECT_NAME}_LIB_DIR}/libgmockd.a") - set(gmock_NINJA BUILD_BYPRODUCTS "${gmock_PATH_${CMAKE_BUILD_TYPE}}") - set(gmock_main_PATH_Release "${${PROJECT_NAME}_LIB_DIR}/libgmock_main.a") - set(gmock_main_PATH_Debug "${${PROJECT_NAME}_LIB_DIR}/libgmock_maind.a") - set(gmock_main_NINJA BUILD_BYPRODUCTS "${gmock_main_PATH_${CMAKE_BUILD_TYPE}}") -else() - set(gtest_PATH_Release "${${PROJECT_NAME}_LIB_DIR}/Release/gtest.lib") - set(gtest_PATH_Debug "${${PROJECT_NAME}_LIB_DIR}/Debug/gtest.lib") - set(gtest_main_PATH_Release "${${PROJECT_NAME}_LIB_DIR}/Release/gtest_main.lib") - set(gtest_main_PATH_Debug "${${PROJECT_NAME}_LIB_DIR}/Debug/gtest_main.lib") - set(gmock_PATH_Release "${${PROJECT_NAME}_LIB_DIR}/Release/gmock.lib") - set(gmock_PATH_Debug "${${PROJECT_NAME}_LIB_DIR}/Debug/gmock.lib") - set(gmock_main_PATH_Release "${${PROJECT_NAME}_LIB_DIR}/Release/gmock_main.lib") - set(gmock_main_PATH_Debug "${${PROJECT_NAME}_LIB_DIR}/Debug/gmock_main.lib") -endif() +string(REPLACE "" "${BINARY_DIR}" gtest_PATH_Release "${gtest_PATH_Release}") +string(REPLACE "" "${BINARY_DIR}" gtest_PATH_Debug "${gtest_PATH_Debug}") +string(REPLACE "" "${BINARY_DIR}" gtest_main_PATH_Release "${gtest_main_PATH_Release}") +string(REPLACE "" "${BINARY_DIR}" gtest_main_PATH_Debug "${gtest_main_PATH_Debug}") +string(REPLACE "" "${BINARY_DIR}" gmock_PATH_Release "${gmock_PATH_Release}") +string(REPLACE "" "${BINARY_DIR}" gmock_PATH_Debug "${gmock_PATH_Debug}") +string(REPLACE "" "${BINARY_DIR}" gmock_main_PATH_Release "${gmock_main_PATH_Release}") +string(REPLACE "" "${BINARY_DIR}" gmock_main_PATH_Debug "${gmock_main_PATH_Debug}") # Import the generated libraries as targets add_library(gtest::gtest STATIC IMPORTED GLOBAL) diff --git a/soup/hdf5/CMakeLists.txt b/soup/hdf5/CMakeLists.txt index 7087841ae..ebb45a38b 100644 --- a/soup/hdf5/CMakeLists.txt +++ b/soup/hdf5/CMakeLists.txt @@ -4,6 +4,16 @@ cmake_minimum_required(VERSION 3.10) # Project and library name project(hdf5_external) +# Define path of the generated zlib library +if(NOT WIN32) + set(zlib_BUILD_LIB_PATH "/libz.a") + set(zlib_PATH "${CMAKE_INSTALL_PREFIX}/lib/libz.a") + set(zlib_NINJA "${zlib_PATH}") +else() + set(zlib_BUILD_LIB_PATH "/Release/zlibstatic.lib") + set(zlib_PATH "${CMAKE_INSTALL_PREFIX}/lib/zlibstatic.lib") +endif() + # Download, build and install zlib. # TGZ overwrites some global cmake flags for no reason, and official GIT repo does # not have fPIC enabled by default, making the whole build process fail. So zlib is @@ -21,8 +31,8 @@ externalproject_add(zlib_external -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} -DCMAKE_C_FLAGS:STRING=${CMAKE_COMPILE_FLAGS_EXTERNAL} - ${EXTERNALPROJECT_BUILD_TYPE_CMD} -Wno-dev # Silent Cmake warnings about deprecated support of Cmake < 2.8.12 + ${EXTERNALPROJECT_BUILD_TYPE_CMD} ${zlib_NINJA} @@ -30,13 +40,7 @@ externalproject_add(zlib_external UPDATE_COMMAND "" # For some reason, it is required to avoid rebuild systematically everything... UPDATE_DISCONNECTED ${BUILD_OFFLINE} ) -if(NOT WIN32) - set(zlib_BUILD_LIB_PATH "/libz.a") - set(zlib_PATH "${CMAKE_INSTALL_PREFIX}/lib/libz.a") -else() - set(zlib_BUILD_LIB_PATH "/Release/zlibstatic.lib") - set(zlib_PATH "${CMAKE_INSTALL_PREFIX}/lib/zlibstatic.lib") -endif() + ExternalProject_Add_Step( zlib_external zlib_install_static_only COMMENT "Manually installing only static library" @@ -44,6 +48,17 @@ ExternalProject_Add_Step( COMMAND ${CMAKE_COMMAND} -E copy ${zlib_BUILD_LIB_PATH} ${zlib_PATH} ) +# Define paths of the generated hdf5 libraries +if(NOT WIN32) + set(hdf5_PATH "${CMAKE_INSTALL_PREFIX}/lib/libhdf5.a") + set(hdf5_NINJA "${hdf5_PATH}") + set(hdf5_cpp_PATH "${CMAKE_INSTALL_PREFIX}/lib/libhdf5_cpp.a") + set(hdf5_cpp_NINJA "${hdf5_cpp_PATH}") +else() + set(hdf5_PATH "${CMAKE_INSTALL_PREFIX}/lib/libhdf5.lib") + set(hdf5_cpp_PATH "${CMAKE_INSTALL_PREFIX}/lib/libhdf5_cpp.lib") +endif() + # Download, build and install hdf5. externalproject_add(${PROJECT_NAME} GIT_REPOSITORY https://github.com/HDFGroup/hdf5.git @@ -94,29 +109,17 @@ externalproject_add(${PROJECT_NAME} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_COMPILE_FLAGS_EXTERNAL} -DCMAKE_C_FLAGS:STRING=${CMAKE_COMPILE_FLAGS_EXTERNAL} ${EXTERNALPROJECT_BUILD_TYPE_CMD} - INSTALL_DIR ${CMAKE_INSTALL_PREFIX} ${hdf5_NINJA} ${hdf5_cpp_NINJA} DEPENDS zlib_external + INSTALL_DIR ${CMAKE_INSTALL_PREFIX} UPDATE_COMMAND "" # For some reason, it is required to avoid rebuild systematically everything... UPDATE_DISCONNECTED ${BUILD_OFFLINE} ) -# Get the path of the generated libraries -if(NOT WIN32) - set(zlib_NINJA BUILD_BYPRODUCTS "${zlib_PATH_${CMAKE_BUILD_TYPE}}") - set(hdf5_PATH "${CMAKE_INSTALL_PREFIX}/lib/libhdf5.a") - set(hdf5_NINJA BUILD_BYPRODUCTS "${hdf5_PATH_${CMAKE_BUILD_TYPE}}") - set(hdf5_cpp_PATH "${CMAKE_INSTALL_PREFIX}/lib/libhdf5_cpp.a") - set(hdf5_cpp_NINJA BUILD_BYPRODUCTS "${hdf5_cpp_PATH_${CMAKE_BUILD_TYPE}}") -else() - set(hdf5_PATH "${CMAKE_INSTALL_PREFIX}/lib/libhdf5.lib") - set(hdf5_cpp_PATH "${CMAKE_INSTALL_PREFIX}/lib/libhdf5_cpp.lib") -endif() - # Import the generated libraries as targets add_library(hdf5::zlib STATIC IMPORTED GLOBAL) set_target_properties(hdf5::zlib PROPERTIES diff --git a/soup/jsoncpp/CMakeLists.txt b/soup/jsoncpp/CMakeLists.txt index 96195c775..99c44fa87 100644 --- a/soup/jsoncpp/CMakeLists.txt +++ b/soup/jsoncpp/CMakeLists.txt @@ -4,6 +4,15 @@ cmake_minimum_required(VERSION 3.10) # Project and library name project(jsoncpp_external) +# Get the path of the generated libraries +include(GNUInstallDirs) +if(NOT WIN32) + set(jsoncpp_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/libjsoncpp.a") + set(jsoncpp_NINJA BUILD_BYPRODUCTS "${jsoncpp_PATH}") +else() + set(jsoncpp_PATH "${CMAKE_INSTALL_PREFIX}/lib/jsoncpp.lib") +endif() + # Download, build and install jsoncpp. externalproject_add(${PROJECT_NAME} GIT_REPOSITORY https://github.com/open-source-parsers/jsoncpp.git @@ -21,23 +30,14 @@ externalproject_add(${PROJECT_NAME} -DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX} -DCMAKE_CXX_FLAGS:STRING=${CMAKE_COMPILE_FLAGS_EXTERNAL} ${EXTERNALPROJECT_BUILD_TYPE_CMD} - INSTALL_DIR ${CMAKE_INSTALL_PREFIX} ${jsoncpp_NINJA} + INSTALL_DIR ${CMAKE_INSTALL_PREFIX} UPDATE_COMMAND "" # Avoid reinstalling systematically everything UPDATE_DISCONNECTED ${BUILD_OFFLINE} ) -# Get the path of the generated libraries -include(GNUInstallDirs) -if(NOT WIN32) - set(jsoncpp_PATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/libjsoncpp.a") - set(jsoncpp_NINJA BUILD_BYPRODUCTS "${jsoncpp_PATH_${CMAKE_BUILD_TYPE}}") -else() - set(jsoncpp_PATH "${CMAKE_INSTALL_PREFIX}/lib/jsoncpp.lib") -endif() - # Import the generated library as a target add_library(jsoncpp::jsoncpp STATIC IMPORTED GLOBAL) set_target_properties(jsoncpp::jsoncpp PROPERTIES