diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bef5ed5a75a4d3c4504e5a10283e789bac24bd9b
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,68 @@
+
+stages:
+  - build-and-test
+
+
+build-and-test:
+  stage: build-and-test
+  image: git.h2t.iar.kit.edu:5050/sw/armarx/meta/axii:latest-bionic
+
+  cache:
+    # https://docs.gitlab.com/ee/ci/caching/#share-caches-across-jobs-in-different-branches
+    key: one-key-to-rule-them-all
+    paths:
+      - .ccache
+
+  before_script:
+    # Ccache configuration and introspection.
+    - apt-get install ccache --yes
+    - ccache --set-config=cache_dir="$CI_PROJECT_DIR/.ccache"
+    - ccache --max-size=5G
+    - ccache --show-stats
+
+    # Activate Axii.
+    - source /axii/scripts/install_axii.sh
+
+  script:
+    # Create workspace.
+    - axii workspace create ~/workspace workspace
+    - axii workspace activate workspace
+    - _axii_auto_env_refresh
+
+    # Use workspace configuration from project.
+    - cp "$CI_PROJECT_DIR/.gitlab/ci/armarx-workspace.json" "$ARMARX_WORKSPACE/armarx-workspace.json"
+    - cat "$ARMARX_WORKSPACE/armarx-workspace.json"
+
+    - axii workspace env
+    - _axii_auto_env_refresh
+
+    - echo "Workspace information:"
+    - axii workspace list-modules
+    - axii workspace list-modules --deps
+    - axii workspace info
+
+    - export PROJECT_PATH_IN_WORKSPACE="$simox__PATH"
+
+    # Symlink project directory into Axii workspace.
+    - mkdir -p "$(dirname $PROJECT_PATH_IN_WORKSPACE)"
+    - ln -s "$CI_PROJECT_DIR" "$PROJECT_PATH_IN_WORKSPACE"
+
+    # Upgrade.
+    - axii workspace system --accept-apt-install
+    - axii workspace update --prefer-https
+    - _axii_auto_env_refresh
+
+    # Upgrade.
+    - axii workspace upgrade -m simox
+    - _axii_auto_env_refresh
+
+    - ccache --show-stats
+
+    # Test.
+    # ToDo: Add and use `axii ws test -m simox`
+    - cd "$PROJECT_PATH_IN_WORKSPACE/build"
+    - ctest . || true
+    - ctest --rerun-failed --output-on-failure . || true
+    - cat Temporary/LastTest.log || true
+    # Once again to make the job fail if an error occurs.
+    - ctest .
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index f3c340d5fa1dc00bc42cbf28a39e5cdfd13ced57..2e5fe5a8abaf792d21f3c7f2fd2fb4515e18a02c 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1,3 +1,19 @@
-/SimDynamics/ @patrickhegemann @fabian.reister
-/VirtualRobot/IK/platform @fabian.reister @dreher 
+/SimDynamics/ @patrickhegemann @reister
 
