diff --git a/python/examples/.drawing_from_input_drawing.py.swp b/python/examples/.drawing_from_input_drawing.py.swp
deleted file mode 100644
index 6cb848bc0e1c7948199fc53aa4c7507705b8526d..0000000000000000000000000000000000000000
Binary files a/python/examples/.drawing_from_input_drawing.py.swp and /dev/null differ
diff --git a/python/examples/drawing_from_input_drawing.py b/python/examples/drawing_from_input_drawing.py
index 31e7cb85677938ec2b636dc93331b7153c95c818..a25e7131a5c190773851ae3c427bd3ca16872e65 100644
--- a/python/examples/drawing_from_input_drawing.py
+++ b/python/examples/drawing_from_input_drawing.py
@@ -133,7 +133,10 @@ def getArgs():
     parser.add_argument('--board-height', type=float, \
             help="height of the board (in meters) the robot will write on", \
             default=0.4)
-    
+    parser.add_argument('--calibration', action=argparse.BooleanOptionalAction, \
+            help="whether you want to do calibration", default=True)
+    parser.add_argument('--n-calibration-tests', type=int, \
+            help="number of calibration tests you want to run", default=10)
 
     args = parser.parse_args()
     if args.gripper and args.simulation:
@@ -240,8 +243,15 @@ if __name__ == "__main__":
     # TODO: fix z axis in 2D to 3D path
     # TODO: make this an argument once the rest is OK
     path = path + np.array([0.0, 0.0, -0.0938])
+    # do calibration if specified
+    if args.calibration:
+        R, translation = calibratePlane(robot, args.n_calibrations_tests)
+    else:
+        # TODO: save this somewhere obviously
+        raise NotImplementedError("you gotta give me that R somehow, 'cos there is no default atm")
     # create a joint space trajectory based on the path
-    joint_trajectory = TODO
+    transf_body_to_board = pin.SE3(R, p)
+    joint_trajectory = clikCartesianPathIntoJointPath(path, robot, transf_body_to_board)
 
     # create DMP based on the trajectory
     dmp = DMP(joint_trajectory)
diff --git a/python/ur_simple_control/.managers.py.swp b/python/ur_simple_control/.managers.py.swp
deleted file mode 100644
index f697a6cfa3b4672a101af84fd21aa323b24910de..0000000000000000000000000000000000000000
Binary files a/python/ur_simple_control/.managers.py.swp and /dev/null differ
diff --git a/python/ur_simple_control/clik/.clik_point_to_point.py.swp b/python/ur_simple_control/clik/.clik_point_to_point.py.swp
deleted file mode 100644
index 44e6abddd15938267cc7d739954e6a98cbf46ee0..0000000000000000000000000000000000000000
Binary files a/python/ur_simple_control/clik/.clik_point_to_point.py.swp and /dev/null differ
diff --git a/python/ur_simple_control/clik/.clik_trajectory_following.py.swp b/python/ur_simple_control/clik/.clik_trajectory_following.py.swp
deleted file mode 100644
index 2712234ef37d6e72b70c659ecaa1c192a007dda8..0000000000000000000000000000000000000000
Binary files a/python/ur_simple_control/clik/.clik_trajectory_following.py.swp and /dev/null differ
diff --git a/python/ur_simple_control/clik/clik_trajectory_following.py b/python/ur_simple_control/clik/clik_trajectory_following.py
index dd2e244a7ade629dadb0cc54ebae1b34f8ee8cc5..f9469c1bb6524c33658bf814304f6977504aafbe 100644
--- a/python/ur_simple_control/clik/clik_trajectory_following.py
+++ b/python/ur_simple_control/clik/clik_trajectory_following.py
@@ -14,11 +14,20 @@ import sys
 #######################################################################
 #                    map the pixels to a 3D plane                     #
 #######################################################################
