diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3ce4720491a26f54baf895e14ea1feb5fe3aa5da..3f6bfc013b4e7043e83de1a94a92ef9b85ae7484 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -64,7 +64,7 @@ stages:
     - cd "$PROJECT_PATH_IN_WORKSPACE/build"
     - ctest . || true
     - ctest --rerun-failed --output-on-failure . || true
-    - cat Temporary/LastTest.log || true
+    - cat Testing/Temporary/LastTest.log || true
     # Once again to make the job fail if an error occurs.
     - ctest .
 
diff --git a/GeometricPlanning/ArticulatedObjectGeometricPlanningHelper.cpp b/GeometricPlanning/ArticulatedObjectGeometricPlanningHelper.cpp
index dabe7e348b3bcbefd3e4f382c1ef1252cacaa65a..f124608da17e0d11f1e8b18cc4b1fe82d11975a3 100644
--- a/GeometricPlanning/ArticulatedObjectGeometricPlanningHelper.cpp
+++ b/GeometricPlanning/ArticulatedObjectGeometricPlanningHelper.cpp
@@ -43,6 +43,9 @@ namespace simox::geometric_planning
         const auto node = articulatedObject->getRobotNode(nodeName);
         REQUIRE(node != nullptr);
 
+        const auto global_T_object_root = articulatedObject->getGlobalPose();
+        articulatedObject->setGlobalPose(Eigen::Matrix4f::Identity());
+
         simox::geometric_planning::ArticulatedObjectGeometricPlanningHelper helper(
             articulatedObject);
 
@@ -84,6 +87,10 @@ namespace simox::geometric_planning
 
         const auto parametricPath = helper.getPathForNode(node->getName(), joint->getName());
 
+        // reset global pose
+        articulatedObject->setGlobalPose(global_T_object_root);
+
+
         return parametricPath;
     }
 
diff --git a/GeometricPlanning/ParametricPath.cpp b/GeometricPlanning/ParametricPath.cpp
index bd5e81e9e4cf9616508bb4c478e9a1de35b68744..9c9de69fdb92049c238af7594ed571f5fe241eac 100644
--- a/GeometricPlanning/ParametricPath.cpp
+++ b/GeometricPlanning/ParametricPath.cpp
@@ -19,6 +19,11 @@ namespace simox::geometric_planning
     {
         return path->progress(toLocalPathFrame(global_T_pose));
     }
+    
+    float ParametricPath::progress(const float param) const
+    {
+        return path->progress(param);
+    }
 
     Pose
     ParametricPath::toLocalPathFrame(const Pose& global_T_pose) const
diff --git a/GeometricPlanning/ParametricPath.h b/GeometricPlanning/ParametricPath.h
index ec58679d9b4d4e33f3a5eb5ce083641b9cd874be..dd2400f85fce57eef08bee5d698c98b6115f76ca 100644
--- a/GeometricPlanning/ParametricPath.h
+++ b/GeometricPlanning/ParametricPath.h
@@ -32,6 +32,7 @@ namespace simox::geometric_planning
 
         // helper functions to obtain the PathPrimitive's parameter and progress directly from a global pose
         float progress(const Pose& global_T_pose) const;
+        float progress(float param) const;
 
         Pose toLocalPathFrame(const Pose& global_T_pose) const;
 
diff --git a/GeometricPlanning/path_primitives/Circle.cpp b/GeometricPlanning/path_primitives/Circle.cpp
index 0f074f8c6bb5df4b267584e938a1706398cf20fd..55137eef3b4f4d3eca7c206b72c7a722ac08852f 100644
--- a/GeometricPlanning/path_primitives/Circle.cpp
+++ b/GeometricPlanning/path_primitives/Circle.cpp
@@ -23,7 +23,9 @@ namespace simox::geometric_planning
     Eigen::Vector3f
     Circle::getPosition(float t) const
     {
-        REQUIRE(parameterRange().isInRange(t));
+        // REQUIRE(parameterRange().isInRange(t));
+
+        t = clampParameter(t);
 
         return radius * Eigen::Vector3f(std::cos(t), std::sin(t), 0.0F);
     }
@@ -31,7 +33,8 @@ namespace simox::geometric_planning
     Eigen::Vector3f
     Circle::getPositionDerivative([[maybe_unused]] float t) const
     {
-        REQUIRE(parameterRange().isInRange(t));
+        // REQUIRE(parameterRange().isInRange(t));
+        
 
         // return radius * Eigen::Vector3f(-std::sin(t + M_PI_2f32), std::cos(t + M_PI_2f32), 0.0F);
 
@@ -80,7 +83,7 @@ namespace simox::geometric_planning
         const float param = phi;
         // ARMARX_DEBUG << "Param is " << param;
 
-        return std::clamp(param, parameterRange().min, parameterRange().max);
+        return clampParameter(param);
     }
 
 } // namespace simox::geometric_planning
diff --git a/GeometricPlanning/path_primitives/PathPrimitive.cpp b/GeometricPlanning/path_primitives/PathPrimitive.cpp
index d78fb0e528b454c1547fa548a596c3f4403a3c76..a00ada676a913c29d879a09eac87ac0371054b3c 100644
--- a/GeometricPlanning/path_primitives/PathPrimitive.cpp
+++ b/GeometricPlanning/path_primitives/PathPrimitive.cpp
@@ -16,9 +16,23 @@ namespace simox::geometric_planning
     PathPrimitive::progress(const Pose& pose) const
     {
         const float param = parameter(pose);
+        return progress(param);
+    }
+    
+    float PathPrimitive::progress(const float param) const
+    {
         const auto range = parameterRange();
 
-        return (param - range.min) / (range.max - range.min);
+        const float paramSanitized = clampParameter(param);
+
+        return (paramSanitized - range.min) / (range.max - range.min);
+    }
+
+
+    float PathPrimitive::clampParameter(float t) const
+    {
+        const auto range = parameterRange();
+        return std::clamp(t, range.min, range.max);
     }
 
     Pose
@@ -26,6 +40,7 @@ namespace simox::geometric_planning
     {
         return Pose(::math::Helpers::CreatePose(getPosition(t), getOrientation(t)));
     }
+    
 
     Eigen::Vector3f
     PathPrimitive::GetPosition(float t)