+/SimoxUtility/algorithm/get_map_keys_values.h @kartmann
+/SimoxUtility/caching/ @kartmann
+/SimoxUtility/color/ @kartmann
+/SimoxUtility/json/ @kartmann
+/SimoxUtility/math/pose/ @kartmann
+/SimoxUtility/math/statistics/ @kartmann
+/SimoxUtility/math/SoftMinMax.* @kartmann
+/SimoxUtility/meta/EnumNames.hpp @kartmann
+/SimoxUtility/meta/type_name.* @kartmann
+/SimoxUtility/shapes/AxisAlignedBoundingBox.* @kartmann
+/SimoxUtility/threads/CountingSemaphore.* @kartmann
+
+/VirtualRobot/IK/platform @reister @dreher
+/VirtualRobot/MJCF/ @kartmann
+/VirtualRobot/Nodes/HemisphereJoint/ @kartmann
+/VirtualRobot/Nodes/RobotNodeHemisphere.* @kartmann
+/VirtualRobot/XML/mujoco/ @kartmann
diff --git a/.gitlab/ci/armarx-workspace.json b/.gitlab/ci/armarx-workspace.json
new file mode 100644
index 0000000000000000000000000000000000000000..87b8897296f6b86dfc610f5a754e7a0a4efd3918
--- /dev/null
+++ b/.gitlab/ci/armarx-workspace.json
@@ -0,0 +1,20 @@
+{
+  "modules": {
+    "tools/ccache/default": {},
+    "simox": {},
+
+    "# Missing for deps/coin/soqt": {},
+    "apt/libqt5opengl5-dev": {}
+  },
+  "global": {
+    "prepare": {
+      "cmake": {
+        "definitions": {
+          "CMAKE_BUILD_TYPE": "RelWithDebInfo",
+          "CMAKE_C_COMPILER_LAUNCHER": "$CCACHE",
+          "CMAKE_CXX_COMPILER_LAUNCHER": "$CCACHE"
+        }
+      }
+    }
+  }
+}
diff --git a/SimoxUtility/filesystem/make_relative.h b/SimoxUtility/filesystem/make_relative.h
index 77bf25be26bc00599f5e1a90261dd5cc629689f8..87c44387322ea9a5a591059320322da9c371b534 100644
--- a/SimoxUtility/filesystem/make_relative.h
+++ b/SimoxUtility/filesystem/make_relative.h
@@ -5,16 +5,21 @@
 namespace simox::fs
 {
     //! Return path when appended to a_From will resolve to same as a_To
-    inline std::filesystem::path make_relative(std::filesystem::path a_From, std::filesystem::path a_To)
+    inline std::filesystem::path
+    make_relative(std::filesystem::path a_From, std::filesystem::path a_To)
     {
-        a_From = std::filesystem::canonical(a_From);
-        a_To = std::filesystem::canonical(a_To);
+        a_From = std::filesystem::weakly_canonical(a_From);
+        a_To = std::filesystem::weakly_canonical(a_To);
         std::filesystem::path ret;
         std::filesystem::path::const_iterator itrFrom(a_From.begin()), itrTo(a_To.begin());
         // Find common base
-        for (std::filesystem::path::const_iterator toEnd(a_To.end()), fromEnd(a_From.end()); itrFrom != fromEnd && itrTo != toEnd && *itrFrom == *itrTo; ++itrFrom, ++itrTo);
+        for (std::filesystem::path::const_iterator toEnd(a_To.end()), fromEnd(a_From.end());
+             itrFrom != fromEnd && itrTo != toEnd && *itrFrom == *itrTo;
+             ++itrFrom, ++itrTo)
+            ;
         // Navigate backwards in directory to reach previously found base
-        for (std::filesystem::path::const_iterator fromEnd(a_From.end()); itrFrom != fromEnd; ++itrFrom)
+        for (std::filesystem::path::const_iterator fromEnd(a_From.end()); itrFrom != fromEnd;
+             ++itrFrom)
         {
             if ((*itrFrom) != ".")
                 ret /= "..";
@@ -25,4 +30,4 @@ namespace simox::fs
             ret /= *itrTo;
         return ret;
     }
-}
+} // namespace simox::fs
diff --git a/VirtualRobot/tests/OmniPlatformKinematicsTest.cpp b/VirtualRobot/tests/OmniPlatformKinematicsTest.cpp
index eac7c48de8cc28bdb6d2a3db226402381276a335..5cceb677c02a138a6abf83834031a7e4854271f5 100755
--- a/VirtualRobot/tests/OmniPlatformKinematicsTest.cpp
+++ b/VirtualRobot/tests/OmniPlatformKinematicsTest.cpp
@@ -74,6 +74,10 @@ BOOST_AUTO_TEST_CASE(testInv)
     BOOST_CHECK_CLOSE(vAngle, velocities(2), 0.001);
 }
 
+
+#define VAROUT(var) #var " = " << var
+
+
 BOOST_AUTO_TEST_CASE(testMoveForward)
 {
     const VirtualRobot::OmniWheelPlatformKinematicsParams params = ARMAR7_OMNI_PLATFORM_CONFIG;
@@ -84,14 +88,16 @@ BOOST_AUTO_TEST_CASE(testMoveForward)
     const auto wheelVelocities = p.calcWheelVelocity(velForward);
 
     // front wheel should not move
-    BOOST_CHECK_CLOSE(0.0, wheelVelocities(0), 0.1);
+    BOOST_TEST_INFO(0.0 << " | " << VAROUT(wheelVelocities(0)));
+    BOOST_CHECK_LE(std::abs(0.0 - wheelVelocities(0)), 0.02);
 
     // rear wheels should move at the same speed ...
-    BOOST_CHECK_CLOSE(std::abs(wheelVelocities(1)), std::abs(wheelVelocities(2)), 0.001);
+    BOOST_TEST_INFO(VAROUT(wheelVelocities(1)) << " | " << VAROUT(wheelVelocities(2)));
+    BOOST_CHECK_LE(std::abs(std::abs(wheelVelocities(1)) - std::abs(wheelVelocities(2))), 0.02);
 
     // .. but with different signs
     BOOST_CHECK_GE(wheelVelocities(1), 0.0);
-    BOOST_CHECK_LE(wheelVelocities(2), 0.0);
+    BOOST_CHECK_LE(wheelVelocities(2), 1e-8);
 }
 
 BOOST_AUTO_TEST_CASE(testRotate)