-# x-y start in top-left corner (natual for western latin script writing)
-# and then it's natural to have the z axis pointing out of the board
-# TODO but that's not a right-handed frame lmao, change that where it should be
-# NOTE: this might as well be in the util file, but whatever for now, it will be done
-#       once it will actually be needed elsewhere
+"""
+map2DPathTo3DPlane
+--------------------
+assumptions:
+- origin in top-left corner (natual for western latin script writing)
+- x goes right (from TCP)
+- z will go away from the board
+- y just completes the right-hand frame
+TODO: RIGHT NOW we don't have a right-handed frame lmao, change that where it should be
+NOTE: this function as well be in the util or drawing file, but whatever for now, it will be done
+      once it will actually be needed elsewhere
+Returns a 3D path appropriately scaled, and placed into the first quadrant
+of the x-y plane of the body-frame (TODO: what is the body frame if we're general?)
+"""
 def map2DPathTo3DPlane(path_2D, width, height):
     z = np.zeros((len(path),1))
     path_3D = np.hstack((path,z))
@@ -32,118 +41,62 @@ def map2DPathTo3DPlane(path_2D, width, height):
     return path
 
 
-# now the path is appropriately scaled and in the first quadrant
-# of the x-y plane of the body-frame
 # we now need a transformation from the body frame to the board
 # such that the first quadrant of the x-y plane is 
 # in the bottom left corner of the board (obvious solution)