diff --git a/GeometricPlanning/path_primitives/PathPrimitive.h b/GeometricPlanning/path_primitives/PathPrimitive.h
index e5e81ae1120548cc4387cb777719f198598ecb68..48aa40203189c1085bb4d3b16256c5111a0c1199 100644
--- a/GeometricPlanning/path_primitives/PathPrimitive.h
+++ b/GeometricPlanning/path_primitives/PathPrimitive.h
@@ -36,6 +36,7 @@ namespace simox::geometric_planning
          * @return the progress in range [0,1]
          */
         float progress(const Pose& pose) const;
+        float progress(float param) const;
 
         // math::AbstractFunctionR1R6 interface
         Eigen::Vector3f GetPosition(float t) override;
@@ -51,6 +52,8 @@ namespace simox::geometric_planning
 
         Pose getPose(float t) const;
 
+        float clampParameter(float t) const;
+
         ~PathPrimitive() override = default;
     };
 
diff --git a/VirtualRobot/IK/DifferentialIK.cpp b/VirtualRobot/IK/DifferentialIK.cpp
index b70bba190863d7a20eb606f6fe7ec7daef499774..23f6b730846254277032f61eafcb1307ce698aee 100644
--- a/VirtualRobot/IK/DifferentialIK.cpp
+++ b/VirtualRobot/IK/DifferentialIK.cpp
@@ -390,11 +390,12 @@ namespace VirtualRobot
                 // Calculus for rotational joints is different as for prismatic joints.
                 if (dof->isRotationalJoint())
                 {
-                    // get axis
+                    // todo: find a better way of handling different joint types
                     std::shared_ptr<RobotNodeRevolute> revolute
                         = std::dynamic_pointer_cast<RobotNodeRevolute>(dof);
                     THROW_VR_EXCEPTION_IF(!revolute, "Internal error: expecting revolute joint");
-                    // todo: find a better way of handling different joint types
+
+                    // get axis
                     axis = revolute->getJointRotationAxis(coordSystem);
 
                     // if necessary calculate the position part of the Jacobian
@@ -452,18 +453,31 @@ namespace VirtualRobot
                     RobotNodeHemispherePtr hemisphere
                             = std::dynamic_pointer_cast<RobotNodeHemisphere>(dof);
 
-                    VR_ASSERT(hemisphere->first.has_value() xor hemisphere->second.has_value());
-
                     if (hemisphere->isSecondHemisphereJointNode())
                     {
                         // Set Jacobian for both DoFs.
                         RobotNodeHemisphere::SecondData& second = hemisphere->getSecondData();
                         const hemisphere::Maths::Jacobian jacobian = second.getJacobian();
 
-                        tmpUpdateJacobianPosition.block<3, 2>(0, i - 1)
-                                = jacobian.block<3, 2>(0, 0).cast<float>();
-                        tmpUpdateJacobianOrientation.block<3, 2>(0, i - 1)
-                                = jacobian.block<3, 2>(3, 0).cast<float>();
+                        using Matrix3x2f = Eigen::Matrix<float, 3, 2>;
+
+                        const Matrix3x2f velocities = jacobian.block<3, 2>(0, 0).cast<float>();
+                        const Matrix3x2f rotAxes = jacobian.block<3, 2>(3, 0).cast<float>();
+
+                        const Eigen::Matrix3f globalOri = second.firstNode->getGlobalOrientation();
+
+                        Matrix3x2f velocitiesCoord = globalOri * velocities;
+                        Matrix3x2f rotAxesCoord = globalOri * rotAxes;
+                        if (coordSystem)
+                        {
+                            auto coordSystemInverseGlobalRot = coordSystem->getGlobalOrientation().transpose();
+
+                            velocitiesCoord = coordSystemInverseGlobalRot * velocitiesCoord;
+                            rotAxesCoord = coordSystemInverseGlobalRot * rotAxesCoord;
+                        }
+
+                        tmpUpdateJacobianPosition.block<3, 2>(0, i - 1) = velocitiesCoord;
+                        tmpUpdateJacobianOrientation.block<3, 2>(0, i - 1) = rotAxesCoord;
                     }
                     else
                     {
diff --git a/VirtualRobot/IK/platform/OmniWheelPlatformKinematics.cpp b/VirtualRobot/IK/platform/OmniWheelPlatformKinematics.cpp
index 329648250c4683cdc13bb76f2b9411451112c64a..6723df86c5c7bfc53842691dd4bf289e4d8dd4c9 100755
--- a/VirtualRobot/IK/platform/OmniWheelPlatformKinematics.cpp
+++ b/VirtualRobot/IK/platform/OmniWheelPlatformKinematics.cpp
@@ -17,6 +17,7 @@
 */
 
 #include "OmniWheelPlatformKinematics.h"
+#include <cmath>
 
 #include <Eigen/Core>
 #include <Eigen/Dense>
@@ -44,15 +45,11 @@ namespace VirtualRobot
     Eigen::Matrix3f
     OmniWheelPlatformKinematicsParams::C() const
     {
-        Eigen::Matrix3f r = Eigen::Vector3f{-1, 1, 1}.asDiagonal();
-
         // Rotation around platform center to define which direction is front
-        // For ARMAR-7, this is 60 degrees (between the two "front" wheels).
-        float relativeAngle = M_PI / 3.0f;
         Eigen::Matrix3f relativeRotation = Eigen::Matrix3f::Identity();
         relativeRotation.block<2, 2>(0, 0) = Eigen::Rotation2Df(relativeAngle).toRotationMatrix();
 
-        return relativeRotation * r * B().transpose().inverse() * R / n;
+        return relativeRotation * wheelFactor.asDiagonal() * B().transpose().inverse() * 2 * M_PI * R / n;
     }
 
     // OmniWheelPlatformKinematics
diff --git a/VirtualRobot/IK/platform/OmniWheelPlatformKinematics.h b/VirtualRobot/IK/platform/OmniWheelPlatformKinematics.h
index 2262d39703b5ffc70371f6983e135c0310363991..7df2cd739763c8ff71097307213674e4a358df08 100755
--- a/VirtualRobot/IK/platform/OmniWheelPlatformKinematics.h
+++ b/VirtualRobot/IK/platform/OmniWheelPlatformKinematics.h
@@ -49,6 +49,13 @@ namespace VirtualRobot
         //! gear ratio
         float n;
 
+        //! rotation to reference
+        // Rotation around platform center to define which direction is front
+        // For ARMAR-7, this is 60 degrees (between the two "front" wheels).
+        float relativeAngle;
+
+        Eigen::Vector3f wheelFactor;
+
         // see Liu et al., formula 2.2.2
         Eigen::Matrix3f B() const;
 
diff --git a/VirtualRobot/Nodes/RobotNode.cpp b/VirtualRobot/Nodes/RobotNode.cpp
index 8298691bfc22221a3daf83ba7a676d53841392a0..077b2d7ab09ebf176ca677d959e82ffc2a9abc17 100644
--- a/VirtualRobot/Nodes/RobotNode.cpp
+++ b/VirtualRobot/Nodes/RobotNode.cpp
@@ -538,11 +538,11 @@ namespace VirtualRobot
     RobotNodePtr RobotNode::clone(RobotPtr newRobot, bool cloneChildren,
                                   RobotNodePtr initializeWithParent,
                                   CollisionCheckerPtr colChecker,
-                                  float scaling,
+                                  std::optional<float> scaling,
                                   bool preventCloningMeshesIfScalingIs1)
     {
-        // If scaling is <= 0 this->scaling is used instead. This enables different scalings while still able to clone the robot
-        auto actualScaling = scaling > 0 ? scaling : this->scaling;
+        const float actualScaling = scaling.value_or(this->scaling);
+        const float scalingFactor = scaling.has_value() ? scaling.value() / this->scaling : 1.0f;
 
         ReadLockPtr lock = getRobot()->getReadLock();
 
@@ -556,20 +556,20 @@ namespace VirtualRobot
 
         VisualizationNodePtr clonedVisualizationNode;
 
-        const bool deepMeshClone = !preventCloningMeshesIfScalingIs1 || std::abs(scaling - 1) <= 0;
+        const bool deepMeshClone = !preventCloningMeshesIfScalingIs1 || std::abs(scalingFactor - 1) <= 1e-12;
         if (visualizationModel)
         {
-            clonedVisualizationNode = visualizationModel->clone(deepMeshClone, actualScaling);
+            clonedVisualizationNode = visualizationModel->clone(deepMeshClone, scalingFactor);
         }
 
         CollisionModelPtr clonedCollisionModel;
 
         if (collisionModel)
         {
-            clonedCollisionModel = collisionModel->clone(colChecker, actualScaling, deepMeshClone);
+            clonedCollisionModel = collisionModel->clone(colChecker, scalingFactor, deepMeshClone);
         }
 
-        RobotNodePtr result = _clone(newRobot, clonedVisualizationNode, clonedCollisionModel, colChecker, scaling > 0 ? scaling : 1.0f);
+        RobotNodePtr result = _clone(newRobot, clonedVisualizationNode, clonedCollisionModel, colChecker, scalingFactor);
 
         if (!result)
         {
diff --git a/VirtualRobot/Nodes/RobotNode.h b/VirtualRobot/Nodes/RobotNode.h
index 454973471658e6eecc0698d000484b2e83b69dc8..1e2c571211c3c0b099b9970f686ae0ffa414be21 100644
--- a/VirtualRobot/Nodes/RobotNode.h
+++ b/VirtualRobot/Nodes/RobotNode.h
@@ -32,6 +32,7 @@
 
 #include <Eigen/Core>
 
+#include <optional>
 #include <string>
 #include <vector>
 #include <map>
@@ -271,7 +272,7 @@ namespace VirtualRobot
                                    bool cloneChildren = true,
                                    RobotNodePtr initializeWithParent = RobotNodePtr(),
                                    CollisionCheckerPtr colChecker = CollisionCheckerPtr(),
-                                   float scaling = 1.0f,
+                                   std::optional<float> scaling = std::nullopt,
                                    bool preventCloningMeshesIfScalingIs1 = false);
 
         inline float getJointValueOffset() const
diff --git a/VirtualRobot/Nodes/RobotNodeFourBar.cpp b/VirtualRobot/Nodes/RobotNodeFourBar.cpp
index a70581dd9fec393485ec191d9d01234d0a67c417..84c486ef0b22430e8ddfab4aeeba9f07f0c5d5fc 100644
--- a/VirtualRobot/Nodes/RobotNodeFourBar.cpp
+++ b/VirtualRobot/Nodes/RobotNodeFourBar.cpp
@@ -125,7 +125,7 @@ namespace VirtualRobot
     void
     RobotNodeFourBar::setJointValueNoUpdate(float q)
     {
-        std::cout << "RobotNodeFourBar: setting joint value no update " << q << std::endl;
+        // std::cout << "RobotNodeFourBar: setting joint value no update " << q << std::endl;
 
         if (active)
         {
@@ -146,9 +146,9 @@ namespace VirtualRobot
     {
         // We must update the preceeding node (the passive node).
         // This usually causes issues as the order to update the kinematic chain is strict.
-        std::cout << "RobotNodeFourBar: setting joint value " << getName() << " " << q << std::endl;
+        // std::cout << "RobotNodeFourBar: setting joint value " << getName() << " " << q << std::endl;
 
-        std::cout << "RobotNodeFourBar: active? " << active.has_value() << std::endl;
+        // std::cout << "RobotNodeFourBar: active? " << active.has_value() << std::endl;
 
         // update this node (without the global / internal pose!)
         {
@@ -160,7 +160,7 @@ namespace VirtualRobot
 
         if (active)
         {
-            std::cout << "RobotNodeFourBar: triggering update of passive joint " << std::endl;
+            // std::cout << "RobotNodeFourBar: triggering update of passive joint " << std::endl;
 
             // update all nodes including this one
             active->passive->updatePose(true);
@@ -176,7 +176,7 @@ namespace VirtualRobot
     {
         this->xmlInfo = info;
 
-        VR_ASSERT(second.has_value());
+        VR_ASSERT(active.has_value());
         switch (info.role)
         {
             case Role::PASSIVE:
@@ -199,16 +199,16 @@ namespace VirtualRobot
     bool
     RobotNodeFourBar::initialize(SceneObjectPtr parent, const std::vector<SceneObjectPtr>& children)
     {
-        VR_ASSERT_MESSAGE(first.has_value() xor second.has_value(),
-                          std::stringstream() << first.has_value() << " / " << second.has_value());
+        VR_ASSERT_MESSAGE(first.has_value() xor active.has_value(),
+                          std::stringstream() << first.has_value() << " / " << active.has_value());
 
-        // The second node needs to store a reference to the first node.
+        // The active node needs to store a reference to the first node.
         // Whenever the joint value has changed, the passive joint will be updated.
         if (active)
         {
-            std::cout << "Initializing active four bar joint" << std::endl;
+            // std::cout << "Initializing active four bar joint" << std::endl;
 
-            VR_ASSERT_MESSAGE(not second->first, "Second must not be initialized yet.");
+            VR_ASSERT_MESSAGE(not active->passive, "Second must not be initialized yet.");
 
             VirtualRobot::SceneObjectPtr currentParent = parent;
 
@@ -281,10 +281,10 @@ namespace VirtualRobot
     void
     RobotNodeFourBar::updateTransformationMatrices(const Eigen::Matrix4f& parentPose)
     {
-        VR_ASSERT_MESSAGE(first.has_value() xor second.has_value(),
-                          std::stringstream() << first.has_value() << " / " << second.has_value());
+        VR_ASSERT_MESSAGE(first.has_value() xor active.has_value(),
+                          std::stringstream() << first.has_value() << " / " << active.has_value());
 
-        std::cout << "Updating RobotNodeFourBar::updateTransformationMatrices" << std::endl;
+        // std::cout << "Updating RobotNodeFourBar::updateTransformationMatrices" << std::endl;
 
         Eigen::Isometry3f tmp = Eigen::Isometry3f::Identity();
 
@@ -292,21 +292,21 @@ namespace VirtualRobot
 
         if (active)
         {
-            std::cout << "active: joint value " << jV << std::endl;
+            // std::cout << "active: joint value " << jV << std::endl;
 
             active->math.update(jV);
             tmp = active->math.joint.computeFk(jV).matrix().cast<float>();
         }
         else // passive
         {
-            std::cout << "passive: joint value " << jV << std::endl;
+            // std::cout << "passive: joint value " << jV << std::endl;
 
             tmp.linear() = Eigen::AngleAxisf(jV + jointValueOffset, Eigen::Vector3f::UnitZ())
                                .toRotationMatrix();
         }
 
 
-        std::cout << "local transformation: " << getName() << tmp.matrix() << std::endl;
+        // std::cout << "local transformation: " << getName() << tmp.matrix() << std::endl;
         globalPose = parentPose * localTransformation * tmp.matrix();
 
 
@@ -316,7 +316,7 @@ namespace VirtualRobot
         // }
         // else if (active)
         // {
-        //     VR_ASSERT_MESSAGE(second->first, "First node must be known to second node.");
+        //     VR_ASSERT_MESSAGE(active->first, "First node must be known to active node.");
 
         //     JointMath& math = active->math();
         //     Eigen::Vector2f actuators(active->passive->getJointValue(), this->getJointValue());
@@ -333,7 +333,7 @@ namespace VirtualRobot
 
         //         Eigen::IOFormat iof(5, 0, " ", "\n", "    [", "]");
         //         std::cout
-        //             << __FUNCTION__ << "() of second actuator with "
+        //             << __FUNCTION__ << "() of active actuator with "
         //             << "\n  lever = " << math.joint.lever << "\n  theta0 = " << math.joint.theta0
         //             << "\n  radius = " << math.joint.radius << "\n  joint value = " << jointValue
         //             << "\n  actuator (angle) = \n"
@@ -351,8 +351,8 @@ namespace VirtualRobot
     RobotNodeFourBar::print(bool printChildren, bool printDecoration) const
     {
         ReadLockPtr lock = getRobot()->getReadLock();
-        VR_ASSERT_MESSAGE(first.has_value() xor second.has_value(),
-                          std::stringstream() << first.has_value() << " / " << second.has_value());
+        VR_ASSERT_MESSAGE(first.has_value() xor active.has_value(),
+                          std::stringstream() << first.has_value() << " / " << active.has_value());
 
         if (printDecoration)
         {
@@ -367,7 +367,7 @@ namespace VirtualRobot
         }
         else if (active)
         {
-            std::cout << "* four_bar joint second node";
+            std::cout << "* four_bar joint active node";
             // std::cout << "* Transform: \n"
             //           << active->math.joint.getEndEffectorTransform() << std::endl;
         }
@@ -519,8 +519,8 @@ namespace VirtualRobot
     std::string
     RobotNodeFourBar::_toXML(const std::string& /*modelPath*/)
     {
-        VR_ASSERT_MESSAGE(first.has_value() xor second.has_value(),
-                          std::stringstream() << first.has_value() << " / " << second.has_value());
+        VR_ASSERT_MESSAGE(first.has_value() xor active.has_value(),
+                          std::stringstream() << first.has_value() << " / " << active.has_value());
 
         if (first)
         {
diff --git a/VirtualRobot/Nodes/RobotNodeHemisphere.cpp b/VirtualRobot/Nodes/RobotNodeHemisphere.cpp
index a2d999867affb1b8adf6f5d79ffc12675ef5607b..d472654467060a64d9de011afd00050be2575c48 100644
--- a/VirtualRobot/Nodes/RobotNodeHemisphere.cpp
+++ b/VirtualRobot/Nodes/RobotNodeHemisphere.cpp
@@ -127,7 +127,7 @@ namespace VirtualRobot
         // The second node needs to store a reference to the first node.
         if (secondData)
         {
-            VR_ASSERT_MESSAGE(not secondData->first, "Second must not be initialized yet.");
+            VR_ASSERT_MESSAGE(not secondData->firstNode, "Second must not be initialized yet.");
 
             RobotNodeHemisphere* firstNode = dynamic_cast<RobotNodeHemisphere*>(parent.get());
             RobotNodeHemisphere* secondNode = this;
@@ -171,7 +171,7 @@ namespace VirtualRobot
         }
         else if (secondData)
         {
-            VR_ASSERT_MESSAGE(secondData->first, "First node must be known to second node.");
+            VR_ASSERT_MESSAGE(secondData->firstNode, "First node must be known to second node.");
 
             hemisphere::CachedMaths& maths = secondData->maths();
             Eigen::Vector2f actuators(secondData->firstNode->getJointValue(), this->getJointValue());
diff --git a/VirtualRobot/Primitive.h b/VirtualRobot/Primitive.h
index 9d03fef485396abd4c647cdfce38adde4fa8a806..0977163be8d85009654d7c94df33440cf04017a2 100644
--- a/VirtualRobot/Primitive.h
+++ b/VirtualRobot/Primitive.h
@@ -22,6 +22,8 @@ namespace VirtualRobot
 
             virtual std::unique_ptr<Primitive> clone() const = 0;
 
+            virtual void scaleLinear(float scalingFactor) = 0;
+
         protected:
             Primitive(int type) : type(type), transform(Eigen::Matrix4f::Identity()) {}
             std::string getTransformString(int tabs = 0);
@@ -47,6 +49,14 @@ namespace VirtualRobot
                 clone->transform = transform;
                 return clone;
             }
+
+            void scaleLinear(float scalingFactor) final
+            {
+                transform.block(0, 3, 3, 1) *= scalingFactor;
+                width *= scalingFactor;
+                height *= scalingFactor;
+                depth *= scalingFactor;
+            }
         };
 
         class VIRTUAL_ROBOT_IMPORT_EXPORT Sphere : public Primitive
@@ -64,6 +74,12 @@ namespace VirtualRobot
                 clone->transform = transform;
                 return clone;
             }
+
+            void scaleLinear(float scalingFactor) final
+            {
+                transform.block(0, 3, 3, 1) *= scalingFactor;
+                radius *= scalingFactor;
+            }
         };
 
         class VIRTUAL_ROBOT_IMPORT_EXPORT Cylinder : public Primitive
@@ -82,6 +98,13 @@ namespace VirtualRobot
                 clone->transform = transform;
                 return clone;
             }
+
+            void scaleLinear(float scalingFactor) final
+            {
+                transform.block(0, 3, 3, 1) *= scalingFactor;
+                height *= scalingFactor;
+                radius *= scalingFactor;
+            }
         };
 
         /**
@@ -103,6 +126,13 @@ namespace VirtualRobot
                 clone->transform = transform;
                 return clone;
             }
+
+            void scaleLinear(float scalingFactor) final
+            {
+                transform.block(0, 3, 3, 1) *= scalingFactor;
+                height *= scalingFactor;
+                radius *= scalingFactor;
+            }
         };
 
         typedef std::shared_ptr<Primitive> PrimitivePtr;
diff --git a/VirtualRobot/Robot.cpp b/VirtualRobot/Robot.cpp
index ea1b6d6e698d919d9b0d8c1de7941b063238a20c..86643243dbec2439c1c739517da975ce66d0dd23 100644
--- a/VirtualRobot/Robot.cpp
+++ b/VirtualRobot/Robot.cpp
@@ -766,7 +766,7 @@ namespace VirtualRobot
             bool cloneRNS,
             bool cloneEEFs,
             CollisionCheckerPtr collisionChecker,
-            float scaling,
+            std::optional<float> scaling,
             bool preventCloningMeshesIfScalingIs1)
     {
         THROW_VR_EXCEPTION_IF(!startJoint, " StartJoint is nullptr");
@@ -787,7 +787,7 @@ namespace VirtualRobot
         RobotNodePtr rootNew = startJoint->clone(result, true, RobotNodePtr(), colChecker, scaling, preventCloningMeshesIfScalingIs1);
         THROW_VR_EXCEPTION_IF(!rootNew, "Clone failed...");
         result->setRootNode(rootNew);
-        result->setScaling(scaling > 0 ? scaling : this->scaling);
+        result->setScaling(scaling.value_or(this->scaling));
 
         std::vector<RobotNodePtr> rn = result->getRobotNodes();
 
@@ -849,7 +849,7 @@ namespace VirtualRobot
     }
 
     RobotPtr Robot::cloneScaling() {
-        return clone(getName(), CollisionCheckerPtr(), -1.0f);
+        return clone();
     }
 
     Eigen::Matrix4f Robot::getGlobalPoseForRobotNode(
@@ -905,7 +905,7 @@ namespace VirtualRobot
 
     VirtualRobot::RobotPtr Robot::clone(const std::string& name,
                                         CollisionCheckerPtr collisionChecker,
-                                        float scaling,
+                                        std::optional<float> scaling,
                                         bool preventCloningMeshesIfScalingIs1)
     {
         VirtualRobot::RobotPtr result = extractSubPart(this->getRootNode(),
@@ -929,7 +929,7 @@ namespace VirtualRobot
     }
 
     RobotPtr Robot::clone(CollisionCheckerPtr collisionChecker,
-                          float scaling,
+                          std::optional<float> scaling,
                           bool preventCloningMeshesIfScalingIs1)
     {
         return clone(getName(), collisionChecker, scaling, preventCloningMeshesIfScalingIs1);
diff --git a/VirtualRobot/Robot.h b/VirtualRobot/Robot.h
index 1ab3255d1128438ff71ff27369eefe55df24a82d..868c00deb126dcac31e13277eb3f37cfef1e549c 100644
--- a/VirtualRobot/Robot.h
+++ b/VirtualRobot/Robot.h
@@ -303,7 +303,7 @@ namespace VirtualRobot
                                         bool cloneRNS = true,
                                         bool cloneEEFs = true,
                                         CollisionCheckerPtr collisionChecker = CollisionCheckerPtr(),
-                                        float scaling = 1.0f,
+                                        std::optional<float> scaling = std::nullopt,
                                         bool preventCloningMeshesIfScalingIs1 = false);
 
         /*!
@@ -315,13 +315,14 @@ namespace VirtualRobot
         */
         virtual RobotPtr clone(const std::string& name,
                                CollisionCheckerPtr collisionChecker = CollisionCheckerPtr(),
-                               float scaling = 1.0f,
+                               std::optional<float> scaling = std::nullopt,
                                bool preventCloningMeshesIfScalingIs1 = false);
         virtual RobotPtr clone(CollisionCheckerPtr collisionChecker = CollisionCheckerPtr(),
-                               float scaling = 1.0f,
+                               std::optional<float> scaling = std::nullopt,
                                bool preventCloningMeshesIfScalingIs1 = false);
 
-        /*! @brief Clones this robot and keeps the current scaling for the robot and each robot node */
+        /*! @brief Clones this robot and keeps the current scaling for the robot and each robot node. */
+        [[deprecated("Use clone() instead. Which now contains scaling as an optional.")]]
         virtual RobotPtr cloneScaling();
 
         //! Just storing the filename.
diff --git a/VirtualRobot/RobotFactory.cpp b/VirtualRobot/RobotFactory.cpp
index e49afc9cb4e22e1fccceaf54eb3987e565d74434..025904d0df8f18d24a4cd0f13f03d3670547fbf1 100644
--- a/VirtualRobot/RobotFactory.cpp
+++ b/VirtualRobot/RobotFactory.cpp
@@ -344,6 +344,80 @@ namespace VirtualRobot
         return true;
     }
 
+    void RobotFactory::scaleLinear(RobotNode &node, float sizeScaling, float weightScaling)
+    {
+        Eigen::Matrix4f localTransformation = node.getLocalTransformation();
+        localTransformation.block(0, 3, 3, 1) *= sizeScaling;
+        node.setLocalTransformation(localTransformation);
+
+        if (node.optionalDHParameter.isSet)
+        {
+            node.optionalDHParameter.setAInMM(node.optionalDHParameter.aMM() * sizeScaling);
+            node.optionalDHParameter.setDInMM(node.optionalDHParameter.dMM() * sizeScaling);
+        }
+
+        node.setMass(node.getMass() * weightScaling);
+        node.setCoMLocal(node.getCoMLocal() * sizeScaling);
+        node.setInertiaMatrix(node.getInertiaMatrix() * pow(sizeScaling, 2) * weightScaling);
+        node.setScaling(node.getScaling() * sizeScaling);
+    }
+
+    void RobotFactory::scaleLinear(Robot &robot, float sizeScaling, float weightScaling,
+                                   const std::map<std::string, float> &customSegmentLengths,
+                                   const std::map<std::string, float>& customSizeScaling)
+    {
+        for (auto node : robot.getRobotNodes())
+        {
+            float model_height_scaling = sizeScaling;
+
+            const auto segLengthIt = customSegmentLengths.find(node->getName());
+            const auto sizeScaleIt = customSizeScaling.find(node->getName());
+
+            if (sizeScaleIt != customSizeScaling.end())
+            {
+                model_height_scaling = sizeScaleIt->second;
+            }
+
+            if (segLengthIt != customSegmentLengths.end())
+            {
+                if (sizeScaleIt != customSizeScaling.end())
+                {
+                    VR_WARNING << "Custom segment length ignored for node " << node->getName()
+                               << " because custom height scaling was already specified ";
+                }
+                else
+                {
+                    const float l = node->getLocalTransformation().block(0, 3, 3, 1).norm();
+                    if (l != 0.0f)
+                        model_height_scaling = 1.0f / l * segLengthIt->second;
+                }
+            }
+
+            for (auto sensor : node->getSensors())
+            {
+                Eigen::Matrix4f lt = sensor->getParentNodeToSensorTransformation();
+                lt.block(0, 3, 3, 1) *= model_height_scaling;
+                sensor->setRobotNodeToSensorTransformation(lt);
+            }
+
+            scaleLinear(*node.get(), model_height_scaling, weightScaling);
+
+            if (auto vis = node->visualizationModel)
+            {
+                node->setVisualization(node->visualizationModel->clone(true, model_height_scaling));
+            }
+
+            if (auto col = node->getCollisionModel())
+            {
+                node->setCollisionModel(col->clone(node->getCollisionChecker(), model_height_scaling, true));
+            }
+
+            node->getPrimitiveApproximation().scaleLinear(model_height_scaling);
+        }
+        robot.setScaling(robot.getScaling() * sizeScaling);
+        robot.setMass(robot.getMass() * weightScaling);
+    }
+
     RobotPtr RobotFactory::cloneChangeStructure(RobotPtr robot, robotStructureDef& newStructure)
     {
         VR_ASSERT(robot);
@@ -1110,32 +1184,47 @@ namespace VirtualRobot
             const Eigen::Matrix4f globalPoseInv = simox::math::inverted_pose(n.node_cloned->getGlobalPose());
             if (!visus.empty())
             {
-                if (const auto visu = n.node->getVisualization())
+                if (visus.size() == 1)
                 {
-                    visus.insert(visus.begin(), visu->clone());
+                    n.node_cloned->setVisualization(visus.front()->clone());
                 }
-                auto v = vf->createUnitedVisualization(visus);
-                if (n.parentNode_cloned)
+                else
                 {
-                    vf->applyDisplacement(v, globalPoseInv);
+                    if (const auto visu = n.node->getVisualization())
+                    {
+                        visus.insert(visus.begin(), visu->clone());
+                    }
+                    auto v = vf->createUnitedVisualization(visus);
+                    if (n.parentNode_cloned)
+                    {
+                        v->setLocalPose(globalPoseInv);
+                    }
+                    n.node_cloned->setVisualization(v);
                 }
-                n.node_cloned->setVisualization(v);
             }
             if (!colVisus.empty())
             {
-                if (const auto colModel = n.node->getCollisionModel())
+                if (colVisus.size() == 1)
                 {
-                    if (const auto vis = colModel->getVisualization())
-                    {
-                        colVisus.insert(colVisus.begin(), vis->clone());
-                    }
+                    n.node_cloned->setCollisionModel(std::make_shared<CollisionModel>(colVisus.front(), n.node->getName()));
                 }
-                auto colVisu = vf->createUnitedVisualization(colVisus);
-                if (n.parentNode_cloned)
+                else
                 {
-                    vf->applyDisplacement(colVisu, globalPoseInv);
+                    if (const auto colModel = n.node->getCollisionModel())
+                    {
+                        if (const auto vis = colModel->getVisualization())
+                        {
+                            colVisus.insert(colVisus.begin(), vis->clone());
+                        }
+                    }
+                    auto colVisu = vf->createUnitedVisualization(colVisus);
+                    if (n.parentNode_cloned)
+                    {
+                        colVisu->setLocalPose(globalPoseInv);
+                    }
+                    n.node_cloned->setCollisionModel(std::make_shared<CollisionModel>(colVisu, n.node->getName()));
                 }
-                n.node_cloned->setCollisionModel(std::make_shared<CollisionModel>(colVisu, n.node->getName()));
+
             }
 
             for (const auto& childNode : n.childNodes)
diff --git a/VirtualRobot/RobotFactory.h b/VirtualRobot/RobotFactory.h
index 0b5303446453bcd3bf55baee5bffc92b7308374a..e73cbe19fe49bbe0680d8ff24d5f820fb391fd69 100644
--- a/VirtualRobot/RobotFactory.h
+++ b/VirtualRobot/RobotFactory.h
@@ -147,6 +147,21 @@ namespace VirtualRobot
 
         static bool detach(RobotPtr robot, RobotNodePtr rn);
 
+        static void scaleLinear(RobotNode& node, float sizeScaling, float weightScaling = 1.);
+
+        /**
+         * Method to linearly scale the size (in all axes) and the weight of the robot, e.g. for the MMM reference model.
+         * This includes scaling all kinematic and dynamic properties as well as the visualization, collision and primitive approximation models.
+         * @param robot
+         * @param sizeScaling linear scaling factor in x,y,z-direction
+         * @param weightScaling linear weight factor
+         * @param customSegmentLengths (Optional) RobotNode name -> custom translation length. Takes priority over sizeScaling
+         * @param customSizeScaling (Optional) RobotNode name -> custom size scaling. Takes priority over customSegmentLengths.
+         */
+        static void scaleLinear(Robot& robot, float sizeScaling, float weightScaling = 1.,
+                                const std::map<std::string, float>& customSegmentLengths = std::map<std::string, float>(),
+                                const std::map<std::string, float>& customSegmentSizeScaling = std::map<std::string, float>());
+
     protected:
 
         // some internal stuff
diff --git a/VirtualRobot/SceneObject.cpp b/VirtualRobot/SceneObject.cpp
index 734ec473976bfd677d1607a1853d0a6e8c878e24..1466ed3e9d7bc6c6538f2783ddd5352d1f483840 100644
--- a/VirtualRobot/SceneObject.cpp
+++ b/VirtualRobot/SceneObject.cpp
@@ -1464,7 +1464,7 @@ namespace VirtualRobot
         return scaling;
     }
 
-    bool SceneObject::reloadVisualizationFromXML(bool useVisAsColModelIfMissing) {
+    bool SceneObject::reloadVisualizationFromXML(bool useVisAsColModelIfMissing, bool loadColOnly) {
         bool reloaded = false;
         if (!collisionModelXML.empty())
         {
@@ -1484,7 +1484,8 @@ namespace VirtualRobot
             }
             reloaded = true;
         }
-        if (!visualizationModelXML.empty())
+        if ((!loadColOnly || (!collisionModel && useVisAsColModelIfMissing))
+             && !visualizationModelXML.empty())
         {
             rapidxml::xml_document<> doc;
             std::vector<char> cstr(visualizationModelXML.size() + 1);  // Create char buffer to store string copy
@@ -1492,13 +1493,16 @@ namespace VirtualRobot
             doc.parse<0>(cstr.data());
             bool useAsColModel;
             auto visualizationModel = BaseIO::processVisualizationTag(doc.first_node(), name, basePath, useAsColModel);
-            if (visualizationModel && scaling != 1.0f)
+            if (!loadColOnly)
             {
-                setVisualization(visualizationModel->clone(true, scaling));
-            }
-            else
-            {
-                setVisualization(visualizationModel);
+                if (visualizationModel && scaling != 1.0f)
+                {
+                    setVisualization(visualizationModel->clone(true, scaling));
+                }
+                else
+                {
+                    setVisualization(visualizationModel);
+                }
             }
             if (visualizationModel && collisionModel == nullptr && (useVisAsColModelIfMissing || useAsColModel))
             {
@@ -1507,7 +1511,7 @@ namespace VirtualRobot
             reloaded = true;
         }
         for (auto child : this->getChildren()) {
-            reloaded |= child->reloadVisualizationFromXML(useVisAsColModelIfMissing);
+            reloaded |= child->reloadVisualizationFromXML(useVisAsColModelIfMissing, loadColOnly);
         }
         return reloaded;
     }
@@ -1662,4 +1666,20 @@ namespace VirtualRobot
         return defaultPrimitives.empty() && primitives.empty();
     }
 
+    void SceneObject::PrimitiveApproximation::scaleLinear(float scalingFactor)
+    {
+        for (auto& primitive : this->defaultPrimitives)
+        {
+            primitive->scaleLinear(scalingFactor);
+        }
+
+        for (auto& [id, primitives] : this->primitives)
+        {
+            for (auto& primitive : primitives)
+            {
+                primitive->scaleLinear(scalingFactor);
+            }
+        }
+    }
+
 } // namespace
diff --git a/VirtualRobot/SceneObject.h b/VirtualRobot/SceneObject.h
index aefd37fc17badf91da02a82b8691284a7be07e23..5572e1cae677c92020227a9095f44536de205ced 100644
--- a/VirtualRobot/SceneObject.h
+++ b/VirtualRobot/SceneObject.h
@@ -105,6 +105,8 @@ namespace VirtualRobot
 
             bool empty() const;
 
+            void scaleLinear(float scalingFactor);
+
         private:
             std::vector<Primitive::PrimitivePtr> defaultPrimitives;
             std::map<std::string, std::vector<Primitive::PrimitivePtr>> primitives;
@@ -418,7 +420,7 @@ namespace VirtualRobot
         void setScaling(float scaling);
         float getScaling();
 
-        bool reloadVisualizationFromXML(bool useVisAsColModelIfMissing = true);
+        bool reloadVisualizationFromXML(bool useVisAsColModelIfMissing = true, bool loadColOnly = false);
 
         const std::string& getVisualizationModelXML() const;
 
diff --git a/VirtualRobot/Visualization/CoinVisualization/CoinVisualizationFactory.cpp b/VirtualRobot/Visualization/CoinVisualization/CoinVisualizationFactory.cpp
index 7a4e74191658a561faadbe05d921508a8340f759..b41899e0af4952759437b26221807efda5acc983 100644
--- a/VirtualRobot/Visualization/CoinVisualization/CoinVisualizationFactory.cpp
+++ b/VirtualRobot/Visualization/CoinVisualization/CoinVisualizationFactory.cpp
@@ -4076,8 +4076,6 @@ namespace VirtualRobot
                 {
                     SoSeparator* ss = new SoSeparator();
 
-                    SoUnits* u = new SoUnits();
-                    u->units = SoUnits::MILLIMETERS;
 
                     const Eigen::Isometry3f localPose(visualizations[i]->getLocalPose());
                     const Eigen::Vector3f& translation = localPose.translation();
@@ -4087,7 +4085,6 @@ namespace VirtualRobot
                     const Eigen::Quaternionf q = simox::math::mat3f_to_quat(localPose.linear());
                     t->rotation.setValue(q.x(), q.y(), q.z(), q.w());
 
-                    ss->addChild(u);
                     ss->addChild(t);
                     ss->addChild(n->copy(FALSE));
 
diff --git a/VirtualRobot/tests/OmniPlatformKinematicsTest.cpp b/VirtualRobot/tests/OmniPlatformKinematicsTest.cpp
index 5cceb677c02a138a6abf83834031a7e4854271f5..a9d23c70145b60c9f05dfeb79a04b68248733153 100755
--- a/VirtualRobot/tests/OmniPlatformKinematicsTest.cpp
+++ b/VirtualRobot/tests/OmniPlatformKinematicsTest.cpp
@@ -19,6 +19,7 @@
  *             GNU General Public License
  */
 
+#include <cmath>
 #include <boost/test/tools/old/interface.hpp>
 #define BOOST_TEST_MODULE VirtualRobot_OmniPlatformKinematics
 
@@ -40,7 +41,9 @@ const inline VirtualRobot::OmniWheelPlatformKinematics::Params ARMAR7_OMNI_PLATF
     .L = 326,                                      // [mm]
     .R = 125 / 2,                                  // [mm]
     .delta = VirtualRobot::MathTools::deg2rad(30), // [rad]
-    .n = 1};
+    .n = 1,
+    .relativeAngle = M_PI / 3,
+    .wheelFactor = Eigen::Vector3f{-1, 1, 1}};
 
 BOOST_AUTO_TEST_CASE(testExample)
 {
diff --git a/VirtualRobot/tests/VirtualRobotManipulabilityTest.cpp b/VirtualRobot/tests/VirtualRobotManipulabilityTest.cpp
index 8cfed15f1940a14c125d5f415267c238f5043ca4..9abb5be5e167a87edceaabcbdbc284ea41a54ad2 100644
--- a/VirtualRobot/tests/VirtualRobotManipulabilityTest.cpp
+++ b/VirtualRobot/tests/VirtualRobotManipulabilityTest.cpp
@@ -12,19 +12,20 @@
 #include <SimoxUtility/algorithm/string/string_conversion_eigen.h>
 
 #define CHECK_SMALL_VELOCITY(a, b, tolerance) { \
-    BOOST_REQUIRE_EQUAL(a.rows(), b.rows()); \
-    for (unsigned int i = 0; i < a.rows(); i++) { \
-        BOOST_CHECK_SMALL(std::abs(a(i) - b(i)), tolerance); \
+    BOOST_REQUIRE_EQUAL((a).rows(), (b).rows()); \
+    for (unsigned int i = 0; i < (a).rows(); i++) { \
+        BOOST_CHECK_SMALL(std::abs((a)(i) - (b)(i)), (tolerance)); \
     } \
 }
 
-#define FLOAT_CLOSE_TO_DIFF 1e-4f
-
-#define CHECK_SMALL_DIFF_VELOCITY(a, b) { \
-    CHECK_SMALL_VELOCITY(a, b, FLOAT_CLOSE_TO_DIFF) \
+#define CHECK_SMALL_DIFF_VELOCITY(a, b, tolerance) { \
+    CHECK_SMALL_VELOCITY(a, b, tolerance) \
 }
 
 
+static const float DefaultTolerance = 1e-4f;
+
+
 BOOST_AUTO_TEST_SUITE(ManipulabilityTest)
 
 BOOST_AUTO_TEST_CASE(testPositionalVelocityManipulabilityTracking)
@@ -55,7 +56,7 @@ BOOST_AUTO_TEST_CASE(testPositionalVelocityManipulabilityTracking)
     VirtualRobot::SingleChainManipulabilityTrackingPtr tracking(new VirtualRobot::SingleChainManipulabilityTracking(manipulability));
     Eigen::VectorXf velocity = tracking->calculateVelocity(desiredManipulability);
 
-    CHECK_SMALL_DIFF_VELOCITY(velocity, expectedVelocity)
+    CHECK_SMALL_DIFF_VELOCITY(velocity, expectedVelocity, DefaultTolerance)
 }
 
 //BOOST_AUTO_TEST_CASE(testOrientationalVelocityManipulabilityTracking)
@@ -120,7 +121,7 @@ BOOST_AUTO_TEST_CASE(testWholeVelocityManipulabilityTracking)
     VirtualRobot::SingleChainManipulabilityTrackingPtr tracking(new VirtualRobot::SingleChainManipulabilityTracking(manipulability));
     Eigen::VectorXf velocity = tracking->calculateVelocity(desiredManipulability);
 
-    CHECK_SMALL_DIFF_VELOCITY(velocity, expectedVelocity)
+    CHECK_SMALL_DIFF_VELOCITY(velocity, expectedVelocity, DefaultTolerance)
 }
 
 BOOST_AUTO_TEST_CASE(testPositionalForceManipulabilityTracking)
@@ -151,7 +152,7 @@ BOOST_AUTO_TEST_CASE(testPositionalForceManipulabilityTracking)
     VirtualRobot::SingleChainManipulabilityTrackingPtr tracking(new VirtualRobot::SingleChainManipulabilityTracking(manipulability));
     Eigen::VectorXf velocity = tracking->calculateVelocity(desiredManipulability);
 
-    CHECK_SMALL_DIFF_VELOCITY(velocity, expectedVelocity)
+    CHECK_SMALL_DIFF_VELOCITY(velocity, expectedVelocity, DefaultTolerance)
 }
 
 //BOOST_AUTO_TEST_CASE(testOrientationalForceManipulabilityTracking)
@@ -216,7 +217,7 @@ BOOST_AUTO_TEST_CASE(testWholeForceManipulabilityTracking)
     VirtualRobot::SingleChainManipulabilityTrackingPtr tracking(new VirtualRobot::SingleChainManipulabilityTracking(manipulability));
     Eigen::VectorXf velocity = tracking->calculateVelocity(desiredManipulability);
 
-    CHECK_SMALL_DIFF_VELOCITY(velocity, expectedVelocity)
+    CHECK_SMALL_DIFF_VELOCITY(velocity, expectedVelocity, 4e-4f)
 }