def METHOD_NAME(objects_to_sample: List[MeshObject], surface: MeshObject,
                            sample_pose_func: Callable[[MeshObject], None], max_tries: int = 100,
                            min_distance: float = 0.25, max_distance: float = 0.6,
                            up_direction: Optional[np.ndarray] = None,
                            check_all_bb_corners_over_surface: bool = True) -> List[MeshObject]:
    """ Samples objects poses on a surface.
    The objects are positioned slightly above the surface due to the non-axis aligned nature of used bounding boxes
    and possible non-alignment of the sampling surface (i.e. on the X-Y hyperplane, can be somewhat mitigated with
    precise "up_direction" value), which leads to the objects hovering slightly above the surface. So it is
    recommended to use the PhysicsPositioning module afterwards for realistically looking placements of objects on
    the sampling surface. If placing fails due to collisions, the object will be moved back to the intial pose
    and hidden from rendering.
    :param objects_to_sample: A list of objects that should be sampled above the surface.
    :param surface: Object to place objects_to_sample on.
    :param sample_pose_func: The function to use for sampling the pose of a given object.
    :param max_tries: Amount of tries before giving up on an object (deleting it) and moving to the next one.
    :param min_distance: Minimum distance to the closest other object from objects_to_sample. Center to center.
    :param max_distance: Maximum distance to the closest other object from objects_to_sample. Center to center.
    :param up_direction: Normal vector of the side of surface the objects should be placed on.
    :param check_all_bb_corners_over_surface: If this is True all bounding box corners have to be above the surface,
                                              else only the center of the object has to be above the surface
    :return: The list of placed objects.
    """
    if up_direction is None:
        up_direction = np.array([0., 0., 1.])
    else:
        up_direction /= np.linalg.norm(up_direction)
    surface_bounds = surface.get_bound_box()
    surface_height = max(up_direction.dot(corner) for corner in surface_bounds)
    # cache to fasten collision detection
    bvh_cache: Dict[str, mathutils.bvhtree.BVHTree] = {}
    placed_objects: List[MeshObject] = []
    for obj in objects_to_sample:
        print(f"Trying to put {obj.get_name()}")
        initial_pose = obj.get_local2world_mat()
        placed_successfully = False
        for i in range(max_tries):
            sample_pose_func(obj)
            # Remove bvh cache, as object has changed
            if obj.get_name() in bvh_cache:
                del bvh_cache[obj.get_name()]
            if not CollisionUtility.check_intersections(obj, bvh_cache, placed_objects, []):
                print("Collision detected, retrying!")
                continue
            if not _OnSurfaceSampler.check_above_surface(obj, surface, up_direction, check_all_bb_corners_over_surface):
                print("Not above surface, retrying!")
                continue
            _OnSurfaceSampler.drop(obj, up_direction, surface_height)
            # Remove bvh cache, as object has changed
            if obj.get_name() in bvh_cache:
                del bvh_cache[obj.get_name()]
            if not _OnSurfaceSampler.check_above_surface(obj, surface, up_direction, check_all_bb_corners_over_surface):
                print("Not above surface after drop, retrying!")
                continue
            if not _OnSurfaceSampler.check_spacing(obj, placed_objects, min_distance, max_distance):
                print("Bad spacing after drop, retrying!")
                continue
            if not CollisionUtility.check_intersections(obj, bvh_cache, placed_objects, []):
                print("Collision detected after drop, retrying!")
                continue
            print(f"Placed object \"{obj.get_name()}\" successfully at {obj.get_location()} after {i + 1} iterations!")
            placed_objects.append(obj)
            placed_successfully = True
            break
        if not placed_successfully:
            print(f"Giving up on {obj.get_name()}, hiding...")
            obj.hide(True)
            obj.set_local2world_mat(initial_pose)
    return placed_objects