-
-# sample stuff
-#translation_body_to_board = np.array([0.2, 0.2, 0.3])
-## here let's assume it's just rotated around x (it's not ofc)
-#rot_mat_body_to_board = np.array([[1.0, 0.0, 0.0],
-#                                  [0.0, 0.0, 1.0],
-#                                  [0.0, 1.0, 0.0]])
-#transf_body_to_board = pin.SE3(rot_mat_body_to_board, translation_body_to_board)
-
-# real stuff
-# TODO measure AND AUTOMATICALLY FIND/CALIBRATE
-# TODO write some algorithm to do this automatically, this is asinine
-# (in pin coordinates, real ones are [-x, -y, z])
-# UPPER LEFT POINT 
-#  0.1065, 0.7083, 0.6362, -2.6137, -0.0248, -0.0033
-#rpy = np.array([-2.6137, -0.0248, -0.0033])
-#R = pin.rpy.rpyToMatrix(rpy)
-#print(R)
-#R = np.array([[ 1.        ,  0.        , 0.14570094],
-#       [ 0.        , -0.70647877, 0.69257417],
-#       [ 0.        , -0.69257417,  -0.70647877]])
-#R = np.array([[ 1.        ,  0.        , 0.1496001 ],
-#       [ 0.        , -0.71010996, 0.68801429],
-#       [ 0.        , -0.68801429,  -0.71010996]])
-#R = np.array([[1.,         0.,         0.03236534],
-#[ 0.,         -0.82404727,  0.56559577],
-#[ 0. ,        -0.56559577, -0.82404727]])
-#R = np.array([[1.,         0. ,        0.02588808],
-#[ 0.,         -0.82340404 , 0.56686471],
-#[ 0. ,        -0.56686471 ,-0.82340404]])
-
-R = np.array([[1.        , 0.       ,  0.02562061],
-[ 0.        , -0.82215985 , 0.56867984],
-[ 0.        , -0.56867984 ,-0.82215985]])
-
-
-
-print(R)
-p = np.array([0.1065, 0.7083, 0.6362])
-transf_body_to_board = pin.SE3(R, p)
-
-# offset in z
-# very close
-#path = path + np.array([0.0, 0.0, -0.0238])
-path = path + np.array([0.0, 0.0, -0.0938])
-#path = path + np.array([0.0, 0.0, -0.1248])
-
-#path = path + np.array([0.0, 0.2, -0.1438])
-for i in range(len(path)):
-    path[i] = transf_body_to_board.act(path[i])
-print(path)
-#######################################################################
-#                      STEP 2: clik that path                         #
-#######################################################################
-
-urdf_path_relative = "../../../robot_descriptions/urdf/ur5e_with_robotiq_hande.urdf"
-urdf_path_absolute = os.path.abspath(urdf_path_relative)
-mesh_dir = "../../../robot_descriptions/"
-mesh_dir_absolute = os.path.abspath(mesh_dir)
-
-model, data = get_model(urdf_path_absolute, mesh_dir_absolute)
-# clik it
-JOINT_ID = 6
-eps = 10**-2
-dt = 0.01
-# skip inital pos tho
-#q = np.array([-2.256,-1.408,0.955,-1.721,-1.405,-0.31, 0.0, 0.0])
-#q = np.array([-2.014, -1.469, 1.248, -1.97, -1.366, -0.327, 0.0, 0.0])
-q = np.array([1.584, -1.859, -0.953, -1.309, 1.578, -0.006, 0.0, 0.0])
-INIT_ITER = 10000
-n_iter = INIT_ITER
-RUNNING_ITER = 1000
-qs = []
-for goal in path:
-    # there's no orientation error (you do need it lmao)
-    #Mgoal = pin.SE3(np.zeros((3,3)), goal)
-    Mgoal = pin.SE3(R, goal)
-    for i in range(n_iter):
-        pin.forwardKinematics(model, data, q)
-        SEerror = data.oMi[JOINT_ID].actInv(Mgoal)
-        err_vector = pin.log6(SEerror).vector 
-        if np.linalg.norm(err_vector) < eps:
-            if not n_iter == INIT_ITER:
-                print("converged in", i)
-                break
-        J = pin.computeJointJacobian(model, data, q, JOINT_ID)
-        v = J.T @ np.linalg.inv(J @ J.T + np.eye(J.shape[0], J.shape[0]) * 10**-2) @ err_vector
-        #v = J.T @ err_vector
-        q = pin.integrate(model, q, v * dt)
-        if (not n_iter == INIT_ITER) and (i % 10 == 0):
-            qs.append(q[:6])
-
-    # just skipping the first run with one stone
-    if n_iter == INIT_ITER:
-        n_iter = RUNNING_ITER
-    else:
-        if i == RUNNING_ITER-1:
-            print("DID NOT CONVERGE")
-
-#######################################################################
-#                       STEP 3: save joint path                       #
-#######################################################################
-qs = np.array(qs)
-# let's assume it takes 10 seconds
-t = np.linspace(0, 10, len(qs)).reshape((len(qs),1))
-timed_qs = np.hstack((t, qs))
-np.savetxt("../new_traj.csv", timed_qs, delimiter=',', fmt='%.5f')
+def clikCartesianPathIntoJointPath(path, robot, transf_body_to_plane):
+    transf_body_to_plane = pin.SE3(R, p)
+
+    for i in range(len(path)):
+        path[i] = transf_body_to_plane.act(path[i])
+    print(path)
+
+    # TODO: finish this
+    # - pass clik alg as argument
+    # - remove magic numbers
+    # - give output in the right format
+    # skip inital pos tho
+    #q = np.array([-2.256,-1.408,0.955,-1.721,-1.405,-0.31, 0.0, 0.0])
+    #q = np.array([-2.014, -1.469, 1.248, -1.97, -1.366, -0.327, 0.0, 0.0])
+    q = np.array([1.584, -1.859, -0.953, -1.309, 1.578, -0.006, 0.0, 0.0])
+    INIT_ITER = 10000
+    n_iter = INIT_ITER
+    RUNNING_ITER = 1000
+    qs = []
+    for goal in path:
+        # there's no orientation error (you do need it lmao)
+        #Mgoal = pin.SE3(np.zeros((3,3)), goal)
+        Mgoal = pin.SE3(R, goal)
+        for i in range(n_iter):
+            pin.forwardKinematics(model, data, q)
+            SEerror = data.oMi[JOINT_ID].actInv(Mgoal)
+            err_vector = pin.log6(SEerror).vector 
+            if np.linalg.norm(err_vector) < eps:
+                if not n_iter == INIT_ITER:
+                    print("converged in", i)
+                    break
+            J = pin.computeJointJacobian(model, data, q, JOINT_ID)
+            v = J.T @ np.linalg.inv(J @ J.T + np.eye(J.shape[0], J.shape[0]) * 10**-2) @ err_vector
+            #v = J.T @ err_vector
+            q = pin.integrate(model, q, v * dt)
+            if (not n_iter == INIT_ITER) and (i % 10 == 0):
+                qs.append(q[:6])
+
+        # just skipping the first run with one stone
+        if n_iter == INIT_ITER:
+            n_iter = RUNNING_ITER
+        else:
+            if i == RUNNING_ITER-1:
+                print("DID NOT CONVERGE")
+
+    #######################################################################
+    #                       STEP 3: save joint path                       #
+    #######################################################################
+    qs = np.array(qs)
+    # let's assume it takes 10 seconds
+    t = np.linspace(0, 10, len(qs)).reshape((len(qs),1))
+    timed_qs = np.hstack((t, qs))
+    np.savetxt("../new_traj.csv", timed_qs, delimiter=',', fmt='%.5f')
 
 
 
