From 31d1a399eb8b631eda13badaeefc52445017bd59 Mon Sep 17 00:00:00 2001 From: Dominic Reber <71256590+domire8@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:24:31 +0200 Subject: [PATCH 01/15] release: version 7.4.0 (#186) --- CHANGELOG.md | 11 ++++++++++- VERSION | 2 +- demos/CMakeLists.txt | 2 +- doxygen/doxygen.conf | 2 +- protocol/clproto_cpp/CMakeLists.txt | 2 +- python/setup.py | 2 +- source/CMakeLists.txt | 2 +- 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2cc7ccf0..bae4bafb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Release Versions: +- [7.4.0](#740) - [7.3.0](#730) - [7.2.0](#720) - [7.1.1](#711) @@ -11,7 +12,15 @@ Release Versions: - [6.3.0](#630) - [6.2.0](#620) -## Upcoming changes (in development) +## 7.4.0 + +Version 7.4.0 brings Analog and Digital IO State types as a new feature to the `state_representation` module. + +### Features + +`AnalogIOState` and `DigitalIOState` classes have been added as new state types in `state_representation`. + +### Full changelog - feat: add IO states to state representation (py) (#173) - feat: add IO states to state representation (proto) (#172) diff --git a/VERSION b/VERSION index ee11304de..ba7f754d0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.3.6 +7.4.0 diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index ec331a3a8..881cc7b8c 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -15,7 +15,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -find_package(control_libraries 7.3.6 CONFIG REQUIRED) +find_package(control_libraries 7.4.0 CONFIG REQUIRED) set(DEMOS_SCRIPTS task_space_control_loop diff --git a/doxygen/doxygen.conf b/doxygen/doxygen.conf index 00e04fbe1..f6f166a8b 100644 --- a/doxygen/doxygen.conf +++ b/doxygen/doxygen.conf @@ -38,7 +38,7 @@ PROJECT_NAME = "Control Libraries" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 7.3.6 +PROJECT_NUMBER = 7.4.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/protocol/clproto_cpp/CMakeLists.txt b/protocol/clproto_cpp/CMakeLists.txt index 5903eb1fb..7dc87086f 100644 --- a/protocol/clproto_cpp/CMakeLists.txt +++ b/protocol/clproto_cpp/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(clproto VERSION 7.3.6) +project(clproto VERSION 7.4.0) # Default to C99 if(NOT CMAKE_C_STANDARD) diff --git a/python/setup.py b/python/setup.py index 12034bfe1..76c455e9e 100644 --- a/python/setup.py +++ b/python/setup.py @@ -11,7 +11,7 @@ # names of the environment variables that define osqp and openrobots include directories osqp_path_var = 'OSQP_INCLUDE_DIR' -__version__ = "7.3.6" +__version__ = "7.4.0" __libraries__ = ['state_representation', 'clproto', 'controllers', 'dynamical_systems', 'robot_model'] __include_dirs__ = ['include'] diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index b0c27c968..cd71d8588 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(control_libraries VERSION 7.3.6) +project(control_libraries VERSION 7.4.0) # Build options option(BUILD_TESTING "Build all tests." OFF) From e04f39be48997055b7175039b68e70204a23270c Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 17 Apr 2024 14:28:19 +0200 Subject: [PATCH 02/15] Revert "Revert "feat(build): handle installation and linking of dependencies for pinocchio collision support (#161)"" This reverts commit 999bad7c5015394fa28a84715a566317fa2779c4. --- CHANGELOG.md | 4 ++ Dockerfile | 45 +++++++++++++------ VERSION | 2 +- apt-packages.txt | 1 + demos/CMakeLists.txt | 2 +- .../{osqp.cmake => base_dependencies.cmake} | 13 ++++-- dependencies/dependencies.cmake | 2 +- doxygen/doxygen.conf | 2 +- protocol/clproto_cpp/CMakeLists.txt | 2 +- python/setup.py | 3 +- source/CMakeLists.txt | 2 +- source/robot_model/CMakeLists.txt | 10 ++++- 12 files changed, 62 insertions(+), 26 deletions(-) rename dependencies/{osqp.cmake => base_dependencies.cmake} (58%) diff --git a/CHANGELOG.md b/CHANGELOG.md index bae4bafb2..a2710b9b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ Release Versions: - [6.3.0](#630) - [6.2.0](#620) +## Upcoming changes (in development) + +- feat(build): handle installation and linking of dependencies for pinocchio collision support (#161) + ## 7.4.0 Version 7.4.0 brings Analog and Digital IO State types as a new feature to the `state_representation` module. diff --git a/Dockerfile b/Dockerfile index 566edfc87..2cc0ecb9e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,42 +76,48 @@ xargs -a /tmp/new-packages.txt dpkg-query -L \ # this root can then be copied to / to install everything globally or use LD_LIBRARY_PATH to use it locally HEREDOC +FROM base as dep-base +ARG TARGETPLATFORM +ARG CACHEID +COPY dependencies/base_dependencies.cmake CMakeLists.txt +RUN --mount=type=cache,target=./build,id=cmake-osqp-${TARGETPLATFORM}-${CACHEID},uid=1000 \ + cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build && cmake --install build --prefix /tmp/deps + FROM base as dep-pinocchio COPY --from=apt-dependencies /tmp/apt / +COPY --from=dep-base /tmp/deps /usr ARG TARGETPLATFORM ARG CACHEID -ARG PINOCCHIO_TAG=v2.6.9 -ARG PINOCCHIO_TESTS=OFF +ARG PINOCCHIO_TAG=v2.9.0 +ARG HPP_FCL_TAG=v1.8.1 # FIXME: it would be nicer to have it all in the root CMakelists.txt but: # * `pinocchio` doesn't provide an include directory we can easily plug into `target_include_directories` and thus needs to be installed first # * `pinocchio` uses hacks relying on undocumented CMake quirks which break if you use `FetchContent` # FIXME: it needs `CMAKE_INSTALL_PREFIX` and `--prefix` because it doesn't install to the right place otherwise +RUN --mount=type=cache,target=./hpp-fcl,id=cmake-hpp-fcl-src-${HPP_FCL_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ + --mount=type=cache,target=./build,id=cmake-hpp-fcl-${HPP_FCL_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ + if [ ! -f hpp-fcl/CMakeLists.txt ]; then rm -rf hpp-fcl/* && git clone --depth 1 -b ${HPP_FCL_TAG} --recursive https://github.com/humanoid-path-planner/hpp-fcl; fi \ + && cmake -B build -S hpp-fcl -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON_INTERFACE=OFF -DCMAKE_INSTALL_PREFIX=/tmp/deps \ + && cmake --build build --target all install RUN --mount=type=cache,target=./pinocchio,id=cmake-pinocchio-src-${PINOCCHIO_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ --mount=type=cache,target=./build,id=cmake-pinocchio-${PINOCCHIO_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ if [ ! -f pinocchio/CMakeLists.txt ]; then rm -rf pinocchio/* && git clone --depth 1 -b ${PINOCCHIO_TAG} --recursive https://github.com/stack-of-tasks/pinocchio; fi \ - && cmake -B build -S pinocchio -DBUILD_TESTING=${PINOCCHIO_TESTS} -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON_INTERFACE=OFF -DCMAKE_INSTALL_PREFIX=/tmp/deps \ + && cmake -B build -S pinocchio -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON_INTERFACE=OFF -DBUILD_WITH_COLLISION_SUPPORT=ON -DCMAKE_INSTALL_PREFIX=/tmp/deps \ && cmake --build build --target all install # FIXME: pinocchio produces non-portable paths RUN find /tmp/deps -type f -exec sed -i 's#/tmp/deps#/usr#g' '{}' \; -FROM base as dep-osqp -ARG TARGETPLATFORM -ARG CACHEID -COPY dependencies/osqp.cmake CMakeLists.txt -RUN --mount=type=cache,target=./build,id=cmake-osqp-${TARGETPLATFORM}-${CACHEID},uid=1000 \ - cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build && cmake --install build --prefix /tmp/deps - FROM base as dependencies ARG TARGETPLATFORM ARG CACHEID # Needed to build `osqp-eigen` -COPY --from=dep-osqp /tmp/deps /usr +COPY --from=dep-base /tmp/deps /usr COPY dependencies/dependencies.cmake CMakeLists.txt RUN --mount=type=cache,target=./build,id=cmake-deps-${TARGETPLATFORM}-${CACHEID},uid=1000 \ cmake -B build -DCMAKE_BUILD_TYPE=Release \ && cmake --build build \ && cmake --install build --prefix /tmp/deps -COPY --from=dep-osqp /tmp/deps /tmp/deps +COPY --from=dep-base /tmp/deps /tmp/deps COPY --from=dep-pinocchio /tmp/deps /tmp/deps FROM base as code @@ -158,7 +164,7 @@ ARG CACHEID RUN --mount=type=cache,target=./build,id=cmake-${TARGETPLATFORM}-${CACHEID},uid=1000 \ cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -FROM build as test +FROM build as cpp-test ARG TARGETPLATFORM ARG CACHEID RUN --mount=type=cache,target=./build,id=cmake-${TARGETPLATFORM}-${CACHEID},uid=1000 \ @@ -176,11 +182,22 @@ ARG CACHEID COPY --from=apt-dependencies /tmp/apt / COPY --from=dependencies /tmp/deps /usr COPY --from=install /tmp/cl /usr -COPY --chown=${USER}:${USER} ./python /python +COPY --chown=${USER}:${USER} ./python/include /python/include +COPY --chown=${USER}:${USER} ./python/source /python/source +COPY --chown=${USER}:${USER} ./python/pyproject.toml ./python/setup.py /python/ RUN --mount=type=cache,target=${HOME}/.cache,id=pip-${TARGETPLATFORM}-${CACHEID},uid=1000 \ python3 -m pip install --prefix=/tmp/python /python RUN mv /tmp/python/local /tmp/python-usr +FROM cpp-test as python-test +ARG TARGETPLATFORM +ARG CACHEID +RUN pip install pytest +COPY --from=install /tmp/cl /usr +COPY --from=python /tmp/python-usr /usr +COPY --chown=${USER}:${USER} ./python/test /test +RUN pytest /test + FROM base as python-stubs ARG TARGETPLATFORM ARG CACHEID diff --git a/VERSION b/VERSION index ba7f754d0..815da58b7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.0 +7.4.1 diff --git a/apt-packages.txt b/apt-packages.txt index 284f4ad48..c7033178b 100644 --- a/apt-packages.txt +++ b/apt-packages.txt @@ -1,2 +1,3 @@ libboost-all-dev liburdfdom-dev +libassimp-dev diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 881cc7b8c..de7227734 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -15,7 +15,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -find_package(control_libraries 7.4.0 CONFIG REQUIRED) +find_package(control_libraries 7.4.1 CONFIG REQUIRED) set(DEMOS_SCRIPTS task_space_control_loop diff --git a/dependencies/osqp.cmake b/dependencies/base_dependencies.cmake similarity index 58% rename from dependencies/osqp.cmake rename to dependencies/base_dependencies.cmake index 8d257f202..fb2527380 100644 --- a/dependencies/osqp.cmake +++ b/dependencies/base_dependencies.cmake @@ -2,13 +2,20 @@ # * `OVERRIDE_FIND_PACKAGE` requires CMake 3.24 # * `osqp::osqp` is somehow not working in OSQP-Eigen when built together cmake_minimum_required(VERSION 3.15) -project(control-libraries-osqp) +project(control-libraries-pre-deps) include(FetchContent) FetchContent_Declare( osqp GIT_REPOSITORY https://github.com/oxfordcontrol/osqp - GIT_TAG v0.6.2 + GIT_TAG v0.6.3 ) -FetchContent_MakeAvailable(osqp) +FetchContent_Declare( + Octomap + GIT_REPOSITORY https://github.com/OctoMap/octomap.git + GIT_TAG devel + SOURCE_SUBDIR octomap +) + +FetchContent_MakeAvailable(osqp octomap) diff --git a/dependencies/dependencies.cmake b/dependencies/dependencies.cmake index 2bb965df3..1b00b5b7b 100644 --- a/dependencies/dependencies.cmake +++ b/dependencies/dependencies.cmake @@ -8,7 +8,7 @@ include(FetchContent) FetchContent_Declare( OsqpEigen GIT_REPOSITORY https://github.com/robotology/osqp-eigen.git - GIT_TAG v0.6.4 + GIT_TAG v0.8.1 ) FetchContent_Declare( diff --git a/doxygen/doxygen.conf b/doxygen/doxygen.conf index f6f166a8b..1a9812c80 100644 --- a/doxygen/doxygen.conf +++ b/doxygen/doxygen.conf @@ -38,7 +38,7 @@ PROJECT_NAME = "Control Libraries" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 7.4.0 +PROJECT_NUMBER = 7.4.1 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/protocol/clproto_cpp/CMakeLists.txt b/protocol/clproto_cpp/CMakeLists.txt index 7dc87086f..1bb3d4367 100644 --- a/protocol/clproto_cpp/CMakeLists.txt +++ b/protocol/clproto_cpp/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(clproto VERSION 7.4.0) +project(clproto VERSION 7.4.1) # Default to C99 if(NOT CMAKE_C_STANDARD) diff --git a/python/setup.py b/python/setup.py index 76c455e9e..e6c45185c 100644 --- a/python/setup.py +++ b/python/setup.py @@ -11,7 +11,7 @@ # names of the environment variables that define osqp and openrobots include directories osqp_path_var = 'OSQP_INCLUDE_DIR' -__version__ = "7.4.0" +__version__ = "7.4.1" __libraries__ = ['state_representation', 'clproto', 'controllers', 'dynamical_systems', 'robot_model'] __include_dirs__ = ['include'] @@ -104,6 +104,7 @@ include_dirs=__include_dirs__, libraries=['state_representation', 'robot_model'], define_macros=[('MODULE_VERSION_INFO', __version__)], + extra_compile_args=['-DPINOCCHIO_WITH_HPP_FCL'] ) ) diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index cd71d8588..3922fc3ed 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(control_libraries VERSION 7.4.0) +project(control_libraries VERSION 7.4.1) # Build options option(BUILD_TESTING "Build all tests." OFF) diff --git a/source/robot_model/CMakeLists.txt b/source/robot_model/CMakeLists.txt index 666202683..87ed8a2d8 100644 --- a/source/robot_model/CMakeLists.txt +++ b/source/robot_model/CMakeLists.txt @@ -1,10 +1,14 @@ set(LIBRARY_NAME robot_model) -set(PINOCCHIO_VERSION 2.6.9) -set(OSQP_EIGEN_VERSION 0.6.4) +set(PINOCCHIO_VERSION 2.9.0) +set(HPP_FCL_VERSION 1.8.1) +set(OSQP_EIGEN_VERSION 0.8.1) find_package(pinocchio ${PINOCCHIO_VERSION} REQUIRED) +find_package(hpp-fcl ${HPP_FCL_VERSION} REQUIRED) find_package(OsqpEigen ${OSQP_EIGEN_VERSION} REQUIRED) +add_definitions(-DPINOCCHIO_WITH_HPP_FCL) + set(CORE_SOURCES src/Model.cpp ) @@ -22,8 +26,10 @@ target_include_directories(${LIBRARY_NAME} target_link_libraries(${LIBRARY_NAME} state_representation ${PINOCCHIO_LIBRARIES} + ${hpp-fcl_LIBRARIES} OsqpEigen::OsqpEigen ) +target_compile_definitions(${LIBRARY_NAME} PUBLIC -DPINOCCHIO_WITH_HPP_FCL) # install the target and create export-set install(TARGETS ${LIBRARY_NAME} From 161f18ed093214e7d0d5f538f9e40ab5b14c0e59 Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 17 Apr 2024 14:30:12 +0200 Subject: [PATCH 03/15] Revert "Revert "build: add missing licenses (#170)"" This reverts commit 6f9394470f35b3f8e7f07692df7d59db3748bf6e. --- CHANGELOG.md | 1 + CMakeLists.txt | 2 +- VERSION | 2 +- demos/CMakeLists.txt | 2 +- doxygen/doxygen.conf | 2 +- licenses/COPYING.ASSIMP | 78 +++++++++++++++++++++++++++++ licenses/COPYING.HPPFCL | 34 +++++++++++++ licenses/COPYING.OCTOMAP | 21 ++++++++ protocol/clproto_cpp/CMakeLists.txt | 2 +- python/setup.py | 2 +- source/CMakeLists.txt | 2 +- 11 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 licenses/COPYING.ASSIMP create mode 100644 licenses/COPYING.HPPFCL create mode 100644 licenses/COPYING.OCTOMAP diff --git a/CHANGELOG.md b/CHANGELOG.md index a2710b9b5..21cf5350e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Release Versions: ## Upcoming changes (in development) - feat(build): handle installation and linking of dependencies for pinocchio collision support (#161) +- build: add missing licenses (#170) ## 7.4.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index e9ba45fae..50d9d59cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,5 +12,5 @@ endif() # add licenses install( DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/licenses" - DESTINATION "share/doc/control-libraries/licenses" + DESTINATION "share/doc/control-libraries" ) diff --git a/VERSION b/VERSION index 815da58b7..f8cb1fa11 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.1 +7.4.2 diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index de7227734..b9ca2ca37 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -15,7 +15,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -find_package(control_libraries 7.4.1 CONFIG REQUIRED) +find_package(control_libraries 7.4.2 CONFIG REQUIRED) set(DEMOS_SCRIPTS task_space_control_loop diff --git a/doxygen/doxygen.conf b/doxygen/doxygen.conf index 1a9812c80..6c0a6f3c7 100644 --- a/doxygen/doxygen.conf +++ b/doxygen/doxygen.conf @@ -38,7 +38,7 @@ PROJECT_NAME = "Control Libraries" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 7.4.1 +PROJECT_NUMBER = 7.4.2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/licenses/COPYING.ASSIMP b/licenses/COPYING.ASSIMP new file mode 100644 index 000000000..7d2194deb --- /dev/null +++ b/licenses/COPYING.ASSIMP @@ -0,0 +1,78 @@ +Open Asset Import Library (assimp) + +Copyright (c) 2006-2021, assimp team +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + +****************************************************************************** + +AN EXCEPTION applies to all files in the ./test/models-nonbsd folder. +These are 3d models for testing purposes, from various free sources +on the internet. They are - unless otherwise stated - copyright of +their respective creators, which may impose additional requirements +on the use of their work. For any of these models, see +.source.txt for more legal information. Contact us if you +are a copyright holder and believe that we credited you inproperly or +if you don't want your files to appear in the repository. + + +****************************************************************************** + +Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors +http://code.google.com/p/poly2tri/ + +All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Poly2Tri nor the names of its contributors may be + used to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licenses/COPYING.HPPFCL b/licenses/COPYING.HPPFCL new file mode 100644 index 000000000..298f32cf2 --- /dev/null +++ b/licenses/COPYING.HPPFCL @@ -0,0 +1,34 @@ +Software License Agreement (BSD License) + + Copyright (c) 2008-2014, Willow Garage, Inc. + Copyright (c) 2014-2015, Open Source Robotics Foundation + Copyright (c) 2014-2023, CNRS + Copyright (c) 2018-2024, INRIA + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Open Source Robotics Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licenses/COPYING.OCTOMAP b/licenses/COPYING.OCTOMAP new file mode 100644 index 000000000..4dd8dd86c --- /dev/null +++ b/licenses/COPYING.OCTOMAP @@ -0,0 +1,21 @@ + + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/protocol/clproto_cpp/CMakeLists.txt b/protocol/clproto_cpp/CMakeLists.txt index 1bb3d4367..3ff3e4383 100644 --- a/protocol/clproto_cpp/CMakeLists.txt +++ b/protocol/clproto_cpp/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(clproto VERSION 7.4.1) +project(clproto VERSION 7.4.2) # Default to C99 if(NOT CMAKE_C_STANDARD) diff --git a/python/setup.py b/python/setup.py index e6c45185c..c77ddc8e0 100644 --- a/python/setup.py +++ b/python/setup.py @@ -11,7 +11,7 @@ # names of the environment variables that define osqp and openrobots include directories osqp_path_var = 'OSQP_INCLUDE_DIR' -__version__ = "7.4.1" +__version__ = "7.4.2" __libraries__ = ['state_representation', 'clproto', 'controllers', 'dynamical_systems', 'robot_model'] __include_dirs__ = ['include'] diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 3922fc3ed..762928c77 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(control_libraries VERSION 7.4.1) +project(control_libraries VERSION 7.4.2) # Build options option(BUILD_TESTING "Build all tests." OFF) From ba732be4781ea511ab352f99ee87b5e41e5945e7 Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 17 Apr 2024 14:32:24 +0200 Subject: [PATCH 04/15] Revert "Revert "ci: use caching from docker to run test in CI (#169)"" This reverts commit 90c959afe36cbf75de6b07503de3817119184ffc. --- .../actions/build-test-protocol/Dockerfile | 5 -- .../actions/build-test-protocol/action.yml | 5 -- .../actions/build-test-protocol/entrypoint.sh | 23 ------ .github/actions/build-test-python/Dockerfile | 5 -- .github/actions/build-test-python/action.yml | 5 -- .../actions/build-test-python/entrypoint.sh | 17 ----- .github/actions/build-test/Dockerfile | 5 -- .github/actions/build-test/action.yml | 12 --- .github/actions/build-test/entrypoint.sh | 24 ------ .github/workflows/build-test.yml | 75 +++++-------------- CHANGELOG.md | 1 + Dockerfile | 65 ++++++++-------- VERSION | 2 +- demos/CMakeLists.txt | 2 +- dependencies/base_dependencies.cmake | 4 +- dependencies/dependencies.cmake | 2 +- doxygen/doxygen.conf | 2 +- protocol/clproto_cpp/CMakeLists.txt | 2 +- python/setup.py | 2 +- source/CMakeLists.txt | 2 +- 20 files changed, 60 insertions(+), 200 deletions(-) delete mode 100644 .github/actions/build-test-protocol/Dockerfile delete mode 100644 .github/actions/build-test-protocol/action.yml delete mode 100755 .github/actions/build-test-protocol/entrypoint.sh delete mode 100644 .github/actions/build-test-python/Dockerfile delete mode 100644 .github/actions/build-test-python/action.yml delete mode 100755 .github/actions/build-test-python/entrypoint.sh delete mode 100644 .github/actions/build-test/Dockerfile delete mode 100644 .github/actions/build-test/action.yml delete mode 100755 .github/actions/build-test/entrypoint.sh diff --git a/.github/actions/build-test-protocol/Dockerfile b/.github/actions/build-test-protocol/Dockerfile deleted file mode 100644 index fb4062ea0..000000000 --- a/.github/actions/build-test-protocol/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/aica-technology/control-libraries/development-dependencies:latest - -# Copy and set the entrypoint commands to execute when the container starts -COPY entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/build-test-protocol/action.yml b/.github/actions/build-test-protocol/action.yml deleted file mode 100644 index 134a24e6f..000000000 --- a/.github/actions/build-test-protocol/action.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: 'Build and Test Protocol' -description: 'Build the protobuf message libraries and run all unit tests' -runs: - using: 'docker' - image: 'Dockerfile' \ No newline at end of file diff --git a/.github/actions/build-test-protocol/entrypoint.sh b/.github/actions/build-test-protocol/entrypoint.sh deleted file mode 100755 index 81b86e807..000000000 --- a/.github/actions/build-test-protocol/entrypoint.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh -l - -echo ">>> Installing control libraries..." -bash /github/workspace/source/install.sh --auto --no-controllers --no-dynamical-systems --no-robot-model || exit 1 -ldconfig - -echo ">>> Building proto bindings..." -cd /github/workspace/protocol/protobuf && make all || exit 2 - -echo ">>> Configuring clproto_cpp cmake..." -cd /github/workspace/protocol/clproto_cpp && mkdir build && cd build \ - && cmake -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_TESTING=ON .. \ - || (echo ">>> [ERROR] Configuration stage failed!" && exit 3) || exit $? - -echo ">>> Building clproto_cpp..." -make all || (echo ">>> [ERROR] Build stage failed!" && exit 4) || exit $? -echo ">>> Build stage completed successfully!" - -echo ">>> Running all test stages..." -CTEST_OUTPUT_ON_FAILURE=1 make test || (echo ">>> [ERROR] Test stage failed!" && exit 5) || exit $? -echo ">>> Test stages completed successfully!" - -exit 0 \ No newline at end of file diff --git a/.github/actions/build-test-python/Dockerfile b/.github/actions/build-test-python/Dockerfile deleted file mode 100644 index fb4062ea0..000000000 --- a/.github/actions/build-test-python/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/aica-technology/control-libraries/development-dependencies:latest - -# Copy and set the entrypoint commands to execute when the container starts -COPY entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/build-test-python/action.yml b/.github/actions/build-test-python/action.yml deleted file mode 100644 index e2d0133f2..000000000 --- a/.github/actions/build-test-python/action.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: 'Build and Test Python' -description: 'Build the python bindings and run all unit tests' -runs: - using: 'docker' - image: 'Dockerfile' diff --git a/.github/actions/build-test-python/entrypoint.sh b/.github/actions/build-test-python/entrypoint.sh deleted file mode 100755 index 23022a820..000000000 --- a/.github/actions/build-test-python/entrypoint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -l - -echo ">>> Installing control libraries..." -bash /github/workspace/source/install.sh --auto || exit 1 -bash /github/workspace/protocol/install.sh --auto || exit 1 - -echo ">>> Building Python bindings..." - -export OSQP_INCLUDE_DIR='/usr/local/include/osqp' -pip3 install /github/workspace/python || (echo ">>> [ERROR] Build stage failed!" && exit 2) || exit $? - -echo ">>> Running all test stages..." -pytest /github/workspace/python -v || (echo ">>> [ERROR] Test stage failed!" && exit 3) || exit $? - -echo ">>> Test stages completed successfully!" - -exit 0 \ No newline at end of file diff --git a/.github/actions/build-test/Dockerfile b/.github/actions/build-test/Dockerfile deleted file mode 100644 index fb4062ea0..000000000 --- a/.github/actions/build-test/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM ghcr.io/aica-technology/control-libraries/development-dependencies:latest - -# Copy and set the entrypoint commands to execute when the container starts -COPY entrypoint.sh /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/build-test/action.yml b/.github/actions/build-test/action.yml deleted file mode 100644 index 0ff45aa08..000000000 --- a/.github/actions/build-test/action.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: 'Build and Test' -description: 'Build the project sources and run all unit tests' -inputs: - configuration: - description: 'CMake build configuration' - required: false - default: '' -runs: - using: 'docker' - image: 'Dockerfile' - args: - - ${{ inputs.configuration }} \ No newline at end of file diff --git a/.github/actions/build-test/entrypoint.sh b/.github/actions/build-test/entrypoint.sh deleted file mode 100755 index 1c7f24aa7..000000000 --- a/.github/actions/build-test/entrypoint.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -l - -CONFIGURATION="" -if [ -n "$1" ]; then - echo ">>> Configuration: $1" - CONFIGURATION="-DCMAKE_BUILD_TYPE=$1" -fi - -cd /github/workspace/source && mkdir build && cd build || exit 1 - -echo ">>> Configuring cmake..." -cmake "${CONFIGURATION}" -DBUILD_TESTING=ON \ - -DBUILD_CONTROLLERS=ON -DBUILD_DYNAMICAL_SYSTEMS=ON -DBUILD_ROBOT_MODEL=ON .. || \ - (echo ">>> [ERROR] Configuration stage failed!" && exit 2) || exit $? - -echo ">>> Building project..." -make -j all || (echo ">>> [ERROR] Build stage failed!" && exit 3) || exit $? -echo ">>> Build stage completed successfully!" - -echo ">>> Running all test stages..." -CTEST_OUTPUT_ON_FAILURE=1 make test || (echo ">>> [ERROR] Test stage failed!" && exit 4) || exit $? -echo ">>> Test stages completed successfully!" - -exit 0 \ No newline at end of file diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 127479460..5b66b291a 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,6 +1,5 @@ name: Build and Test -# Run workflow on pushes to main and develop branches, on any pull request, or by manual dispatch on: push: branches: @@ -9,7 +8,6 @@ on: pull_request: workflow_dispatch: -# Define the build test jobs for each configuration jobs: check-contribution: @@ -17,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Check contributions if: ${{ github.event.pull_request.base.sha }} @@ -39,7 +37,6 @@ jobs: fi shell: bash - # check if jobs can be skipped check-skippable-changes: name: Check skippable changes runs-on: ubuntu-latest @@ -54,62 +51,30 @@ jobs: paths_ignore: '["**.md", ".**/**", "**.gitignore"]' skip_after_successful_duplicate: 'true' - build-test-debug: + build-test: needs: check-skippable-changes if: ${{ needs.check-skippable-changes.outputs.skip != 'true' }} runs-on: ubuntu-latest - name: Debug configuration test + name: Run tests steps: - # First check out the repository - name: Checkout - uses: actions/checkout@v3 - # Load the repository build-test action - - name: Build and Test - uses: ./.github/actions/build-test + uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build Cache for Docker + uses: actions/cache@v3 with: - configuration: Debug - - build-test-release: - needs: check-skippable-changes - if: ${{ needs.check-skippable-changes.outputs.skip != 'true' }} - runs-on: ubuntu-latest - name: Release configuration test - steps: - # First check out the repository - - name: Checkout - uses: actions/checkout@v3 - # Load the repository build-test action + path: build-cache + key: ${{ runner.os }}-build-cache-${{ hashFiles('./') }} + - name: Inject build-cache into docker + uses: reproducible-containers/buildkit-cache-dance@v2.1.2 + with: + cache-source: build-cache + cache-target: /build - name: Build and Test - uses: ./.github/actions/build-test + uses: docker/build-push-action@v5 with: - configuration: Release - - build-test-python: - needs: - - check-skippable-changes - - build-test-release - if: ${{ needs.check-skippable-changes.outputs.skip != 'true' }} - runs-on: ubuntu-latest - name: Python bindings test - steps: - # First check out the repository - - name: Checkout - uses: actions/checkout@v3 - # Load the repository build-test-python action - - name: Build and Test Python - uses: ./.github/actions/build-test-python - - build-test-protocol: - needs: - - check-skippable-changes - - build-test-release - if: ${{ needs.check-skippable-changes.outputs.skip != 'true' }} - runs-on: ubuntu-latest - name: Protocol library test - steps: - # First check out the repository - - name: Checkout - uses: actions/checkout@v3 - # Load the repository build-test-python action - - name: Build and Test Protocol - uses: ./.github/actions/build-test-protocol + context: . + target: python-test + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/CHANGELOG.md b/CHANGELOG.md index 21cf5350e..887058828 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Release Versions: - feat(build): handle installation and linking of dependencies for pinocchio collision support (#161) - build: add missing licenses (#170) +- ci: use caching from docker to run tests in CI (#429) ## 7.4.0 diff --git a/Dockerfile b/Dockerfile index 2cc0ecb9e..8b160bd8f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,16 +76,16 @@ xargs -a /tmp/new-packages.txt dpkg-query -L \ # this root can then be copied to / to install everything globally or use LD_LIBRARY_PATH to use it locally HEREDOC -FROM base as dep-base +FROM base as base-dependencies ARG TARGETPLATFORM ARG CACHEID COPY dependencies/base_dependencies.cmake CMakeLists.txt -RUN --mount=type=cache,target=./build,id=cmake-osqp-${TARGETPLATFORM}-${CACHEID},uid=1000 \ +RUN --mount=type=cache,target=/build,id=cmake-base-deps-${TARGETPLATFORM}-${CACHEID},uid=1000 \ cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build && cmake --install build --prefix /tmp/deps -FROM base as dep-pinocchio +FROM base as pinocchio-dependencies COPY --from=apt-dependencies /tmp/apt / -COPY --from=dep-base /tmp/deps /usr +COPY --from=base-dependencies /tmp/deps /usr ARG TARGETPLATFORM ARG CACHEID ARG PINOCCHIO_TAG=v2.9.0 @@ -94,13 +94,13 @@ ARG HPP_FCL_TAG=v1.8.1 # * `pinocchio` doesn't provide an include directory we can easily plug into `target_include_directories` and thus needs to be installed first # * `pinocchio` uses hacks relying on undocumented CMake quirks which break if you use `FetchContent` # FIXME: it needs `CMAKE_INSTALL_PREFIX` and `--prefix` because it doesn't install to the right place otherwise -RUN --mount=type=cache,target=./hpp-fcl,id=cmake-hpp-fcl-src-${HPP_FCL_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ - --mount=type=cache,target=./build,id=cmake-hpp-fcl-${HPP_FCL_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ +RUN --mount=type=cache,target=/hpp-fcl,id=cmake-hpp-fcl-src-${HPP_FCL_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ + --mount=type=cache,target=/build,id=cmake-hpp-fcl-${HPP_FCL_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ if [ ! -f hpp-fcl/CMakeLists.txt ]; then rm -rf hpp-fcl/* && git clone --depth 1 -b ${HPP_FCL_TAG} --recursive https://github.com/humanoid-path-planner/hpp-fcl; fi \ && cmake -B build -S hpp-fcl -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON_INTERFACE=OFF -DCMAKE_INSTALL_PREFIX=/tmp/deps \ && cmake --build build --target all install -RUN --mount=type=cache,target=./pinocchio,id=cmake-pinocchio-src-${PINOCCHIO_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ - --mount=type=cache,target=./build,id=cmake-pinocchio-${PINOCCHIO_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ +RUN --mount=type=cache,target=/pinocchio,id=cmake-pinocchio-src-${PINOCCHIO_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ + --mount=type=cache,target=/build,id=cmake-pinocchio-${PINOCCHIO_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ if [ ! -f pinocchio/CMakeLists.txt ]; then rm -rf pinocchio/* && git clone --depth 1 -b ${PINOCCHIO_TAG} --recursive https://github.com/stack-of-tasks/pinocchio; fi \ && cmake -B build -S pinocchio -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON_INTERFACE=OFF -DBUILD_WITH_COLLISION_SUPPORT=ON -DCMAKE_INSTALL_PREFIX=/tmp/deps \ && cmake --build build --target all install @@ -111,20 +111,16 @@ FROM base as dependencies ARG TARGETPLATFORM ARG CACHEID # Needed to build `osqp-eigen` -COPY --from=dep-base /tmp/deps /usr +COPY --from=base-dependencies /tmp/deps /usr COPY dependencies/dependencies.cmake CMakeLists.txt -RUN --mount=type=cache,target=./build,id=cmake-deps-${TARGETPLATFORM}-${CACHEID},uid=1000 \ - cmake -B build -DCMAKE_BUILD_TYPE=Release \ - && cmake --build build \ - && cmake --install build --prefix /tmp/deps -COPY --from=dep-base /tmp/deps /tmp/deps -COPY --from=dep-pinocchio /tmp/deps /tmp/deps +RUN --mount=type=cache,target=/build,id=cmake-deps-${TARGETPLATFORM}-${CACHEID},uid=1000 \ + cmake -B build -Dprotobuf_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release && cmake --build build && cmake --install build --prefix /tmp/deps +COPY --from=base-dependencies /tmp/deps /tmp/deps +COPY --from=pinocchio-dependencies /tmp/deps /tmp/deps FROM base as code -WORKDIR /src COPY --from=apt-dependencies /tmp/apt / COPY --from=dependencies /tmp/deps /usr -COPY . /src FROM code as development # create and configure a new user @@ -152,61 +148,60 @@ RUN ( \ COPY ./docker/sshd_entrypoint.sh /sshd_entrypoint.sh RUN chmod 744 /sshd_entrypoint.sh -RUN chown -R ${USER}:${USER} /src RUN mkdir /guidelines && cd /guidelines \ && wget https://raw.githubusercontent.com/aica-technology/.github/v0.9.0/guidelines/.clang-format USER ${USER} +WORKDIR /src +COPY --chown=${USER}:${USER} . . FROM code as build ARG TARGETPLATFORM ARG CACHEID -RUN --mount=type=cache,target=./build,id=cmake-${TARGETPLATFORM}-${CACHEID},uid=1000 \ +COPY licenses licenses +COPY protocol protocol +COPY source source +COPY CMakeLists.txt CMakeLists.txt +RUN --mount=type=cache,target=/build,id=cmake-build-${TARGETPLATFORM}-${CACHEID},uid=1000 \ cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build FROM build as cpp-test ARG TARGETPLATFORM ARG CACHEID -RUN --mount=type=cache,target=./build,id=cmake-${TARGETPLATFORM}-${CACHEID},uid=1000 \ +RUN --mount=type=cache,target=/build,id=cmake-build-${TARGETPLATFORM}-${CACHEID},uid=1000 \ cmake -B build -DBUILD_TESTING=ON && cd build && make && CTEST_OUTPUT_ON_FAILURE=1 make test FROM build as install ARG TARGETPLATFORM ARG CACHEID -RUN --mount=type=cache,target=./build,id=cmake-${TARGETPLATFORM}-${CACHEID},uid=1000 \ +RUN --mount=type=cache,target=/build,id=cmake-build-${TARGETPLATFORM}-${CACHEID},uid=1000 \ cmake --install build --prefix /tmp/cl -FROM base as python +FROM code as python ARG TARGETPLATFORM ARG CACHEID -COPY --from=apt-dependencies /tmp/apt / -COPY --from=dependencies /tmp/deps /usr COPY --from=install /tmp/cl /usr -COPY --chown=${USER}:${USER} ./python/include /python/include -COPY --chown=${USER}:${USER} ./python/source /python/source -COPY --chown=${USER}:${USER} ./python/pyproject.toml ./python/setup.py /python/ -RUN --mount=type=cache,target=${HOME}/.cache,id=pip-${TARGETPLATFORM}-${CACHEID},uid=1000 \ +COPY ./python/include /python/include +COPY ./python/source /python/source +COPY ./python/pyproject.toml ./python/setup.py /python/ +RUN --mount=type=cache,target=/.cache,id=pip-${TARGETPLATFORM}-${CACHEID},uid=1000 \ python3 -m pip install --prefix=/tmp/python /python RUN mv /tmp/python/local /tmp/python-usr FROM cpp-test as python-test -ARG TARGETPLATFORM -ARG CACHEID RUN pip install pytest COPY --from=install /tmp/cl /usr COPY --from=python /tmp/python-usr /usr -COPY --chown=${USER}:${USER} ./python/test /test +COPY ./python/test /test RUN pytest /test -FROM base as python-stubs +FROM code as python-stubs ARG TARGETPLATFORM ARG CACHEID -COPY --from=apt-dependencies /tmp/apt / -COPY --from=dependencies /tmp/deps /usr COPY --from=install /tmp/cl /usr COPY --from=python /tmp/python-usr /usr RUN pip install pybind11-stubgen -RUN --mount=type=cache,target=${HOME}/.cache,id=pip-${TARGETPLATFORM}-${CACHEID},uid=1000 \ +RUN --mount=type=cache,target=/.cache,id=pip-${TARGETPLATFORM}-${CACHEID},uid=1000 \ < Date: Wed, 17 Apr 2024 14:34:27 +0200 Subject: [PATCH 05/15] Revert "Revert "feat: integrate collision detection feature into robot model (#163)"" This reverts commit a83d1a35a0a136d2a61ac2f1b6e3634062bcbe1a. --- CHANGELOG.md | 1 + VERSION | 2 +- demos/CMakeLists.txt | 2 +- doxygen/doxygen.conf | 2 +- protocol/clproto_cpp/CMakeLists.txt | 2 +- python/setup.py | 2 +- python/source/robot_model/bind_exceptions.cpp | 2 + python/source/robot_model/bind_model.cpp | 23 +- .../test/model/meshes/ur5e/collision/base.stl | Bin 0 -> 21084 bytes .../model/meshes/ur5e/collision/forearm.stl | Bin 0 -> 53284 bytes .../model/meshes/ur5e/collision/shoulder.stl | Bin 0 -> 70084 bytes .../model/meshes/ur5e/collision/upperarm.stl | Bin 0 -> 99684 bytes .../model/meshes/ur5e/collision/wrist1.stl | Bin 0 -> 59584 bytes .../model/meshes/ur5e/collision/wrist2.stl | Bin 0 -> 67584 bytes .../model/meshes/ur5e/collision/wrist3.stl | Bin 0 -> 7184 bytes python/test/model/test_collisions.py | 83 +++++ python/test/model/ur5e.urdf | 296 ++++++++++++++++++ source/CMakeLists.txt | 2 +- .../robot_model/include/robot_model/Model.hpp | 67 +++- .../exceptions/CollisionGeometryException.hpp | 11 + source/robot_model/src/Model.cpp | 186 +++++++++-- .../fixtures/meshes/ur5e/collision/base.stl | Bin 0 -> 21084 bytes .../meshes/ur5e/collision/forearm.stl | Bin 0 -> 53284 bytes .../meshes/ur5e/collision/shoulder.stl | Bin 0 -> 70084 bytes .../meshes/ur5e/collision/upperarm.stl | Bin 0 -> 99684 bytes .../fixtures/meshes/ur5e/collision/wrist1.stl | Bin 0 -> 59584 bytes .../fixtures/meshes/ur5e/collision/wrist2.stl | Bin 0 -> 67584 bytes .../fixtures/meshes/ur5e/collision/wrist3.stl | Bin 0 -> 7184 bytes source/robot_model/test/fixtures/ur5e.urdf | 153 ++------- .../test/tests/test_collisions.cpp | 120 +++++++ 30 files changed, 789 insertions(+), 165 deletions(-) create mode 100644 python/test/model/meshes/ur5e/collision/base.stl create mode 100644 python/test/model/meshes/ur5e/collision/forearm.stl create mode 100644 python/test/model/meshes/ur5e/collision/shoulder.stl create mode 100644 python/test/model/meshes/ur5e/collision/upperarm.stl create mode 100644 python/test/model/meshes/ur5e/collision/wrist1.stl create mode 100644 python/test/model/meshes/ur5e/collision/wrist2.stl create mode 100644 python/test/model/meshes/ur5e/collision/wrist3.stl create mode 100644 python/test/model/test_collisions.py create mode 100644 python/test/model/ur5e.urdf create mode 100644 source/robot_model/include/robot_model/exceptions/CollisionGeometryException.hpp create mode 100644 source/robot_model/test/fixtures/meshes/ur5e/collision/base.stl create mode 100644 source/robot_model/test/fixtures/meshes/ur5e/collision/forearm.stl create mode 100644 source/robot_model/test/fixtures/meshes/ur5e/collision/shoulder.stl create mode 100644 source/robot_model/test/fixtures/meshes/ur5e/collision/upperarm.stl create mode 100644 source/robot_model/test/fixtures/meshes/ur5e/collision/wrist1.stl create mode 100644 source/robot_model/test/fixtures/meshes/ur5e/collision/wrist2.stl create mode 100644 source/robot_model/test/fixtures/meshes/ur5e/collision/wrist3.stl create mode 100644 source/robot_model/test/tests/test_collisions.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 887058828..cf27b097d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Release Versions: - feat(build): handle installation and linking of dependencies for pinocchio collision support (#161) - build: add missing licenses (#170) - ci: use caching from docker to run tests in CI (#429) +- feat: integrate collision detection feature into robot model (#163) ## 7.4.0 diff --git a/VERSION b/VERSION index 0f4a1d6e3..4e61aeef9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.3 +7.4.4 diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 98450023f..370802b96 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -15,7 +15,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -find_package(control_libraries 7.4.3 CONFIG REQUIRED) +find_package(control_libraries 7.4.4 CONFIG REQUIRED) set(DEMOS_SCRIPTS task_space_control_loop diff --git a/doxygen/doxygen.conf b/doxygen/doxygen.conf index 57760b333..f010ff338 100644 --- a/doxygen/doxygen.conf +++ b/doxygen/doxygen.conf @@ -38,7 +38,7 @@ PROJECT_NAME = "Control Libraries" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 7.4.3 +PROJECT_NUMBER = 7.4.4 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/protocol/clproto_cpp/CMakeLists.txt b/protocol/clproto_cpp/CMakeLists.txt index 7e65710a7..763468414 100644 --- a/protocol/clproto_cpp/CMakeLists.txt +++ b/protocol/clproto_cpp/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(clproto VERSION 7.4.3) +project(clproto VERSION 7.4.4) # Default to C99 if(NOT CMAKE_C_STANDARD) diff --git a/python/setup.py b/python/setup.py index cfbed784f..52f95fe56 100644 --- a/python/setup.py +++ b/python/setup.py @@ -11,7 +11,7 @@ # names of the environment variables that define osqp and openrobots include directories osqp_path_var = 'OSQP_INCLUDE_DIR' -__version__ = "7.4.3" +__version__ = "7.4.4" __libraries__ = ['state_representation', 'clproto', 'controllers', 'dynamical_systems', 'robot_model'] __include_dirs__ = ['include'] diff --git a/python/source/robot_model/bind_exceptions.cpp b/python/source/robot_model/bind_exceptions.cpp index 114e34dcd..4d9a01d4b 100644 --- a/python/source/robot_model/bind_exceptions.cpp +++ b/python/source/robot_model/bind_exceptions.cpp @@ -3,9 +3,11 @@ #include #include #include +#include void bind_exceptions(py::module_& m) { py::register_exception(m, "FrameNotFoundError", PyExc_RuntimeError); py::register_exception(m, "InvalidJointStateSizeError", PyExc_RuntimeError); py::register_exception(m, "InverseKinematicsNotConvergingErrors", PyExc_RuntimeError); + py::register_exception(m, "CollisionGeometryError", PyExc_RuntimeError); } diff --git a/python/source/robot_model/bind_model.cpp b/python/source/robot_model/bind_model.cpp index 74e379517..eaab80a3b 100644 --- a/python/source/robot_model/bind_model.cpp +++ b/python/source/robot_model/bind_model.cpp @@ -30,7 +30,24 @@ void model(py::module_& m) { py::class_ c(m, "Model"); - c.def(py::init(), "Constructor with robot name and path to URDF file.", "robot_name"_a, "urdf_path"_a); + c.def(py::init([](const std::string& robot_name, const std::string& urdf_path, py::object meshloader_callback) { + std::function callback_cpp = nullptr; + if (!meshloader_callback.is_none()) { + callback_cpp = [meshloader_callback](const std::string& package_name) -> std::string { + auto result = meshloader_callback(package_name).template cast(); + return result; + }; + } + return new Model(robot_name, urdf_path, callback_cpp); + }), "Constructor that creates a robot model instance with a name, URDF path, and an optional custom mesh loader callback. This constructor loads the Robot Geometries.", + py::arg("robot_name"), py::arg("urdf_path"), py::arg("meshloader_callback")); + + + c.def(py::init(), "Constructor that creates a robot model instance with a name and URDF path. This constructor doesn't loads the Robot Geometries.", + py::arg("robot_name"), + py::arg("urdf_path") + ); + c.def(py::init(), "Copy constructor from another Model", "model"_a); c.def("get_robot_name", &Model::get_robot_name, "Getter of the robot name."); @@ -44,6 +61,10 @@ void model(py::module_& m) { c.def("set_gravity_vector", &Model::set_gravity_vector, "Setter of the gravity vector.", "gravity"_a); // c.def("get_pinocchio_model", &Model::get_pinocchio_model, "Getter of the pinocchio model."); + + c.def("check_collision", py::overload_cast(&Model::check_collision), "Check if the robot is in collision at a given joint state.", "joint_positions"_a); + c.def("get_number_of_collision_pairs", &Model::get_number_of_collision_pairs, "Get the number of collision pairs in the model."); + c.def("is_geometry_model_initialized", &Model::is_geometry_model_initialized, "Check if the geometry model is initialized."); c.def( "compute_jacobian", py::overload_cast(&Model::compute_jacobian), "Compute the Jacobian from a given joint state at the frame given in parameter.", "joint_positions"_a, "frame"_a = std::string("")); diff --git a/python/test/model/meshes/ur5e/collision/base.stl b/python/test/model/meshes/ur5e/collision/base.stl new file mode 100644 index 0000000000000000000000000000000000000000..c5881046676ce1ca5c88bbc482e05473ffb616bc GIT binary patch literal 21084 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 IuRLq(Kf)s8ZvX%Q literal 0 HcmV?d00001 diff --git a/python/test/model/meshes/ur5e/collision/shoulder.stl b/python/test/model/meshes/ur5e/collision/shoulder.stl new file mode 100644 index 0000000000000000000000000000000000000000..ba79079f6e9386eb2616ad9c2a4f20b48373be1c GIT binary patch literal 70084 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 literal 0 HcmV?d00001 diff --git a/python/test/model/meshes/ur5e/collision/upperarm.stl b/python/test/model/meshes/ur5e/collision/upperarm.stl new file mode 100644 index 0000000000000000000000000000000000000000..02f6edfdde2bf08188ec6fc5d73f9b8d8773e0d0 GIT binary patch literal 99684 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 literal 0 HcmV?d00001 diff --git a/python/test/model/meshes/ur5e/collision/wrist2.stl b/python/test/model/meshes/ur5e/collision/wrist2.stl new file mode 100644 index 0000000000000000000000000000000000000000..667d60e302146b015bee66af38ce88973c87ba01 GIT binary patch literal 67584 zcmbTfbzBzB8}Pf*Dj`UUC*d)$can|wJr6Z0tpbn!d6qg(oZEs;ui`u+WJXfDaR()-#^NNAFN>U&vb_J!d zvNS^){7(XfEs%J*!=C!zeXfjjKF^6Z4W_AG`$Q79Q&l%+9Z`Ia3`G+=EJ5{Zt*ZXJ z>4EuZcK88h-wS`VZ_q+c7-*k8E=j3m^5QnfloBK4&xrwjkD;x}bL7GN@0g6a$ZAmC z>c2_5T72fdS5wF(A0~fD(?&i2u|eS%cwf7(ATw`!^KHB<1LvsSq($QQw$AL(k>|2)k4{AO zxT0uZ@kjNxYT+UCij}7?{ZS)_DpXpwM>T5sKBKy=})RtCxT0YxVnGf96zCAU|1Ok3cC%k8k)z>De+! zSy-ov6Q}H4S>}Zt_2_GJGO|yB;=H>bJvZ??I)Gm&Ug6!T?#6G(@@j?hHnKmBTJ(Vv zcmJ8jPTn{#Z-@)WRweal#Xnh!QF##>y48|?JMEABWWCRD5R`(m74P#8 z4%5m0^#-cP&!+-0y01C9WsIqJ5LOf$BqH0(V4{vKJCOO+-Bz_)&F@I`J8xB zl{T+d63YFNZN*&SIS8MRe60d~)Rb-!T&al)lbed75^Yei^QjzgVeJN!pN#+ zcLk#T$TWrUy=xdQ(){;h)_3)OR1i6MeS@&89e>xQUi`V+{C7TjI@_9#=TD3!Q}6JZ z8_F_ssMCh@d(?|MZnL5IqCc87<_ek{Z%uEs3P4|aovl0Q1FWdB9P9!+64&a_sXruLwL z(`~3N{m0cZIS|3$r6a8x(dI`UDCy0w@KNDIDQw#yqGT-XB@plz4$^$B?xnC1#;QEB z?qdbc9O@HwOgBkkukJ)4_ljA94oHh@rL%n+>)fZMde+QJg*$HSTT2>y_-F1*en-tu zHlzI$`%pKgd6sB*N@PXe=4!H?mI|dH4fnAmotT-(@{by;`;Y&kkbqL~%|Mb`cc0GQ z?5U!0Ti*+B0FV}mQ-_Rj_j_UF_>5<&=68D#-%ELKXQB<`eXIW-7d{(Rm3YXJd{>Uu zoyF%-%cMmY%^QH%V7)9T1!?#;AW08?xOCPKUHThljefkt&5!G7KV$|9>zn4P`8#1( zYV|wAAKj8PHvJHq{V{^L-F&L1*Szmm_Rt?$TBjmNLw%CeZtm6$jgD9UdSn={{j7NN zem}q1g0@s@Q_bH?%XbO)yd;G+xmNuqv*O<7(N9YZe)hfMbWNAW4r1tZz=lqdBxKP!HN<{LbxUymLNsaXQhCK&f3)oD9ATv;X&K#Fy{{4; zJ+$0l9L0Y^XTYfICCO#WY%YX!& z$Es`Zs`a&Awvp?YrgNZBeX8L0eW@AxRx)TVWg5 zN<8k;2!Vi7HE#`*Kq9bx$(s{3Sf2XAQP?>o>WC|3Ypl+k`qU(niUAr7cZ% zpyK8_=;gIlvSzfBRFD))rY82q7JFjVnZDNO^O5;(>-n$Ru$OgF{DXRO7ykG1i56%Q zr;`nGIC0D(p4@-g7!PgIT^(}B3Wf3+HoM9Yq(#DL{6TyJhmi7ugV=XbvFyWt!&hDZ zjsC3kxymX8PcM?xw3ijhx^72qx#|jM97v1A^%EJm>$>B#ZqIQ9o@uy7y`J)#*G$&#E+;ejB?p^dr zofNWiM1e~q+?l@VT^nzG@0k-qi89a7_^-3GW2F(h+v~Yp|Lq_$KKh7qZBHAec!)l> zF4(Q4E(lV#HaEbNM;%b!^6P|Bb-51r;(t{Wrm z)JOT8TO04(?5%|K?o2IW3^-9I(v*?RUNptummFDrR*7#Ctc0G@#YzjNoW3$iv23m< z=#!*TdiB}G$oKO8yne#T3)14KLiU)lNg3(#?a98vJRkw*BuP^S)@82l&*U@Re1uWK zRS*B8Z2OXDRAo0wpPB2xfYSdD~zZ(ix3Q1}bbO`mV8A(hNpJL7LtLFEv7Qce< zs==!%N%~He>Yxw7#4_g~np(?(erp$iE?szp{=5Nd-bLZLO_DkvvuCO6+=*$RHHPOi zNQ=aUmUGoxt3ruV?;`&5OZK9+4xZHQgwnfx0J0dO^Vc<>H|e?RWZsq>{9sO?6g(A+ zYvuf8zp6VXgd9pfB%GHa@kG8-{XQQux(44R`TLfu)8|HzKDI|OY=QdVXeFs%%nG%> zR|I*OeiXxgApv`mr2X}s*-+P~azmv(fhTu(qF?#_l@dA3N3q|b8C9Q^?bUj;w;F37 zN1)XypiU0I*D%d4tcmr;ch8v}i0G+2jZG$g%@1|9{XKdwU<83X?&!?TO5~v~wDWfz zyfJ*f@~L)bsy#=K6UP$_*+uu;bW6w}0;M1g?-fZZ8q|U9sdz;FS9T^1M_o{^6m_Ky zy4ApNPHpQvQqs(wY3it7{F&J2bVrsu{~=9k--&$aQ>g^hovT>bN*GE(n%^Jm`mq{g zGUfcx`T_y15ztB@Ny~4zvf_hl*84TBIQkAlDM*KWFsGKzdz6jS z-*94hVl?w9TO^;1e<0`xsbNO1u=$Ed|My5F;@-AmFXn5p&JSym$oX#+emd$U@yoScSidg2`niJu|gl-xB#C>~dWtR@sIy$u8CG^=txs*T==ok=^Y){d!5 zU|-O-)H36X(#4?%JvsR!CknPqW2Gyzbb*SZgfAk>c1%gjkNwV4=%W6!#CePlp zL3r-MnZuqWDY5e;cDdxTys3Y>paWiKsE_}ihfZR%ULKI0#_SLXr~}gcU3Ag_LT5cv z`#I;UmJZse&cZ%)``ulF#ml?nOZojnFPc4M1HXa^w>*ip^?fxp@t#0DIIM%7uj)mA z-WF*|TGVP3`SyKBe^{{lNfPTEO%kfoRbsYOg;LN`2yJqb zbgkudvW=OmY>G;uMG)Hkw7Z$3>6^RLvRfK!l(nA?S+(4NeJ-|U=_7OHu>1hpr~XRx z?BgwYpy?oLw{+_I0-ZWh7llpnpf9`R z306mFjg+K4HxkL|OM2?$cRvK1rC%p|)GF<|qU&@AiNxM%@g(Adq~`1TVkiY?3rEPU z=g;EF6&r1J-tj&H0i{G869y*{-?>xdrR&TD0-hS7K1tF_O(A_Q&ycNB;}9GbwA?}u z4ZnidlSu0-Rd(O5E$DzJR;W*sW;aYB>2puXeFttsPzvf3b@ZQZLtNAJ*pK=37~IwH zUh%N3kiRrKtNeHJN^9JT*l(&*zxx<7*aG)4>`9V7`{Yu!$c;_%Y|YkLp2=*iah*hqzWxwOHS+361@PF+hC_){gR}2feWbJ!Emxa?v)DdN#)zN zwbD5CrPo(fx_)d=<^Nxki@(0tuUl3i7- zLTjD$dZFt(KB`(jlLWh;BsqDsN1kb6L7^}iNa>BP;{%21BGZ#iE84t5lf7wP6XZt+C3jkO5{?lGclq2_9bl2Ek8;`b7 z(dGbxI%_l70Ku2>b2Tc)a-c1 z4U1~;LbF*HguV3Vq^I!+NK)*=LiL?<6d7T{Jq`c!M`&804*~WhNt0hcSM7d95RD5S z+Sva{*tANs%R@#sa@YWwch`%N<- z0i~c_oO?`K=w@jw!p|Rc$%HNYv}#xDYclE(f<1BndFAw+1=BXTe5p!d!#7*faas@D zUU6Gkvj?X10qrc?af{dJRc6$@p~e4fWc9X=VR1h3`2PJ>DkS`d)S*?}a^n$qM42qg{lAE~huCdtnCd8|R>*@872TDc|Z;%aA{d_ID-8JQ;#(0c-H%aWwG zoniY2644Fq&7cn0Z@zP;{9|dw|5mp&-4LJO5<-@FzEwxQw~;4vOXF0(8G>I$^y9S7 zC`6}J2tL6j$^Y_7bnUbsDc@ntpcK?05;g~xpu=IIWP8JM6%z3G1JaT-;M%3? z`{Ta%+04cLo>lvbTte0f{vnM`M&mbH9;>mQYvq_%6PB}IWk3S{?*6g+XyRwM-#IT^ zo#_)!e6}rB^WJ<=G_fk94{kt^fIUglpuL&Zo`!O-XGb(142{oYs*5g${ua>=SLvUG zT?|4=!JeBc+!@gSVQ0KU4&%CCvH5&d2{YE=`$I#>k@hFm{k4wBt+@^PRQpAOUj=#u zBGjKi1?bIW03IrSpq`8IODHZ>G{1ZF09?YKdh=gZ( z+SR|Da4(5_w|~|y^MuRP{2>_kYwo+PQtZ8Ltev(92()42F1?j| z@HnCuv(^bd57G0J=GK*rD=NVBq>(^C?}_L!+12$t?q(Z8RA9MR2k;G68%KqZu+aLsPzus8dQXzVZzYk_&kmsQ@XNxe zpa%<%P?9PJ_a{e(&yu$%*b*39w{W5ks(iCt>A2t<5~K81BQLT-x+f29YDu6JjPVnR z18fx8@U@iw^g4q}d)G$#zujod9xnx}0kkGa(qZ=rWJ_N&we9mQxPPz_qB%XOd$S@0 zPus93N!svx0`WKaO~-HCBItkwv_EhgS^YDZB?OaoFV^Ef`^Pl<8-kuVI44QciMUhk z4UhSJq6;M8ufd;vtnvKuC)}Bx_;Ya-VXn(CY=JspPm*L)rxE#8&`xb>+JpG)%$6sn zb)zpEYvCQEFU!|vcBSSe5*}reC2tPzO10lH6^Sw(Q^J1LRF^mOCa?t(WvQEGvkP75$*Ss6 z$@=8!(JI%|liEAvH~dY%o`)X(wJ1a0&Fuk;b2OtGxw1C7Hn&*bkvN15 zt~?@7h-#xO>#L6;A?~Xcze1`bInwhxcG0{?Ya&l3eKZjCiEm4^cUbiu^}JH*3W+~& z-sAh}3pBSHjBJ1(^m{LNx-p$VZ)m%vYu#Qia8>%;F~TD+7Q409)>kZojWqKhgYlVz zWpbiN0)Z{iGY)&=-i(_E)sWH=%&IgS|8r)ulH?sq8#;YMw{w`%Y)=qP$ge~$7Y``b z`Vlm7mnKqn(;8Pb#=V28Dsd#yCpWhf?L?jW)xyx*2fcvYZ{XyshTmzgW-Xs85YRIY zX&#Xh`#Yn@Gr~ zw#urZn5G>yz)(u`(Q8lNqpq~;uC6*VhQO9iEk`Rq{&At}*XU!|lO!qj!RoZMKI+!9 zlL?$5)FBc+ztiaSrF-ejWy$1CyYab!x;>PN2Syl1UBgx2u?GK6r;lFmp|7?i3*J{4 z&j5Rpq%ECOX-fHQ`t*aS0}@c5Buz+5p@*;?t@KJEP^xg#q3oUYS}2K)jfD~NNWkFc zIPS_B8WfaB;C_V>7YWDf$R~QbE0;|*d-vO+*|_=KEb41CmB1Dl>kgmV{5<%wGk$!* zU9K!i6bQKUp>BS5?(L6%zbcgrS|tbsygwk#&x4LL&|STK)L~6BIk3L7+lA}y%E4Dg z7|uh~vFCeD)cD0tnkl6S1Y9>rOVW_G1JV1WCUi@Dim+C2b)jy)cdrQmU+9m#JjKX<81XMjefPSnn{wXCt6Pf%jO2iM0sOuF zz!9}{<{Np%_wl5Oou~5sS{KKLfVdC`Q0YQt7R;>Wuj>!$)dNfhI z`BNK45lB*0>$9p!k6W~b_Gp2C_c_$ZZG9t3)Zm}jsb_kaK)`;*c!nWGchq8bn_};1 z0$ZRCI6q0s+t7w(EqEli(vr#I$h*pcmpzmn&$NZ84lz2RRX_{o)hbC|lGjZjpcIV# zk)-lB%~9)rw8Yf)zlg@chFvulY)$meNT1q5B#-zzcqh`E{GMmP zRz#1+R^GMH=24z>di+yVYfLTl&2k9!>Us}F*qfk$Ap@yX(LMf)V{^C*aocXlDi;=F zmkvMWhOPr>ySAlh)BQK{y!*a%VuK2_cKB~udz2roWExLge&$cSG^_`kSHA$W&IR)5 z4I}6&zf$yi{uNnID}Xlh{eli0U~=0}LDXnbB`2~S>`~@nch%M+kytiQKk?DF8(n&? zmT;eo&)pB7YN`?0K57T2c;UGVBVAxm{5R@l8#V1~tlHx0WI+cc;I9NfJNNI5>dF1t zl5VzS@0Go7QHH_P;;A<7uVlIn7(a}r2kPS1t@NCuhx*ZBR+{%+i^-O_k##%v{e2gb zKIe?vg26u2YO=mS)a+JYUX&L=Kb2_SeTImpg*lzUjsvZkd~x1v$Ami zm>z6kjoJb+Xa79;+@c_Av91b1n#Xe1up)J~WT^SaUC74xbMg_pJ~XL@gyDIo!Zt&m zU)7iTP0&PBz05p{7kOx@qYsP~Vj$t~pj*0+++}5F8Xl^-<}dP_u%CnS>Ft6ZWcbY6 z%C0*}%8({HLZlOnfZ{QCc@Au&x0kX;%UvMgeGZ=zJO|6fmR*kAN@pGEM_`;4B%p2{ z>6F)m4c_UcUR9A0f0c3Lrjl`>8~xa`3MFP`D;G?{E7-j98}K^#=++e@)S95lww!o}!DF?kgs`ku-boGc;rM6XoFQC_38fK7aaH zpW3A6b)Cp&E~$Z`RGP;-W%$SlYPRS8UqpX{-t5OIFSSRcHF2CBX+@Ds#gtwNTQg5W4*A8C0W&0gCz+Nc-@J3#d<$ z9234_HaD2Lh9DWXz!AcpxQ{;SBR=UI%693s6m&qM!1J@LwKIZF);`7O@%rizlI>)` zE?B>&15O#CZqK}Fw=LO-T&jZ}*Y~Cks!pR{dWI-*r$6m#rm^pSYd4Z?^tz~~2kWVj zfZ3~%mZVFK#*p{#_o~JLMDy({h# zFAE0}JfuA{8k(-gt-dXvZyrpe?rcJD-aL@M-5Exg6>LOeTwl-Su4Kb@Gq&F1qza{A zG?hpU^{YcJY-`7~CcYI2s85V&+@05i=!~t)c3~|BTc8f8n_Fuy&&Tt2)?=&k34>Y6 zw!_-UwSJADWX2o>vzqx^xQh?=JlKehjw1|8jSG3`R!c94&Ip+O7g4W87u>@Iu@ui{ z3`#+|qJN>=f~Zhx@1x0(cE7pHZC6G|w#~Z>D_LOeR&Ra?oe?=($h?Lb+5BzkgD&cG zyESv%FS8yyyxoF#1kz4!vk;_3;;z?8G_qF*w#`A*0cki|NisQf6Qy+N#$KB=7e)o8 z#CaGl*o_9R^JdMTo3XS{pIqPG45v=3=Ln+`iA@a$DBJ$=WxY0;3IvRe6XW5|mppXU z>Eg-G7}^M3>_A^@s)_#7_9}6UYc!0OmTF?7)+f}! z2XEN1ib@X#Tj0#$y73s61>V?0w-K9c+D#zfUV=1_(|_KbuD@c;zS|FCIy}a!IIMp4 zNZ?h2afFiev2Q8zc$ma`#73fq!4Kr_BcthpRrzS9^A-7qQ#5TBQ-qwaUz3k|#n3?x zn$vdt>3;a!`2=>nwvj-TW=Opt2KRddfbkta@Su7o+iZ;WQp$#6y%l2c33#8`g48drAMNv;l1VPIlAoD z^jS15T(}s$jMtNgKZ>HioEPzV?0Z}fM>d|yTnhMKAoOUK5MLi?6#<9dBDjG=|TyKHQVPOk1n{WCSWmeGCV$eXVo>W2qy zF>HaqGO#DUuY4zxEA@t|k3Y{62&e;k8oA%Va4c!1yI)O;bHcD+s86)XT(OTLbXulb zHD;!u1NIB8JKT%hJd*57VXA9Pyg)!HQOC8ry~x4x3)r=;#Gv;c`ng}n7@?+t5&w%q zvTYqq4s62NF;>&xyO0OKtZhu<`^K)u&fiB_uP7X8JA>;^qteW++cBVrb** ztNBjcTeXCGFOFxrgGUJjjKCM8?p?$7sw00*U_o=k>D|R26r0#c`l8!u^z8k6Zg-8K zDOOqN`-xXUq zkx*8PHqF4WzF>51J=XnzGkz~&lZ--@()94t9NN=y?z&E>s6st29w9ZG!eRj5J>$UPt%404bEYc`YwdbP@W0-29SVtM!2`nYqZ*DNC%Sivkik% zFk3;)Y54TYj-8w5PMlWt5bi-3<-Ib>lp-Esyx-tBU)?D;-mA&YLddA+`YM!yG3_Fe zTJyR3)i0Dxt8A{q7I>e-J6MupM&_$#y(5YDtGWUKbwFB@-hZi)y|EL|NRd)m!A~oi zU*0uGv#$>EnrBmuvvW0n!zxES29oZ~9<%n*Cjj#Uu z+^0J9Ya4&%KL3Ay64q;!^WbFT>|cKoPzqKD;yS`hvghQ`6IFi^P)Zz?kx^lGjk(89 zXmr4_!x4(ag2VRM|8odwc>bjdqj6zG{Mf5Q<>5TjzXeI-^GKHaV%HCYh(WOqgDo%` z7xpAc0cM?Wvs0nui1bv21k?d(9;H`A)pO+`#HxI)5VH;Av=28bRxEk_nme`D@KMpm zPAnn&DaI9b8EpCN{!2M$ySw_y38TMx96(AVHvI5A>^rBSK)`1rr1_}M?dG9AVPs0d zZWT&Feen6n^Un*nt3OR5$*Yl@RVW4LArj9!RaJYDUu~|{1;!4-XhThuwL2?XpHM!NBAr!6yZ;O-EzaOzEg zfKpH&x1Mjjg6j{CASXhGV@)Knrb5%OJ#P`b2KCPypj8k2k$rqA-@7_LnvgaX-HG=i zNo`_miQe;^s5ZA1A*xucK;t&Q6whBBLTVShrLg7C341uoMX)DcE%e?K+%hqeoQnRJ z!WKw~amX72O7XTQp``nlMHEI4Ls}&CJ)hu78zYHo_%Hg?ujZ_u+`$qJ<@pWzwhz!B z;^7O8HzTyqTfA?77{U6B1u2-LDiXfWdlF-hCZyZ77HG*WYgCu7)hxFgLRPC79ql#h zGd{}Wsg2r>$%ZX3o*MSVvxH_m#(Pdg5i;+L47EckNb?M7=L%f7Ae3YtXd{dY(r`|a z^mS29GA8dUJ~Y{h$vls?YTJqGXB3S87c&%|wr@ekzkP{Q((4HXjQ@wW0d9>tXH8-b zf4~=*gFwK3Ao&nstQ{m z@h8^#zdBw4#r3MAKZ`Ei%Ye1JAT1Jl%fD0;BYw=zgi-_cw(Dp0s-waq7MJ(!-?PCZTMHOOM(>#j!K-zWWVRQMgLIZ)v&o58)1gz zcif-7{WyYBaNQ(n<GciKT z#%xr6ZTx_WGHRj6W~oZY-kL}!Z~tiWZTY`)JHv+-t<{k3o;9dRc+RfjQUieLUP5#vVt9s|tN;89^; zbjk3F9BTR^Ko}Lwh=4uuT9dc+NwliafDC^@2PB|Ao`dDkjQkk0oEp6BP6DDI$?H3f zR}>d*3`Zj7m>gfxoXn_OBx^^D1bln_8hKgXUEroznrQ0WjosarjGy{S9#U1}`^hfEw9$HHMW>EJ))v$u&ci#`g}iAoS8mhDNvPir z37G%It1Z}=kyaX4d$mZEw#41l&R8Raj&iSd{*krn#Q{Q4#Q9i`aDHH z=fpX?)h|80*^_afaM!%He6}HZWz`2u7w-i@2_+<45b4qc`WY-Yvpw;-%oQi zF~W{5byS}~FXq6XbgdTc$u$@oOucipv3-r)-1Ut6TikX3B2xRB(czT?*zX250s$k5 zAuUO#JLhFLkMd#yvw?l2*N`qyF9!#X>FeaLO@5C8|7x>V$~%$TRJ({M-;h z6Q*dcVB0>AXq(ofS@ZPM7`DJ`1AF49iQx0}+J}*>%d<;@4oJYbXa0MRvuOS1A?#$$ zj~K2M>{leVCA+I53cT4ER-3?eht~tr-1fR&Pwg1(%}Q?8A+QDNgFQ*o5u2TA`WJ6j zmRX76{(-wB`R**GZOc&l*;unyZQSOlC%btwk8~XZTi}j?J@GG8l%%PHKX@~$;Y%AZE9uGHY`0EHohbKS^ZVmi17|$8 z`BiE(H-W$wNQgN%XXUbxKnp}qxun?P?*~@dDKQ2@?n?C1$?3&R#%2 zM1+yYvG0WItf_ybso!3?trj-xpO?Fy*MXH{H8p;-mK~;h6NZueRoVo$z|1GOV|ZMj z{XRM)KZH2ftiVtT(oi4I`8mgH^|j{j)Yg-P+$EU#G$^f|@|yc8{MKuB3EjF_eRb-U z9C&&Pfh~}L`HH+!h*OeU_@GR7D32pB?+Vf)QPX(3THd8xp05=zx8Iwg zsl8LTS&J^3s_U96?+XX&VU3qW^LNkI23VZ==M#F>MC6vwonX!nq(!1}?PHl59qOn8 zL5Ic)toi1Trn>DPkC~>*t|X<~zpbuDGQ=gQ6YLl26Z4Afw|S|Tl?Y<`JfMm-AX@Yve&4r;v>7 zu1eh&y_HE3VhvVMV6T0LoY9D3RRNNEVz6OZKRQ=q2ub90mPr-cgBuy(XaOj6#d86<=($uPvQ1=dCt zGZe~(zE^vW4(mjN{kDrVGc-DO*J|mbz0dqmPefLBca+5F9 zE88E~;@VqT-qN4$kF6B4=wNmoKXVRkLH1@DFsf@nAOW+hU=ALy9ett&v0bmtzRWQc z>^_iyJ@IM_ydL1Mg=T8BS0UbWUl(QXaHsOpHwad|7d!@b?2c# zzdZmHsIKMIKy=S=>U`>fklQIq4JsOwwo)(VxNi`euvQm2n~b22LvIP0 z4=}1+lFl}$Lz36{GSiA}0s*rtV0Hwre`UVTtyG_T%T*`APv9a;Z{ zjWCpgv^aC^V~AuNYRS?~9|@y^(eWa2t-Uibz2U@6_5KxdJ|F@0@pw43B^h(Xp8b34 zCeF$DB-ecyKpo6WgnU==9;{l_k$nB$n4PxUArSBiio4`lf)?=_K$xTN0}L}HVdiXl z!85sdOaM*!rMU+S-`bEc*G6p3|Qf+cy6PhEkA*x_O217tP6< z6^+@CPd5buu7ao|b*2gbN&#V0rez3OzmR~sc}?=H_IPh(FZGigN8sBwd<(bE?d95{ z4Wdh{E7zCKYK2$7OH*H^#S+*8GksxCe66;XBaheK>iui+0s(bEn&(G(>0`T=M$E0y zm%tM*%*g%S(ZQwjMjtwIxF!R1oI6p+@3LkGANMAm|Fu_gJO)wUkGeSK-RazM=Y43$ zV14YizK)_a^`uP~Y2Mqjn#ZeY&K+3h&(;C~Pj})8(kgqFs_)d64cgO=Z2Ub&@mSzX zAFtBEkbt_m{VKG+TDiYHJ7(L7z_UJ-5_Q~IYo%^l(vCG4(?u8+q~V+-=~d)=*JDRU zv4rHSxU}Umx0*Y{sKa7DJi-GJPSZ8lIhDPy9CumE^qA3SNU50i~>Nu9W@nM$)6VG#;$5BeluYSQ{4p#)4QqWAcIiUeqh+ zw~*-#pDR2rm#aNdtXx4d!@W*LSH)3T?a!; z^XPrEu~1L?1nrvRExkCnBxrJmbrzc%Lw+p zbgMjoS6qftkcRps$!)kl)2{uJE(+5pclB%1x6{@uFK&GjGNfUyEw|8H2QsS+Oxec98suPCl>LSSDoBNyuCZ)70@*{hsOv@Fm<_`)jGAre2+JQ+)WLi3F* zg;7BrVh*okFb z_}vspbK7eZnc0tR%H}={!nPi|w2FHzG6z3JFm4{k)AMRQJzBGOer?#|kEeWAiN&$Uh2}s4(_x~y5`$8cZ&kpk|Yl`%l#LI zEil_2_QbPHFGREZ>pH0e={XGhg?kM4BuTZMli3K*WAffl?~vy%d#cs?hGNkwPsphk z^XMn=FO%-_rSj~u$1)^fRdM*e07*KKIi0FCChq^IXawb8SW0s*DOoVahjMw7w|m(&$&Kjgx^ZJ1dO zb@P1A7DI{UGJR&FZR84D;F%NlBuO=E`V;G|`YhYDTo@G`iFmGk88L)(x@5q7Gu{Yi zeW(NK=2lMn2BUZrwsM&hE9RBND@}e?M~}ZQ*eMUr8b){TSc=AY%#-8#45PyrX>zb? z6lddI3r*Rq?=pieFe4lG#A`s@&c+`Wn6f9oWkCld;1{U*m#y18z&VbMne`9{21oMh z_B^?kQ4l?uwCJx58F8i z1eAih`FF6ISK!D^#%#J(J7FGBpGf4yG$AJ3mN8-cHnp`Xp_tepdbn{0f?4rm9{o)} zT{3ftH=DP7ra%PM_$Gh26-u?8G6kC3`Z8bRjt9cn>19Xhw}$0%Wy=WKCN2{}0^T?L zZE3IxX*$x0om%)-s0V;^ z1oOdRhCGkniQJDCtoLL0CmXXB^X|I&yG2kmWFCT<-jL=s&u~M$w&o!A>3U5;2c$(} za8pO*(>$I{dK0I@oMV`aJZzw;TP+qvJ)Uano^R}RN*=#FfdvoqR$&Vy;J1soeYed# zwC?>Fw)BxI5O7pbH_uS;ia~XH#jrQtcU3rBNI>2E92MJ8_E|fUt%>-Ze#My&T^fk%Ofh94Vf|S%{$12D zoz1HAHepsg%&(WEb*nny3-3)>;)j6(0i|HxKCfc_>L2`Rm?^87-diBx(*Wv|B!}!) z$hh2?1=JtJJbz7b3p{39Jt~;d53}$kY3=9La%X*GHmOX^)rYi5T-|vReLQ2#y4d>( zI^fu$K1n(=&p~svr4MTRw#jerxxn3z+>vU93HjbcOCRZTw zL|?ysp8)kLcvDp>k877&=6&5p?p>#e=CTl`O~wI_|DMZ*a026oWf3+Ty}-E3?MBMy9=(O zbqUEV^IHl z@3^d%Ey_~ZGy9(*I+;Fq|ox(ys&C7-B1FwNd{1cpsnz>J7*>{KF){kq*B~7Af z$l;lB7E$Ps(+cBrv~C-U0!wjxgR!k|v#>ghI5ESksFp0s;3i)W>s1AFo0q z_`XW*yaO#*c38gQ9YdR@JQsF5q{ImJIGH`ujSvX9k6{fE9<83HjgRk|&PH{= zAXEo|cNEmk&rw%peBNX-D?B_|)$-gb|2`Z=mF?>g+$Ca#jnU1laqQs)7HeT75ODWF zn*VZkiX#I;FUcvfX?W@c3-pagQ{3qJSg5BDzvsuli|jdt_=Mk*n><}65U?hPSj8jk zSUefB^}IZO=0t&j{fb|&Tc25mO~OLR`Pdf-jtb`d{K*UWukJ_g<@dPr@euOXutFf9 z6dWOsz|Kh|-Ak^@x8j^J)DAOhpl*IjJ=lSG4DCev?$;LP0SP!ljfZzU`Qm;{ZrD6S z_{Jfucp?=Vq3{xaG`3h%SvC0D1mc8l%Y*tHz)(sUyFg3~A4x3k-jZ{^-@>pjm^}b< z5BT?;Z^V$6C-da{bMIgn#|{bD6Mq-gdV*`t2q6a^dkH#VCK~KXlAe9PiI?AsB>$di zDdelce*ffxXezq!ddQCVaZX2`skftrpaafaBu*Zk?{f3&>J!|m!4mDwk>A%g$_<_s z`)-8^YWCK*`kY`>VU7l`)~WuRU%^!=MCZz0BTXISi z|2wBqC#qb{l38?ec&>7hy|Lx)oaYti)B5oeRH+!7HBJOCfby zoR&++JX9b7v&5io{su5Ag>?F-3sQ`|DJ0-`Iv_1co{`CPYD( zD%a5+*1A;kd06o^DeUmjO*3|wB_w9PY#vkBZDq&nE_ePSpp>X1^vYY;)kl(Co^t}O z57Z|TeyeM^z3;a&N28-(pb@evPgD%~y2DB;@TA8pdG5!qosu_Z|M-i5*BtHz{)8*_ zBAc}H)P{j#gI1UMUkZbvCZn~(oMpbVxg3%qxrfKWMJnEb&zj{3Z>w8W?{uV zUZ?1jD;v|J4eR!t(v#Xc^xKsY^s85n@C``#ZAf1E>t2BxyFZ$B_I#8B>tevVFVXux zE30Nj(2*-H^Sv7#@=IMXB8Uy#eMyEbu-XRfiTe%i{7`Ks1hMIAID&-xno6bAGlKTN zcJr^gA8z)))C8L#)^N~p1p9@wSSjR&>tS`a)1jXiqVLLgvGA4qdQ&cIdb-aD~u&iS1*Z+4-QIy{8oC;HCti3qp1G*l?YY@g6qaD z6Q#}7r0{sQ(xZ#8x{wx$HoDDJzj`Ctk|U?pK?8;;?phHvj4nd3(h$_gYrRZ#Rc+12 zut(}afq+s_pCmn=5Th?+i>hYql6wdCAhok{t!f0F_jC^G zuyCBRFDr<)>%9PZcJfz_oES>Y4{7QSh6c=4Bkx(U@R>arY=QNbU{5@rVb3P|;Ohuh z`soM%R`u#!$3>A;tAg7%z}Ig@tHG@okLZUBA?(hHQh|VL1!+n8RQ^i&Jz@fzwepZ) z%Yc>%sGCP2X>F!+v?sEQUv{amUnm7>Zr`O7*^=!e)Q~$#7A*sEO>*Vl(yERY zKq)vvURz{zC=1b3)eo;%38RAgL}GXHD7IdEo4RTKbU_DPH@MRLchGPe3tOYDnm6br zR0WRjV@|u|BRX!vT_mnVRZMc)}_Pfr)mUql!OFV7HX^bh8|wSrCcoR^a-l{A0J;3?0=q8)xl zo_ZTej+DKSbqYsPGjD!Mm~~YR>Q*wbfy565?Yl=S)I4_7Y9J$XUt?t|Imz2a?bZ5! zoNjp+MNepsl)(j=^me(xtdzzC3ABNZ3rx(&H#-yUPyua=%7hO2Fa8AZ7%u_{bWNWXIGx*4Bui4fUp@hJETO^Z~ zrl)S?G4oUgjNM@pThIpD*0k#n%_6`}Gad|VA@jFcYYuk%nsXF!<}{ntpu^im_ax1z z@7Y>n3+Ac9k<<#3O&6~{zOmDt{fG@DXf`XCk*88y+uUei$+xsaWqvB>JSAu4g&Cn} z_N3a@-P5ke&hq%A2&_|#G$jIU#-t5wbim_-BC!535;&5|wh;e3mAmivuw|kIo^y*c&S9(-8 zPj1)FqM&R3!Otk$HG@|y>ttXH5|~Xu)2eQAH>zIBZPe@dfnnW8%plOj>6?B+)gNu> z|4!L>SLzZ=?Gt8bO@=XSLBf)y#FF=?)k{mAxXR;>vRxseMwaISLj1^bVyQogwpD0C zatd29KGul(I)o57F4F4j(zWI6R+x`0>gr7DvGlIqRqvGDna5nqO_Yu*7P5!CekV*S4(6|Q|iiAxm*c>9#Xw%=iPp+P{F3sZd(aLU@x?%RudC~*yGrRVzWmt z!*^O9eciXFe9d+{_VcWo`i?VOH|9HL=-taSGtghmqky!k6Bk^@DDvi+eke~% z;uR7!gIRdvLdJ^FC;EeC9SDKFFt3HG^rx!B1nZCWIw>7UB~0{$5}EGmIn{Ny*qG19 z$Xk4^{&sguUh!FOhALLf;-d1yEz4^d-aEwhE=@?b1*9qARqYE4IcpYu&bB1FS0qpa ztInXqBUU+ht#~jx&_G2j_S$@Bs9w08!I$2$QA*kMD|fKdcRPtd+js--3G7AdE;VXW zg1?M!BZr+SZiG~>mNtB)KW~<8OQPaYzpqwBNqL3qPB+=;O?^V3rX98Jnr2M7!EF*9 zW%b$qgurq2l1oxACA8qz?^xo5J?7*UpF+R%@qOQ$F#7>!MNqLB)^EjR_d4>@clCq=0`m+Yt$v-)KM{eW8_J+g*(6H=5;&5k zwOF1@-fP%U7RhyuVGC+UsWNlK7)>748p$SKuMz@#VTJ@1OIEOi-0WUXMp}O(1gZ_u zo~n=2sDw;hTUPe@LJ2%RNGnDB+HL$+Vz&M{i$<(p?Or)O-XYi&;_N`8n$@*CcqEr& zvh>KPQ3m$Hcy}7H-g#Ad-n{K6J#gD716zj9ZtUf;GRWla?!a&)l^3T&5?@IU{guka|SugH}XO}A1T=<`O7 zGIsP_>~;QymARFb6T{<1D|#k5JDXgOWa=Jil)!P3RvGw1&x=!iU+O(abvJs3O)}Sh zSHj%bBQL|acC2@)uEa4HMAv4Sx-@ksQSnGqBB9nrVS4&fpIVgKz*uCor)g6^tPvS& z%NV=rHZa~E9cb>Xe#2*6w_`|98(xO5n0h$UnBa4sA%St!NNZZahzi25WT>%u#tTyS z6XVbGR;`;8S>ABD<#{#Q%0>K|9$}PwbDLoc+M{0ma<8Ijay-(w`}=hgrH%xSq_pQT zO>^ReZ=AAffGrq*jB`>uEV&-=G4WAGa_PIoE6fdmw90Fcq={iR;l_aR&j^7tM_SV| zPhAvk8^jo&5(+X)W?oCYbM?EAN&GR56J8&6U&P)DGj3e+Bn0-ts%A=a_xZGF)-2j^ zit`}^_WBVAZK<`UX%ntx<{Ycpy=T|QmLGd0mMCLOCRa<`zSAF;n4jT2tLG3$uB~v# z7Mv4~q-iIt*NR6QqKrzDRHi>3AGnrdWAfRCDd-gvKhEHvnd`6qMT}Aew&1zJk<`)cdQ=SU5@qy_>cuKI zvEq$XcI?s@-k5&SmaC7(<4n1DSk$eph>U?G<_FIQj->Jw-98{5`b8TFLDU8kXivSD zWH=f2+d9ZED{UFZ17iem1DU074~XEoezVkL$*O*fMf3@fRj>cb-lm<=ou7sCv?0$; z7%hu6PSqFQ%KO-fjiGX2>UxG6IHV~NG0V~DH$$y!Pi|ww*<*dl%uxPfP=*O3S<#-V zQ(f={D>OSmZrHnwc!e}2=3OZ#o_DvEUq%HQ7#oW3h8}shr@L2a$z5((qD;dI77=}Y z?Bu(Vfd;-!VlSjs+?|J?d4HD^qD_SaW4ZS@&stF}_>TMb4C7sKB=s#d(w2>U?I73W z_9q0!yJ8%yrbW%m&C_3?iV#rE9)T5-W`bz^aS6p)eg&8XE}v6 z75Sao7)>2knxBRk{B@5!4CCWyyc+B5AWa1-i9Y51NQ4{G7$2wB&Yi~yRwzq^Zw)f; zTe+I|ylcv%3fQq_v2D#srCW3V9X9`n;k___qPY8@yvz>qq7k_e0sO_ag}HV_d;YkT zWsOs4{uZ&kyN&GR)WX0Pw1Kg`n%2p-m#F4cNZLH71jg{<)uXagwizQnxcb4<&f2qO6w~C8 z{_%h$_qZej7#;e+yfok6#$h&+q@Bo}xCLx3EuBL*?s;Um3RG^N~Ii%as|;K3#~H ztIsSY1nPp3R#*G;zU@{*;v8JSHM=o@1?@SSsVGu-Q=JCJQ(J!Ttr=aOTNdlLe8 zKQJ0t1@lwy)T4hAC6x{2#eww+^47&9LSRI$=kcxPpKPP}@b#7*db{%Lgquu|9elcy zn#D+g{gX2=KlG2WfT6Q7QyZI(! z19xt4M@Q3o|ItniOL#)86Oicbm51?M^&ZGdI0zE35TR5mvL_GVFz# z2}-yo=(5w@TylTnBZe&)Gm5rV^qF4^>2u#oUa(Igaj8gC1;mmYaCs5_~K9Jyw?7iQ%_n$~?CQmK;Bsb3R$$RmYRAVva5 zQeQ?rbR#9pRW@}_<2V+2buYiYDPw#vPi~W@&fuF}0mhn+_A;z@F;X2Edts$umAkED zM`OVaJL%f#8OL5&5g2W2+Ma7IjL6MJ<(~b`1riuJM{VRDP|rAAyq26*sWu@n1`laf z#kE3hQ&%S(nmS#G5IE;IKG@GyrG(G1|POsPn z$8fo%)pkN)FO1q#S^kGtFn&{CmnyeUCIp^Aw5Prstf*iddeuZOX*7jgSLh+mPkmQ= zRLH0k-%hrFpG%;1oH^3!8)VON#!%a)@>cav#49AIjR7u~*{aI5Y14KJ99e}psER`iDzu5zE8b72cBPOLIOup_s7{htPt-fUksZgTKII;&&`VF%|1*g zRa|KtiS0d92z+k>*!T->ygTQp={0da@Wr zsWdE;%Cdpe1LUbsb`lBfh37_n1+Xc?zU1p6ZC36jHqa9~kK2iw_Z6jnnAc`PT7>mZ zy>_=aUf3zcgz-uk$D}GqR@x8qyge$+_W z()u##g*1&Tn*7eqGp+;AsTo$S>QAN~FZQP&RN5$vW3XuJ55)QA)DKg!mao@)zIR)h zQ_l-k=}6#6n&$BAXD>@eM~j9YTX0;|@2fYq{FD9>5uwh* z;eSOaoLy&Y%5!yEP9zFOE2z76!a1)g!+NAWQb!kiVI%>Kg7EM1KpZLt0IQb#u)$x%s;1dgO>wW=4A4IcZ-ZkGmetjUSJsEr*1 zi^$yN+RJixCX<*3Byc2^HM>(AdGhyLqN(@q;4|bq&=fPSSa6k6QCu@GQ1|Bekw+AfyK=O`t!euXCW%!a6Xc?z^9A-oeLF@+sNcczrDSDy2f1>kvuxer zym^jo03Y6Wz6oOw9M+#RpK8;YFKN6;c{L#_MJ!A%C8L(P%jzS?n(cyvxmVs~qJl?t zyiz)4Na2#doZQab2!XvCbo#~IxMx>>ez&DIx!>_A!v98FxuT}6#8`l=qRHmcxgz+W z`ZI_EUu8-!JWPCU6D~_9o)g#$X?m>Mp5H38Qil9#q_ac(t zV?c?)=iJ54sIIcwz*i)C0MExbqni2ow{ZUOz+`pKdyOd|y4C0`7iDYe`aP6h!z+T< z={u3ce^A0bF;UET(^@vF>L{@VZO}8g#B-GBQOHNuZd!s6*b9%eisqQ$Cdys&k-I9E zBLvPCy;Zv(ZJGGx0F%|KFAc5LyO?#yI8$0S35XWMv z?iCfShwr`D&&K=k`hHpJj>_A=i7_;KznGv|8`y#wrfHVuqE~{9y*0ZD?eS-Z1m;{u z&8>Q~wDmQ9jb1N4J{}2KcQbcnx79^n47J0xv5X^ z_2kEWzp8m8Jg#kY-t&%s8s=_b3lg-FnU!!et{!j^Cx^O|YH3JQqQ?w>*8N&Nu|H&x zv8-Du{bONwzP^_|!)g^&{W-Z~DBI=GO2lp*KnU!GIyrTZ+?C(+oJsl96pbEOM8*mrysF_Iy*|Zjh@Sb7E}=L`t~IRdV;j3mCtpW758{1 zmVIbttU5hf-%+?7&pMEY;f^^f1Xb=G|MkrFabcOYlM&*1_15~w%k6oOch+RLUail| zq%-^MlCqm?Jwo7~KHWXvo_K}H1=do&HW31Qp@-_Z%Oo z9b2N`MP09^tu83p+9&Dak*X1hGekurj-+b1X`9*DYD+}R4pGDg5_qIl#Nq9ttp2x} zvT3dchQC`G-EEH_pBik#@Ea{^lGQ3TVK|#J_h;G4zYZa=7aljY&ro9mbKhA}M&14#uJq-i~tquaMI&r*-|dJp>;I2Kmq zLt5pt?6a3WtMx+H9`+)2`%r;R3B9u;OYw*>&gOc=P+x+cP-4e=N4Dges@XU2J~>u+ zd~hU{HR4ky^9pS$rtAnYE+&lE``4_`3!Skcd26w9n);23&txkqH5Vp93A_?f%d2vE zCuOmFmF&c<#oY`X3+t<)ZMA2)`xRR@!$#!l8ERlJtd>TJJQJ?6*CEy-WmYtq2NJX* zTd30sX5Yv`SS^S%umzt{)JANs-`EEgyWQzUtbx67=9EZU_(Xj3C@n|Sbs?3^P=R}Q z@f`Eg^8tK;v!#0WlXFi*m0hLe>*X#6w%`|Vtcj-1;Pdk$rcNChcdsZRP`^v7LA5A% zQKYP`E$@^pW}qiXplwxCFHDFTNu}jnm5Coc!Cq7c5azT|oZnho&N@=jKn^t(0GDeMWWh8|5^bS+c*P1q} z+6}0DQ8lZo(O%BS#L?G!1uGhqv=&2S`5tC%{2`TsgV z?!L8}*eDx%U2oJqmQR^?h1gS>Bc~l^F-^M5K6#5X91CsS82U^PS8LHnad*_aXmFV+ z%w^sHd27{k6OM~C%^UTH*K$_zO1umkHj+7ykb3^t(fr`KrzTXlQC-HN{Pmb=%TSr5 z*%JaYO;H;~Dn_#jDx=)2i(QC<4hghv*yA^Cbk1i?qrbF{zNTvSwp>V%nca=% zog&iq%xuQ%e6(k%heO4l(q%N8X$pOQNVuO*Fltn*;#J;g%y&fQCAvPUx$8WmDvRlT zMr`XBLkR3e-(Ee&xw0We7m11;`;gik=n2|Z=lqwt?8?<`f=BcqHgH^Ay{I?+Ud36x zvDPx$F34DZH;?}DLMuMg*^Z%C_$I5)$L^}^*_H>QTSPd~4kC^ARP^#n##}!-N!M5H z3>*vJUXfNA_-Bn_m1o=*gG+TKUg5hswUL|E76Zp@6)SlUBc=7d^j_&rdA)2qaz26! zq@^Fx8Be`!S$Q>W5hU6+S}A_X>|tPw@wTRUUMI%K?XqJyl8Qdt8Y-Ub-XwM%k2kOv zY85HLRy7d`vmc9j@469%De6TjamVl%j$iJI8s|dEJkSQ!4*F+S5o4BdF+YDiA@DlJ zD@Lt3>s1l$Tb&UXlVb>hUg4Fm)NP+Ci^c&b#Z32DqI*RfbROnVpYOUQEHz*(Lka-O2psE7Vfj_hzY?V#0GjoHK}bnycNZh>WS2{AqKXfS7=Y| zgC&Of-AGkLJ!5-kY$xr`n-U86ak#kC){ut!CH@E0a=%kZ1dN zF{?#)xxM{%LSO|^q?Kmk^#LBl`bihRWh^tHgn8<-7``m-iwUc^Qm>vle&L(143u>% zCJ_RAp|?s&{{9l*J9nsD7txXs*efJ>sJZ!zSiWhsW&djVQ%An0^f0+TB8cI4S@a6M z)wD@XAMle`5@pn_NhYkiaDL7)bC_Qow+Xy%+PH9&`B#+<^=o*Bvhg)<$#ky1+f~?o z+^gJ{zUIvlalGvN2c#F$>d8=IJD*s0q+B<3JJH^t?XamQ^44+E9bIHy;;p-?lA+vNk68LQj?Ww!A z?@LkedpFr#lx3*QL(ScixG(0gJ)yiEw&>oJYa`yLB(S~mtOSg<73|p{<0@_o1GHxYhnW0VO=Y@fUKouj>s&>sR zd)arRuYA6`D^NUKPsxCAzT+7NkY!z7ORa8XnCbmo11)0kL3X^~kStLAkcn7T!c zl*{(_F=Gqn!^M$Q#>LUGY+2W#@^P=*=}2G>eWaC6-+u)&-|HtoKdZq}tAqrOq@rct ztzc~r_LDK*^*FZRJa8nXHCXXG`^4HxbD8VnNYJnP+T?D$-=4Wdk%CGUr3$-qoH=%A zFBkaTAq4h9t&z(0QrphhUZj#tS?eg#24;MpQ0}yD=h>P6?7C1L-A`Sujh>@x$&ME^ zLSQe{%&0fAy}68XTPn)iPK619>L#?Op1UibvA*GM^3k*cgh1bsRxwyN(^%cx{<41X zb#c1-EdBgTrF;*cZ9)Q%g38b|IFaofpCFxQEG7hIi$#r&T0zE6WgVu)$*I#<5`_>F zXj^4mtQEob2gS+e=k^f24=QUYaiaZF7CM>9j%SL=AseIhBR&y)Y?WV3sMta!m-^x! zJdPc!+gSd5xfsb2j|7gSGGLw?!py@%rOV{20$b1%^ib0_Sa)HSJp<&i7I}zQs4t?L zqQb9y+3)sYa@vZ!L{o(Js10Y+ZkD|#L8iW{LNs1@e@v((_0wT7yl%r)>Pqza{W!Zb zGDd#5*ic{#sVj7%DqHDn{}MqdjUP&Uy~hCnd;U(G$r$@VcVcuDrg2jX4}Aea}uIS0c_F?WuU@ zJBz)t)Nk#GBRxH{YbWY8!s7S`=Z7X#Z&AfoXYZq42Ncn>Qekf-P;=#)J6<2AM)FK} zq|Qf+H^sb{E24gS1wvqEW~9}7d+Czi#}$#Qax%$cjs)7a*s%4!s))L+s(4`w`i^;| z)q6>DcWLE*R78Kb6GfLe^01@6d{(hk6Xp#|9_h?m4{yP}C0FxkoaiDa#M{ae2OQ+m zth?rABLaC4Ut&Uy2GwQU$Y>?YCO#DF#U+6RYH?7bqt=7DUy2S*!sMb(RwO$m>V4h~ z{b+7EH=GxB+M~Qm-Qg_PS7|RhXXg>vf&^wmRd1GCev(OHEv5IiiUM0+yv)UIth@37 z<9;>aNGiY2tayH9`Y7q`;G@s>xnchNRb?3Oea?h$GW3n?<(n`O7BEzvxe!D0i=y5K zM^eA%m)ePdO~d4k%Ug8ph3Xwjqz04|eJ_uY(#w{3g#>!2Y5OzliOB`%P8I!(z0sQI^!uX1#2N=-F2LtI&1S-u>g6g)tnIt8S47t_Kg%~mN)iG! zEJ&*=F;7m25+_T_SwJ4$-&$e? z2UNYZQQz)o)OE_K_@KU2pGX#?J)Gt8pk@+VkidMMN&~sBnml#=yQpx|iD;wn?t0Pq ztNHq(l)sK{NGnxw!LhK&Z?rO_V&g$_5$@TfA%9f$h$&!xVcxD~b6&U7am(|n)-ZXp zW1u*;sx)aq%><65R>HsZk>bWgb;LW(S_IV4jd64a~qgZjyg z?#o5r&&xPIm+*N-iHxa3rD>9%SXN~x#}=G9j-)Dpoo`_{ylyIr-z;hj3(C-&oY(or zLZ3{i96)V@%4QYQ&{&Xhhku(~nJ5SFt3HmTY6+dMZ@gdIR(#pyN^Bru6M0Tw(7F+i zZuniDkL$^ejqh%^`RY3r2!Z**`X^`UNv$WEOM6+5>CK2con_P~Vu#&p+tRNGlBcjf-F#=zWU z_Z(?Tyj{oH+UfCUkI1n$-=Z4?{Jc2|5T{+Qc^>_B!%YO9x+)tZl7X2Wnd zQt1FXZelTi*vK+p)x{6FTdKu>O?#g&kCxzZK$M*vqf)JBi=sXCg*W#KakJSe@qBv-iK#|{+GssBMKpSOLsYX5BsMU<8soV&t>(20V(;ml zqUE+ALf|}*)-h=(XV+L>ZuN5$-6N~_X*YlMI8pA|oNmHi zxXVR}Deu$SbWxX3PNT92d#31$XXnBu&dG?IWta7$QT* zqYhvpV8>ix>yBc1Hr;XKf`%Bs>mH=o-qQO*c2NOlKtN3eH*y;-WgzPeT) z)U$m;+c178E>YU%w_(_V1U**K51*%pM~sp_HGeT7f$<->GpXX83!FDQic!+ONHKDz za2`04(q;JCrO#BWiGcJb-q?Z!?o2AhudS`eQ$=_OU-m)*d(l+EGhote4B0H7#`l^Vq zwo5&*7t*-?RMAc|*Lzwjk{tKjwhvp7z!)PZq#qoR?hDAF{D#oM-tnIJ6^@&ll6`c;ao1CuFl}suG`s` zr)}kk*&hVu3nMwARkVbn-U(z*3 zhJW2F&Q?qmJzVnhC%szo?!W9IaV)rQSFr#Gd&oiS*Ngczn-Kzg(f3`eOTA^IJxSu8 z<4{8Md{&hA-|;0qLq&-ld0T*gs1anos{XBMPv7;IH%3H=d_@BU_CgP-jX_JjW#-_Y zWzTU3d7k0+{N9=%?mj2ggez~FVf_1|Ao(K8MoyXcm=M?t-}*IeVBuitukxOU=X%bu z1!s=7RU~Vv269*P8Zs((9f2*l2Y?=`7_6EuvcgY=r0*k5Zr*auoYb=mZ*yv?30Gcp z_roWjhg|fgkQ~F02qYQ|ykm}S9L#H#T}EiND)P-IZL?d;AxkHSRe3L%*L@7-W9u&` zyAkN2dR}>zl~vX>klpsJAO!Y8+p1Em<6cqzLqWMC*d%kSjMjC7^5m>Wah*xmwKh@9 z#i1SrWwdQ$Lg0!OX;uH@_e-LjT00M0R$tz+dVg?`hezd=CDJR9sw5a!M2W3-s!HtgeDdS| z$`V^}PIPDD$>4*j+akSFEt$DJGwt|J`dxu)A|H>uJS4|=^iW+_y|lF8rJ<>o z-w?dlkie1Dd;5g;@^J5qV(-LD0wdXQSB(-qZnlwu`L2r1)vpU|!M!&eNz*E2d@0}siPGl1TXN$DT-a5|Jc+h5__}#s-@i6ti-h6H|{@U@Q z30J>W>Ri!7*SF>imw!@EpXNOq7@t2(5=Wj_A_T6mk=C?fUX6@8*CqlFzcNyQJY=`1((N*C{IadKT@FKT1%(;#V_KOju*hlp2r<)dwNU8Q)+ zLEWp#jc*!}UxUJ7HoV%ZAYOZEiV2^n>dVi5fB7hP9_f}IC9oI%;!qpYj`+!nBW+}d z7hMQ}zdlGS4ZVYpoabjFzlI+r#|rNvw5=+Wwsn;av#QA_A2tyi_zOVi5%#;646WcS z7tY&H2(*E}SL!>?Sr0imwuqeDMF?y`8`LZR?>4eobbHyZ{8+L>kH2swVo#c#WCY({ z!&1#7`;V(ayZ%0q8GGCf)m_1$mZ}Rk9$i%D{Leo*goZ5{ z_C<4gm0o+&^yB~cjXL!I5ZLQSmC_JX`u{`x@X%5(^9S)~_0xaz>PIijxIbvi{Qi9& zSkdzb@n?O`f3tzd3h5su=0=ifQ~lm4WVNF_{sXT++Qe_QzHJh0L@R_p$E0;}s_RT@fQ-4(1WgdS1?Ysp|W zmwzX)C2aa-z0=J5Iq$m`M>`4-u|~0dUUb2&$%xHy{~5#`TOWvpd`z&!WKLq zX!~yq|2(>PSI*Y&U$Dv<_lolB^NiQJrAkx|p~koTNs@ue_i*PcIW0Kue-VEI|F{w{ z%L!&b`KxozUv%c!3p1?zJAoeRMPKXRSMB?I9{=%byQ&N{$SE}^rwg7k~aDtSy9r|0F& z5bvV@N&J^rc=f$h=RahYeU3fq6~l3FAnVz%?Kq-G3*r z1+NS|hSUZ=`|z&Ck#dNnKe;4{M-iX9|4!f&8J}?fMf~L&y{_;o!SCvSbEC0Bouopfb|L*_Kdk~-0_{`5C z)Eq2-l1N4%^zh%06=n)R0`HqYNB{G`^ynguM}ZP}Zt-40dz8Qy+-Jc1=HCgt2XRM7 G)BX=XKqZa< literal 0 HcmV?d00001 diff --git a/python/test/model/meshes/ur5e/collision/wrist3.stl b/python/test/model/meshes/ur5e/collision/wrist3.stl new file mode 100644 index 0000000000000000000000000000000000000000..d6bdb411e5eacf6ee17746bfc19665143ae59514 GIT binary patch literal 7184 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 g&wlt!$aB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 94fda00ea..566e59b0a 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(control_libraries VERSION 7.4.3) +project(control_libraries VERSION 7.4.4) # Build options option(BUILD_TESTING "Build all tests." OFF) diff --git a/source/robot_model/include/robot_model/Model.hpp b/source/robot_model/include/robot_model/Model.hpp index 098c621f4..af3276e2f 100644 --- a/source/robot_model/include/robot_model/Model.hpp +++ b/source/robot_model/include/robot_model/Model.hpp @@ -2,11 +2,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -67,18 +69,28 @@ class Model { std::vector frames_; ///< name of the frames pinocchio::Model robot_model_; ///< the robot model with pinocchio pinocchio::Data robot_data_; ///< the robot data with pinocchio + std::optional> meshloader_callback_; ///< callback function to resolve package paths + pinocchio::GeometryModel geom_model_; ///< the robot geometry model with pinocchio + pinocchio::GeometryData geom_data_; ///< the robot geometry data with pinocchio OsqpEigen::Solver solver_; ///< osqp solver for the quadratic programming based inverse kinematics Eigen::SparseMatrix hessian_; ///< hessian matrix for the quadratic programming based inverse kinematics Eigen::VectorXd gradient_; ///< gradient vector for the quadratic programming based inverse kinematics Eigen::SparseMatrix constraint_matrix_; ///< constraint matrix for the quadratic programming based inverse kinematics Eigen::VectorXd lower_bound_constraints_; ///< lower bound matrix for the quadratic programming based inverse kinematics Eigen::VectorXd upper_bound_constraints_; ///< upper bound matrix for the quadratic programming based inverse kinematics + bool load_collision_geometries_ = false; ///< flag to load collision geometries + // @format:on /** * @brief Initialize the pinocchio model from the URDF */ void init_model(); + /** + * @brief Initialize the pinocchio geometry model from the URDF and the package paths + */ + void init_geom_model(std::string urdf); + /** * @brief initialize the constraints for the QP solver */ @@ -98,6 +110,13 @@ class Model { */ unsigned int get_frame_id(const std::string& frame); + /** + * @brief Find all the package paths in the URDF and replaces them with the absolute path using meshloader_callback_ + * @param urdf string containing the URDF description of the robot + * @return vector of the package paths + */ + std::vector resolve_package_paths_in_urdf(std::string& urdf) const; + /** * @brief Compute the Jacobian from given joint positions at the frame in parameter * @param joint_positions containing the joint positions of the robot @@ -185,12 +204,37 @@ class Model { const state_representation::JointPositions& joint_positions, const std::vector& frames); + /** + * @brief Generates a list of collision pairs to exclude based on the kinematic tree of the model + * @return the list of collision pairs to exclude + */ + std::vector generate_joint_exclusion_list(); + public: /** - * @brief Constructor with robot name and path to URDF file + * @brief Construct with robot name and path to URDF file + * @details If the URDF contains references to collision geometry meshes, they will not be loaded into memory. + * To enable collision detection, use the alternate constructor. + * @param robot_name the name to associate with the model + * @param urdf_path the path to the URDF file */ explicit Model(const std::string& robot_name, const std::string& urdf_path); + /** + * @brief Construct a robot model with collision geometries from a URDF file + * @details If the URDF contains references to collision geometry meshes, they will be loaded into memory. + * Subsequently, the check_collision() method can be used to check for self-collisions in the robot model. + * If geometry meshes are referenced with a relative package path using the `package://` prefix, then + * the optional meshloader_callback function should be defined to return an absolute path to a package + * given the package name. + * @param robot_name the name to associate with the model + * @param urdf_path the path to the URDF file + * @param meshloader_callback optional callback to resolve the absolute package path from a package name + */ + explicit Model(const std::string& robot_name, + const std::string& urdf_path, + const std::optional>& meshloader_callback); + /** * @brief Copy constructor * @param model the model to copy @@ -220,6 +264,26 @@ class Model { */ static bool create_urdf_from_string(const std::string& urdf_string, const std::string& desired_path); + /** + * @brief Check if the links of the robot are in collision + * @param joint_positions containing the joint positions of the robot + * @throws robot_model::exceptions::CollisionGeometryException if collision geometry is not initialized + * @return true if the robot is in collision, false otherwise + */ + bool check_collision(const state_representation::JointPositions& joint_positions); + + /** + * @brief Getter of the number of collision pairs in the model + * @return the number of collision pairs + */ + unsigned int get_number_of_collision_pairs(); + + /** + * @brief Check if geometry model is initialized + * @return true if the geometry model is initialized, false otherwise + */ + bool is_geometry_model_initialized(); + /** * @brief Getter of the robot name * @return the robot name @@ -517,7 +581,6 @@ inline Model& Model::operator=(const Model& model) { return *this; } - inline const std::string& Model::get_robot_name() const { return this->robot_name_->get_value(); } diff --git a/source/robot_model/include/robot_model/exceptions/CollisionGeometryException.hpp b/source/robot_model/include/robot_model/exceptions/CollisionGeometryException.hpp new file mode 100644 index 000000000..a52122ab9 --- /dev/null +++ b/source/robot_model/include/robot_model/exceptions/CollisionGeometryException.hpp @@ -0,0 +1,11 @@ +#include +#include + +namespace robot_model::exceptions { +class CollisionGeometryException : public std::runtime_error { + +public: + explicit CollisionGeometryException(const std::string& error_message) + : runtime_error("Collision geometry error: " + error_message) {} +}; +} // namespace robot_model::exceptions diff --git a/source/robot_model/src/Model.cpp b/source/robot_model/src/Model.cpp index 1cd4b34ed..0f6f798bd 100644 --- a/source/robot_model/src/Model.cpp +++ b/source/robot_model/src/Model.cpp @@ -1,21 +1,39 @@ -#include +#include +#include #include #include #include "robot_model/Model.hpp" #include "robot_model/exceptions/FrameNotFoundException.hpp" #include "robot_model/exceptions/InverseKinematicsNotConvergingException.hpp" #include "robot_model/exceptions/InvalidJointStateSizeException.hpp" +#include "robot_model/exceptions/CollisionGeometryException.hpp" namespace robot_model { +Model::Model(const std::string& robot_name, + const std::string& urdf_path, + const std::optional>& meshloader_callback + ): + robot_name_(std::make_shared>("robot_name", robot_name)), + urdf_path_(std::make_shared>("urdf_path", urdf_path)), + meshloader_callback_(meshloader_callback), + load_collision_geometries_(true) + { + this->init_model(); +} + Model::Model(const std::string& robot_name, const std::string& urdf_path) : robot_name_(std::make_shared>("robot_name", robot_name)), - urdf_path_(std::make_shared>("urdf_path", urdf_path)) { + urdf_path_(std::make_shared>("urdf_path", urdf_path)) + { this->init_model(); } Model::Model(const Model& model) : robot_name_(model.robot_name_), - urdf_path_(model.urdf_path_) { + urdf_path_(model.urdf_path_), + meshloader_callback_(model.meshloader_callback_), + load_collision_geometries_(model.load_collision_geometries_) + { this->init_model(); } @@ -29,9 +47,60 @@ bool Model::create_urdf_from_string(const std::string& urdf_string, const std::s return false; } +std::vector Model::resolve_package_paths_in_urdf(std::string& urdf) const { + std::set package_names; + std::regex package_path_pattern(R"(filename=\"([^\"]+)\")"); + std::regex package_name_pattern(R"(package://([^/]+)/)"); + + std::smatch matches_file; + std::smatch matches_package_name; + auto start = urdf.cbegin(); + auto end = urdf.cend(); + + // Extract package paths + while (std::regex_search(start, end, matches_file, package_path_pattern)) { + std::string path = matches_file[1]; + if (std::regex_search(path, matches_package_name, package_name_pattern)) { + package_names.insert(matches_package_name[1]); + } + start = matches_file[0].second; + } + + std::vector package_paths; + for (const auto& package_name : package_names) { + if (meshloader_callback_) { + auto package_path = (*this->meshloader_callback_)(package_name); + auto target = "package://" + package_name + "/"; + auto replacement = package_path; + size_t start_position = 0; + while ((start_position = urdf.find(target, start_position)) != std::string::npos) { + // Replace the target with the replacement string + urdf.replace(start_position, target.length(), replacement); + // Move past the last replacement + start_position += replacement.length(); + } + package_paths.push_back(package_path); + } + } + return package_paths; +} + void Model::init_model() { - pinocchio::urdf::buildModel(this->get_urdf_path(), this->robot_model_); + std::ifstream file_stream(this->get_urdf_path()); + if (!file_stream.is_open()) { + throw std::runtime_error("Unable to open file: " + this->get_urdf_path()); + } + std::stringstream buffer; + buffer << file_stream.rdbuf(); + auto urdf = buffer.str(); + + pinocchio::urdf::buildModelFromXML(urdf, this->robot_model_); this->robot_data_ = pinocchio::Data(this->robot_model_); + + if (this->load_collision_geometries_) { + this->init_geom_model(urdf); + } + // get the frames std::vector frames; for (auto& f : this->robot_model_.frames) { @@ -42,6 +111,79 @@ void Model::init_model() { this->init_qp_solver(); } +void Model::init_geom_model(std::string urdf) { + try { + auto package_paths = this->resolve_package_paths_in_urdf(urdf); + pinocchio::urdf::buildGeom( + this->robot_model_, std::istringstream(urdf), pinocchio::COLLISION, this->geom_model_, package_paths); + this->geom_model_.addAllCollisionPairs(); + + std::vector excluded_pairs = this->generate_joint_exclusion_list(); + + // remove collision pairs for linked joints (i.e. parent-child joints) + for (const auto& pair : excluded_pairs) { + this->geom_model_.removeCollisionPair(pair); + } + + this->geom_data_ = pinocchio::GeometryData(this->geom_model_); + } catch (const std::exception& ex) { + throw robot_model::exceptions::CollisionGeometryException( + "Failed to initialize geometry model for " + this->get_robot_name() + ": " + ex.what()); + } +} + +std::vector Model::generate_joint_exclusion_list() { + std::vector excluded_pairs; + // Iterate through all joints, except the universe joint (0), which has no parent + for (pinocchio::JointIndex joint_id = 1u; joint_id < static_cast(this->robot_model_.njoints); + ++joint_id) { + // Find the parent joint of the current joint + pinocchio::JointIndex parent_id = this->robot_model_.parents[joint_id]; + + // TODO: Replace this logic with actual geometry index lookup + auto getGeometryIndexForJoint = [](pinocchio::JointIndex joint_id) -> int { + return static_cast(joint_id); + }; + + int geometryIndex1 = getGeometryIndexForJoint(joint_id); + int geometryIndex2 = getGeometryIndexForJoint(parent_id); + + // Check if the geometry indices are not equal + if (geometryIndex1 != geometryIndex2) { + excluded_pairs.push_back(pinocchio::CollisionPair(geometryIndex2, geometryIndex1)); + } + } + return excluded_pairs; +} + +unsigned int Model::get_number_of_collision_pairs() { + return this->geom_model_.collisionPairs.size(); +} + +bool Model::is_geometry_model_initialized() { + return !this->geom_model_.collisionPairs.empty(); +} + +bool Model::check_collision(const state_representation::JointPositions& joint_positions) { + if (!this->is_geometry_model_initialized()) { + throw robot_model::exceptions::CollisionGeometryException( + "Geometry model not loaded for " + this->get_robot_name()); + } + + Eigen::VectorXd configuration = joint_positions.get_positions(); + + pinocchio::computeCollisions( + this->robot_model_, this->robot_data_, this->geom_model_, this->geom_data_, configuration, true); + + for (size_t pair_index = 0; pair_index < this->geom_model_.collisionPairs.size(); ++pair_index) { + const auto& collision_result = this->geom_data_.collisionResults[pair_index]; + if (collision_result.isCollision()) { + return true; + } + } + return false; +} + bool Model::init_qp_solver() { // clear the solver this->solver_.data()->clearHessianMatrix(); @@ -111,7 +253,7 @@ std::vector Model::get_frame_ids(const std::vector& f } else { // throw error if specified frame does not exist if (!this->robot_model_.existFrame(frame)) { - throw (exceptions::FrameNotFoundException(frame)); + throw exceptions::FrameNotFoundException(frame); } frame_ids.push_back(this->robot_model_.getFrameId(frame)); } @@ -126,7 +268,7 @@ unsigned int Model::get_frame_id(const std::string& frame) { state_representation::Jacobian Model::compute_jacobian(const state_representation::JointPositions& joint_positions, unsigned int frame_id) { if (joint_positions.get_size() != this->get_number_of_joints()) { - throw (exceptions::InvalidJointStateSizeException(joint_positions.get_size(), this->get_number_of_joints())); + throw exceptions::InvalidJointStateSizeException(joint_positions.get_size(), this->get_number_of_joints()); } // compute the Jacobian from the joint state pinocchio::Data::Matrix6x J(6, this->get_number_of_joints()); @@ -155,10 +297,10 @@ Eigen::MatrixXd Model::compute_jacobian_time_derivative(const state_representati const state_representation::JointVelocities& joint_velocities, unsigned int frame_id) { if (joint_positions.get_size() != this->get_number_of_joints()) { - throw (exceptions::InvalidJointStateSizeException(joint_positions.get_size(), this->get_number_of_joints())); + throw exceptions::InvalidJointStateSizeException(joint_positions.get_size(), this->get_number_of_joints()); } if (joint_velocities.get_size() != this->get_number_of_joints()) { - throw (exceptions::InvalidJointStateSizeException(joint_velocities.get_size(), this->get_number_of_joints())); + throw exceptions::InvalidJointStateSizeException(joint_velocities.get_size(), this->get_number_of_joints()); } // compute the Jacobian from the joint state pinocchio::Data::Matrix6x dJ = Eigen::MatrixXd::Zero(6, this->get_number_of_joints()); @@ -228,13 +370,13 @@ state_representation::CartesianPose Model::forward_kinematics(const state_repres std::vector Model::forward_kinematics(const state_representation::JointPositions& joint_positions, const std::vector& frame_ids) { if (joint_positions.get_size() != this->get_number_of_joints()) { - throw (exceptions::InvalidJointStateSizeException(joint_positions.get_size(), this->get_number_of_joints())); + throw exceptions::InvalidJointStateSizeException(joint_positions.get_size(), this->get_number_of_joints()); } std::vector pose_vector; pinocchio::forwardKinematics(this->robot_model_, this->robot_data_, joint_positions.data()); for (unsigned int id : frame_ids) { if (id >= static_cast(this->robot_model_.nframes)) { - throw (exceptions::FrameNotFoundException(std::to_string(id))); + throw exceptions::FrameNotFoundException(std::to_string(id)); } pinocchio::updateFramePlacement(this->robot_model_, this->robot_data_, id); pinocchio::SE3 pose = this->robot_data_.oMf[id]; @@ -311,7 +453,7 @@ Model::inverse_kinematics(const state_representation::CartesianPose& cartesian_p const std::string& frame) { std::string actual_frame = frame.empty() ? this->robot_model_.frames.back().name : frame; if (!this->robot_model_.existFrame(actual_frame)) { - throw (exceptions::FrameNotFoundException(actual_frame)); + throw exceptions::FrameNotFoundException(actual_frame); } // 1 second for the Newton-Raphson method const std::chrono::nanoseconds dt(static_cast(1e9)); @@ -378,14 +520,14 @@ void Model::check_inverse_velocity_arguments(const std::vector& frames) { if (cartesian_twists.size() != frames.size()) { - throw (std::invalid_argument("The number of provided twists and frames does not match")); + throw std::invalid_argument("The number of provided twists and frames does not match"); } if (joint_positions.get_size() != this->get_number_of_joints()) { - throw (exceptions::InvalidJointStateSizeException(joint_positions.get_size(), this->get_number_of_joints())); + throw exceptions::InvalidJointStateSizeException(joint_positions.get_size(), this->get_number_of_joints()); } for (auto& frame : frames) { if (!this->robot_model_.existFrame(frame)) { - throw (exceptions::FrameNotFoundException(frame)); + throw exceptions::FrameNotFoundException(frame); } } } @@ -412,25 +554,25 @@ Model::inverse_velocity(const std::vector& dX.tail(6) = cartesian_twists.back().data(); jacobian.bottomRows(6) = this->compute_jacobian(joint_positions, frames.back()).data(); - if (dls_lambda == 0.0){ + if (dls_lambda == 0.0) { return state_representation::JointVelocities(joint_positions.get_name(), - joint_positions.get_names(), - jacobian.colPivHouseholderQr().solve(dX)); + joint_positions.get_names(), + jacobian.colPivHouseholderQr().solve(dX)); } // add damped least square term - if (jacobian.rows() > jacobian.cols()){ + if (jacobian.rows() > jacobian.cols()) { Eigen::MatrixXd j_prime = jacobian.transpose() * jacobian + dls_lambda * dls_lambda * Eigen::MatrixXd::Identity(jacobian.cols(), jacobian.cols()); return state_representation::JointVelocities(joint_positions.get_name(), - joint_positions.get_names(), - j_prime.colPivHouseholderQr().solve(jacobian.transpose() * dX)); + joint_positions.get_names(), + j_prime.colPivHouseholderQr().solve(jacobian.transpose() * dX)); } else { Eigen::MatrixXd j_prime = jacobian * jacobian.transpose() + dls_lambda * dls_lambda * Eigen::MatrixXd::Identity(jacobian.rows(), jacobian.rows()); return state_representation::JointVelocities(joint_positions.get_name(), - joint_positions.get_names(), - jacobian.transpose() * j_prime.colPivHouseholderQr().solve(dX)); + joint_positions.get_names(), + jacobian.transpose() * j_prime.colPivHouseholderQr().solve(dX)); } } diff --git a/source/robot_model/test/fixtures/meshes/ur5e/collision/base.stl b/source/robot_model/test/fixtures/meshes/ur5e/collision/base.stl new file mode 100644 index 0000000000000000000000000000000000000000..c5881046676ce1ca5c88bbc482e05473ffb616bc GIT binary patch literal 21084 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 IuRLq(Kf)s8ZvX%Q literal 0 HcmV?d00001 diff --git a/source/robot_model/test/fixtures/meshes/ur5e/collision/shoulder.stl b/source/robot_model/test/fixtures/meshes/ur5e/collision/shoulder.stl new file mode 100644 index 0000000000000000000000000000000000000000..ba79079f6e9386eb2616ad9c2a4f20b48373be1c GIT binary patch literal 70084 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 literal 0 HcmV?d00001 diff --git a/source/robot_model/test/fixtures/meshes/ur5e/collision/upperarm.stl b/source/robot_model/test/fixtures/meshes/ur5e/collision/upperarm.stl new file mode 100644 index 0000000000000000000000000000000000000000..02f6edfdde2bf08188ec6fc5d73f9b8d8773e0d0 GIT binary patch literal 99684 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 literal 0 HcmV?d00001 diff --git a/source/robot_model/test/fixtures/meshes/ur5e/collision/wrist2.stl b/source/robot_model/test/fixtures/meshes/ur5e/collision/wrist2.stl new file mode 100644 index 0000000000000000000000000000000000000000..667d60e302146b015bee66af38ce88973c87ba01 GIT binary patch literal 67584 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 G)BX=XKqZa< literal 0 HcmV?d00001 diff --git a/source/robot_model/test/fixtures/meshes/ur5e/collision/wrist3.stl b/source/robot_model/test/fixtures/meshes/ur5e/collision/wrist3.stl new file mode 100644 index 0000000000000000000000000000000000000000..d6bdb411e5eacf6ee17746bfc19665143ae59514 GIT binary patch literal 7184 zcmbW6X+V|L*2kA43W6!4h)ShF$oE$<>8!Www=EUuKaS9RMM?U?xA74JQ*KhB&*R$99uf5Os31htH zBz~Ou$;_0*1n>A2-tQ+ao;zk{JpDiaatyBi`Pe|BPxbzN_XkY+^|9nvGLCD)sZdg?a4YB|KGQEy_M)M@1@*)YNhh~&P^Kgb7x1wE$qh-Ck3sB5ADJx z8bq~6xnr&@C}nGd+*P8HT^?)pCA*~jvN#>B(nCs>D1-i=&zqF+ zk{adq5(^t&dfV`7J)I1AxpTJ+trT~gU9Unl-HjwZQ=Tt%@BEX7G5K{-v((Sn z`8PGkg@_Pcb}w!H9)-sm1286Wjm~V6;T5~^ri9;#D1GB%p5f+wq)0M6Ie464i#Kv|HxginxRX>+Le_kZdtzN{@%KS@K*}?wX zSmVIKY^|NM)GpY<-nr$>h&oQWt$n^4z|<% zJi75czrHRIXjScXPw_F%ahA&&gXnN%wSKbdd#PVZm3ruOcl9$PQ=^uSRq({%_a{15 zQLBycd?J5$BwWZ^%+$~xGZgpHD)S%J$;k7*3nlKgEk|ky9>6gVKAhQsoxN7d5^wC5 zmajM^b$e0D5^^3HUaeZ4$V*eUD4R~)kTEBaz?q0LeoN-{^$Vo%!UBQ7>_VF8?t?V> z%8OpS+>i6!dlpJoCmV}j@w;?=&Q;UXEjAVu`A`y1;#{RvUKi-d!z)5K`irzk%=Df2E=S2c55z*A_mD+*&>)M);vBKHidw-s_+{o<-_U=qPi4Uq{0-9RPy3@tJ z!G)1MrLl(9yHU;z3Cseb8t09z^-6iGA`qkai+o6WsQ?M%nglH&O03IiCHBm74MHni zPh6uz#5+8*tFKbcWFb~a zWlh}m1zB))v_hIl`f^pPezXt&m!KbLg<3+y;}h90JI#Dsd}l!?@TWF>Urt5Te`GTqeC`R0CT>qk>T&n)2ka?3*cD2d@~(&a0&u6SP9w*vE-L{!`IVlgseIIz|);oQY_zV-W8cHPy7J&R-yK zPe>EB{qJS@#Giq(-N{)(4q}ap@}Fb!Se?a2HTxUN`FM1K+$%3tPB$kDI*3+C6M3w4 z|lG@kIw+_k|x{3PlORe4x zHkNIDB2;anwBV&$!e4HDSz@@LwMd|f61{jcPOsQMQ8^g?RK_0XKPT#2d(nSX>Js)u zM5)uK=#lx}rhto`IBF_dA#LP&Q*BwZQK{{dKMleWTo3CQ(PI0)`la``ST|;d33`sS zNJJI()lVf)v|8GS30@(AJqpqEDS`UVP91sV8+SGA03tWuQD&J`w(I=SSGsW9`~ZE~ z+AL*W<`5lw^>J64T4Qyf=hv?gF-ep4vM+0;ct3j`*T9|uX`-V8=IOCB_9<=mofo`9 zE9_B-K3p2!+9AgrXf8!7jE_hJHg|0$uKIKl^aE+!AJOP>VWzXqX5O)|NVrW5dV9BZ zZ={VaT16@<1JS}7f74xq@JJ}IqLp`6xw2!ejVS^7!d--@^h}sCy4K8FxTDZBJV+{1 zTvpiF-}8zj@$44vIIN{d#_;Ry&db>8p%v0bZJ%dv)mzN`;J@z%qrYf{{R&Y;CnxJ= zgXs6MLooV|1kOaXKlkvdm+G9`-${t>#9p_p;|x8o-0J#@p67qlrt~FIe!Z*3-gl_- zfnoJ%O*a(@{F+4f`+d?%Y>Mt~!7p{vCApREcFI8&iO_FiT8Yxvx?9i+*At1t*c zQLLg!8;<~u4`sDFg74dRPUtdlUXk#55zZIP@#LK^jg+yQ$iGF*FQk$^toz7>{R&ai zAGPxRwR8EpJUrEFR(={b+42?TcGNE_9s!#Vk#l3iM5W2~UgXoa+)1~dMWEtk2r%{P{#zeu3Y z6Mc5}nv@Xy8s9r>yr8w%C)7O2GPO^sWLK)1j1Kt^d4du?um>OE_CCiERA+n}FtoOA zn7nyIFTOlGjALCw8rLK0;h8BpmPPW$a4#;*3!dOiMA?aVwOMof@QxSW&@1D=R_cti zwBx+0_?V)Vmb+EVTJi5!RINw+MJe1XI@Fos0)Lo-_UQ9JoU8TyU{ z-X@8@xY37y+{TM9aA_3k?xY7#r0igo`E1xFp-K?>=)HNaS5H34r$xpQB*aQxulDA& z(u*fucrN1z5?GIknpeb1>o1x4I|ZFoJPlaYrmR^aoo%dQF#~QHcbs(RL-P6KGkCLd zO2bNqR!9?l75qf*<?s+zVGG4d-%)3of8SX`w$)UX`@<_%sh1L z5@8>yN4`>q#@g86@4i;Vv+G_Er0%=8nm@Tzq+(7Wfe|6|SH%h(4_UDfCi-u1 zq`vZ;7vDPnwuD)W1kOa{vOiCKW1yLj*lHGHf&|V)v^{OKKHkZRS45`@@4J6b>a1=J zQ`wd1K;f;|c&#mt(CH&*-nRH78COR_T%%z?y*l}F8ZR5Qkl{$)l55K1vMQGAo+4o- zB2v7E=x(vy_~HKllJOpe1m3BP+ue4qFK_6<>&I=8(F(m1i9Oj|j~(pE|D2yAqZQKV zEm4~_UC9U_4<5xrhNJs-O3 zriS-Sv_jhWJ!(O<_U8P_e5i6#!x7A2%uS*(V_&F&Kc(?QX>Y4&g*19g^yPkU-F%`a g&wlt!$aB - + - + - - - robot_interface/universal_robots/UniversalRobotsInterface - 172.16.0.2 - 1.0 - - - - -6.28318530718 - 6.28318530718 - - - -3.15 - 3.15 - - - - - 0.0 - - - - - -6.28318530718 - 6.28318530718 - - - -3.15 - 3.15 - - - - - -1.57 - - - - - -3.14159265359 - 3.14159265359 - - - -3.15 - 3.15 - - - - - 0.0 - - - - - -6.28318530718 - 6.28318530718 - - - -3.2 - 3.2 - - - - - -1.57 - - - - - -6.28318530718 - 6.28318530718 - - - -3.2 - 3.2 - - - - - 0.0 - - - - - -6.28318530718 - 6.28318530718 - - - -3.2 - 3.2 - - - - - 0.0 - - - - - - - - - - - - - - + + - - - @@ -196,9 +87,6 @@ - - - @@ -218,9 +106,6 @@ - - - @@ -240,9 +125,6 @@ - - - @@ -262,9 +144,6 @@ - - - @@ -284,9 +163,6 @@ - - - @@ -306,9 +182,6 @@ - - - @@ -322,9 +195,15 @@ + + + + + + - + @@ -390,7 +275,7 @@ the UR controller. --> - + @@ -408,4 +293,4 @@ - + \ No newline at end of file diff --git a/source/robot_model/test/tests/test_collisions.cpp b/source/robot_model/test/tests/test_collisions.cpp new file mode 100644 index 000000000..a309269c3 --- /dev/null +++ b/source/robot_model/test/tests/test_collisions.cpp @@ -0,0 +1,120 @@ +#include "robot_model/Model.hpp" +#include "robot_model/exceptions/CollisionGeometryException.hpp" + +#include +#include +#include + +using namespace robot_model; + +class RobotModelCollisionTesting : public testing::Test { +protected: + void SetUp() override { + // create a callback function to get that takes as input a package name and returns the path to the package + auto package_paths = [](const std::string& package_name) -> std::string { + if (package_name == "ur_description") { + return std::string(TEST_FIXTURES); + } else { + return ""; + } + }; + + ur5e_without_geometries = std::make_unique("ur5e", std::string(TEST_FIXTURES) + "ur5e.urdf"); + ur5e_with_geometries = std::make_unique("ur5e", std::string(TEST_FIXTURES) + "ur5e.urdf", package_paths); + }; + + std::unique_ptr ur5e_with_geometries; + std::unique_ptr ur5e_without_geometries; + std::vector test_non_coliding_configs; + std::vector test_coliding_configs; + + // create custom test fixture for robot model without collision + void set_test_non_coliding_configurations() { + // Random test configuration 1: + state_representation::JointPositions config1(ur5e_with_geometries->get_robot_name(), 6); + config1.set_positions(std::vector{0.0, -1.63, 1.45, 0.38, 0.0, 0.0}); + test_non_coliding_configs.push_back(config1); + + // Random test configuration 2: + state_representation::JointPositions config2(ur5e_with_geometries->get_robot_name(), 6); + config2.set_positions(std::vector{0.0, -1.88, 1.45, 0.38, -4.4, -3.14}); + test_non_coliding_configs.push_back(config2); + + // Random test configuration 3: + state_representation::JointPositions config3(ur5e_with_geometries->get_robot_name(), 6); + config3.set_positions(std::vector{1.26, -1.26, 0.82, 0.38, -4.4, 3.14}); + test_non_coliding_configs.push_back(config3); + }; + + // create custom test fixture for robot model with collision geometries + void set_test_coliding_configurations() { + // Random test configuration 1: + state_representation::JointPositions config1(ur5e_with_geometries->get_robot_name(), 6); + config1.set_positions(std::vector{1.26, -1.76, 2.89, 0.38, -4.4, -6.16}); + test_coliding_configs.push_back(config1); + + // Random test configuration 2: + state_representation::JointPositions config2(ur5e_with_geometries->get_robot_name(), 6); + config2.set_positions(std::vector{1.26, -1.76, 2.89, 0.38, -1.38, -1.16}); + test_coliding_configs.push_back(config2); + + // Random test configuration 3: + state_representation::JointPositions config3(ur5e_with_geometries->get_robot_name(), 6); + config3.set_positions(std::vector{1.26, -1.76, -3.08, 0.75, -1.38, -6.16}); + test_coliding_configs.push_back(config3); + }; +}; + +// Test calling check_collision() with an uninitialized geometry model +TEST_F(RobotModelCollisionTesting, CheckCollisionWithoutGeometries) { + // Random test configuration: + state_representation::JointPositions config(ur5e_without_geometries->get_robot_name(), 6); + config.set_positions(std::vector{0.0, -1.63, 1.45, 0.38, 0.0, 0.0}); + EXPECT_THROW(ur5e_without_geometries->check_collision(config), exceptions::CollisionGeometryException) + << "Expected exception for model without geometries."; +} + +// Test that get_number_of_collision_pairs() returns 0 for a model without collision geometries loaded +TEST_F(RobotModelCollisionTesting, NumberOfCollisionPairsWithoutGeometries) { + unsigned int num_pairs = ur5e_without_geometries->get_number_of_collision_pairs(); + EXPECT_EQ(num_pairs, 0) << "Expected zero collision pairs for model without geometries."; +} + +// Test that get_number_of_collision_pairs() returns a non-zero value for a model with collision geometries loaded +TEST_F(RobotModelCollisionTesting, NumberOfCollisionPairsWithGeometries) { + // Assuming your model initialization actually loads collision geometries if available + unsigned num_pairs = ur5e_with_geometries->get_number_of_collision_pairs(); + EXPECT_EQ(num_pairs, 15) << "Expected 15 collision pairs for ur5e with geometries."; +} + +// Test that is_geometry_model_initialized() returns true for a model with collision geometries loaded +TEST_F(RobotModelCollisionTesting, GeomModelInitializedWithGeometries) { + bool is_initialized = ur5e_with_geometries->is_geometry_model_initialized(); + EXPECT_TRUE(is_initialized) << "Expected geometry model to be initialized for model with geometries."; +} + +// Test that is_geometry_model_initialized() returns false for a model without collision geometries loaded +TEST_F(RobotModelCollisionTesting, GeomModelInitializedWithoutGeometries) { + bool is_initialized = ur5e_without_geometries->is_geometry_model_initialized(); + EXPECT_FALSE(is_initialized) << "Expected geometry model to not be initialized for model without geometries."; +} + +// Test that check_collision correctly identifies a collision-free state +TEST_F(RobotModelCollisionTesting, NoCollisionDetected) { + // iterate over test configurations and check for collision + set_test_non_coliding_configurations(); + for (auto& config : test_non_coliding_configs) { + bool is_colliding = ur5e_with_geometries->check_collision(config); + EXPECT_FALSE(is_colliding) << "Expected no collision for configuration " << config; + } +} + +// Test that check_collision correctly identifies a colliding state +TEST_F(RobotModelCollisionTesting, CollisionDetected) { + // iterate over test configurations and check for collision + set_test_coliding_configurations(); + for (auto& config : test_coliding_configs) { + bool is_colliding = ur5e_with_geometries->check_collision(config); + EXPECT_TRUE(is_colliding) << "Expected collision for configuration " << config; + } +} From ddd564705e97f9f506d87516e8425b0f1033a043 Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 17 Apr 2024 14:36:42 +0200 Subject: [PATCH 06/15] Revert "Revert "ci: update workflows (#175)"" This reverts commit 4158f343eb0c7c24330f8119890c3587c0a00a32. --- .github/workflows/build-push.yml | 57 --------------------- .github/workflows/build-release.yaml | 74 ++++++++++++++++++++++------ .github/workflows/build-test.yml | 64 +++++++++++++++++------- .github/workflows/generate-docs.yml | 52 +++++++++++-------- CHANGELOG.md | 7 +-- VERSION | 2 +- demos/CMakeLists.txt | 2 +- doxygen/doxygen.conf | 2 +- protocol/clproto_cpp/CMakeLists.txt | 2 +- python/setup.py | 2 +- source/CMakeLists.txt | 2 +- 11 files changed, 148 insertions(+), 118 deletions(-) delete mode 100644 .github/workflows/build-push.yml diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml deleted file mode 100644 index cd567e6f6..000000000 --- a/.github/workflows/build-push.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Build and Push - -# Run workflow on pushes to main branch or by manual dispatch -on: - push: - branches: - - main - workflow_dispatch: - -jobs: - build-publish-proto-dependencies-focal-fossa: - name: Build and publish proto dependencies image on ubuntu 20.04 - uses: aica-technology/.github/.github/workflows/build-push-multi-arch.yml@v0.1 - with: - image_name: ${{ github.repository }}/proto-dependencies - image_tags: 20.04 - dockerfile_path: Dockerfile.proto - platforms: linux/amd64 - build_flags: '--build-arg BASE_TAG=20.04' - secrets: - token: ${{ secrets.GITHUB_TOKEN }} - - build-publish-proto-dependencies-jammy-jellyfish: - name: Build and publish proto dependencies image on ubuntu 22.04 - uses: aica-technology/.github/.github/workflows/build-push-multi-arch.yml@v0.1 - with: - image_name: ${{ github.repository }}/proto-dependencies - image_tags: 22.04,latest - platforms: linux/amd64,linux/arm64 - dockerfile_path: Dockerfile.proto - build_flags: '--build-arg BASE_TAG=22.04' - secrets: - token: ${{ secrets.GITHUB_TOKEN }} - - build-publish-development-dependencies-focal-fossa: - needs: build-publish-proto-dependencies-focal-fossa - uses: aica-technology/.github/.github/workflows/build-push-multi-arch.yml@v0.1 - with: - image_name: ${{ github.repository }}/development-dependencies - image_tags: 20.04 - platforms: linux/amd64 - dockerfile_path: Dockerfile.base - build_flags: '--build-arg BASE_TAG=20.04' - secrets: - token: ${{ secrets.GITHUB_TOKEN }} - - build-publish-development-dependencies-jammy-jellyfish: - needs: build-publish-proto-dependencies-jammy-jellyfish - uses: aica-technology/.github/.github/workflows/build-push-multi-arch.yml@v0.1 - with: - image_name: ${{ github.repository }}/development-dependencies - image_tags: 22.04,latest - platforms: linux/amd64,linux/arm64 - dockerfile_path: Dockerfile.base - build_flags: '--build-arg BASE_TAG=22.04' - secrets: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-release.yaml b/.github/workflows/build-release.yaml index 95ec0513f..bf869b26b 100644 --- a/.github/workflows/build-release.yaml +++ b/.github/workflows/build-release.yaml @@ -3,24 +3,68 @@ name: Build and Push Multi-Arch Images on: push: branches: - - develop - main - tags: - - "v*.*.*" jobs: - get-tag: - runs-on: ubuntu-latest - name: Get tag + check-version: + name: Check if the version has been updated outputs: - tag: ${{ steps.parse-tag.outputs.tag }} + has_changed: ${{ steps.check.outputs.has_changed }} + version: ${{ steps.versions.outputs.new_version }} + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: aica-technology/.github/.github/actions/docker-tag-from-git@v0.6.1 - id: parse-tag + with: + fetch-depth: 2 + - id: versions + run: | + PREV_VERSION=$(git show HEAD^:VERSION) + NEW_VERSION=$(git show HEAD:VERSION) + echo "prev_version=${PREV_VERSION}" >> $GITHUB_OUTPUT + echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT + shell: bash + - uses: aica-technology/.github/.github/actions/compare-versions@v0.11.0 + id: check + with: + previous_version: ${{ steps.versions.outputs.prev_version }} + new_version: ${{ steps.versions.outputs.new_version }} + + metadata: + name: Get metadata + needs: check-version + runs-on: ubuntu-latest + outputs: + image_name: ${{ steps.ensure-image.outputs.image_name }} + image_tags: ${{ steps.tags.outputs.image_tags }} + create_tag: ${{ steps.tags.outputs.create_tag }} + git_tag: ${{ steps.tags.outputs.git_tag }} + steps: + - uses: aica-technology/.github/.github/actions/ghcr-ensure-prefix@v0.6.0 + id: ensure-image + with: + image_name: aica-technology/control-libraries + + - run: | + CREATE_TAG=false + GIT_TAG="" + if [ ${{ needs.check-version.outputs.has_changed }} = 'true' ]; then + CREATE_TAG=true + GIT_TAG="v${{ needs.check-version.outputs.version }}" + IMAGE_TAGS=latest,"${GIT_TAG}",rolling + else + IMAGE_TAGS=rolling + fi + echo "Image tags: ${IMAGE_TAGS}" + echo "Create tag: ${CREATE_TAG}" + echo "Git tag: ${GIT_TAG}" + echo "image_tags=${IMAGE_TAGS}" >> $GITHUB_OUTPUT + echo "create_tag=${CREATE_TAG}" >> $GITHUB_OUTPUT + echo "git_tag=${GIT_TAG}" >> $GITHUB_OUTPUT + id: tags + shell: bash build: - needs: [get-tag] + needs: metadata strategy: matrix: arch: [amd64, arm64] @@ -38,13 +82,13 @@ jobs: - uses: aica-technology/.github/.github/actions/list-add-suffixes@v0.6.1 id: merge-tags with: - list: ${{ needs.get-tag.outputs.tag }} + list: ${{ needs.metadata.outputs.image_tags }} suffixes: ${{ matrix.arch }} glue_separator: "-" - uses: aica-technology/.github/.github/actions/ghcr-build@v0.6.1 with: - image_name: aica-technology/control-libraries + image_name: ${{ needs.metadata.outputs.image_name }} image_tags: ${{ steps.merge-tags.outputs.list }} dockerfile_path: Dockerfile token: ${{ secrets.GITHUB_TOKEN }} @@ -52,11 +96,11 @@ jobs: multi-arch: runs-on: ubuntu-latest name: Merge into a multi-arch image - needs: [get-tag, build] + needs: [ metadata, build ] steps: - uses: aica-technology/.github/.github/actions/ghcr-manifest-merge@v0.6.1 with: - image_name: aica-technology/control-libraries - image_tags: ${{ needs.get-tag.outputs.tag }} + image_name: ${{ needs.metadata.outputs.image_name }} + image_tags: ${{ needs.metadata.outputs.image_tags }} archs: amd64,arm64 token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 5b66b291a..ed7b1cbc4 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -4,40 +4,70 @@ on: push: branches: - main - - develop pull_request: workflow_dispatch: jobs: - check-contribution: - name: Check if changelog and version have been updated + check-changelog: + name: Check if changelog has been updated runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: Check contributions + - name: Check changelog if: ${{ github.event.pull_request.base.sha }} run: | - git config user.name github-actions[bot] - git config user.email github-actions[bot]@users.noreply.github.com - git fetch origin main ${{ github.event.pull_request.base.sha }} ${{ github.head_ref }} - git checkout ${{ github.head_ref }} - VER_DIFF=$(git diff ${{ github.event.pull_request.base.sha }} ${{ github.sha }} -- ./VERSION) - if ! [ "${VER_DIFF}" ]; then - bash update_version.sh --commit && git push - fi - CL_DIFF=$(git diff ${{ github.event.pull_request.base.sha }} ${{ github.sha }} -- ./CHANGELOG.md) + git fetch origin main ${{ github.event.pull_request.base.sha }} + CL_DIFF=$(git diff ${{ github.event.pull_request.base.sha }} ${{ github.sha }} -- CHANGELOG.md) if ! [ "${CL_DIFF}" ]; then - SEARCH_STRING="## Upcoming changes (in development)" - NEW_TITLE="${{ github.event.pull_request.title }} (#${{ github.event.pull_request.number }})" - sed -z "s/${SEARCH_STRING}\n/${SEARCH_STRING}\n\n- ${NEW_TITLE}/" -i CHANGELOG.md - git add CHANGELOG.md && git commit -m "Update CHANGELOG" && git push + echo "::error file="CHANGELOG.md",title=Check failed::CHANGELOG.md must be updated!" + exit 1 + fi + shell: bash + + check-version: + name: Check if version has been updated + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check version + if: ${{ github.event.pull_request.base.sha }} + run: | + VERSION=$(echo $(> $GITHUB_OUTPUT + echo "new_version=${NEW_VERSION}" >> $GITHUB_OUTPUT + shell: bash + - uses: aica-technology/.github/.github/actions/compare-versions@v0.11.0 + id: check + with: + previous_version: ${{ steps.versions.outputs.prev_version }} + new_version: ${{ steps.versions.outputs.new_version }} + deploy: + needs: check-version name: Generate and Deploy runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v3 - name: Generate docs uses: mattnotmitt/doxygen-action@v1 @@ -21,23 +41,15 @@ jobs: working-directory: 'doxygen' doxyfile-path: 'doxygen.conf' - - name: Tag release version - if: ${{ github.event_name == 'release' }} - shell: bash - run: | - TAG="${GITHUB_REF#refs/tags/}" - TAG="${TAG/\//-}" - mkdir -p doxygen/docs/versions - sudo mv doxygen/docs/html doxygen/docs/versions/${TAG} - - - name: Tag branch version - if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} + - name: Tag version shell: bash run: | - BRANCH="${GITHUB_REF#refs/heads/}" - BRANCH="${BRANCH/\//-}" mkdir -p doxygen/docs/versions - sudo mv doxygen/docs/html doxygen/docs/versions/${BRANCH} + sudo mv doxygen/docs/html doxygen/docs/versions/rolling + if [ ${{ needs.check-version.outputs.has_changed }} ]; then + sudo cp doxygen/docs/versions/rolling doxygen/docs/versions/latest + sudo cp doxygen/docs/versions/rolling doxygen/docs/versions/${{ needs.check-version.outputs.version }} + fi - name: Deploy to documentation branch uses: peaceiris/actions-gh-pages@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index cf27b097d..246b59def 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,10 +14,11 @@ Release Versions: ## Upcoming changes (in development) -- feat(build): handle installation and linking of dependencies for pinocchio collision support (#161) -- build: add missing licenses (#170) -- ci: use caching from docker to run tests in CI (#429) +- ci: update workflows (#175) - feat: integrate collision detection feature into robot model (#163) +- ci: use caching from docker to run tests in CI (#429) +- build: add missing licenses (#170) +- feat(build): handle installation and linking of dependencies for pinocchio collision support (#161) ## 7.4.0 diff --git a/VERSION b/VERSION index 4e61aeef9..e91f10444 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.4 +7.4.5 diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 370802b96..e222c3cfa 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -15,7 +15,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -find_package(control_libraries 7.4.4 CONFIG REQUIRED) +find_package(control_libraries 7.4.5 CONFIG REQUIRED) set(DEMOS_SCRIPTS task_space_control_loop diff --git a/doxygen/doxygen.conf b/doxygen/doxygen.conf index f010ff338..a78faf9cc 100644 --- a/doxygen/doxygen.conf +++ b/doxygen/doxygen.conf @@ -38,7 +38,7 @@ PROJECT_NAME = "Control Libraries" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 7.4.4 +PROJECT_NUMBER = 7.4.5 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/protocol/clproto_cpp/CMakeLists.txt b/protocol/clproto_cpp/CMakeLists.txt index 763468414..f4ec7e751 100644 --- a/protocol/clproto_cpp/CMakeLists.txt +++ b/protocol/clproto_cpp/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(clproto VERSION 7.4.4) +project(clproto VERSION 7.4.5) # Default to C99 if(NOT CMAKE_C_STANDARD) diff --git a/python/setup.py b/python/setup.py index 52f95fe56..378063d39 100644 --- a/python/setup.py +++ b/python/setup.py @@ -11,7 +11,7 @@ # names of the environment variables that define osqp and openrobots include directories osqp_path_var = 'OSQP_INCLUDE_DIR' -__version__ = "7.4.4" +__version__ = "7.4.5" __libraries__ = ['state_representation', 'clproto', 'controllers', 'dynamical_systems', 'robot_model'] __include_dirs__ = ['include'] diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 566e59b0a..15c484d7e 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(control_libraries VERSION 7.4.4) +project(control_libraries VERSION 7.4.5) # Build options option(BUILD_TESTING "Build all tests." OFF) From 6c6610425ed1b25ee8f32d9f6caf290cf323d735 Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 17 Apr 2024 14:37:32 +0200 Subject: [PATCH 07/15] Revert "Revert "feat: Integrate min distance (#167)"" This reverts commit 565e3b10c476a361dfee206bc9b9efb0804356a5. --- CHANGELOG.md | 1 + VERSION | 2 +- demos/CMakeLists.txt | 2 +- doxygen/doxygen.conf | 2 +- protocol/clproto_cpp/CMakeLists.txt | 2 +- python/setup.py | 2 +- python/source/robot_model/bind_model.cpp | 1 + python/test/model/test_collisions.py | 24 +++++++++ source/CMakeLists.txt | 2 +- .../robot_model/include/robot_model/Model.hpp | 9 ++++ source/robot_model/src/Model.cpp | 28 ++++++++++ .../test/tests/test_collisions.cpp | 54 +++++++++++++++++++ 12 files changed, 123 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 246b59def..85c74fdd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Release Versions: ## Upcoming changes (in development) +- feat: integrate minimum distance calculation feature into robot model(#167) - ci: update workflows (#175) - feat: integrate collision detection feature into robot model (#163) - ci: use caching from docker to run tests in CI (#429) diff --git a/VERSION b/VERSION index e91f10444..bd8293a6e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.5 +7.4.6 diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index e222c3cfa..799e6b9df 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -15,7 +15,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -find_package(control_libraries 7.4.5 CONFIG REQUIRED) +find_package(control_libraries 7.4.6 CONFIG REQUIRED) set(DEMOS_SCRIPTS task_space_control_loop diff --git a/doxygen/doxygen.conf b/doxygen/doxygen.conf index a78faf9cc..b807a5606 100644 --- a/doxygen/doxygen.conf +++ b/doxygen/doxygen.conf @@ -38,7 +38,7 @@ PROJECT_NAME = "Control Libraries" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 7.4.5 +PROJECT_NUMBER = 7.4.6 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/protocol/clproto_cpp/CMakeLists.txt b/protocol/clproto_cpp/CMakeLists.txt index f4ec7e751..e9cc7eb39 100644 --- a/protocol/clproto_cpp/CMakeLists.txt +++ b/protocol/clproto_cpp/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(clproto VERSION 7.4.5) +project(clproto VERSION 7.4.6) # Default to C99 if(NOT CMAKE_C_STANDARD) diff --git a/python/setup.py b/python/setup.py index 378063d39..01154a6c4 100644 --- a/python/setup.py +++ b/python/setup.py @@ -11,7 +11,7 @@ # names of the environment variables that define osqp and openrobots include directories osqp_path_var = 'OSQP_INCLUDE_DIR' -__version__ = "7.4.5" +__version__ = "7.4.6" __libraries__ = ['state_representation', 'clproto', 'controllers', 'dynamical_systems', 'robot_model'] __include_dirs__ = ['include'] diff --git a/python/source/robot_model/bind_model.cpp b/python/source/robot_model/bind_model.cpp index eaab80a3b..26f866cd0 100644 --- a/python/source/robot_model/bind_model.cpp +++ b/python/source/robot_model/bind_model.cpp @@ -63,6 +63,7 @@ void model(py::module_& m) { c.def("check_collision", py::overload_cast(&Model::check_collision), "Check if the robot is in collision at a given joint state.", "joint_positions"_a); + c.def("compute_minimum_collision_distances", py::overload_cast(&Model::compute_minimum_collision_distances), "Compute the minimum distances between the robot links.", "joint_positions"_a); c.def("get_number_of_collision_pairs", &Model::get_number_of_collision_pairs, "Get the number of collision pairs in the model."); c.def("is_geometry_model_initialized", &Model::is_geometry_model_initialized, "Check if the geometry model is initialized."); c.def( diff --git a/python/test/model/test_collisions.py b/python/test/model/test_collisions.py index 132ec5541..d9b1e3785 100644 --- a/python/test/model/test_collisions.py +++ b/python/test/model/test_collisions.py @@ -1,9 +1,11 @@ import os import unittest +import numpy as np from robot_model import Model from state_representation import JointPositions + class RobotModelCollisionTesting(unittest.TestCase): ur5e_with_geometries = None ur5e_without_geometries = None @@ -78,6 +80,28 @@ def test_collision_detected(self): is_colliding = self.ur5e_with_geometries.check_collision(config) self.assertTrue(is_colliding, "Expected collision for configuration") + def test_minimum_distance_computed_no_collision(self): + for config in self.test_non_colliding_configs: + distances = self.ur5e_with_geometries.compute_minimum_collision_distances(config) + self.assertEqual(distances.shape, (6, 6), "Distance matrix has incorrect shape.") + + # Check that no element is equal to zero besides the diagonals + for i in range(distances.shape[0]): + for j in range(distances.shape[1]): + if i != j and j != i+1 and i != j+1: # Skip diagonal and adjacent elements + self.assertGreaterEqual(distances[i, j], 0.01, "Found a distance at non-diagonal element indicating a collision.") + + def test_minimum_distance_computed_collision(self): + for config in self.test_colliding_configs: + distances = self.ur5e_with_geometries.compute_minimum_collision_distances(config) + self.assertEqual(distances.shape, (6, 6), "Distance matrix has incorrect shape.") + + # Initialize a variable to keep track of the minimum non-diagonal distance + minimum_distance = np.min(distances[np.triu_indices(n=6, k=2)]) + # Expect the minimum non-diagonal distance to indicate a collision + self.assertLessEqual(minimum_distance, 0.01, "Did not find a minimum distance less than a threshold indicating a collision.") + + if __name__ == '__main__': unittest.main() diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 15c484d7e..35ccfac79 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(control_libraries VERSION 7.4.5) +project(control_libraries VERSION 7.4.6) # Build options option(BUILD_TESTING "Build all tests." OFF) diff --git a/source/robot_model/include/robot_model/Model.hpp b/source/robot_model/include/robot_model/Model.hpp index af3276e2f..7bbdeeb9f 100644 --- a/source/robot_model/include/robot_model/Model.hpp +++ b/source/robot_model/include/robot_model/Model.hpp @@ -264,6 +264,15 @@ class Model { */ static bool create_urdf_from_string(const std::string& urdf_string, const std::string& desired_path); + /** + * @brief Compute the minimum distances between the robot links + * @details The distances are computed for each collision pair, resulting in a square matrix with + * the same size as the number of joints. The diagonal entries are always zero. + * @param joint_positions state_representation object containing the joint positions of the robot + * @return the matrix containing the minimum distance between the robot links + */ + Eigen::MatrixXd compute_minimum_collision_distances(const state_representation::JointPositions& joint_positions); + /** * @brief Check if the links of the robot are in collision * @param joint_positions containing the joint positions of the robot diff --git a/source/robot_model/src/Model.cpp b/source/robot_model/src/Model.cpp index 0f6f798bd..75d12d8bb 100644 --- a/source/robot_model/src/Model.cpp +++ b/source/robot_model/src/Model.cpp @@ -184,6 +184,34 @@ bool Model::check_collision(const state_representation::JointPositions& joint_po return false; } +Eigen::MatrixXd Model::compute_minimum_collision_distances(const state_representation::JointPositions& joint_positions) { + if (!this->is_geometry_model_initialized()) { + throw robot_model::exceptions::CollisionGeometryException( + "Geometry model not loaded for " + this->get_robot_name()); + } + Eigen::VectorXd configuration = joint_positions.get_positions(); + pinocchio::computeDistances( + this->robot_model_, this->robot_data_, this->geom_model_, this->geom_data_, configuration); + + // nb_joints is the number of joints in the robot model + unsigned int nb_joints = this->get_number_of_joints(); + + // create a square matrix to store the distances and initialize to zero + Eigen::MatrixXd distances = Eigen::MatrixXd::Zero(nb_joints, nb_joints); + + // iterate over the collision pairs and extract the distances + unsigned int pair_index = 0; + for (unsigned int row_index = 0; row_index < nb_joints; ++row_index) { + for (unsigned int column_index = row_index + 1; column_index < nb_joints; ++column_index) { + distances(row_index, column_index) = this->geom_data_.distanceResults[pair_index].min_distance; + distances(column_index, row_index) = distances(row_index, column_index); + pair_index++; + } + } + + return distances; +} + bool Model::init_qp_solver() { // clear the solver this->solver_.data()->clearHessianMatrix(); diff --git a/source/robot_model/test/tests/test_collisions.cpp b/source/robot_model/test/tests/test_collisions.cpp index a309269c3..0edb5eedd 100644 --- a/source/robot_model/test/tests/test_collisions.cpp +++ b/source/robot_model/test/tests/test_collisions.cpp @@ -118,3 +118,57 @@ TEST_F(RobotModelCollisionTesting, CollisionDetected) { EXPECT_TRUE(is_colliding) << "Expected collision for configuration " << config; } } + +// Test that compute_minimum_collision_distances method identifies a collision-free state +TEST_F(RobotModelCollisionTesting, MinimumDistanceComputedNoCollision) { + // iterate over test configurations and check for collision + set_test_non_coliding_configurations(); + + for (auto& config : test_non_coliding_configs) { + Eigen::MatrixXd distances = ur5e_with_geometries->compute_minimum_collision_distances(config); + + // check that no element is equal to zero besided the diagonals + EXPECT_EQ(distances.rows(), 6) << "Distance matrix has incorrect number of rows."; + EXPECT_EQ(distances.cols(), 6) << "Distance matrix has incorrect number of columns."; + + // Then check that no element is equal to zero besides the diagonals + for (int i = 0; i < distances.rows(); ++i) { + for (int j = 0; j < distances.cols(); ++j) { + if (i != j && j != i + 1 && i != j + 1) {// Skip diagonal elements & adjacent links + EXPECT_GE(distances(i, j), 0.01) + << "Found a distance at non-diagonal element [" << i << ", " << j << "], indicating a collision."; + } + } + } + } +} + +TEST_F(RobotModelCollisionTesting, MinimumDistanceComputedCollision) { + // Iterate over test configurations expected to result in collisions + set_test_coliding_configurations(); + + for (auto& config : test_coliding_configs) { + Eigen::MatrixXd distances = ur5e_with_geometries->compute_minimum_collision_distances(config); + + // Check the size of the distance matrix is 6x6 + EXPECT_EQ(distances.rows(), 6) << "Distance matrix has incorrect number of rows."; + EXPECT_EQ(distances.cols(), 6) << "Distance matrix has incorrect number of columns."; + + // Initialize a variable to keep track of the minimum non-diagonal distance + double minimum_distance = std::numeric_limits::max(); + + // Iterate over the matrix to find the minimum non-diagonal distance + for (int i = 0; i < distances.rows(); ++i) { + for (int j = 0; j < distances.cols(); ++j) { + if (i != j && j != i + 1 && i != j + 1 + && distances(i, j) < minimum_distance) {// Skip diagonal elements & adjacent links + minimum_distance = distances(i, j); + } + } + } + + // Expect the minimum non-diagonal distance to be 0, indicating a collision + EXPECT_LE(minimum_distance, 0.01) + << "Did not find a minimum distance less than a threshold indicating a collision."; + } +} From ed74e04bbcadd54f3f2aaacdc1e0388844c3c081 Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 17 Apr 2024 14:45:02 +0200 Subject: [PATCH 08/15] Revert "Revert "fix: refactor cmake project to deal with robot model dependencies (#178)"" This reverts commit 640240813f76a21e31b56d29520eeea1e914b45f. --- CHANGELOG.md | 1 + Dockerfile | 47 +++++++++++++------- VERSION | 2 +- demos/CMakeLists.txt | 2 +- doxygen/doxygen.conf | 2 +- protocol/clproto_cpp/CMakeLists.txt | 2 +- python/setup.py | 2 +- source/CMakeLists.txt | 30 +++---------- source/control_librariesConfig.cmake.in | 46 ++++++++++++++----- source/controllers/CMakeLists.txt | 2 +- source/dynamical_systems/CMakeLists.txt | 2 +- source/robot_model/CMakeLists.txt | 6 +-- source/robot_model/test/tests/test_model.cpp | 2 +- 13 files changed, 85 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85c74fdd7..dda89b14d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Release Versions: ## Upcoming changes (in development) +- fix: refactor cmake project to deal with robot model dependencies (#178) - feat: integrate minimum distance calculation feature into robot model(#167) - ci: update workflows (#175) - feat: integrate collision detection feature into robot model (#163) diff --git a/Dockerfile b/Dockerfile index 8b160bd8f..3e312d515 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,8 @@ RUN echo "Set disable_coredump false" >> /etc/sudo.conf # create the credentials to be able to pull private repos using ssh RUN mkdir /root/.ssh/ && ssh-keyscan github.com | tee -a /root/.ssh/known_hosts +ARG CMAKE_BUILD_TYPE=Release + FROM base as apt-dependencies COPY apt-packages.tx[t] / @@ -81,31 +83,44 @@ ARG TARGETPLATFORM ARG CACHEID COPY dependencies/base_dependencies.cmake CMakeLists.txt RUN --mount=type=cache,target=/build,id=cmake-base-deps-${TARGETPLATFORM}-${CACHEID},uid=1000 \ - cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build && cmake --install build --prefix /tmp/deps + cmake -B build -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} && cmake --build build && cmake --install build --prefix /tmp/deps FROM base as pinocchio-dependencies COPY --from=apt-dependencies /tmp/apt / COPY --from=base-dependencies /tmp/deps /usr ARG TARGETPLATFORM ARG CACHEID -ARG PINOCCHIO_TAG=v2.9.0 -ARG HPP_FCL_TAG=v1.8.1 +ARG PINOCCHIO_TAG=v2.6.20 +ARG HPP_FCL_TAG=v2.4.4 # FIXME: it would be nicer to have it all in the root CMakelists.txt but: # * `pinocchio` doesn't provide an include directory we can easily plug into `target_include_directories` and thus needs to be installed first # * `pinocchio` uses hacks relying on undocumented CMake quirks which break if you use `FetchContent` # FIXME: it needs `CMAKE_INSTALL_PREFIX` and `--prefix` because it doesn't install to the right place otherwise -RUN --mount=type=cache,target=/hpp-fcl,id=cmake-hpp-fcl-src-${HPP_FCL_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ - --mount=type=cache,target=/build,id=cmake-hpp-fcl-${HPP_FCL_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ - if [ ! -f hpp-fcl/CMakeLists.txt ]; then rm -rf hpp-fcl/* && git clone --depth 1 -b ${HPP_FCL_TAG} --recursive https://github.com/humanoid-path-planner/hpp-fcl; fi \ - && cmake -B build -S hpp-fcl -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON_INTERFACE=OFF -DCMAKE_INSTALL_PREFIX=/tmp/deps \ - && cmake --build build --target all install RUN --mount=type=cache,target=/pinocchio,id=cmake-pinocchio-src-${PINOCCHIO_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ - --mount=type=cache,target=/build,id=cmake-pinocchio-${PINOCCHIO_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ - if [ ! -f pinocchio/CMakeLists.txt ]; then rm -rf pinocchio/* && git clone --depth 1 -b ${PINOCCHIO_TAG} --recursive https://github.com/stack-of-tasks/pinocchio; fi \ - && cmake -B build -S pinocchio -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON_INTERFACE=OFF -DBUILD_WITH_COLLISION_SUPPORT=ON -DCMAKE_INSTALL_PREFIX=/tmp/deps \ - && cmake --build build --target all install + --mount=type=cache,target=/hpp-fcl,id=cmake-hpp-fcl-src-${HPP_FCL_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ + --mount=type=cache,target=/build,id=cmake-pinocchio-${PINOCCHIO_TAG}-${HPP_FCL_TAG}-${TARGETPLATFORM}-${CACHEID},uid=1000 \ +< ) -target_link_libraries(${LIBRARY_NAME} robot_model) +target_link_libraries(${LIBRARY_NAME} ${PROJECT_NAME}::robot_model) # install the target and create export-set install(TARGETS ${LIBRARY_NAME} diff --git a/source/dynamical_systems/CMakeLists.txt b/source/dynamical_systems/CMakeLists.txt index 083cbb1e3..526f3cbd2 100644 --- a/source/dynamical_systems/CMakeLists.txt +++ b/source/dynamical_systems/CMakeLists.txt @@ -18,7 +18,7 @@ target_include_directories(${LIBRARY_NAME} $ ) -target_link_libraries(${LIBRARY_NAME} state_representation) +target_link_libraries(${LIBRARY_NAME} ${PROJECT_NAME}::state_representation) # install the target and create export-set install(TARGETS ${LIBRARY_NAME} diff --git a/source/robot_model/CMakeLists.txt b/source/robot_model/CMakeLists.txt index 87ed8a2d8..275ed3b12 100644 --- a/source/robot_model/CMakeLists.txt +++ b/source/robot_model/CMakeLists.txt @@ -1,7 +1,7 @@ set(LIBRARY_NAME robot_model) -set(PINOCCHIO_VERSION 2.9.0) -set(HPP_FCL_VERSION 1.8.1) +set(PINOCCHIO_VERSION 2.6.20) +set(HPP_FCL_VERSION 2.4.4) set(OSQP_EIGEN_VERSION 0.8.1) find_package(pinocchio ${PINOCCHIO_VERSION} REQUIRED) find_package(hpp-fcl ${HPP_FCL_VERSION} REQUIRED) @@ -24,7 +24,7 @@ target_include_directories(${LIBRARY_NAME} ) target_link_libraries(${LIBRARY_NAME} - state_representation + ${PROJECT_NAME}::state_representation ${PINOCCHIO_LIBRARIES} ${hpp-fcl_LIBRARIES} OsqpEigen::OsqpEigen diff --git a/source/robot_model/test/tests/test_model.cpp b/source/robot_model/test/tests/test_model.cpp index 244f67e03..157b9bf94 100644 --- a/source/robot_model/test/tests/test_model.cpp +++ b/source/robot_model/test/tests/test_model.cpp @@ -128,4 +128,4 @@ TEST_F(RobotModelTest, TestCreateURDFFromStringFail) { TEST_F(RobotModelTest, TestModelGetter) { const pinocchio::Model& robot_model = franka->get_pinocchio_model(); EXPECT_TRUE(robot_model.existBodyName("panda_link0")); -} \ No newline at end of file +} From 0ef9ad8c20e423891deb8ff6f856e4342568b9df Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 17 Apr 2024 14:45:10 +0200 Subject: [PATCH 09/15] Revert "Revert "refactor: optimize copy and swap constructors for robot_model::Model (#177)"" This reverts commit 272f048c91c05f2475fa7c598f392959c3421eb1. --- CHANGELOG.md | 1 + python/source/robot_model/bind_model.cpp | 1 - python/test/model/test_model.py | 3 +- source/robot_model/CMakeLists.txt | 1 + .../robot_model/include/robot_model/Model.hpp | 90 ++++------ .../include/robot_model/QPSolver.hpp | 98 +++++++++++ source/robot_model/src/Model.cpp | 143 ++++------------ source/robot_model/src/QPSolver.cpp | 162 ++++++++++++++++++ source/robot_model/test/tests/test_model.cpp | 5 + 9 files changed, 331 insertions(+), 173 deletions(-) create mode 100644 source/robot_model/include/robot_model/QPSolver.hpp create mode 100644 source/robot_model/src/QPSolver.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index dda89b14d..39e329edc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Release Versions: ## Upcoming changes (in development) +- refactor: optimize copy and swap constructor for robot model (#174) - fix: refactor cmake project to deal with robot model dependencies (#178) - feat: integrate minimum distance calculation feature into robot model(#167) - ci: update workflows (#175) diff --git a/python/source/robot_model/bind_model.cpp b/python/source/robot_model/bind_model.cpp index 26f866cd0..8feb02e00 100644 --- a/python/source/robot_model/bind_model.cpp +++ b/python/source/robot_model/bind_model.cpp @@ -106,7 +106,6 @@ void model(py::module_& m) { c.def("inverse_velocity", py::overload_cast(&Model::inverse_velocity), "Compute the inverse velocity kinematics, i.e. joint velocities from the twist of the end-effector using the QP optimization method", "cartesian_twist"_a, "joint_positions"_a, "parameters"_a, "frame"_a = std::string("")); - c.def("print_qp_problem", &Model::print_qp_problem, "Helper function to print the qp problem (for debugging)."); c.def("in_range", [](Model& self, const JointPositions& joint_positions) -> bool { return self.in_range(joint_positions); }, "Check if the joint positions are inside the limits provided by the model", "joint_positions"_a); c.def("in_range", [](Model& self, const JointVelocities& joint_velocities) -> bool { return self.in_range(joint_velocities); }, diff --git a/python/test/model/test_model.py b/python/test/model/test_model.py index b9c302905..aefa1c324 100644 --- a/python/test/model/test_model.py +++ b/python/test/model/test_model.py @@ -28,8 +28,7 @@ 'forward_velocity', 'inverse_velocity', 'in_range', - 'clamp_in_range', - 'print_qp_problem', + 'clamp_in_range' ] diff --git a/source/robot_model/CMakeLists.txt b/source/robot_model/CMakeLists.txt index 275ed3b12..67b1fd13d 100644 --- a/source/robot_model/CMakeLists.txt +++ b/source/robot_model/CMakeLists.txt @@ -11,6 +11,7 @@ add_definitions(-DPINOCCHIO_WITH_HPP_FCL) set(CORE_SOURCES src/Model.cpp + src/QPSolver.cpp ) add_library(${LIBRARY_NAME} SHARED ${CORE_SOURCES}) diff --git a/source/robot_model/include/robot_model/Model.hpp b/source/robot_model/include/robot_model/Model.hpp index 7bbdeeb9f..a43bf76ca 100644 --- a/source/robot_model/include/robot_model/Model.hpp +++ b/source/robot_model/include/robot_model/Model.hpp @@ -3,18 +3,22 @@ #include #include #include + #include #include #include #include #include #include + #include #include #include #include #include +#include "robot_model/QPSolver.hpp" + using namespace std::chrono_literals; /** @@ -40,22 +44,6 @@ struct InverseKinematicsParameters { unsigned int max_number_of_iterations = 1000; }; -/** - * @brief parameters for the inverse velocity kinematics function - * @param alpha gain associated to the time slack variable - * @param proportional_gain gain to weight the cartesian coordinates in the gradient - * @param linear_velocity_limit maximum linear velocity allowed in Cartesian space (m/s) - * @param angular_velocity_limit maximum angular velocity allowed in Cartesian space (rad/s) - * @param period of the control loop (ns) - */ -struct QPInverseVelocityParameters { - double alpha = 0.1; - double proportional_gain = 1.0; - double linear_velocity_limit = 2.0; - double angular_velocity_limit = 2.0; - std::chrono::nanoseconds dt = 1000ns; -}; - /** * @class Model * @brief The Model class is a wrapper around pinocchio dynamic computation library with state_representation @@ -63,24 +51,18 @@ struct QPInverseVelocityParameters { */ class Model { private: - // @format:off - std::shared_ptr> robot_name_;///< name of the robot - std::shared_ptr> urdf_path_; ///< path to the urdf file - std::vector frames_; ///< name of the frames - pinocchio::Model robot_model_; ///< the robot model with pinocchio - pinocchio::Data robot_data_; ///< the robot data with pinocchio - std::optional> meshloader_callback_; ///< callback function to resolve package paths - pinocchio::GeometryModel geom_model_; ///< the robot geometry model with pinocchio - pinocchio::GeometryData geom_data_; ///< the robot geometry data with pinocchio - OsqpEigen::Solver solver_; ///< osqp solver for the quadratic programming based inverse kinematics - Eigen::SparseMatrix hessian_; ///< hessian matrix for the quadratic programming based inverse kinematics - Eigen::VectorXd gradient_; ///< gradient vector for the quadratic programming based inverse kinematics - Eigen::SparseMatrix constraint_matrix_; ///< constraint matrix for the quadratic programming based inverse kinematics - Eigen::VectorXd lower_bound_constraints_; ///< lower bound matrix for the quadratic programming based inverse kinematics - Eigen::VectorXd upper_bound_constraints_; ///< upper bound matrix for the quadratic programming based inverse kinematics - bool load_collision_geometries_ = false; ///< flag to load collision geometries - - // @format:on + std::string robot_name_; ///< name of the robot + std::string urdf_path_; ///< path to the urdf file + std::vector frames_;///< name of the frames + pinocchio::Model robot_model_; ///< the robot model with pinocchio + pinocchio::Data robot_data_; ///< the robot data with pinocchio + std::optional> + meshloader_callback_; ///< callback function to resolve package paths + pinocchio::GeometryModel geom_model_; ///< the robot geometry model with pinocchio + pinocchio::GeometryData geom_data_; ///< the robot geometry data with pinocchio + std::unique_ptr qp_solver_; ///< the QP solver for the inverse velocity kinematics + bool load_collision_geometries_ = false;///< flag to load collision geometries + /** * @brief Initialize the pinocchio model from the URDF */ @@ -91,11 +73,6 @@ class Model { */ void init_geom_model(std::string urdf); - /** - * @brief initialize the constraints for the QP solver - */ - bool init_qp_solver(); - /** * @brief Check if frames exist in robot model and return its ids * @param frames containing the frame names to check @@ -533,11 +510,6 @@ class Model { const QPInverseVelocityParameters& parameters, const std::string& frame = ""); - /** - * @brief Helper function to print the qp_problem (for debugging) - */ - void print_qp_problem(); - /** * @brief Check if the joint positions are inside the limits provided by the model * @param joint_positions the joint positions to check @@ -576,12 +548,22 @@ class Model { state_representation::JointState clamp_in_range(const state_representation::JointState& joint_state) const; }; -inline void swap(Model& model1, Model& model2) { - std::swap(model1.robot_name_, model2.robot_name_); - std::swap(model1.urdf_path_, model2.urdf_path_); - // initialize both models - model1.init_model(); - model2.init_model(); +inline const std::string& Model::get_robot_name() const { + return this->robot_name_; +} + +inline void swap(Model& first, Model& second) { + using std::swap; + swap(first.robot_name_, second.robot_name_); + swap(first.urdf_path_, second.urdf_path_); + swap(first.frames_, second.frames_); + swap(first.robot_model_, second.robot_model_); + swap(first.robot_data_, second.robot_data_); + swap(first.meshloader_callback_, second.meshloader_callback_); + swap(first.geom_model_, second.geom_model_); + swap(first.geom_data_, second.geom_data_); + swap(first.qp_solver_, second.qp_solver_); + swap(first.load_collision_geometries_, second.load_collision_geometries_); } inline Model& Model::operator=(const Model& model) { @@ -590,16 +572,12 @@ inline Model& Model::operator=(const Model& model) { return *this; } -inline const std::string& Model::get_robot_name() const { - return this->robot_name_->get_value(); -} - inline void Model::set_robot_name(const std::string& robot_name) { - this->robot_name_->set_value(robot_name); + this->robot_name_ = robot_name; } inline const std::string& Model::get_urdf_path() const { - return this->urdf_path_->get_value(); + return this->urdf_path_; } inline unsigned int Model::get_number_of_joints() const { diff --git a/source/robot_model/include/robot_model/QPSolver.hpp b/source/robot_model/include/robot_model/QPSolver.hpp new file mode 100644 index 000000000..05b907041 --- /dev/null +++ b/source/robot_model/include/robot_model/QPSolver.hpp @@ -0,0 +1,98 @@ + +#include +#include + +#include +#include + +using namespace std::chrono_literals; + +namespace robot_model { + +/** + * @brief parameters for the inverse velocity kinematics function + * @param alpha gain associated to the time slack variable + * @param proportional_gain gain to weight the cartesian coordinates in the gradient + * @param linear_velocity_limit maximum linear velocity allowed in Cartesian space (m/s) + * @param angular_velocity_limit maximum angular velocity allowed in Cartesian space (rad/s) + * @param period of the control loop (ns) + */ +struct QPInverseVelocityParameters { + double alpha = 0.1; + double proportional_gain = 1.0; + double linear_velocity_limit = 2.0; + double angular_velocity_limit = 2.0; + std::chrono::nanoseconds dt = 1000ns; +}; + +/** + * @class QPSolver + * @brief A class to solve the Quadratic Programming problem (QP) for the robot model +*/ +class QPSolver { +private: + unsigned nb_joints_;///< number of joints in the robot model + pinocchio::ModelTpl::ConfigVectorType + lower_position_limit_;///< lower position limits of the joints from the URDF + pinocchio::ModelTpl::ConfigVectorType + upper_position_limit_; ///< upper position limits of the joints from the URDF + pinocchio::ModelTpl::TangentVectorType velocity_limit_;///< velocity limits of the joints from the URDF + OsqpEigen::Solver solver_; ///< osqp solver for the quadratic programming based inverse kinematics + Eigen::SparseMatrix hessian_;///< hessian matrix for the quadratic programming based inverse kinematics + Eigen::VectorXd gradient_; ///< gradient vector for the quadratic programming based inverse kinematics + Eigen::SparseMatrix + constraint_matrix_;///< constraint matrix for the quadratic programming based inverse kinematics + Eigen::VectorXd + lower_bound_constraints_;///< lower bound vector for the quadratic programming based inverse kinematics + Eigen::VectorXd + upper_bound_constraints_;///< upper bound vector for the quadratic programming based inverse kinematics + + /** + * @brief Initialize the QP solver + */ + bool init_solver(); + +public: + /** + * @brief Constructor of the QP solver + * @param nb_joints: number of joints in the robot model + * @param lower_position_limit: lower position limits of the joints + * @param upper_position_limit: upper position limits of the joints + * @param velocity_limit: velocity limits of the joints + */ + explicit QPSolver( + const unsigned nb_joints, pinocchio::ModelTpl::ConfigVectorType lower_position_limit, + pinocchio::ModelTpl::ConfigVectorType upper_position_limit, + pinocchio::ModelTpl::TangentVectorType velocity_limit); + + /** + * @brief Copy constructor of the QP solver + */ + QPSolver(const QPSolver& other); + + /** + * @brief Solve the QP problem + */ + Eigen::VectorXd solve(); + + /** + * @brief Set the matrices for the QP problem + * @param coefficients: coefficients for the matrices + * @param parameters: parameters for the inverse velocity kinematics function + * @param joint_positions: joint positions of the robot model + * @param full_displacement: full displacement of the robot model + * @param delta_robot: delta robot + * @param jacobian: jacobian matrix + */ + void set_matrices( + std::vector> coefficients, const QPInverseVelocityParameters& parameters, + const state_representation::JointPositions& joint_positions, + const state_representation::CartesianPose& full_displacement, const Eigen::VectorXd& delta_robot, + const Eigen::MatrixXd& jacobian); + + /** + * @brief Helper function to print the qp_problem (for debugging) + */ + void print_qp_problem(); +}; +}// namespace robot_model diff --git a/source/robot_model/src/Model.cpp b/source/robot_model/src/Model.cpp index 75d12d8bb..2f5b8b30c 100644 --- a/source/robot_model/src/Model.cpp +++ b/source/robot_model/src/Model.cpp @@ -13,8 +13,8 @@ Model::Model(const std::string& robot_name, const std::string& urdf_path, const std::optional>& meshloader_callback ): - robot_name_(std::make_shared>("robot_name", robot_name)), - urdf_path_(std::make_shared>("urdf_path", urdf_path)), + robot_name_(robot_name), + urdf_path_(urdf_path), meshloader_callback_(meshloader_callback), load_collision_geometries_(true) { @@ -22,19 +22,24 @@ Model::Model(const std::string& robot_name, } Model::Model(const std::string& robot_name, const std::string& urdf_path) : - robot_name_(std::make_shared>("robot_name", robot_name)), - urdf_path_(std::make_shared>("urdf_path", urdf_path)) + robot_name_(robot_name), + urdf_path_(urdf_path) { this->init_model(); } -Model::Model(const Model& model) : - robot_name_(model.robot_name_), - urdf_path_(model.urdf_path_), - meshloader_callback_(model.meshloader_callback_), - load_collision_geometries_(model.load_collision_geometries_) +Model::Model(const Model& other): + robot_name_(other.robot_name_), + urdf_path_(other.urdf_path_), + robot_model_(other.robot_model_), + robot_data_(other.robot_data_), + geom_model_(other.geom_model_), + geom_data_(other.geom_data_), + qp_solver_(std::make_unique(*other.qp_solver_)), + frames_(other.frames_), + meshloader_callback_(other.meshloader_callback_), + load_collision_geometries_(other.load_collision_geometries_) { - this->init_model(); } bool Model::create_urdf_from_string(const std::string& urdf_string, const std::string& desired_path) { @@ -108,7 +113,11 @@ void Model::init_model() { } // remove universe and root_joint frame added by Pinocchio this->frames_ = std::vector(frames.begin() + 2, frames.end()); - this->init_qp_solver(); + + // define the QP solver + this->qp_solver_ = std::make_unique( + this->get_number_of_joints(), this->robot_model_.lowerPositionLimit, this->robot_model_.upperPositionLimit, + this->robot_model_.velocityLimit); } void Model::init_geom_model(std::string urdf) { @@ -211,65 +220,7 @@ Eigen::MatrixXd Model::compute_minimum_collision_distances(const state_represent return distances; } - -bool Model::init_qp_solver() { - // clear the solver - this->solver_.data()->clearHessianMatrix(); - this->solver_.data()->clearLinearConstraintsMatrix(); - this->solver_.clearSolver(); - - unsigned int nb_joints = this->get_number_of_joints(); - // initialize the matrices - this->hessian_ = Eigen::SparseMatrix(nb_joints + 1, nb_joints + 1); - this->gradient_ = Eigen::VectorXd::Zero(nb_joints + 1); - this->constraint_matrix_ = Eigen::SparseMatrix(3 * nb_joints + 1 + 2, nb_joints + 1); - this->lower_bound_constraints_ = Eigen::VectorXd::Zero(3 * nb_joints + 1 + 2); - this->upper_bound_constraints_ = Eigen::VectorXd::Zero(3 * nb_joints + 1 + 2); - - // reserve the size of the matrices - this->hessian_.reserve(nb_joints * nb_joints + 1); - this->constraint_matrix_.reserve(5 * nb_joints + 2 * (nb_joints * nb_joints + nb_joints) + 4 * nb_joints + 3); - - Eigen::VectorXd lower_position_limit = this->robot_model_.lowerPositionLimit; - Eigen::VectorXd upper_position_limit = this->robot_model_.upperPositionLimit; - Eigen::VectorXd velocity_limit = this->robot_model_.velocityLimit; - - // configure the QP problem - this->solver_.settings()->setVerbosity(false); - this->solver_.settings()->setWarmStart(true); - - // joint dependent constraints - for (unsigned int n = 0; n < nb_joints; ++n) { - // joint limits - this->constraint_matrix_.coeffRef(n, n) = 1.0; - // joint velocity limits - this->constraint_matrix_.coeffRef(n + nb_joints, n) = 1.0; - this->constraint_matrix_.coeffRef(n + nb_joints, nb_joints) = velocity_limit(n); - this->upper_bound_constraints_(n + nb_joints) = std::numeric_limits::infinity(); - this->constraint_matrix_.coeffRef(n + 2 * nb_joints, n) = 1.0; - this->constraint_matrix_.coeffRef(n + 2 * nb_joints, nb_joints) = -velocity_limit(n); - this->lower_bound_constraints_(n + 2 * nb_joints) = -std::numeric_limits::infinity(); - } - - // time constraint - this->constraint_matrix_.coeffRef(3 * nb_joints, nb_joints) = 1.0; - this->upper_bound_constraints_(3 * nb_joints) = std::numeric_limits::infinity(); - // cartesian velocity constraints - this->upper_bound_constraints_(3 * nb_joints + 1) = std::numeric_limits::infinity(); - this->upper_bound_constraints_(3 * nb_joints + 2) = std::numeric_limits::infinity(); - - // set the initial data of the QP solver_ - this->solver_.data()->setNumberOfVariables(static_cast(nb_joints) + 1); - this->solver_.data()->setNumberOfConstraints(this->lower_bound_constraints_.size()); - if (!this->solver_.data()->setHessianMatrix(this->hessian_)) { return false; } - if (!this->solver_.data()->setGradient(this->gradient_)) { return false; } - if (!this->solver_.data()->setLinearConstraintsMatrix(this->constraint_matrix_)) { return false; } - if (!this->solver_.data()->setLowerBound(this->lower_bound_constraints_)) { return false; } - if (!this->solver_.data()->setUpperBound(this->upper_bound_constraints_)) { return false; } - // instantiate the solver_ - return this->solver_.initSolver(); -} - + std::vector Model::get_frame_ids(const std::vector& frames) { std::vector frame_ids; frame_ids.reserve(frames.size()); @@ -652,36 +603,18 @@ Model::inverse_velocity(const std::vector& } } coefficients.emplace_back(Eigen::Triplet(nb_joints, nb_joints, parameters.alpha)); - this->hessian_.setFromTriplets(coefficients.begin(), coefficients.end()); - //set the gradient - this->gradient_.head(nb_joints) = -parameters.proportional_gain * delta_r.transpose() * jacobian; - // update minimal time as dt expressed in seconds - this->lower_bound_constraints_(3 * nb_joints) = duration_cast>(parameters.dt).count(); - // update joint position constraints - Eigen::VectorXd lower_position_limit = this->robot_model_.lowerPositionLimit; - Eigen::VectorXd upper_position_limit = this->robot_model_.upperPositionLimit; - for (unsigned int n = 0; n < nb_joints; ++n) { - this->lower_bound_constraints_(n) = lower_position_limit(n) - joint_positions.data()(n); - this->upper_bound_constraints_(n) = upper_position_limit(n) - joint_positions.data()(n); - } - // update Cartesian velocity - this->constraint_matrix_.coeffRef(3 * nb_joints + 1, nb_joints) = parameters.linear_velocity_limit; - this->constraint_matrix_.coeffRef(3 * nb_joints + 2, nb_joints) = parameters.angular_velocity_limit; - this->lower_bound_constraints_(3 * nb_joints + 1) = full_displacement.get_position().norm(); - this->lower_bound_constraints_(3 * nb_joints + 2) = full_displacement.get_orientation().vec().norm(); - - // update the constraints - this->solver_.updateHessianMatrix(this->hessian_); - this->solver_.updateGradient(this->gradient_); - this->solver_.updateBounds(this->lower_bound_constraints_, this->upper_bound_constraints_); - this->solver_.updateLinearConstraintsMatrix(this->constraint_matrix_); + + // set the matrices + this->qp_solver_->set_matrices(coefficients, parameters, joint_positions, full_displacement, delta_r, jacobian); + // solve the QP problem - this->solver_.solve(); + auto solution = this->qp_solver_->solve(); + // extract the solution JointPositions joint_displacement(joint_positions.get_name(), joint_positions.get_names(), - this->solver_.getSolution().head(nb_joints)); - double dt = this->solver_.getSolution().tail(1)(0); + solution.head(nb_joints)); + auto dt = solution.tail(1)(0); return JointPositions(joint_displacement) / dt; } @@ -696,24 +629,6 @@ state_representation::JointVelocities Model::inverse_velocity(const state_repres std::vector({actual_frame})); } -void Model::print_qp_problem() { - std::cout << "hessian:" << std::endl; - std::cout << this->hessian_ << std::endl; - - std::cout << "gradient:" << std::endl; - std::cout << this->gradient_ << std::endl; - - for (unsigned int i = 0; i < this->constraint_matrix_.rows(); ++i) { - std::cout << this->lower_bound_constraints_(i); - std::cout << " < | "; - for (unsigned int j = 0; j < this->constraint_matrix_.cols(); ++j) { - std::cout << this->constraint_matrix_.coeffRef(i, j) << " | "; - } - std::cout << " < "; - std::cout << this->upper_bound_constraints_(i) << std::endl; - } -} - bool Model::in_range(const Eigen::VectorXd& vector, const Eigen::VectorXd& lower_limits, const Eigen::VectorXd& upper_limits) { diff --git a/source/robot_model/src/QPSolver.cpp b/source/robot_model/src/QPSolver.cpp new file mode 100644 index 000000000..17e7c434e --- /dev/null +++ b/source/robot_model/src/QPSolver.cpp @@ -0,0 +1,162 @@ +#include "robot_model/QPSolver.hpp" + +namespace robot_model { + +QPSolver::QPSolver( + const unsigned int nb_joints, pinocchio::ModelTpl::ConfigVectorType lower_position_limit, + pinocchio::ModelTpl::ConfigVectorType upper_position_limit, + pinocchio::ModelTpl::TangentVectorType velocity_limit) + : nb_joints_(nb_joints), + lower_position_limit_(lower_position_limit), + upper_position_limit_(upper_position_limit), + velocity_limit_(velocity_limit) { + + if(!this->init_solver()){ + throw std::runtime_error("QP solver initialization failed"); + } +} + +QPSolver::QPSolver(const QPSolver& other) + : nb_joints_(other.nb_joints_), + lower_position_limit_(other.lower_position_limit_), + upper_position_limit_(other.upper_position_limit_), + velocity_limit_(other.velocity_limit_), + hessian_(other.hessian_), + gradient_(other.gradient_), + constraint_matrix_(other.constraint_matrix_), + lower_bound_constraints_(other.lower_bound_constraints_), + upper_bound_constraints_(other.upper_bound_constraints_) { + + if(!this->init_solver()){ + throw std::runtime_error("QP solver initialization failed"); + } +} + +bool QPSolver::init_solver() { + // Clear the solver + this->solver_.data()->clearHessianMatrix(); + this->solver_.data()->clearLinearConstraintsMatrix(); + this->solver_.clearSolver(); + + // Initialize the matrices + this->hessian_ = Eigen::SparseMatrix(this->nb_joints_ + 1, this->nb_joints_ + 1); + this->gradient_ = Eigen::VectorXd::Zero(this->nb_joints_ + 1); + this->constraint_matrix_ = Eigen::SparseMatrix(3 * this->nb_joints_ + 1 + 2, this->nb_joints_ + 1); + this->lower_bound_constraints_ = Eigen::VectorXd::Zero(3 * this->nb_joints_ + 1 + 2); + this->upper_bound_constraints_ = Eigen::VectorXd::Zero(3 * this->nb_joints_ + 1 + 2); + + // reserve the size of the matrices + this->hessian_.reserve(this->nb_joints_ * this->nb_joints_ + 1); + this->constraint_matrix_.reserve( + 5 * this->nb_joints_ + 2 * (this->nb_joints_ * this->nb_joints_ + this->nb_joints_) + 4 * this->nb_joints_ + 3); + + // configure the QP problem + this->solver_.settings()->setVerbosity(false); + this->solver_.settings()->setWarmStart(true); + + // joint dependent constraints + for (unsigned int n = 0; n < this->nb_joints_; ++n) { + // joint limits + this->constraint_matrix_.coeffRef(n, n) = 1.0; + + // joint velocity limits + this->constraint_matrix_.coeffRef(n + this->nb_joints_, n) = 1.0; + this->constraint_matrix_.coeffRef(n + this->nb_joints_, this->nb_joints_) = this->velocity_limit_(n); + this->upper_bound_constraints_(n + this->nb_joints_) = std::numeric_limits::infinity(); + this->constraint_matrix_.coeffRef(n + 2 * this->nb_joints_, n) = 1.0; + this->constraint_matrix_.coeffRef(n + 2 * this->nb_joints_, this->nb_joints_) = -this->velocity_limit_(n); + this->lower_bound_constraints_(n + 2 * this->nb_joints_) = -std::numeric_limits::infinity(); + } + + // time constraint + this->constraint_matrix_.coeffRef(3 * this->nb_joints_, this->nb_joints_) = 1.0; + this->upper_bound_constraints_(3 * this->nb_joints_) = std::numeric_limits::infinity(); + + // cartesian velocity constraints + this->upper_bound_constraints_(3 * this->nb_joints_ + 1) = std::numeric_limits::infinity(); + this->upper_bound_constraints_(3 * this->nb_joints_ + 2) = std::numeric_limits::infinity(); + + // set the initial data of the QP solver_ + this->solver_.data()->setNumberOfVariables(static_cast(this->nb_joints_) + 1); + this->solver_.data()->setNumberOfConstraints(this->lower_bound_constraints_.size()); + + if (!this->solver_.data()->setHessianMatrix(this->hessian_)) { + return false; + } + if (!this->solver_.data()->setGradient(this->gradient_)) { + return false; + } + if (!this->solver_.data()->setLinearConstraintsMatrix(this->constraint_matrix_)) { + return false; + } + if (!this->solver_.data()->setLowerBound(this->lower_bound_constraints_)) { + return false; + } + if (!this->solver_.data()->setUpperBound(this->upper_bound_constraints_)) { + return false; + } + + // instantiate the solver_ + return this->solver_.initSolver(); +} + +Eigen::VectorXd QPSolver::solve() { + // update the constraints + this->solver_.updateHessianMatrix(this->hessian_); + this->solver_.updateGradient(this->gradient_); + this->solver_.updateBounds(this->lower_bound_constraints_, this->upper_bound_constraints_); + this->solver_.updateLinearConstraintsMatrix(this->constraint_matrix_); + + // solve the QP problem + this->solver_.solveProblem(); + + // return the solution + return this->solver_.getSolution(); +} + +void QPSolver::set_matrices( + std::vector> coefficients, const QPInverseVelocityParameters& parameters, + const state_representation::JointPositions& joint_positions, + const state_representation::CartesianPose& full_displacement, const Eigen::VectorXd& delta_robot, + const Eigen::MatrixXd& jacobian) { + using namespace std::chrono; + + // update the hessian matrix + this->hessian_.setFromTriplets(coefficients.begin(), coefficients.end()); + + // update the gradient vector + this->gradient_.head(this->nb_joints_) = -parameters.proportional_gain * delta_robot.transpose() * jacobian; + + // lower bound constraints + this->lower_bound_constraints_(3 * this->nb_joints_) = duration_cast>(parameters.dt).count(); + + for (unsigned int n = 0; n < this->nb_joints_; ++n) { + this->lower_bound_constraints_(n) = this->lower_position_limit_(n) - joint_positions.data()(n); + this->upper_bound_constraints_(n) = this->upper_position_limit_(n) - joint_positions.data()(n); + } + + // update the constraint matrix + this->constraint_matrix_.coeffRef(3 * this->nb_joints_ + 1, this->nb_joints_) = parameters.linear_velocity_limit; + this->constraint_matrix_.coeffRef(3 * this->nb_joints_ + 2, this->nb_joints_) = parameters.angular_velocity_limit; + this->lower_bound_constraints_(3 * this->nb_joints_ + 1) = full_displacement.get_position().norm(); + this->lower_bound_constraints_(3 * this->nb_joints_ + 2) = full_displacement.get_orientation().vec().norm(); +} + +void QPSolver::print_qp_problem() { + std::cout << "hessian:" << std::endl; + std::cout << this->hessian_ << std::endl; + + std::cout << "gradient:" << std::endl; + std::cout << this->gradient_ << std::endl; + + for (unsigned int i = 0; i < this->constraint_matrix_.rows(); ++i) { + std::cout << this->lower_bound_constraints_(i); + std::cout << " < | "; + for (unsigned int j = 0; j < this->constraint_matrix_.cols(); ++j) { + std::cout << this->constraint_matrix_.coeffRef(i, j) << " | "; + } + std::cout << " < "; + std::cout << this->upper_bound_constraints_(i) << std::endl; + } +} +}// namespace robot_model diff --git a/source/robot_model/test/tests/test_model.cpp b/source/robot_model/test/tests/test_model.cpp index 157b9bf94..2f30ac5ed 100644 --- a/source/robot_model/test/tests/test_model.cpp +++ b/source/robot_model/test/tests/test_model.cpp @@ -48,6 +48,11 @@ TEST_F(RobotModelTest, TestGetUrdfPath) { } TEST_F(RobotModelTest, TestCopyConstructor) { + Model tmp(robot_name, urdf_path); + EXPECT_NO_THROW(Model frankCopy(tmp)); +} + +TEST_F(RobotModelTest, TestEqualityConstructor) { Model tmp(robot_name, urdf_path); EXPECT_NO_THROW(*franka = tmp); } From 877eb8f4e7b69af3da0efd74c549d995f6d4cf3a Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 17 Apr 2024 14:46:27 +0200 Subject: [PATCH 10/15] Revert "Revert "build: remove deprecated Dockerfiles and scripts and update installation instructions (#176)"" This reverts commit fb7c9e926510a390598c7f02ec21b7daa007efa7. --- .github/workflows/build-test.yml | 2 +- .github/workflows/generate-docs.yml | 2 +- CHANGELOG.md | 1 + CMakeLists.txt | 6 +- Dockerfile | 4 +- Dockerfile.base | 160 ------------------------- Dockerfile.proto | 34 ------ README.md | 152 ++++++++++++++++-------- VERSION | 2 +- apt-packages.txt | 1 + demos/CMakeLists.txt | 2 +- doxygen/doxygen.conf | 2 +- install.sh | 86 ++++++++++++++ protocol/Dockerfile.protocol | 25 ---- protocol/README.md | 31 ----- protocol/build-test.sh | 43 ------- protocol/clproto_cpp/CMakeLists.txt | 2 +- protocol/dev-server.sh | 90 -------------- protocol/install.sh | 177 ---------------------------- python/Dockerfile.python | 28 ----- python/README.md | 94 ++++----------- python/build-test.sh | 48 -------- python/dev-server.sh | 95 --------------- python/setup.py | 9 +- source/CMakeLists.txt | 2 +- source/Dockerfile.source | 21 ---- source/README.md | 75 ------------ source/build-test.sh | 43 ------- source/dev-server.sh | 76 ------------ source/install.sh | 163 ------------------------- 30 files changed, 230 insertions(+), 1246 deletions(-) delete mode 100644 Dockerfile.base delete mode 100644 Dockerfile.proto create mode 100755 install.sh delete mode 100644 protocol/Dockerfile.protocol delete mode 100755 protocol/build-test.sh delete mode 100755 protocol/dev-server.sh delete mode 100755 protocol/install.sh delete mode 100644 python/Dockerfile.python delete mode 100755 python/build-test.sh delete mode 100755 python/dev-server.sh delete mode 100644 source/Dockerfile.source delete mode 100755 source/build-test.sh delete mode 100755 source/dev-server.sh delete mode 100755 source/install.sh diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index ed7b1cbc4..80fbffb72 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -60,7 +60,7 @@ jobs: exit 1 fi DEMO_CMAKE="./demos/CMakeLists.txt" - if [ $(echo $(grep "project(clproto VERSION" "${DEMO_CMAKE}") | tr -d -c 0-9) -ne "${VERSION}" ]; then + if [ $(echo $(grep "find_package(control_libraries" "${DEMO_CMAKE}") | tr -d -c 0-9) -ne "${VERSION}" ]; then echo "::error file="${DEMO_CMAKE}",title=Check failed::Version in "${DEMO_CMAKE}" does not correspond to VERSION. ${MESSAGE}" exit 1 fi diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml index 0beed7a76..5353ddffd 100644 --- a/.github/workflows/generate-docs.yml +++ b/.github/workflows/generate-docs.yml @@ -1,6 +1,6 @@ name: Generate and Deploy Documentation on: - push: + push: branches: - main diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e329edc..d7a5e7052 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Release Versions: ## Upcoming changes (in development) +- build: remove deprecated Dockerfiles and scripts and update installation instructions (#176) - refactor: optimize copy and swap constructor for robot model (#174) - fix: refactor cmake project to deal with robot model dependencies (#178) - feat: integrate minimum distance calculation feature into robot model(#167) diff --git a/CMakeLists.txt b/CMakeLists.txt index 50d9d59cf..4b065887f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,12 @@ cmake_minimum_required(VERSION 3.15) project(all_control_libraries) +option(BUILD_PROTOCOL "Build and install the protocol" ON) + add_subdirectory(source) -add_subdirectory(protocol/clproto_cpp) +if(BUILD_PROTOCOL) + add_subdirectory(protocol/clproto_cpp) +endif() if(BUILD_TESTING) # reexport the test target defined in the subdirectories diff --git a/Dockerfile b/Dockerfile index 3e312d515..eedf3983f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,6 @@ RUN apt-get update && apt-get install -y \ g++ \ git \ libgtest-dev \ - libeigen3-dev \ python3-pip \ ssh \ sudo \ @@ -83,7 +82,7 @@ ARG TARGETPLATFORM ARG CACHEID COPY dependencies/base_dependencies.cmake CMakeLists.txt RUN --mount=type=cache,target=/build,id=cmake-base-deps-${TARGETPLATFORM}-${CACHEID},uid=1000 \ - cmake -B build -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} && cmake --build build && cmake --install build --prefix /tmp/deps + cmake -B build -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} && cmake --build build && cmake --install build --prefix /tmp/deps FROM base as pinocchio-dependencies COPY --from=apt-dependencies /tmp/apt / @@ -126,6 +125,7 @@ FROM base as dependencies ARG TARGETPLATFORM ARG CACHEID # Needed to build `osqp-eigen` +COPY --from=apt-dependencies /tmp/apt / COPY --from=base-dependencies /tmp/deps /usr COPY dependencies/dependencies.cmake CMakeLists.txt RUN --mount=type=cache,target=/build,id=cmake-deps-${TARGETPLATFORM}-${CACHEID},uid=1000 \ diff --git a/Dockerfile.base b/Dockerfile.base deleted file mode 100644 index 1832f4b31..000000000 --- a/Dockerfile.base +++ /dev/null @@ -1,160 +0,0 @@ -ARG BASE_TAG=22.04 -FROM ubuntu:${BASE_TAG} as core-build-dependencies -ENV DEBIAN_FRONTEND=noninteractive - -# install core compilation and access dependencies for building the libraries -RUN apt-get update && apt-get install -y \ - autoconf \ - automake \ - build-essential \ - cmake \ - curl \ - g++ \ - gcc \ - git \ - gnupg2 \ - libtool \ - lsb-release \ - make \ - pkg-config \ - wget \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - - -FROM core-build-dependencies as google-dependencies - -RUN apt-get update && apt-get install -y \ - libgtest-dev \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# install gtest -WORKDIR /tmp -RUN mkdir gtest_build && cd gtest_build && cmake /usr/src/gtest && make -j \ - && cp lib/* /usr/local/lib || cp *.a /usr/local/lib - -RUN rm -rf /tmp/* && ldconfig - - -FROM core-build-dependencies as robot-model-dependencies - -RUN apt-get update && apt-get install -y \ - libboost-all-dev \ - liburdfdom-dev \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /tmp -ARG EIGEN_TAG=3.4.0 -RUN wget -c https://gitlab.com/libeigen/eigen/-/archive/${EIGEN_TAG}/eigen-${EIGEN_TAG}.tar.gz -O - | tar -xz \ - && cd eigen-${EIGEN_TAG} && mkdir build && cd build && env CXXFLAGS=-DEIGEN_MPL2_ONLY cmake .. && make install \ - && cd ../.. && rm -r eigen-${EIGEN_TAG} || exit 1 - -ARG OSQP_TAG=0.6.2 -RUN git clone --depth 1 -b v${OSQP_TAG} --recursive https://github.com/oxfordcontrol/osqp \ - && cd osqp && mkdir build && cd build && cmake -G "Unix Makefiles" .. && cmake --build . --target install \ - && cd ../.. && rm -r osqp || exit 1 - -ARG OSQP_EIGEN_TAG=0.6.4 -RUN git clone --depth 1 -b v${OSQP_EIGEN_TAG} https://github.com/robotology/osqp-eigen.git \ - && cd osqp-eigen && mkdir build && cd build && cmake .. && make -j && make install \ - && cd ../.. && rm -r osqp-eigen || exit 1 - -ARG PINOCCHIO_TAG=2.6.9 -RUN git clone --depth 1 -b v${PINOCCHIO_TAG} --recursive https://github.com/stack-of-tasks/pinocchio \ - && cd pinocchio && mkdir build && cd build \ - && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_PYTHON_INTERFACE=OFF \ - -DBUILD_TESTING=OFF && make -j $(nproc --ignore=1) && make install && cd ../.. && rm -r pinocchio || exit 1 - -RUN ldconfig - - -FROM robot-model-dependencies as development-dependencies -RUN apt-get update && apt-get install -y \ - clang \ - gdb \ - python3 \ - python3-dev \ - python3-pip \ - tar \ - unzip \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# install python requirements -RUN pip3 install pytest numpy setuptools pybind11 - -# install google dependencies -COPY --from=google-dependencies /usr/include/gtest /usr/include/gtest -COPY --from=google-dependencies /usr/local/lib/libgtest* /usr/local/lib/ - - -FROM development-dependencies as proto-dependencies-20.04 -COPY --from=ghcr.io/aica-technology/control-libraries/proto-dependencies:20.04 /usr/local/include/google /usr/local/include/google -COPY --from=ghcr.io/aica-technology/control-libraries/proto-dependencies:20.04 /usr/local/lib/libproto* /usr/local/lib/ -COPY --from=ghcr.io/aica-technology/control-libraries/proto-dependencies:20.04 /usr/local/bin/protoc /usr/local/bin -RUN ldconfig - - -FROM development-dependencies as proto-dependencies-22.04 -COPY --from=ghcr.io/aica-technology/control-libraries/proto-dependencies:22.04 /usr/local/include/google /usr/local/include/google -COPY --from=ghcr.io/aica-technology/control-libraries/proto-dependencies:22.04 /usr/local/lib/libproto* /usr/local/lib/ -COPY --from=ghcr.io/aica-technology/control-libraries/proto-dependencies:22.04 /usr/local/bin/protoc /usr/local/bin -RUN ldconfig - - -FROM development-dependencies as proto-dependencies-latest -COPY --from=ghcr.io/aica-technology/control-libraries/proto-dependencies:latest /usr/local/include/google /usr/local/include/google -COPY --from=ghcr.io/aica-technology/control-libraries/proto-dependencies:latest /usr/local/lib/libproto* /usr/local/lib/ -COPY --from=ghcr.io/aica-technology/control-libraries/proto-dependencies:latest /usr/local/bin/protoc /usr/local/bin -RUN ldconfig - - -FROM proto-dependencies-${BASE_TAG} as license-information -RUN mkdir -p /usr/share/doc/control-libraries -COPY ./licenses /usr/share/doc/control-libraries/licenses - - -FROM license-information as ssh-configuration - -RUN apt-get update && apt-get install -y \ - sudo \ - libssl-dev \ - ssh \ - iputils-ping \ - rsync \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -# Configure sshd server settings -RUN ( \ - echo 'LogLevel DEBUG2'; \ - echo 'PubkeyAuthentication yes'; \ - echo 'Subsystem sftp /usr/lib/openssh/sftp-server'; \ - ) > /etc/ssh/sshd_config_development \ - && mkdir /run/sshd - -ENV USER developer -ENV HOME /home/${USER} - -# create and configure a new user -ARG UID=1000 -ARG GID=1000 -RUN addgroup --gid ${GID} ${USER} -RUN adduser --gecos "Remote User" --uid ${UID} --gid ${GID} ${USER} && yes | passwd ${USER} -RUN usermod -a -G dialout ${USER} -RUN echo "${USER} ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/99_aptget -RUN chmod 0440 /etc/sudoers.d/99_aptget && chown root:root /etc/sudoers.d/99_aptget - -# Configure sshd entrypoint to authorise the new user for ssh access and -# optionally update UID and GID when invoking the container with the entrypoint script -COPY ./docker/sshd_entrypoint.sh /sshd_entrypoint.sh -RUN chmod 744 /sshd_entrypoint.sh - -# create the credentials to be able to pull private repos using ssh -RUN mkdir /root/.ssh/ && ssh-keyscan github.com | tee -a /root/.ssh/known_hosts - -RUN echo "session required pam_limits.so" | tee --append /etc/pam.d/common-session > /dev/null - -WORKDIR ${HOME} diff --git a/Dockerfile.proto b/Dockerfile.proto deleted file mode 100644 index e468f892e..000000000 --- a/Dockerfile.proto +++ /dev/null @@ -1,34 +0,0 @@ -ARG BASE_TAG=22.04 -FROM ubuntu:${BASE_TAG} as build-stage -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && apt-get install -y \ - autoconf \ - cmake \ - g++ \ - gcc \ - libtool \ - make \ - wget \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /tmp -ARG PROTOBUF_VERSION=21.0 -RUN wget -O protobuf-cpp-"${PROTOBUF_VERSION}".tar.gz \ - https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protobuf-cpp-3.${PROTOBUF_VERSION}.tar.gz \ - && tar -xzf protobuf-cpp-"${PROTOBUF_VERSION}".tar.gz \ - && rm protobuf-cpp-"${PROTOBUF_VERSION}".tar.gz - -WORKDIR /tmp/protobuf-3."${PROTOBUF_VERSION}" -RUN ./autogen.sh \ - && ./configure \ - && make -j $(nproc --ignore=1) \ - && make install - - -FROM ubuntu:${BASE_TAG} as google-dependencies -COPY --from=build-stage /usr/local/include/google /usr/local/include/google -COPY --from=build-stage /usr/local/lib/libproto* /usr/local/lib/ -COPY --from=build-stage /usr/local/bin/protoc /usr/local/bin -RUN ldconfig diff --git a/README.md b/README.md index cda084a8c..489562e3d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ - build-push workflow badge + build-release workflow badge + build-test workflow badge contribution license agreement workflow badge page build and deployment workflow badge @@ -10,44 +11,12 @@ The `control-libraries` project is a collection of modules to facilitate the creation of control loop algorithms for robotics, including trajectory planning, kinematics, dynamics and control. -Code documentation is available at -aica-technology.github.io/control-libraries. - -## Releases - -The latest stable version of the project is available on the -[`main`](https://github.com/aica-technology/control-libraries/tree/main), while the latest pre-release development -build is available on the [`develop`](https://github.com/aica-technology/control-libraries/tree/develop) branch. - -Refer to the [Releases](https://github.com/aica-technology/control-libraries/releases) page for other versions. - - - - - - - - - - - - - - - -
BranchStatus
- - main - - - main branch build-test workflow badge -
- - develop - - - develop branch build-test workflow badge -
+Code documentation is available at +[aica-technology.github.io/control-libraries](https://aica-technology.github.io/control-libraries). + +The rolling version of the project is available on the +[`main`](https://github.com/aica-technology/control-libraries/tree/main) branch. Refer to the +[Releases](https://github.com/aica-technology/control-libraries/releases) page for other versions. ## Core libraries @@ -60,32 +29,115 @@ The core libraries are implemented in C++ and comprise the following modules: Source code, documentation and installation instructions are available under the [source](./source) folder. +## Protocol + +There is a module that defines the protocol for sending and receiving messages containing control libraries data across +any network, based on the Google Protocol Buffer. For its implementation, installation and documentation, see the +[protocol](./protocol) folder. + ## Python bindings There exist Python bindings for the control library modules and the protocol module. See the [python](./python) folder for installation instructions. -## Protocol - -There is a module that defines the protocol for sending and receiving messages containing control libraries -data across any network, based on the Google Protocol Buffer. For its implementation, installation and -documentation, see the [protocol](./protocol) folder. - ## Demos For examples and demos in C++ and Python, refer to the [demos](./demos) folder. ## Contributing -We welcome user engagement to find bugs, resolve issues and suggest useful features. -Refer to the [contribution guidelines](./CONTRIBUTING.md) for more information. +We welcome user engagement to find bugs, resolve issues and suggest useful features. Refer to the +[contribution guidelines](./CONTRIBUTING.md) for more information. ## License -This project is provided free and open-source under the GPLv3 license. -See the [licenses](./licenses) folder for more information. +This project is provided free and open-source under the GPLv3 license. See the [licenses](./licenses) folder for more +information. + +## Installation + +### Supported platforms + +These libraries have been developed and tested on Linux Ubuntu 20.04 and 22.04. They should also work on macOS and +Windows, though the installation steps may differ. At this time no guarantees are made for library support on non-Linux +systems. + +### Installation with the install script + +This project uses CMake to generate static library objects for each of the modules. To facilitate the installation +process, an [install script](./install.sh) is provided. + +The install script takes care of all the installation steps, including the installation and configuration of all +dependencies. It can be run with several optional arguments: +- `-y`, `--auto`: Any input prompts will be suppressed and install steps automatically approved. +- `-d [path]`, `--dir [path]`: If provided, the installation directory will be changed to `[path]`. +- `--clean`: Any previously installed header files from `/usr/local/include` and any shared library files from + `/usr/local/lib` will be deleted before the installation. +- `--cleandir [path]`: Any previously installed header files shared library files from `[path]` will be deleted before + the installation. + +### Advanced options + +Users who prefer to perform the installation manually and/or have already installed some dependencies can selectively +do the steps from the install script. + +The CMake configuration flags for control libraries `BUILD_CONTROLLERS`, `BUILD_DYNAMICAL_SYSTEMS` and +`BUILD_ROBOT_MODEL` determine which modules are built, and are all defined as `ON` by default. The building of the +`state_representation` library cannot be disabled, as all other libraries depend on it. To selectively disable the build +of a particular module, set the flag to `=OFF`. For example, the following flags will prevent the `robot_model` module +from being built, which is useful if the Pinocchio dependency is not fulfilled on your system. + +```shell script +-DBUILD_ROBOT_MODEL=OFF +``` + +Similarly, if one is not interested in the installation of the [protocol](./protocol/README.md), it can be disabled with + +```shell script +-DBUILD_PROTOCOL_=OFF +``` + +The C++ `clproto` library requires control libraries [`state_representation`](../source/state_representation/README.md) +and [Google Protobuf](https://github.com/protocolbuffers/protobuf/blob/master/src/README.md) +to be installed on your computer, which includes the compiler `protoc` and the runtime library `libprotobuf.so`. + +To also build the tests, add the CMake flag `-DBUILD_TESTING=ON`. This requires GTest to be installed on your system. +You can then use `make test` to run all test targets. + +Alternatively, you can include the source code for each library as submodules in your own CMake project, using the CMake +directive `add_subdirectory(...)` to link it with your project. + +### Installation of Python bindings + +You must first install the C++ modules before you can install the Python bindings. Additionally, the installation of the +bindings requires the following prerequisites: +- `python3` >= 3.0 +- `pip3` >= 10.0.0 + +The installation itself is then quite straightforward: +```shell +python3 pip install ./python +``` + +If the installation fails, it may be because of non-default installation directories for some dependencies. In this +case, the include path for OSQP can be set through environment variables before the pip install. + +```shell +export OSQP_INCLUDE_DIR='/path/to/include/osqp' # replace /path/to/include with installation directory +python3 pip install ./python +``` + +The example above installs the module to the default dist-packages location. You can see more information about the +installed module using `pip3 show control-libraries`. + +The process also works with Python virtual environments. For example, with `pipenv`: + +```shell script +## pip3 install pipenv + +pipenv install ./python +``` ## External resources -- [Docker images with control libraries installations](https://github.com/aica-technology/docker-images) - [Modulo: an extension layer to ROS2 based on control libraries](https://github.com/aica-technology/modulo) diff --git a/VERSION b/VERSION index 25627bcf1..da8d65311 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.7 +7.4.8 diff --git a/apt-packages.txt b/apt-packages.txt index c7033178b..2988803ab 100644 --- a/apt-packages.txt +++ b/apt-packages.txt @@ -1,3 +1,4 @@ libboost-all-dev +libeigen3-dev liburdfdom-dev libassimp-dev diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index ef9a9b478..05bbe12f2 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -15,7 +15,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -find_package(control_libraries 7.4.7 CONFIG REQUIRED) +find_package(control_libraries 7.4.8 CONFIG REQUIRED) set(DEMOS_SCRIPTS task_space_control_loop diff --git a/doxygen/doxygen.conf b/doxygen/doxygen.conf index 73a3dd529..bb703f7d4 100644 --- a/doxygen/doxygen.conf +++ b/doxygen/doxygen.conf @@ -38,7 +38,7 @@ PROJECT_NAME = "Control Libraries" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 7.4.7 +PROJECT_NUMBER = 7.4.8 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/install.sh b/install.sh new file mode 100755 index 000000000..d7da83d17 --- /dev/null +++ b/install.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" + +INSTALL_DESTINATION="/usr/local" +AUTO_INSTALL="" + +PINOCCHIO_TAG=v2.9.0 +HPP_FCL_TAG=v1.8.1 + +FAIL_MESSAGE="The provided input arguments are not valid. +Run the script with the '--help' argument." + +HELP_MESSAGE="Usage: [sudo] ./install.sh [OPTIONS] + +An install script for the control libraries. + +Options: + -y, --auto Suppress any input prompts and + automatically approve install steps. + -d, --dir [path] Configure the installation directory + (default: ${INSTALL_DESTINATION}). + + --clean Delete any previously installed header + files from /usr/local/include and any + shared library files from /usr/local/lib. + --cleandir [path] Delete any previously installed header + and library files from the specified path. + + -h, --help Show this help message." + +function uninstall { + function delete_components { + rm -r "${INSTALL_DESTINATION}"/include/controllers + rm -r "${INSTALL_DESTINATION}"/include/dynamical_systems + rm -r "${INSTALL_DESTINATION}"/include/robot_model + rm -r "${INSTALL_DESTINATION}"/include/state_representation + rm -r "${INSTALL_DESTINATION}"/lib/libcontrollers*.so + rm -r "${INSTALL_DESTINATION}"/lib/libdynamical_systems*.so + rm -r "${INSTALL_DESTINATION}"/lib/librobot_model*.so + rm -r "${INSTALL_DESTINATION}"/lib/libstate_representation*.so + rm -r "${INSTALL_DESTINATION}"/include/clproto + rm -r "${INSTALL_DESTINATION}"/lib/libclproto*.so + } + + delete_components >/dev/null 2>&1 + + echo "Deleted any control library artefacts from ${INSTALL_DESTINATION}." +} + +while [ "$#" -gt 0 ]; do + case "$1" in + -y|--auto) AUTO_INSTALL="-y"; shift 1;; + --clean) uninstall; exit 0;; + --cleandir) INSTALL_DESTINATION=$2; uninstall; exit 0;; + -d|--dir) INSTALL_DESTINATION=$2; shift 2;; + -h|--help) echo "$HELP_MESSAGE"; exit 0;; + -*) echo "Unknown option: $1" >&2; echo "$FAIL_MESSAGE"; exit 1;; + esac +done + +mkdir -p "${SCRIPT_DIR}"/tmp || exit 1 + +echo ">>> INSTALLING DEPENDENCIES" + +apt update +xargs -a <(awk '! /^ *(#|$)/' "${SCRIPT_DIR}/apt-packages.txt") -r -- apt install "${AUTO_INSTALL}" + +cd "${SCRIPT_DIR}"/tmp +cp "${SCRIPT_DIR}"/dependencies/base_dependencies.cmake CMakeLists.txt || exit 1 +cmake -B build -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release && cmake --build build && cmake --install build || exit 1 +rm -rf build +git clone --depth 1 -b ${HPP_FCL_TAG} --recursive https://github.com/humanoid-path-planner/hpp-fcl || exit 1 +cmake -B build -S hpp-fcl -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON_INTERFACE=OFF && cmake --build build --target all install || exit 1 +rm -rf build +git clone --depth 1 -b ${PINOCCHIO_TAG} --recursive https://github.com/stack-of-tasks/pinocchio +cmake -B build -S pinocchio -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_PYTHON_INTERFACE=OFF -DBUILD_WITH_COLLISION_SUPPORT=ON && cmake --build build --target all install || exit 1 +rm -rf build +cp "${SCRIPT_DIR}"/dependencies/dependencies.cmake CMakeLists.txt || exit 1 +cmake -B build -Dprotobuf_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release && cmake --build build && cmake --install build || exit 1 +rm -rf build + +echo ">>> INSTALLING CONTROL LIBRARIES" +cd "${SCRIPT_DIR}" && rm -rf "${SCRIPT_DIR}"/tmp +cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build && cmake --install build --prefix "${INSTALL_DESTINATION}" || exit 1 +rm -rf build diff --git a/protocol/Dockerfile.protocol b/protocol/Dockerfile.protocol deleted file mode 100644 index bf99aeea9..000000000 --- a/protocol/Dockerfile.protocol +++ /dev/null @@ -1,25 +0,0 @@ -ARG BASE_TAG=latest -FROM ghcr.io/aica-technology/control-libraries/development-dependencies:${BASE_TAG} as source -ARG BRANCH=develop - -WORKDIR /source -RUN git clone --depth 1 --branch ${BRANCH} https://github.com/aica-technology/control-libraries -RUN bash control-libraries/source/install.sh --auto --no-controllers --no-dynamical-systems --no-robot-model - -WORKDIR /tmp/protocol -COPY ./ ./ - -WORKDIR /tmp/protocol/protobuf -RUN make all - - -FROM source as build - -WORKDIR /tmp/protocol/clproto_cpp/build -RUN cmake -DBUILD_TESTING=ON .. && make -j all && make install - - -FROM build as testing - -RUN CTEST_OUTPUT_ON_FAILURE=1 make test -RUN rm -rf /tmp/protocol/ diff --git a/protocol/README.md b/protocol/README.md index 3d4b71373..df8792329 100644 --- a/protocol/README.md +++ b/protocol/README.md @@ -18,34 +18,3 @@ message formats hidden behind a neat header file. The API allows compatible objects to be easily encoded into a serialized binary string for transmitting across. Equivalently, well-formatted string messages can be decoded back into the equivalent objects. - -## Installation - -The C++ `clproto` library requires control libraries [`state_representation`](../source/state_representation/README.md) -and [Google Protobuf](https://github.com/protocolbuffers/protobuf/blob/master/src/README.md) -to be installed on your computer, which includes the compiler `protoc` and the runtime library `libprotobuf.so`. - -An [install script](./install.sh) is provided in this directory. Run `./install.sh -h` for more information. - -### Automatic dependency installation - -The easiest way to install `clproto` is to use the included install script in automatic mode. -By supplying the `--auto` flag to this script, it will automatically and recursively install any dependencies. -If Protobuf is not yet installed, this step will take some time. -```shell -git clone https://github.com/aica-technology/control-libraries.git -sudo control-libraries/protocol/install.sh --auto -``` - -### Copying protobuf dependencies - -If you are using Docker, the Protobuf dependencies are already built in the [`development-dependencies`](ghcr.io/aica-technology/control-libraries/development-dependencies:latest) image. -Since building and installing Protobuf from source takes quite a long time, you can instead copy the final artefacts -from this image into your image using docker `COPY` functionality: - -```Dockerfile -COPY --from=ghcr.io/aica-technology/control-libraries/development-dependencies:latest /usr/local/include/google /usr/local/include/google -COPY --from=ghcr.io/aica-technology/control-libraries/development-dependencies:latest /usr/local/lib/libproto* /usr/local/lib -COPY --from=ghcr.io/aica-technology/control-libraries/development-dependencies:latest /usr/local/bin/protoc /usr/local/bin -RUN ldconfig -``` diff --git a/protocol/build-test.sh b/protocol/build-test.sh deleted file mode 100755 index 21eb57f13..000000000 --- a/protocol/build-test.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash - -BASE_TAG="latest" - -BRANCH=$(git branch --show-current) - -HELP_MESSAGE="Usage: build-test.sh [-b ] [--base-tag ] [-r] [-v] -Options: - -b, --branch Specify the branch of control libraries - that should be used to build the image. - --base-tag Tag of the development image. - (default: ${BASE_TAG}) - -r, --rebuild Rebuild the image using the docker - --no-cache option. - -v, --verbose Use the verbose option during the building - process. - -h, --help Show this help message. -" - -BUILD_FLAGS=() -while [[ $# -gt 0 ]]; do - opt="$1" - case $opt in - -b|--branch) BRANCH=$2; shift 2;; - --base-tag) BASE_TAG=$2; shift 2;; - -r|--rebuild) BUILD_FLAGS+=(--no-cache); shift ;; - -v|--verbose) BUILD_FLAGS+=(--progress=plain); shift ;; - -h|--help) echo "${HELP_MESSAGE}"; exit 0 ;; - *) echo 'Error in command line parsing' >&2 - echo -e "\n${HELP_MESSAGE}" - exit 1 - esac -done - -IMAGE_NAME=aica-technology/control-libraries/protocol/testing:"${BASE_TAG}" -BUILD_FLAGS+=(--build-arg BASE_TAG="${BASE_TAG}") -BUILD_FLAGS+=(-t "${IMAGE_NAME}") - -echo "Using control libraries branch ${BRANCH}" -BUILD_FLAGS+=(--build-arg BRANCH="${BRANCH}") - -docker pull ghcr.io/aica-technology/control-libraries/development-dependencies:"${BASE_TAG}" || exit 1 -DOCKER_BUILDKIT=1 docker build . --file ./Dockerfile.protocol "${BUILD_FLAGS[@]}" diff --git a/protocol/clproto_cpp/CMakeLists.txt b/protocol/clproto_cpp/CMakeLists.txt index 81bc74590..e97dad1fb 100644 --- a/protocol/clproto_cpp/CMakeLists.txt +++ b/protocol/clproto_cpp/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(clproto VERSION 7.4.7) +project(clproto VERSION 7.4.8) # Default to C99 if(NOT CMAKE_C_STANDARD) diff --git a/protocol/dev-server.sh b/protocol/dev-server.sh deleted file mode 100755 index b004081ea..000000000 --- a/protocol/dev-server.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env bash - -BASE_TAG="latest" - -CONTAINER_NAME=aica-technology-control-libraries-protocol-development-ssh -BRANCH=$(git branch --show-current) - -SSH_PORT=2244 -SSH_KEY_FILE="${HOME}/.ssh/id_rsa.pub" - -HELP_MESSAGE="Usage: ./dev-server.sh [-b ] [-p ] [-k ] [--base-tag ] [-r] - -Build and run a docker container as an SSH toolchain server for remote development. - -The server is bound to the specified port on localhost (127.0.0.1) -and uses passwordless RSA key-pair authentication. The host public key -is read from the specified key file and copied to the server on startup. -On linux hosts, the UID and GID of the specified user will also be -set to match the UID and GID of the host user by the entry script. - -The server will run in the background as ${CONTAINER_NAME}. - -You can connect with 'ssh developer@localhost -p '. - -Close the server with 'docker stop ${CONTAINER_NAME}'. - -Options: - -b, --branch Specify the branch of control libraries - that should be used to build the image. - -p, --port Specify the port to bind for SSH - connection. - (default: ${SSH_PORT}) - -k, --key-file Specify the path of the RSA - public key file. - (default: ${SSH_KEY_FILE}) - --base-tag Tag of the development image. - (default: ${BASE_TAG}) - -r, --rebuild Rebuild the image with the --no-cache option. - -h, --help Show this help message." - -BUILD_FLAGS=() -while [ "$#" -gt 0 ]; do - case "$1" in - -b|--branch) BRANCH=$2; shift 2;; - -p|--port) SSH_PORT=$2; shift 2;; - -k|--key-file) SSH_KEY_FILE=$2; shift 2;; - --base-tag) BASE_TAG=$2; shift 2;; - -h|--help) echo "${HELP_MESSAGE}"; exit 0;; - -r|--rebuild) BUILD_FLAGS+=(--no-cache); shift 1;; - *) echo 'Error in command line parsing' >&2 - echo -e "\n${HELP_MESSAGE}" - exit 1 - esac -done - -PUBLIC_KEY=$(cat "${SSH_KEY_FILE}") - -COMMAND_FLAGS=() -COMMAND_FLAGS+=(--key "${PUBLIC_KEY}") -COMMAND_FLAGS+=(--user developer) - -if [[ "${OSTYPE}" != "darwin"* ]]; then - USER_ID=$(id -u "${USER}") - GROUP_ID=$(id -g "${USER}") - COMMAND_FLAGS+=(--uid "${USER_ID}") - COMMAND_FLAGS+=(--gid "${GROUP_ID}") -fi - -IMAGE_NAME=aica-technology/control-libraries/protocol/development:"${BASE_TAG}" -BUILD_FLAGS+=(--build-arg BASE_TAG="${BASE_TAG}") -BUILD_FLAGS+=(-t "${IMAGE_NAME}") - -echo "Using control libraries branch ${BRANCH}" -BUILD_FLAGS+=(--build-arg BRANCH="${BRANCH}") -BUILD_FLAGS+=(--target source) -docker pull ghcr.io/aica-technology/control-libraries/development-dependencies:"${BASE_TAG}" || exit 1 -DOCKER_BUILDKIT=1 docker build . --file ./Dockerfile.protocol "${BUILD_FLAGS[@]}" || exit 1 - -docker container stop "${CONTAINER_NAME}" >/dev/null 2>&1 -docker rm --force "${CONTAINER_NAME}" >/dev/null 2>&1 - -echo "Starting background container with access port ${SSH_PORT} for user developer" -docker run -d --rm --cap-add sys_ptrace \ - --user root \ - --publish 127.0.0.1:"${SSH_PORT}":22 \ - --name "${CONTAINER_NAME}" \ - --hostname "${CONTAINER_NAME}" \ - "${IMAGE_NAME}" /sshd_entrypoint.sh "${COMMAND_FLAGS[@]}" - -echo "${CONTAINER_NAME}" diff --git a/protocol/install.sh b/protocol/install.sh deleted file mode 100755 index 476741229..000000000 --- a/protocol/install.sh +++ /dev/null @@ -1,177 +0,0 @@ -#!/bin/bash -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" -PROTOBUF_DIR="${SCRIPT_DIR}"/protobuf -CLPROTO_DIR="${SCRIPT_DIR}"/clproto_cpp - -INSTALL_DESTINATION="/usr/local" -BUILD_TESTING="OFF" -AUTO_INSTALL="" -BINDINGS_ONLY=false -PROTOBUF_VERSION="3.17.0" - -HELP_MESSAGE="Usage: [sudo] ./install.sh [OPTIONS] - -An install script for the clproto library. - -Options: - -y, --auto Suppress any input prompts and - automatically approve install steps. - - --bindings-only Only generate the protobuf bindings - without building or installing clproto. - - -d, --dir [path] Configure the installation directory - (default: ${INSTALL_DESTINATION}). - - --build-tests Build the unittest targets. - - --clean-bindings Clean any previously generated protobuf - bindings. - - --clean Delete any previously installed header - files from ${INSTALL_DESTINATION}/include and any - shared library files from ${INSTALL_DESTINATION}/lib. - - --cleandir [path] Delete any previously installed header - and library files from the specified path. - - -h, --help Show this help message." - -function make_bindings() { - cd "${PROTOBUF_DIR}" && make all || exit 1 -} - -function clean_bindings() { - cd "${PROTOBUF_DIR}" && make clean -} - -function install_protobuf() { - echo ">>> INSTALLING PROTOBUF DEPENDENCIES" - apt-get update && apt-get install "${AUTO_INSTALL}" autoconf automake libtool curl make g++ unzip || exit 1 - - mkdir -p "${SCRIPT_DIR}"/install - cd "${SCRIPT_DIR}"/install || exit 1 - wget -O protobuf-cpp-"${PROTOBUF_VERSION}".tar.gz \ - https://github.com/protocolbuffers/protobuf/releases/download/v"${PROTOBUF_VERSION}"/protobuf-cpp-"${PROTOBUF_VERSION}".tar.gz && - tar -xzf protobuf-cpp-"${PROTOBUF_VERSION}".tar.gz && - rm protobuf-cpp-"${PROTOBUF_VERSION}".tar.gz - - cd "${SCRIPT_DIR}"/install/protobuf-"${PROTOBUF_VERSION}" || exit 1 - ./autogen.sh && ./configure && make && make install || exit 1 - ldconfig -} - -function install_state_representation() { - echo ">> INSTALLING CONTROL LIBRARY DEPENDENCIES" - mkdir -p "${SCRIPT_DIR}"/install - cd "${SCRIPT_DIR}"/install || exit 1 - CL_INSTALL_SCRIPT="$(dirname "${SCRIPT_DIR}")"/source/install.sh - if [ -f "${CL_INSTALL_SCRIPT}" ]; then - bash "${CL_INSTALL_SCRIPT}" --no-controllers --no-dynamical-systems --no-robot-model "${AUTO_INSTALL}" - else - echo ">>> INSTALL SCRIPT NOT FOUND: ${CL_INSTALL_SCRIPT}!" - exit 1 - fi - ldconfig -} - -function install_clproto() { - cd "${CLPROTO_DIR}" && mkdir -p build && cd build || exit 1 - - cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING="${BUILD_TESTING}" \ - -DCMAKE_INSTALL_PREFIX="${INSTALL_DESTINATION}" .. || exit 1 - - make -j && make install || exit 1 - ldconfig -} - -function uninstall() { - function delete_components() { - rm -r "${INSTALL_DESTINATION}"/include/clproto - rm -r "${INSTALL_DESTINATION}"/lib/libclproto*.so - clean_bindings - } - - delete_components >/dev/null 2>&1 - - echo "Deleted any clproto artefacts from ${INSTALL_DESTINATION} and ${PROTOBUF_DIR}." -} - -while [ "$#" -gt 0 ]; do - case "$1" in - -y | --auto) - AUTO_INSTALL="-y" - shift 1 - ;; - -d | --dir) - INSTALL_DESTINATION=$2 - shift 2 - ;; - --bindings-only) - BINDINGS_ONLY=true - shift 1 - ;; - --build-tests) - BUILD_TESTING="ON" - shift 1 - ;; - --clean-bindings) - clean_bindings - exit 0 - ;; - --clean) - uninstall - exit 0 - ;; - --cleandir) - INSTALL_DESTINATION=$2 - uninstall - exit 0 - ;; - -h | --help) - echo "${HELP_MESSAGE}" - exit 0 - ;; - - -*) - echo "Unknown option: $1" >&2 - echo "${FAIL_MESSAGE}" - exit 1 - ;; - esac -done - -if ! [ -x "$(command -v protoc)" ]; then - echo ">>> PROTOC NOT FOUND" - install_protobuf || exit 1 -fi - -echo ">>> GENERATING PROTOBUF BINDINGS" -make_bindings || exit 1 - -if [ "${BINDINGS_ONLY}" == true ]; then - echo ">>> DONE!" - exit 0 -fi - -PROTOBUF_INSTALL=$(ldconfig -p | grep libprotobuf) -if [ -z "${PROTOBUF_INSTALL}" ]; then - echo ">>> LIBPROTOBUF NOT FOUND" - install_protobuf || exit 1 -fi - -if [ -z $(which pkg-config) ]; then - echo ">>> INSTALLING pkg-config tool" - apt-get update && apt-get install "${AUTO_INSTALL}" pkg-config || exit 1 -fi - -pkg-config state_representation --atleast-version=$(cat "$(dirname "${SCRIPT_DIR}")"/VERSION) -if [ "$?" != 0 ]; then - echo ">>> STATE REPRESENTATION LIBRARY NOT FOUND!" - install_state_representation -fi - -echo ">>> INSTALLING CLPROTO" -install_clproto || exit 1 - -echo ">>> DONE" diff --git a/python/Dockerfile.python b/python/Dockerfile.python deleted file mode 100644 index 4b3753478..000000000 --- a/python/Dockerfile.python +++ /dev/null @@ -1,28 +0,0 @@ -ARG BASE_TAG=latest -FROM ghcr.io/aica-technology/control-libraries/development-dependencies:${BASE_TAG} as source -ARG BRANCH=develop - -WORKDIR /source -RUN git clone --depth 1 --branch ${BRANCH} https://github.com/aica-technology/control-libraries -RUN bash control-libraries/source/install.sh --auto -RUN bash control-libraries/protocol/install.sh --auto -RUN rm -rf control-libraries - -FROM source as build - -USER developer -WORKDIR ${HOME}/python -# copy these files separately because otherwise any changes in the test directory would trigger the install again -COPY include include -COPY source source -COPY pyproject.toml setup.py . -ENV OSQP_INCLUDE_DIR /usr/local/include/osqp -RUN pip3 install . - - -FROM build as testing - -COPY test test -RUN pytest - -CMD ["/bin/bash"] diff --git a/python/README.md b/python/README.md index 35f17a059..fe5bbdc16 100644 --- a/python/README.md +++ b/python/README.md @@ -2,47 +2,31 @@ This directory defines Python bindings for the control libraries. -## Installation +## Structure -You must first install `control-libraries` before you can install the Python bindings. -Refer to the installation instructions in the top-level [README](../README.md) for more information. - -Additionally, the installation of the bindings requires the following prerequisites: -- `python3` >= 3.0 -- `pip3` >= 10.0.0 - -The installation itself is then quite straightforward: -```shell -git clone https://github.com/aica-technology/control-libraries - -## install control-libraries (skip this stage if already done) -sudo control-libraries/source/install.sh +[PyBind11](https://PyBind11.readthedocs.io/en/stable/index.html) is used to generate +Python bindings for the classes and functions in control libraries. -## install protocol (skip this stage if already done or not desired) -sudo control-libraries/protocol/install.sh +The generated package is named `control-libraries`, but contains specific modules for importing. +These are named the same as the standard modules of control libraries (e.g. `state_representation`). -## install the bindings using the pip installer -pip3 install control-libraries/python -``` +The contents of the [`source`](./source) directory define the bindings between +each Python module and the respective C++ library. The source files to bind each module are +contained within a subdirectory of the same name. -If the installation fails, it may be because of non-default installation directories for some dependencies. -In this case, the include path for OSQP can be set through environment variables before the pip install. -```shell -export OSQP_INCLUDE_DIR='/path/to/include/osqp' # default /usr/local/include/osqp -pip3 install control-libraries/python -``` +The `setup.py` and `pyproject.toml` files are used to configure the build and installation +of the Python bindings. The `.toml` file allows `pip` to automatically fetch the +installation dependencies (namely `setuptools` and `pybind11`) in a temporary cache, +allowing the subsequent `setup.py` to be evaluated without needing a local installation of `pybind11`. +This feature requires a [`pip`](https://pypi.org/project/pip/) version 10.0 or newer. -The example above installs the module to the default dist-packages location. -You can see more information about the installed module using `pip3 show control-libraries`. +The [`test`](./test) directory contains some Python scripts that import and check the bindings +using the Python `unittest` and `pytest` frameworks. They are not currently comprehensive. -The process also works with Python virtual environments. For example, with `pipenv`: -```shell script -## pip3 install pipenv +## Usage -pipenv install control-libraries/python -``` +You can import the modules with an optional short alias: -Once installed, you can simply import the module with an optional short alias: ```python #!/usr/bin/env python import state_representation as sr @@ -56,6 +40,7 @@ print(A) ``` Or, directly import specific classes from the module. + ```python #!/usr/bin/env python from state_representation import JointState @@ -65,8 +50,8 @@ B = JointState().Random("B", 3) ds = create_cartesian_ds(DYNAMICAL_SYSTEM_TYPE.POINT_ATTRACTOR) ``` -If the `clproto` C++ library is installed, the installation steps above will automatically install the `clproto` -Python module which can be used to encode and decode objects into bytes of serialized data. +The `clproto` Python module can be used to encode and decode objects into bytes of serialized data. + ```python #!/usr/bin/env python from state_representation import JointState @@ -77,42 +62,3 @@ encoded_msg = clproto.encode(B, clproto.MessageType.JOINT_STATE_MESSAGE) decoded_object = clproto.decode(encoded_msg) ``` - -## About - -[PyBind11](https://PyBind11.readthedocs.io/en/stable/index.html) is used to generate -Python bindings for the classes and functions in control libraries. - -The generated package is named `control-libraries`, but contains specific modules for importing. -These are named the same as the standard modules of control libraries (e.g. `state_representation`). - -The contents of the [`source`](./source) directory define the bindings between -each Python module and the respective C++ library. The source files to bind each module are -contained within a subdirectory of the same name. - -The `setup.py` and `pyproject.toml` files are used to configure the build and installation -of the Python bindings. The `.toml` file allows `pip` to automatically fetch the -installation dependencies (namely `setuptools` and `pybind11`) in a temporary cache, -allowing the subsequent `setup.py` to be evaluated without needing a local installation of `pybind11`. -This feature requires a [`pip`](https://pypi.org/project/pip/) version 10.0 or newer. - -The [`test`](./test) directory contains some Python scripts that import and check the bindings -using the Python `unittest` framework. They are not currently comprehensive. - -## Dockerfile - -A Dockerfile and run script are provided to test the installation of the bindings. - -The docker image installs the core control libraries and subsequently installs the python bindings. - -The [`build-test.sh`](./build-test.sh) script will build the docker image and launch an interactive container -with the test files in the [`test`](./test) directory copied to the local path. - -The run script tries to the clone the current local git branch when installing the control libraries -in the Dockerfile. If the local branch does not exist on the remote, or if you want to test the -python bindings against a difference control libraries source branch, you can supply a specific -branch as the first argument to the run script. For example, `./run.sh develop` to use the `develop` branch. - -You can run a single test with `python `, or just enter a python shell with `python`. -Run all tests with `python -m unittest discover `, or just `python -m unittest` if -the [`test`](./test) directory in your current working directory. diff --git a/python/build-test.sh b/python/build-test.sh deleted file mode 100755 index fd84936af..000000000 --- a/python/build-test.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash - -BASE_TAG="latest" - -BRANCH=$(git branch --show-current) - -HELP_MESSAGE="Usage: build-test.sh [-b ] [--base-tag ] [-r] [-v] -Options: - -b, --branch Specify the branch of control libraries - that should be used to build the image. - --base-tag Tag of the development image. - (default: ${BASE_TAG}) - -r, --rebuild Rebuild the image using the docker - --no-cache option. - -v, --verbose Use the verbose option during the building - process. - -h, --help Show this help message. -" - -BUILD_FLAGS=() -while [[ $# -gt 0 ]]; do - opt="$1" - case $opt in - -b|--branch) BRANCH=$2; shift 2;; - --base-tag) BASE_TAG=$2; shift 2;; - -r|--rebuild) BUILD_FLAGS+=(--no-cache); shift ;; - -v|--verbose) BUILD_FLAGS+=(--progress=plain); shift ;; - -h|--help) echo "${HELP_MESSAGE}"; exit 0 ;; - *) echo 'Error in command line parsing' >&2 - echo -e "\n${HELP_MESSAGE}" - exit 1 - esac -done - -IMAGE_NAME=aica-technology/control-libraries/python/testing:"${BASE_TAG}" -BUILD_FLAGS+=(--build-arg BASE_TAG="${BASE_TAG}") -BUILD_FLAGS+=(-t "${IMAGE_NAME}") - -echo "Using control libraries branch ${BRANCH}" -BUILD_FLAGS+=(--build-arg BRANCH="${BRANCH}") - -docker pull ghcr.io/aica-technology/control-libraries/development-dependencies:"${BASE_TAG}" || exit 1 -DOCKER_BUILDKIT=1 docker build . --file ./Dockerfile.python "${BUILD_FLAGS[@]}" || exit 1 - -docker run -it --rm \ - --volume "$(pwd)":/home/developer/python:rw \ - --name "${IMAGE_NAME//[\/.:]/-}" \ - "${IMAGE_NAME}" diff --git a/python/dev-server.sh b/python/dev-server.sh deleted file mode 100755 index 733025d15..000000000 --- a/python/dev-server.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env bash - -BASE_TAG="latest" - -CONTAINER_NAME=aica-technology-control-libraries-python-development-ssh -BRANCH=$(git branch --show-current) - -SSH_PORT=2233 -SSH_KEY_FILE="${HOME}/.ssh/id_rsa.pub" - -HELP_MESSAGE="Usage: ./dev-server.sh [-b ] [-p ] [-k ] [--base-tag ] [-r] [-v] - -Build and run a docker container as an SSH toolchain server for remote development. - -The server is bound to the specified port on localhost (127.0.0.1) -and uses passwordless RSA key-pair authentication. The host public key -is read from the specified key file and copied to the server on startup. -On linux hosts, the UID and GID of the specified user will also be -set to match the UID and GID of the host user by the entry script. - -The server will run in the background as ${CONTAINER_NAME}. - -You can connect with 'ssh developer@localhost -p '. - -Close the server with 'docker stop ${CONTAINER_NAME}'. - -Options: - -b, --branch Specify the branch of control libraries - that should be used to build the image. - -p, --port Specify the port to bind for SSH - connection. - (default: ${SSH_PORT}) - -k, --key-file [path] Specify the path of the RSA - public key file. - (default: ${SSH_KEY_FILE}) - --base-tag Tag of the development image. - (default: ${BASE_TAG}) - -r, --rebuild Rebuild the image using the docker - --no-cache option. - -v, --verbose Use the verbose option during the building - process. - -h, --help Show this help message." - -BUILD_FLAGS=() -while [ "$#" -gt 0 ]; do - case "$1" in - -p|--port) SSH_PORT=$2; shift 2;; - -b|--branch) BRANCH=$2; shift 2;; - -k|--key-file) SSH_KEY_FILE=$2; shift 2;; - --base-tag) BASE_TAG=$2; shift 2;; - -r|--rebuild) BUILD_FLAGS+=(--no-cache); shift ;; - -v|--verbose) BUILD_FLAGS+=(--progress=plain); shift ;; - -h|--help) echo "${HELP_MESSAGE}"; exit 0;; - *) echo 'Error in command line parsing' >&2 - echo -e "\n${HELP_MESSAGE}" - exit 1 - esac -done - -PUBLIC_KEY=$(cat "${SSH_KEY_FILE}") - -COMMAND_FLAGS=() -COMMAND_FLAGS+=(--key "${PUBLIC_KEY}") -COMMAND_FLAGS+=(--user developer) - -if [[ "${OSTYPE}" != "darwin"* ]]; then - USER_ID=$(id -u "${USER}") - GROUP_ID=$(id -g "${USER}") - COMMAND_FLAGS+=(--uid "${USER_ID}") - COMMAND_FLAGS+=(--gid "${GROUP_ID}") -fi - -IMAGE_NAME=aica-technology/control-libraries/python/development:"${BASE_TAG}" -BUILD_FLAGS+=(--build-arg BASE_TAG="${BASE_TAG}") -BUILD_FLAGS+=(-t "${IMAGE_NAME}") - -echo "Using control libraries branch ${BRANCH}" -BUILD_FLAGS+=(--build-arg BRANCH="${BRANCH}") -BUILD_FLAGS+=(--target build) - -docker pull ghcr.io/aica-technology/control-libraries/development-dependencies:"${BASE_TAG}" || exit 1 -DOCKER_BUILDKIT=1 docker build . --file ./Dockerfile.python "${BUILD_FLAGS[@]}" || exit 1 - -docker container stop "${CONTAINER_NAME}" >/dev/null 2>&1 -docker rm --force "${CONTAINER_NAME}" >/dev/null 2>&1 - -echo "Starting background container with access port ${SSH_PORT} for user developer" -docker run -d --rm --cap-add sys_ptrace \ - --user root \ - --publish 127.0.0.1:"${SSH_PORT}":22 \ - --name "${CONTAINER_NAME}" \ - --hostname "${CONTAINER_NAME}" \ - "${IMAGE_NAME}" /sshd_entrypoint.sh "${COMMAND_FLAGS[@]}" - -echo "${CONTAINER_NAME}" diff --git a/python/setup.py b/python/setup.py index 2a4a89e3d..a344c5a8a 100644 --- a/python/setup.py +++ b/python/setup.py @@ -11,7 +11,7 @@ # names of the environment variables that define osqp and openrobots include directories osqp_path_var = 'OSQP_INCLUDE_DIR' -__version__ = "7.4.7" +__version__ = "7.4.8" __libraries__ = ['state_representation', 'clproto', 'controllers', 'dynamical_systems', 'robot_model'] __include_dirs__ = ['include'] @@ -48,8 +48,11 @@ raise Exception(msg) if __install_robot_model_module__: - osqp_path = os.environ[osqp_path_var] if osqp_path_var in os.environ.keys() else '/usr/include/osqp' - __include_dirs__.append(osqp_path) + if osqp_path_var in os.environ.keys(): + __include_dirs__.append(os.environ[osqp_path_var]) + else: + __include_dirs__.append('/usr/local/include/osqp') + __include_dirs__.append('/usr/include/osqp') if __install_controllers_module__ and not __install_robot_model_module__: warnings.warn( diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index d0347500b..49a009736 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(control_libraries VERSION 7.4.7) +project(control_libraries VERSION 7.4.8) # Build options option(BUILD_TESTING "Build all tests." OFF) diff --git a/source/Dockerfile.source b/source/Dockerfile.source deleted file mode 100644 index 4c770b5a7..000000000 --- a/source/Dockerfile.source +++ /dev/null @@ -1,21 +0,0 @@ -ARG BASE_TAG=latest -FROM ghcr.io/aica-technology/control-libraries/development-dependencies:${BASE_TAG} as build -ARG BUILD_CONTROLLERS=ON -ARG BUILD_DYNAMICAL_SYSTEMS=ON -ARG BUILD_ROBOT_MODEL=ON - -WORKDIR /tmp/control_lib -COPY ./ ./ - -WORKDIR /tmp/control_lib/build -RUN cmake -DBUILD_CONTROLLERS="${BUILD_CONTROLLERS}" \ - -DBUILD_DYNAMICAL_SYSTEMS="${BUILD_DYNAMICAL_SYSTEMS}" \ - -DBUILD_ROBOT_MODEL="${BUILD_ROBOT_MODEL}" \ - -DBUILD_TESTING=ON .. \ - && make -j all && make install - - -FROM build as testing - -RUN CTEST_OUTPUT_ON_FAILURE=1 make test -RUN rm -rf /tmp/control_lib/ diff --git a/source/README.md b/source/README.md index e2d7f9f29..494ad35e6 100644 --- a/source/README.md +++ b/source/README.md @@ -49,81 +49,6 @@ Dependencies: `state_representation`, `robot_model` --- - -## Installation - -### Supported platforms - -These libraries have been developed and tested on Linux Ubuntu 18.04 and 20.04. -They should also work on macOS and Windows, though the installation -steps may differ. At this time no guarantees are made for library support on -non-Linux systems. - -### Installation with the install script -This project uses CMake to generate static library objects for each of the modules. - -To facilitate the installation process, an [install script](./install.sh) is provided. Users who are interested in -the manual installation steps and/or have already installed Pinocchio refer to the -[manual installation steps](#manual-installation-steps) in the next section. - -The install script takes care of all the installation steps, including the installation and configuration of Pinocchio. -It can be run with several optional arguments: -- `-y`, `--auto`: Any input prompts will be suppressed and install steps automatically approved. -- `-d [path]`, `--dir [path]`: If provided, the installation directory will be changed to `[path]`. -- `--no-controllers`: The controllers library will be excluded from the installation. -- `--no-dynamical-systems`: The dynamical systems library will be excluded from the installation. -- `--no-robot-model`: The robot model library, and therefore Pinocchio, will be excluded from the installation. -- `--build-tests`: The unittest targets will be included in the installation. -- `--clean`: Any previously installed header files from `/usr/local/include` and any shared library files from - `/usr/local/lib` will be deleted before the installation. -- `--cleandir [path]`: Any previously installed header files shared library files from `[path]` will be deleted before - the installation. - -### Manual installation steps - -Eigen3 (release [3.4.0](https://gitlab.com/libeigen/eigen/-/releases/3.4.0)) is required for -building `state_representation` and all other libraries. You can install it with: -```shell script -wget -c https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.gz -O - | tar -xz -cd eigen-3.4.0 && mkdir build && cd build && cmake .. && make install -cd ../.. && rm -r eigen-3.4.0 -``` - -Pinocchio is required for building the `robot_model` library. Installing this requires -some additional steps; see the [install script](./install.sh) for reference. -If the `robot_model` library is not needed, you can skip the installation of Pinocchio. - -Once the dependencies are installed, build and install the libraries by navigating -to the source folder and invoking `cmake` and `make` as shown below. -The library files are installed to `usr/local/lib`, and the library header files -are copied to `/usr/local/include`. - -```shell script -cd control-libraries/source -mkdir build && cd build -cmake -DCMAKE_BUILD_TYPE=Release .. -make -j -make install -``` - -The CMake configuration flags `BUILD_CONTROLLERS`, `BUILD_DYNAMICAL_SYSTEMS` and `BUILD_ROBOT_MODEL` -determine which libraries are built, and are all defined as `ON` by default. -The building of the `state_representation` library cannot be disabled, as all other libraries depend on it. - -To selectively disable the build of a particular library, set the flag to `=OFF`. -For example, the following flags will prevent the `robot_model` library from being built, -which is useful if the Pinocchio dependency is not fulfilled on your system. - -```shell script -cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_ROBOT_MODEL=OFF .. -``` - -To also build the library tests, add the CMake flag `-DBUILD_TESTING=ON`. -This requires GTest to be installed on your system. You can then use `make test` to run all test targets. - -Alternatively, you can include the source code for each library as submodules in your own CMake project, -using the CMake directive `add_subdirectory(...)` to link it with your project. - ## Usage in a cmake project If you have a target library or executable `my_target`, you can link all required libraries diff --git a/source/build-test.sh b/source/build-test.sh deleted file mode 100755 index e1398072b..000000000 --- a/source/build-test.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash - -BASE_TAG="latest" - -BUILD_CONTROLLERS="ON" -BUILD_DYNAMICAL_SYSTEMS="ON" -BUILD_ROBOT_MODEL="ON" - -HELP_MESSAGE="Usage: build-test.sh [--base-tag ] [-r] [-v] -Options: - --base-tag Tag of the development image. - (default: ${BASE_TAG}) - -r, --rebuild Rebuild the image using the docker - --no-cache option. - -v, --verbose Use the verbose option during the building - process. - -h, --help Show this help message. -" - -BUILD_FLAGS=() -while [[ $# -gt 0 ]]; do - opt="$1" - case $opt in - --base-tag) BASE_TAG=$2; shift 2;; - -r|--rebuild) BUILD_FLAGS+=(--no-cache); shift ;; - -v|--verbose) BUILD_FLAGS+=(--progress=plain); shift ;; - -h|--help) echo "${HELP_MESSAGE}"; exit 0 ;; - *) echo 'Error in command line parsing' >&2 - echo -e "\n${HELP_MESSAGE}" - exit 1 - esac -done - -IMAGE_NAME=aica-technology/control-libraries/source/testing:"${BASE_TAG}" - -BUILD_FLAGS+=(--build-arg "BASE_TAG=${BASE_TAG}") -BUILD_FLAGS+=(--build-arg "BUILD_CONTROLLERS=${BUILD_CONTROLLERS}") -BUILD_FLAGS+=(--build-arg "BUILD_DYNAMICAL_SYSTEMS=${BUILD_DYNAMICAL_SYSTEMS}") -BUILD_FLAGS+=(--build-arg "BUILD_ROBOT_MODEL=${BUILD_ROBOT_MODEL}") -BUILD_FLAGS+=(-t "${IMAGE_NAME}") - -docker pull ghcr.io/aica-technology/control-libraries/development-dependencies:"${BASE_TAG}" || exit 1 -DOCKER_BUILDKIT=1 docker build . --file ./Dockerfile.source "${BUILD_FLAGS[@]}" diff --git a/source/dev-server.sh b/source/dev-server.sh deleted file mode 100755 index 4576a06ae..000000000 --- a/source/dev-server.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash - -BASE_TAG="latest" -CONTAINER_NAME=aica-technology-control-libraries-development-dependencies-ssh - -SSH_PORT=2222 -SSH_KEY_FILE="${HOME}/.ssh/id_rsa.pub" - -HELP_MESSAGE="Usage: ./dev-server.sh [-p ] [-k ] [--base-tag ] - -Build and run a docker container as an SSH toolchain server for remote development. - -The server is bound to the specified port on localhost (127.0.0.1) -and uses passwordless RSA key-pair authentication. The host public key -is read from the specified key file and copied to the server on startup. -On linux hosts, the UID and GID of the specified user will also be -set to match the UID and GID of the host user by the entry script. - -The server will run in the background as ${CONTAINER_NAME}. - -You can connect with 'ssh developer@localhost -p '. - -Close the server with 'docker stop ${CONTAINER_NAME}'. - -Options: - -p, --port Specify the port to bind for SSH - connection. - (default: ${SSH_PORT}) - -k, --key-file [path] Specify the path of the RSA - public key file. - (default: ${SSH_KEY_FILE}) - --base-tag Tag of the development image. - (default: ${BASE_TAG}) - -h, --help Show this help message." - -while [ "$#" -gt 0 ]; do - case "$1" in - -p|--port) SSH_PORT=$2; shift 2;; - -k|--key-file) SSH_KEY_FILE=$2; shift 2;; - --base-tag) BASE_TAG=$2; shift 2;; - -h|--help) echo "${HELP_MESSAGE}"; exit 0;; - *) echo 'Error in command line parsing' >&2 - echo -e "\n${HELP_MESSAGE}" - exit 1 - esac -done - -IMAGE_NAME=ghcr.io/aica-technology/control-libraries/development-dependencies:"${BASE_TAG}" - -PUBLIC_KEY=$(cat "${SSH_KEY_FILE}") - -COMMAND_FLAGS=() -COMMAND_FLAGS+=(--key "${PUBLIC_KEY}") -COMMAND_FLAGS+=(--user developer) - -if [[ "${OSTYPE}" != "darwin"* ]]; then - USER_ID=$(id -u "${USER}") - GROUP_ID=$(id -g "${USER}") - COMMAND_FLAGS+=(--uid "${USER_ID}") - COMMAND_FLAGS+=(--gid "${GROUP_ID}") -fi - -docker pull "${IMAGE_NAME}" || exit 1 - -docker container stop "${CONTAINER_NAME}" >/dev/null 2>&1 -docker rm --force "${CONTAINER_NAME}" >/dev/null 2>&1 - -echo "Starting background container with access port ${SSH_PORT} for user developer" -docker run -d --rm --cap-add sys_ptrace \ - --user root \ - --publish 127.0.0.1:"${SSH_PORT}":22 \ - --name "${CONTAINER_NAME}" \ - --hostname "${CONTAINER_NAME}" \ - "${IMAGE_NAME}" /sshd_entrypoint.sh "${COMMAND_FLAGS[@]}" - -echo "${CONTAINER_NAME}" diff --git a/source/install.sh b/source/install.sh deleted file mode 100755 index ee9679b11..000000000 --- a/source/install.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/bin/bash -SCRIPT=$(readlink -f "${BASH_SOURCE[0]}") -SOURCE_PATH=$(dirname "$SCRIPT") - -BUILD_CONTROLLERS="ON" -BUILD_DYNAMICAL_SYSTEMS="ON" -BUILD_ROBOT_MODEL="ON" -BUILD_TESTING="OFF" -INSTALL_DESTINATION="/usr/local" -AUTO_INSTALL="" - -EIGEN_VERSION=3.4.0 -OSQP_TAG=0.6.2 -OSQP_EIGEN_TAG=0.6.4 -PINOCCHIO_TAG=2.6.9 - -FAIL_MESSAGE="The provided input arguments are not valid. -Run the script with the '--help' argument." - -HELP_MESSAGE="Usage: [sudo] ./install.sh [OPTIONS] - -An install script for the control libraries. - -Options: - -y, --auto Suppress any input prompts and - automatically approve install steps. - -d, --dir [path] Configure the installation directory - (default: /usr/local). - - --no-controllers Exclude the controllers library. - --no-dynamical-systems Exclude the dynamical systems library. - --no-robot-model Exclude the robot model library. - --build-tests Build the unittest targets. - - --clean Delete any previously installed header - files from /usr/local/include and any - shared library files from /usr/local/lib. - --cleandir [path] Delete any previously installed header - and library files from the specified path. - - -h, --help Show this help message." - -function uninstall { - function delete_components { - rm -r "${INSTALL_DESTINATION}"/include/controllers - rm -r "${INSTALL_DESTINATION}"/include/dynamical_systems - rm -r "${INSTALL_DESTINATION}"/include/robot_model - rm -r "${INSTALL_DESTINATION}"/include/state_representation - rm -r "${INSTALL_DESTINATION}"/lib/libcontrollers*.so - rm -r "${INSTALL_DESTINATION}"/lib/libdynamical_systems*.so - rm -r "${INSTALL_DESTINATION}"/lib/librobot_model*.so - rm -r "${INSTALL_DESTINATION}"/lib/libstate_representation*.so - } - - delete_components >/dev/null 2>&1 - - echo "Deleted any control library artefacts from ${INSTALL_DESTINATION}." -} - -while [ "$#" -gt 0 ]; do - case "$1" in - -y|--auto) AUTO_INSTALL="-y"; shift 1;; - --build-tests) BUILD_TESTING="ON"; shift 1;; - --clean) uninstall; exit 0;; - --cleandir) INSTALL_DESTINATION=$2; uninstall; exit 0;; - -d|--dir) INSTALL_DESTINATION=$2; shift 2;; - -h|--help) echo "$HELP_MESSAGE"; exit 0;; - --no-controllers) BUILD_CONTROLLERS="OFF"; shift 1;; - --no-dynamical-systems) BUILD_DYNAMICAL_SYSTEMS="OFF"; shift 1;; - --no-robot-model) BUILD_ROBOT_MODEL="OFF"; shift 1;; - - -*) echo "Unknown option: $1" >&2; echo "$FAIL_MESSAGE"; exit 1;; - esac -done - -if [ "${BUILD_CONTROLLERS}" == "ON" ] && [ "${BUILD_ROBOT_MODEL}" == "OFF" ]; then - echo "The robot model library is required to build the controllers library!" - echo "Either disable controller installation with '--no-controllers' or enable" - echo "the robot model installation by removing the '--no-robot-model' flag." - exit 1 -fi - -# cleanup any previous build folders -rm -rf "${SOURCE_PATH}"/tmp - -# install base dependencies -echo ">>> INSTALLING BASE DEPENDENCIES" - -if [ -z $(which pkg-config) ]; then - echo ">>> INSTALLING pkg-config tool" - apt-get update && apt-get install "${AUTO_INSTALL}" pkg-config || exit 1 -fi - -pkg-config eigen3 --atleast-version="${EIGEN_VERSION}" -if [ "$?" != 0 ]; then - echo ">>> INSTALLING EIGEN" - mkdir -p "${SOURCE_PATH}"/tmp/lib && cd "${SOURCE_PATH}"/tmp/lib || exit 1 - wget -c "https://gitlab.com/libeigen/eigen/-/archive/${EIGEN_VERSION}/eigen-${EIGEN_VERSION}.tar.gz" -O - | tar -xz || exit 1 - cd "eigen-${EIGEN_VERSION}" && mkdir -p build && cd build && env CXXFLAGS=-DEIGEN_MPL2_ONLY cmake .. && make install || exit 1 -fi -EIGEN_PATH=$(cmake --find-package -DNAME=Eigen3 -DCOMPILER_ID=GNU -DLANGUAGE=C -DMODE=COMPILE) -if [ "${EIGEN_PATH::14}" != "-I/usr/include" ]; then - rm -rf /usr/include/eigen3 && ln -s ${EIGEN_PATH:2} /usr/include/eigen3 || exit 1 -fi - -# install module-specific dependencies -if [ "${BUILD_ROBOT_MODEL}" == "ON" ]; then - echo ">>> INSTALLING ROBOT MODEL DEPENDENCIES" - apt-get update && apt-get install "${AUTO_INSTALL}" libboost-all-dev liburdfdom-dev || exit 1 - - pkg-config pinocchio --atleast-version=${PINOCCHIO_TAG} - if [ "$?" != 0 ]; then - mkdir -p "${SOURCE_PATH}"/tmp/lib && cd "${SOURCE_PATH}"/tmp/lib || exit 1 - - echo ">>> INSTALLING OSQP [1/3]" - git clone --depth 1 -b v${OSQP_TAG} --recursive https://github.com/oxfordcontrol/osqp \ - && cd osqp && mkdir build && cd build && cmake -G "Unix Makefiles" .. && cmake --build . --target install \ - && cd ../.. && rm -r osqp || exit 1 - - echo ">>> INSTALLING OSQP_EIGEN [2/3]" - git clone --depth 1 -b v${OSQP_EIGEN_TAG} https://github.com/robotology/osqp-eigen.git \ - && cd osqp-eigen && mkdir build && cd build && cmake .. && make -j && make install \ - && cd ../.. && rm -r osqp-eigen || exit 1 - - echo ">>> INSTALLING PINOCCHIO [3/3]" - git clone --depth 1 -b v${PINOCCHIO_TAG} --recursive https://github.com/stack-of-tasks/pinocchio \ - && cd pinocchio && mkdir build && cd build \ - && cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_PYTHON_INTERFACE=OFF \ - && make -j1 && make install && cd ../.. && rm -r pinocchio || exit 1 - fi - ldconfig -fi - -# install testing dependencies -if [ "${BUILD_TESTING}" == "ON" ]; then - echo ">>> INSTALLING TEST DEPENDENCIES" - apt-get update && apt-get install "${AUTO_INSTALL}" libgtest-dev || exit 1 - - mkdir -p "${SOURCE_PATH}"/tmp/lib/gtest && cd "${SOURCE_PATH}"/tmp/lib/gtest || exit 1 - cmake /usr/src/gtest && make || exit 1 - cp lib/* /usr/local/lib || cp ./*.a /usr/local/lib -fi - -# build and install the specified modules -echo ">>> BUILDING CONTROL LIBRARIES" -cd "${SOURCE_PATH}" && mkdir -p build && cd build || exit 1 - -cmake -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX="${INSTALL_DESTINATION}" \ - -DBUILD_CONTROLLERS="${BUILD_CONTROLLERS}" \ - -DBUILD_DYNAMICAL_SYSTEMS="${BUILD_DYNAMICAL_SYSTEMS}" \ - -DBUILD_ROBOT_MODEL="${BUILD_ROBOT_MODEL}" \ - -DBUILD_TESTING="${BUILD_TESTING}" .. || exit 1 - -make -j && make install || exit 1 - -ldconfig - -# cleanup any temporary folders -rm -rf "${SOURCE_PATH}"/tmp - -# reset location -cd "${SOURCE_PATH}" || return From a39cba9ae40a5a7bc97c3b4657e9f25f336c2842 Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 17 Apr 2024 14:47:09 +0200 Subject: [PATCH 11/15] Revert "Revert "fix: update copy constructor to avoid warnings (#180)"" This reverts commit 099c8e1f49b0dc735a670ba9762f24dfe53f077a. --- CHANGELOG.md | 1 + VERSION | 2 +- demos/CMakeLists.txt | 2 +- doxygen/doxygen.conf | 2 +- protocol/clproto_cpp/CMakeLists.txt | 2 +- python/setup.py | 2 +- source/CMakeLists.txt | 2 +- source/robot_model/src/Model.cpp | 8 +++----- 8 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7a5e7052..4915222b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Release Versions: ## Upcoming changes (in development) +- fix: update copy constructor to avoid warnings (#180) - build: remove deprecated Dockerfiles and scripts and update installation instructions (#176) - refactor: optimize copy and swap constructor for robot model (#174) - fix: refactor cmake project to deal with robot model dependencies (#178) diff --git a/VERSION b/VERSION index da8d65311..14ebea1f1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.8 +7.4.9 diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index 05bbe12f2..e86a2d837 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -15,7 +15,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -find_package(control_libraries 7.4.8 CONFIG REQUIRED) +find_package(control_libraries 7.4.9 CONFIG REQUIRED) set(DEMOS_SCRIPTS task_space_control_loop diff --git a/doxygen/doxygen.conf b/doxygen/doxygen.conf index bb703f7d4..0bd379943 100644 --- a/doxygen/doxygen.conf +++ b/doxygen/doxygen.conf @@ -38,7 +38,7 @@ PROJECT_NAME = "Control Libraries" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 7.4.8 +PROJECT_NUMBER = 7.4.9 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/protocol/clproto_cpp/CMakeLists.txt b/protocol/clproto_cpp/CMakeLists.txt index e97dad1fb..8cb5dacf4 100644 --- a/protocol/clproto_cpp/CMakeLists.txt +++ b/protocol/clproto_cpp/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(clproto VERSION 7.4.8) +project(clproto VERSION 7.4.9) # Default to C99 if(NOT CMAKE_C_STANDARD) diff --git a/python/setup.py b/python/setup.py index a344c5a8a..07769c4b0 100644 --- a/python/setup.py +++ b/python/setup.py @@ -11,7 +11,7 @@ # names of the environment variables that define osqp and openrobots include directories osqp_path_var = 'OSQP_INCLUDE_DIR' -__version__ = "7.4.8" +__version__ = "7.4.9" __libraries__ = ['state_representation', 'clproto', 'controllers', 'dynamical_systems', 'robot_model'] __include_dirs__ = ['include'] diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 49a009736..071d8602a 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(control_libraries VERSION 7.4.8) +project(control_libraries VERSION 7.4.9) # Build options option(BUILD_TESTING "Build all tests." OFF) diff --git a/source/robot_model/src/Model.cpp b/source/robot_model/src/Model.cpp index 2f5b8b30c..447ee4089 100644 --- a/source/robot_model/src/Model.cpp +++ b/source/robot_model/src/Model.cpp @@ -31,16 +31,14 @@ Model::Model(const std::string& robot_name, const std::string& urdf_path) : Model::Model(const Model& other): robot_name_(other.robot_name_), urdf_path_(other.urdf_path_), + frames_(other.frames_), robot_model_(other.robot_model_), robot_data_(other.robot_data_), + meshloader_callback_(other.meshloader_callback_), geom_model_(other.geom_model_), geom_data_(other.geom_data_), qp_solver_(std::make_unique(*other.qp_solver_)), - frames_(other.frames_), - meshloader_callback_(other.meshloader_callback_), - load_collision_geometries_(other.load_collision_geometries_) - { -} + load_collision_geometries_(other.load_collision_geometries_) {} bool Model::create_urdf_from_string(const std::string& urdf_string, const std::string& desired_path) { std::ofstream file(desired_path); From 4d537ac097d4631e6b51eb6e5b7c9042bc7bbd47 Mon Sep 17 00:00:00 2001 From: Dominic Reber Date: Wed, 17 Apr 2024 14:47:41 +0200 Subject: [PATCH 12/15] Revert "Revert "feat: update demos directory (#179)"" This reverts commit 486367b9dff47cc889765e89aa3642f0991e8754. --- CHANGELOG.md | 1 + VERSION | 2 +- demos/CMakeLists.txt | 2 +- demos/Dockerfile | 27 +++++++++---------- demos/README.md | 4 +-- demos/run-demo.sh | 41 ++--------------------------- doxygen/doxygen.conf | 2 +- protocol/clproto_cpp/CMakeLists.txt | 2 +- python/setup.py | 2 +- source/CMakeLists.txt | 2 +- 10 files changed, 23 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4915222b7..51f8c7dd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Release Versions: ## Upcoming changes (in development) +- feat: update demos directory (#179) - fix: update copy constructor to avoid warnings (#180) - build: remove deprecated Dockerfiles and scripts and update installation instructions (#176) - refactor: optimize copy and swap constructor for robot model (#174) diff --git a/VERSION b/VERSION index 14ebea1f1..ef137167c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.4.9 +7.4.10 diff --git a/demos/CMakeLists.txt b/demos/CMakeLists.txt index e86a2d837..a4c552fca 100644 --- a/demos/CMakeLists.txt +++ b/demos/CMakeLists.txt @@ -15,7 +15,7 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic) endif() -find_package(control_libraries 7.4.9 CONFIG REQUIRED) +find_package(control_libraries 7.4.10 CONFIG REQUIRED) set(DEMOS_SCRIPTS task_space_control_loop diff --git a/demos/Dockerfile b/demos/Dockerfile index 6902e9974..89d35cd45 100644 --- a/demos/Dockerfile +++ b/demos/Dockerfile @@ -1,20 +1,17 @@ -ARG BASE_TAG=latest -FROM ghcr.io/aica-technology/control-libraries/development-dependencies:${BASE_TAG} as build -ARG BRANCH=develop +FROM ghcr.io/aica-technology/control-libraries:rolling as cl +FROM ubuntu:22.04 +ENV DEBIAN_FRONTEND=noninteractive -WORKDIR /source -RUN git clone --depth 1 --branch ${BRANCH} https://github.com/aica-technology/control-libraries -RUN bash control-libraries/source/install.sh --auto -RUN bash control-libraries/protocol/install.sh --auto -RUN pip3 install control-libraries/python +RUN apt-get update && apt-get install -y \ + cmake \ + g++ \ + python3 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* -RUN rm -rf /source +COPY --from=cl / / - -FROM build as demos - -USER developer -WORKDIR ${HOME}/control_loop_examples +WORKDIR /control_loop_examples COPY ./ ./ -RUN mkdir build && cd build && cmake .. && sudo make -j all && sudo make install +RUN mkdir build && cd build && cmake .. && make -j all && make install diff --git a/demos/README.md b/demos/README.md index 96cc96dd4..e9b79b247 100644 --- a/demos/README.md +++ b/demos/README.md @@ -19,9 +19,9 @@ demo scripts and run the one of your choice: ```console ./run-demo-script.sh # Run a python script -developer@xxxxxxxxx:~/control_loop_examples$ python3 python_scripts/