diff --git a/python/ur_simple_control/managers.py b/python/ur_simple_control/managers.py
index e55168d9b946f72bfdb9b9aa02b409876cc90456..c7f17cfac856e0881e5fa5cbf8bbcf7073934819 100644
--- a/python/ur_simple_control/managers.py
+++ b/python/ur_simple_control/managers.py
@@ -193,6 +193,9 @@ class RobotManager:
     # just pass all of the arguments here and store them as is
     # so as to minimize the amount of lines.
     # might be changed later if that seems more appropriate
+    # TODO: put the end to all modes not compatible with control
+    #       like freedrive or forcemode or whatever.
+    #       you shouldn't care about previous states
     def __init__(self, args):
         self.args = args
         self.pinocchio_only = args.pinocchio_only
diff --git a/python/ur_simple_control/util/.calib_board_hacks.py.swp b/python/ur_simple_control/util/.calib_board_hacks.py.swp
deleted file mode 100644
index 654ccdea21df40efc4deb0f2c418c7e9e9736fc5..0000000000000000000000000000000000000000
Binary files a/python/ur_simple_control/util/.calib_board_hacks.py.swp and /dev/null differ
diff --git a/python/ur_simple_control/util/calib_board_hacks.py b/python/ur_simple_control/util/calib_board_hacks.py
index 84539ec3a31e7f47b859b4a996507c5b35274d89..0d8acd5a5daacbf59c470dfb5fa698a953f28525 100644
--- a/python/ur_simple_control/util/calib_board_hacks.py
+++ b/python/ur_simple_control/util/calib_board_hacks.py
@@ -1,7 +1,5 @@
-# TODO:
-# ideally make this a continuous update process
-# where you use the previous estimate to try to hit the board at the
-# right orientation, thereby making the estimate more precise
+# TODO: make prints prettier (remove \ in code, and also
+# make it look good in the terminal)
 
 import pinocchio as pin
 import numpy as np
@@ -9,15 +7,24 @@ import sys
 import time
 import copy
 from ur_simple_control.managers import RobotManager
+from ur_simple_control.util.freedrive import freedrive
 
 """
+general
+-----------
 Estimate a plane by making multiple contacts with it. 
 You need to start with a top left corner of it,
 and you thus don't need to find an offset (you have to know it in advance).
 TODO: test and make sure the above statement is in fact correct.
 Thus the offset does not matter, we only need the angle,
 i.e. the normal vector to the plane.
-Returns R because that's what I wan a 
+Returns R because that's what's needed to construct the hom transf. mat.
+"""
+
+"""
+fitNormalVector
+----------------
+classic least squares fit
 """
 def fitNormalVector(positions):
     positions = np.array(positions)
@@ -31,12 +38,16 @@ def fitNormalVector(positions):
     for p in positions:
         print("cdot", p @ n)
 
-# constuct a frame around the found normal vector
-# we just assume the x axis is parallel with the robot's x axis
-# this is of course completly arbitrary, so
-# TODO fix the fact that you just assume the x axis
-# or write down why you don't need it (i'm honestly not sure atm, but it is late)
-def constructFrameFromNormalVector(R_intial_estimate, n):
+"""
+constructFrameFromNormalVector
+----------------------------------
+constuct a frame around the found normal vector
+we just assume the x axis is parallel with the robot's x axis
+this is of course completly arbitrary, so
+TODO fix the fact that you just assume the x axis
+or write down why you don't need it (i'm honestly not sure atm, but it is late)
+"""
+def constructFrameFromNormalVector(R_initial_estimate, n):
     z_new = n
     x_new = np.array([1.0, 0.0, 0.0])
     y_new = np.cross(x_new, z_new)
@@ -51,35 +62,71 @@ def constructFrameFromNormalVector(R_intial_estimate, n):
     #z_new[0] = np.abs(z_new[0])
     #z_new[1] = np.abs(z_new[1])
     #z_new[2] = np.abs(z_new[2]) * -1
-    y_new[0] = np.abs(y_new[0])
-    y_new[1] = np.abs(y_new[1])
-    y_new[2] = np.abs(y_new[2])
-    z_new[0] = np.abs(z_new[0])
-    z_new[1] = np.abs(z_new[1])
-    z_new[2] = np.abs(z_new[2])
     # y is good 'cos it was obtained with a cross
     R = np.hstack((x_new.reshape((3,1)), y_new.reshape((3,1))))
     R = np.hstack((R, z_new.reshape((3,1))))
     # now ensure all the signs are the signs that you want,
     # which we get from the initial estimate (which can not be that off)
-    for i in range(R.shape[0]):
-        for j in range(R.shape[1]):
-            # TODO ensure all signs are the same
-            continue
-
+    R = np.abs(R) * np.sign(R_initial_estimate)
 
     print('rot mat to new frame:')
     print(*R, sep=',\n')
     return R
 
+"""
+handleUserToHandleTCPPose
+-----------------------------
+1. tell the user what to do with prints, namely where to put the end-effector
+  to both not break things and also actually succeed
+2. start freedrive
+3. use some keyboard input [Y/n] as a blocking call,
+4. release the freedrive and then start doing the calibration process
+"""
+def handleUserToHandleTCPPose(robot):
+    print("Whatever code you ran wants you to calibrate the plane on which you will be doing\
+            your things. Put the end-effector at the top left corner of the plane \
+            where you'll be doing said things. \n
+            Make sure the orientation is reasonably correct as that will be \
+            used as the initial estimate of the orientation, \
+            which is what you will get as an output from this calibration procedure.\
+            The end-effector will go down (it's TCP z pozitive direction) and touch the thing\
+            the number of times you specified (if you are not aware of this, check the\
+            arguments of the program you ran.\n \
+            The robot will now enter freedrive mode so that you can manually put\
+            the end-effector where it's supposed to be.\n \
+            When you did it, press 'Y', or press 'n' to exit.")
+    while True:
+        answer = input("Ready to calibrate or exit? [Y/n]")
+        if answer == 'n' or answer == 'N':
+            print("The whole program will exit. Change the argument to --no-calibrate or \
+                    change code that lead you here.")
+            exit()
+        elif answer == 'y' or answer == 'Y':
+            print("The robot will now enter freedrive mode. Put the end-effector to the \
+                    top left corner of your plane and mind the orientation.")
+            break
+        else:
+            print("Whatever you typed in is neither 'Y' nor 'n'. Give it to me straight cheif!")
+            continue
+    freedrive()
+    while True:
+        answer = input("You got the end-effector in the correct pose and are ready \
+                to start calibrating or do you want to exit? [Y/n]")
+        if answer == 'n' or answer == 'N':
+            print("The whole program will exit. Goodbye!")
+            exit()
+        elif answer == 'y' or answer == 'Y':
+            print("Calibration about to start. Have your hand on the big red stop button!")
+            break
+        else:
+            print("Whatever you typed in is neither 'Y' nor 'n'. Give it to me straight cheif!")
+            continue
+    robot.rtde_control.endFreedriveMode()
+
 
 def calibratePlane(robot, n_tests):
-    # TODO: 
-    # - tell the user what to do with prints, namely where to put the end-effector
-    #   to both not break things and also actually succeed
-    # - start freedrive
-    # - use some keyboard input [Y/n] as a blocking call,
-    #   release the freedrive and then start doing the calibration process
+    handleUserToHandleTCPPose(robot)
+
     init_pose = robot.rtde_receive.getActualTCPPose()
     new_pose = copy.deepcopy(init_pose)
 
@@ -99,47 +146,51 @@ def calibratePlane(robot, n_tests):
     q = robot.getQ()
     pin.forwardKinematics(robot.model, robot.data, q)
     # this apsolutely has to be deepcopied aka copy-constructed
-    R_intial_estimate = copy.deepcopy(robot.data.oMi[robot.JOINT_ID].rotation)
+    R_initial_estimate = copy.deepcopy(robot.data.oMi[robot.JOINT_ID].rotation)
+    translation = copy.deepcopy(robot.data.oMi[robot.JOINT_ID].translation)
 
     positions = []
     for i in range(n_tests):
-        rtde_control.moveUntilContact(speed)
-        q = rtde_receive.getActualQ()
+        robot.rtde_control.moveUntilContact(speed)
+        q = robot.rtde_receive.getActualQ()
         q.append(0.0)
         q.append(0.0)
-        pin.forwardKinematics(model, data, np.array(q))
-        print("pin:", *data.oMi[6].translation.round(4), *pin.rpy.matrixToRpy(data.oMi[6].rotation).round(4))
-        print("ur5:", *np.array(rtde_receive.getActualTCPPose()).round(4))
-        positions.append(copy.deepcopy(data.oMi[6].translation))
+        pin.forwardKinematics(robot.model, robot.data, np.array(q))
+        print("pin:", *robot.data.oMi[6].translation.round(4), \
+                *pin.rpy.matrixToRpy(robot.data.oMi[6].rotation).round(4))
+        print("ur5:", *np.array(robot.rtde_receive.getActualTCPPose()).round(4))
+        positions.append(copy.deepcopy(robot.data.oMi[6].translation))
         if i < n_tests -1:
-            current_pose = rtde_receive.getActualTCPPose()
+            current_pose = robot.rtde_receive.getActualTCPPose()
             new_pose = copy.deepcopy(current_pose)
             # go back up (assuming top-left is highest incline)
             # TODO: make this assumption an argument, or print it at least
             new_pose[2] = init_pose[2]
-            rtde_control.moveL(new_pose)
+            robot.rtde_control.moveL(new_pose)
             new_pose[0] = init_pose[0] + np.random.random() * 0.3
             new_pose[1] = init_pose[1] - np.random.random() * 0.2
-            rtde_control.moveL(new_pose)
+            robot.rtde_control.moveL(new_pose)
             # fix orientation
-            rpy = pin.rpy.matrixToRpy(R)
+            rpy = pin.rpy.matrixToRpy(R_initial_estimate)
             print("rpy", rpy)
             if rpy[0] > 0.0:
                 rpy[0] = rpy[0] - 2*np.pi
-            # who knows if this is ok
+            # TODO: ensure these rpy's make sense (no weird trig messing it up)
             new_pose[3] = rpy[0]
             new_pose[4] = rpy[1]
             new_pose[5] = rpy[2]
-            rtde_control.moveL(new_pose)
+            robot.rtde_control.moveL(new_pose)
         n = fitNormalVector(positions)
-        R = constructFrameFromNormalVector(n)
-            
+        R = constructFrameFromNormalVector(R_initial_estimate, n)
 
     current_pose = rtde_receive.getActualTCPPose()
     new_pose = copy.deepcopy(current_pose)
     new_pose[2] = init_pose[2]
-    rtde_control.moveL(new_pose)
-    rtde_control.moveL(init_pose)
+    robot.rtde_control.moveL(new_pose)
+    robot.rtde_control.moveL(init_pose)
+    
+    print('also, the translation vector is:', translation)
+    return R, translation
 
 if __name__ == "__main__":
     robot = RobotManager()
diff --git a/python/ur_simple_control/util/freedrive.py b/python/ur_simple_control/util/freedrive.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c89d701c31e87193b01127d667a2529638170b5
--- /dev/null
+++ b/python/ur_simple_control/util/freedrive.py
@@ -0,0 +1,24 @@
+import pinocchio as pin
+import numpy as np
+import time
+from ur_simple_control.managers import RobotManager
+
+def freedrive(robot):
+    robot.rtde_control.freedriveMode()
+
+    while True:
+        q = robot.rtde_receive.getActualQ()
+        q.append(0.0)
+        q.append(0.0)
+        pin.forwardKinematics(robot.model, robot.data, np.array(q))
+        print(robot.data.oMi[6])
+        print("pin:", *robot.data.oMi[6].translation.round(4), \
+                *pin.rpy.matrixToRpy(robot.data.oMi[6].rotation).round(4))
+        print("ur5:", *np.array(robot.rtde_receive.getActualTCPPose()).round(4))
+        time.sleep(0.005)
+
+if __name__ == "__main__":
+    robot = RobotManager()
+    freedrive()
+    # TODO possibly you want to end freedrive here as well.
+    # or end everything imaginable in the signal handler