diff --git a/examples/drawing/drawing_ctrl_loop.py b/examples/drawing/drawing_ctrl_loop.py
new file mode 100644
index 0000000000000000000000000000000000000000..432db383ba15a059816c02733f760e64d5b0b182
--- /dev/null
+++ b/examples/drawing/drawing_ctrl_loop.py
@@ -0,0 +1,146 @@
+from smc.robots.interfaces.force_torque_sensor_interface import (
+    ForceTorqueOnSingleArmWrist,
+)
+from smc.control.dmp.dmp import (
+    DMP,
+    NoTC,
+    TCVelAccConstrained,
+)
+from smc.control.cartesian_space import (
+    moveL,
+    compliantMoveL,
+)
+from smc.control import ControlLoopManager
+
+import pinocchio as pin
+import numpy as np
+from argparse import Namespace
+from functools import partial
+from collections import deque
+
+
+def controlLoopWriting(
+    args: Namespace,
+    robot: ForceTorqueOnSingleArmWrist,
+    dmp: DMP,
+    tc: NoTC | TCVelAccConstrained,
+    i: int,
+    past_data: dict[str, deque[np.ndarray]],
+) -> tuple[bool, dict[str, np.ndarray], dict[str, np.ndarray]]:
+    """
+    controlLoopWriting
+    -----------------------
+    dmp reference on joint path + compliance
+    """
+    breakFlag = False
+    save_past_dict = {}
+    log_item = {}
+    dmp.step(robot.dt)
+    # temporal coupling step
+    tau_dmp = dmp.tau + tc.update(dmp, robot.dt) * robot.dt
+    dmp.set_tau(tau_dmp)
+    q = robot.q
+    Z = np.diag(np.array([0.0, 0.0, 1.0, 0.5, 0.5, 0.0]))
+
+    wrench = robot.wrench
+    save_past_dict["wrench"] = wrench.copy()
+    # rolling average
+    # wrench = np.average(np.array(past_data['wrench']), axis=0)
+
+    # first-order low pass filtering instead
+    # beta is a smoothing coefficient, smaller values smooth more, has to be in [0,1]
+    # wrench = args.beta * wrench + (1 - args.beta) * past_data['wrench'][-1]
+    wrench = args.beta * wrench + (1 - args.beta) * np.average(
+        np.array(past_data["wrench"]), axis=0
+    )
+
+    wrench = Z @ wrench
+    J = robot.getJacobian()
+    # get joint
+    tau = J.T @ wrench
+    tau = tau.reshape((-1, 1))
+    # compute control law:
+    # - feedforward the velocity and the force reading
+    # - feedback the position
+    v_cmd = dmp.vel + args.kp * (dmp.pos - q.reshape((-1, 1))) + args.alpha * tau
+    v_cmd = v_cmd.reshape((-1,))
+
+    # tau0 is the minimum time needed for dmp
+    # 500 is the frequency
+    # so we need tau0 * 500 iterations minimum
+    if (np.linalg.norm(dmp.vel) < 0.01) and (i > int(args.tau0 * 500)):
+        breakFlag = True
+        v_cmd = np.zeros(robot.nv)
+
+    # immediatelly stop if something weird happened (some non-convergence)
+    if np.isnan(v_cmd[0]):
+        print("GO NAN FROM INTO VEL_CMD!!! EXITING!!")
+        breakFlag = True
+        v_cmd = np.zeros(robot.nv)
+
+    robot.sendVelocityCommand(v_cmd)
+    log_item["qs"] = robot.q
+    log_item["dmp_qs"] = dmp.pos.reshape((6,))
+    log_item["dqs"] = robot.v
+    log_item["dmp_dqs"] = dmp.vel.reshape((6,))
+    log_item["wrench"] = wrench.reshape((6,))
+    log_item["tau"] = tau.reshape((6,))
+
+    return breakFlag, save_past_dict, log_item
+
+
+def write(
+    joint_trajectory: np.ndarray,
+    args: Namespace,
+    robot: ForceTorqueOnSingleArmWrist,
+) -> None:
+    dmp = DMP(joint_trajectory, a_s=1.0)
+    if not args.temporal_coupling:
+        tc = NoTC()
+    else:
+        v_max_ndarray = np.ones(robot.nq) * robot._max_v
+        a_max_ndarray = np.ones(robot.nq) * args.acceleration
+        tc = TCVelAccConstrained(
+            args.gamma_nominal, args.gamma_a, v_max_ndarray, a_max_ndarray, args.eps_tc
+        )
+
+    print("going to starting write position")
+    dmp.step(1 / 500)
+    first_q = dmp.pos.reshape((6,))
+    # move to initial pose
+    T_w_goal = robot.computeT_w_e(first_q)
+    # start a bit above
+    go_away_from_plane_transf = pin.SE3.Identity()
+    go_away_from_plane_transf.translation[2] = -1 * args.mm_into_board
+    T_w_goal = T_w_goal.act(go_away_from_plane_transf)
+    if not args.board_wiping:
+        compliantMoveL(T_w_goal, args, robot)
+    else:
+        moveL(args, robot, T_w_goal)
+
+    save_past_dict = {
+        "wrench": np.zeros(6),
+    }
+    # here you give it it's initial value
+    log_item = {
+        "qs": np.zeros(robot.nq),
+        "dmp_qs": np.zeros(robot.nq),
+        "dqs": np.zeros(robot.nv),
+        "dmp_dqs": np.zeros(robot.nq),
+        "wrench": np.zeros(6),
+        "tau": np.zeros(robot.nv),
+    }
+    # moveJ(args, robot, dmp.pos.reshape((6,)))
+    controlLoop = partial(controlLoopWriting, args, robot, dmp, tc)
+    loop_manager = ControlLoopManager(
+        robot, controlLoop, args, save_past_dict, log_item
+    )
+    # and now we can actually run
+    loop_manager.run()
+
+    print("move a bit back")
+    T_w_e = robot.T_w_e
+    go_away_from_plane_transf = pin.SE3.Identity()
+    go_away_from_plane_transf.translation[2] = -0.1
+    goal = T_w_e.act(go_away_from_plane_transf)
+    compliantMoveL(goal, args, robot)
diff --git a/examples/drawing/drawing_from_input_drawing.py b/examples/drawing/drawing_from_input_drawing.py
index 4f9a66a38cc0a7b297dd21b418769706f10a1d3f..a6cd2d9e666ba1ce4e454e59e0e16275e48dd06e 100644
--- a/examples/drawing/drawing_from_input_drawing.py
+++ b/examples/drawing/drawing_from_input_drawing.py
@@ -1,27 +1,18 @@
-from utils import getArgs
+from utils import getArgsForDrawing
 from get_marker import getMarker
 from find_marker_offset import findMarkerOffset
+from drawing_ctrl_loop import write
 
 from smc.robots.interfaces.force_torque_sensor_interface import (
     ForceTorqueOnSingleArmWrist,
 )
 from smc.util.draw_path import drawPath
-from smc.control.dmp.dmp import (
-    DMP,
-    NoTC,
-    TCVelAccConstrained,
-)
 from smc.control.cartesian_space.ik_solvers import getIKSolver
-from smc.control.cartesian_space import (
-    moveL,
-    compliantMoveL,
-)
 from smc.path_generation.cartesian_to_joint_space_path_mapper import (
     clikCartesianPathIntoJointPath,
 )
 from smc.util.map2DPathTo3DPlane import map2DPathTo3DPlane
 from smc import getRobotFromArgs
-from smc.control.control_loop_manager import ControlLoopManager
 from smc.util.calib_board_hacks import (
     calibratePlane,
 )
@@ -29,141 +20,7 @@ from smc.util.calib_board_hacks import (
 import pinocchio as pin
 import numpy as np
 import matplotlib
-from argparse import Namespace
 import pickle
-from functools import partial
-from collections import deque
-
-#######################################################################
-#                            arguments                                #
-#######################################################################
-
-
-def controlLoopWriting(
-    args: Namespace,
-    robot: ForceTorqueOnSingleArmWrist,
-    dmp: DMP,
-    tc,
-    i: int,
-    past_data: dict[str, deque[np.ndarray]],
-) -> tuple[bool, dict[str, np.ndarray], dict[str, np.ndarray]]:
-    """
-    controlLoopWriting
-    -----------------------
-    dmp reference on joint path + compliance
-    """
-    breakFlag = False
-    save_past_dict = {}
-    log_item = {}
-    dmp.step(robot.dt)
-    # temporal coupling step
-    tau_dmp = dmp.tau + tc.update(dmp, robot.dt) * robot.dt
-    dmp.set_tau(tau_dmp)
-    q = robot.q
-    Z = np.diag(np.array([0.0, 0.0, 1.0, 0.5, 0.5, 0.0]))
-
-    wrench = robot.wrench
-    save_past_dict["wrench"] = wrench.copy()
-    # rolling average
-    # wrench = np.average(np.array(past_data['wrench']), axis=0)
-
-    # first-order low pass filtering instead
-    # beta is a smoothing coefficient, smaller values smooth more, has to be in [0,1]
-    # wrench = args.beta * wrench + (1 - args.beta) * past_data['wrench'][-1]
-    wrench = args.beta * wrench + (1 - args.beta) * np.average(
-        np.array(past_data["wrench"]), axis=0
-    )
-
-    wrench = Z @ wrench
-    J = robot.getJacobian()
-    # get joint
-    tau = J.T @ wrench
-    tau = tau.reshape((-1, 1))
-    # compute control law:
-    # - feedforward the velocity and the force reading
-    # - feedback the position
-    # TODO solve this q[:6] bs (clean it up)
-    vel_cmd = dmp.vel + args.kp * (dmp.pos - q.reshape((-1, 1))) + args.alpha * tau
-    robot.sendVelocityCommand(vel_cmd)
-
-    # tau0 is the minimum time needed for dmp
-    # 500 is the frequency
-    # so we need tau0 * 500 iterations minimum
-    if (np.linalg.norm(dmp.vel) < 0.01) and (i > int(args.tau0 * 500)):
-        breakFlag = True
-    # immediatelly stop if something weird happened (some non-convergence)
-    if np.isnan(vel_cmd[0]):
-        print("GO NAN FROM INTO VEL_CMD!!! EXITING!!")
-        breakFlag = True
-
-    # log what you said you'd log
-    # TODO fix the q6 situation (hide this)
-    log_item["qs"] = robot.q
-    log_item["dmp_qs"] = dmp.pos.reshape((6,))
-    log_item["dqs"] = robot.v
-    log_item["dmp_dqs"] = dmp.vel.reshape((6,))
-    log_item["wrench"] = wrench.reshape((6,))
-    log_item["tau"] = tau.reshape((6,))
-
-    return breakFlag, save_past_dict, log_item
-
-
-def write(
-    joint_trajectory: np.ndarray,
-    args: Namespace,
-    robot: ForceTorqueOnSingleArmWrist,
-) -> None:
-    # create DMP based on the trajectory
-    dmp = DMP(joint_trajectory, a_s=1.0)
-    if not args.temporal_coupling:
-        tc = NoTC()
-    else:
-        v_max_ndarray = np.ones(robot.nq) * robot._max_v
-        a_max_ndarray = np.ones(robot.nq) * args.acceleration
-        tc = TCVelAccConstrained(
-            args.gamma_nominal, args.gamma_a, v_max_ndarray, a_max_ndarray, args.eps_tc
-        )
-
-    print("going to starting write position")
-    dmp.step(1 / 500)
-    first_q = dmp.pos.reshape((6,))
-    # move to initial pose
-    T_w_goal = robot.computeT_w_e(first_q)
-    # start a bit above
-    go_away_from_plane_transf = pin.SE3.Identity()
-    go_away_from_plane_transf.translation[2] = -1 * args.mm_into_board
-    T_w_goal = T_w_goal.act(go_away_from_plane_transf)
-    if not args.board_wiping:
-        compliantMoveL(T_w_goal, args, robot)
-    else:
-        moveL(args, robot, T_w_goal)
-
-    save_past_dict = {
-        "wrench": np.zeros(6),
-    }
-    # here you give it it's initial value
-    log_item = {
-        "qs": np.zeros(robot.nq),
-        "dmp_qs": np.zeros(robot.nq),
-        "dqs": np.zeros(robot.nv),
-        "dmp_dqs": np.zeros(robot.nq),
-        "wrench": np.zeros(6),
-        "tau": np.zeros(robot.nv),
-    }
-    # moveJ(args, robot, dmp.pos.reshape((6,)))
-    controlLoop = partial(controlLoopWriting, args, robot, dmp, tc)
-    loop_manager = ControlLoopManager(
-        robot, controlLoop, args, save_past_dict, log_item
-    )
-    # and now we can actually run
-    loop_manager.run()
-
-    print("move a bit back")
-    T_w_e = robot.T_w_e
-    go_away_from_plane_transf = pin.SE3.Identity()
-    go_away_from_plane_transf.translation[2] = -0.1
-    goal = T_w_e.act(go_away_from_plane_transf)
-    compliantMoveL(goal, args, robot)
 
 
 if __name__ == "__main__":
@@ -171,7 +28,7 @@ if __name__ == "__main__":
     #######################################################################
     #                           software setup                            #
     #######################################################################
-    args = getArgs()
+    args = getArgsForDrawing()
     if not args.board_wiping:
         assert args.mm_into_board > 0.0 and args.mm_into_board < 5.0
     args.mm_into_board = args.mm_into_board / 1000
@@ -183,9 +40,7 @@ if __name__ == "__main__":
         rand_pertb = np.random.random(robot.nq) * 0.1
         if args.robot != "ur5e":
             raise NotImplementedError
-        robot._q = (
-            np.array([1.32, -1.40, -1.27, -1.157, 1.76, -0.238, 0.0, 0.0]) + rand_pertb
-        )
+        robot._q = np.array([1.32, -1.40, -1.27, -1.157, 1.76, -0.238]) + rand_pertb
         robot._step()
 
     #######################################################################
@@ -200,33 +55,28 @@ if __name__ == "__main__":
         matplotlib.use("qtagg")
     else:
         if not args.board_wiping:
-            pixel_path_file_path = "./path_in_pixels.csv"
+            pixel_path_file_path = "./parameters/path_in_pixels.csv"
             pixel_path = np.genfromtxt(pixel_path_file_path, delimiter=",")
         else:
-            pixel_path_file_path = "./wiping_path.csv_save"
+            pixel_path_file_path = "./parameters/wiping_path.csv_save"
             pixel_path = np.genfromtxt(pixel_path_file_path, delimiter=",")
     # do calibration if specified
-    if args.calibration:
+    if args.calibrate_plane:
         plane_pose, q_init = calibratePlane(
             args, robot, args.board_width, args.board_height, args.n_calibration_tests
         )
         print("finished calibration")
     else:
         print("using existing plane calibration")
-        file = open("./plane_pose.pickle_save", "rb")
+        file = open("./parameters/plane_pose.pickle_save", "rb")
         plane_calib_dict = pickle.load(file)
         file.close()
         plane_pose = plane_calib_dict["plane_top_left_pose"]
         q_init = plane_calib_dict["q_init"]
-        # stupid fix dw about this it's supposed to be 0.0 on the gripper
-        # because we don't use that actually
-        q_init[-1] = 0.0
-        q_init[-2] = 0.0
 
     # make the path 3D
     path_points_3D = map2DPathTo3DPlane(pixel_path, args.board_width, args.board_height)
     if args.pick_up_marker:
-        # raise NotImplementedError("sorry")
         getMarker(args, robot)
 
     # marker moves between runs, i change markers etc,
@@ -250,7 +100,7 @@ if __name__ == "__main__":
         )
 
     # create a joint space trajectory based on the 3D path
-    if args.draw_new or args.calibration or args.find_marker_offset:
+    if args.draw_new or args.calibrate_plane or args.find_marker_offset:
         path = []
         for i in range(len(path_points_3D)):
             path_pose = pin.SE3.Identity()
@@ -264,9 +114,9 @@ if __name__ == "__main__":
         it has to look reasonable, otherwise we can't run it!
         """
             )
-        clikController = getIKSolver(args, robot)
+        ik_solver = getIKSolver(args, robot)
         joint_trajectory = clikCartesianPathIntoJointPath(
-            args, robot, path, clikController, q_init, plane_pose
+            ik_solver, path, q_init, args.tau0, args, robot
         )
         if args.viz_test_path:
             answer = input("did the movement of the manipulator look reasonable? [Y/n]")
@@ -278,7 +128,7 @@ if __name__ == "__main__":
         else:
             answer = True
     else:
-        joint_trajectory_file_path = "./joint_trajectory.csv"
+        joint_trajectory_file_path = "./parameters/joint_trajectory.csv"
         joint_trajectory = np.genfromtxt(joint_trajectory_file_path, delimiter=",")
 
     if answer:
diff --git a/examples/drawing/get_marker.py b/examples/drawing/get_marker.py
index aa6bd57be7628cb0be3d97249c76cf8b8468b168..67a0d986f8dd6e12aaa224ae6437aa0b81ba407f 100644
--- a/examples/drawing/get_marker.py
+++ b/examples/drawing/get_marker.py
@@ -16,7 +16,17 @@ def getMarker(args: Namespace, robot: SingleArmInterface) -> None:
     if not, generate a different joint trajectory for your situation.
     """
     # load traj
-    file = open("./data/from_writing_to_handover.pickle_save", "rb")
+    try:
+        file = open("./data/from_writing_to_handover.pickle_save", "rb")
+    except FileNotFoundError:
+        print(
+            "there is not file on path './data/from_writing_to_handover.pickle_save'!"
+        )
+        print("(1) check from which directory you're running the script")
+        print(
+            "(2) if you don't wave a from_writing_to_handover.pickle_save, you can create it by teaching by demostration available within freedrive_v2.0 found in convenience_tool_box (details on it are there)"
+        )
+        exit()
     point_dict = pickle.load(file)
     file.close()
     #####################
diff --git a/examples/drawing/parameters/plane_pose.pickle_save b/examples/drawing/parameters/plane_pose.pickle_save
index d664870c456e2d2ec0674aab04531818a00a3430..f1d2e30606528e2ec935b3629cd202fd0b78db0a 100644
Binary files a/examples/drawing/parameters/plane_pose.pickle_save and b/examples/drawing/parameters/plane_pose.pickle_save differ
diff --git a/examples/drawing/parameters/plane_pose_sim.pickle_save b/examples/drawing/parameters/plane_pose_sim.pickle_save
new file mode 100644
index 0000000000000000000000000000000000000000..f1d2e30606528e2ec935b3629cd202fd0b78db0a
Binary files /dev/null and b/examples/drawing/parameters/plane_pose_sim.pickle_save differ
diff --git a/examples/drawing/parameters/wiping_path.csv_save b/examples/drawing/parameters/wiping_path.csv_save
deleted file mode 100644
index 3971e66aef355df0007209be0e714e1b957757d3..0000000000000000000000000000000000000000
--- a/examples/drawing/parameters/wiping_path.csv_save
+++ /dev/null
@@ -1,546 +0,0 @@
-0.15129,0.89211
-0.15129,0.89211
-0.15277,0.89211
-0.15573,0.89078
-0.15870,0.88145
-0.15870,0.87346
-0.15870,0.85614
-0.15870,0.83217
-0.15722,0.81618
-0.15573,0.80553
-0.15129,0.78288
-0.14981,0.76557
-0.14685,0.74958
-0.14536,0.73893
-0.14240,0.72294
-0.13944,0.70962
-0.13796,0.69897
-0.13648,0.68831
-0.13500,0.67899
-0.13500,0.67366
-0.13500,0.66567
-0.13500,0.65634
-0.13351,0.64436
-0.13351,0.62970
-0.13351,0.61772
-0.13351,0.60706
-0.13203,0.57642
-0.13203,0.56044
-0.13055,0.54579
-0.13055,0.53646
-0.13055,0.52181
-0.13055,0.50583
-0.13055,0.48984
-0.13055,0.47786
-0.13055,0.46720
-0.13203,0.45122
-0.13203,0.43656
-0.13351,0.42058
-0.13351,0.40593
-0.13500,0.39261
-0.13648,0.37130
-0.13796,0.33666
-0.13796,0.32601
-0.13796,0.31002
-0.13796,0.29537
-0.13796,0.28338
-0.13796,0.27273
-0.13648,0.26474
-0.13648,0.23410
-0.13648,0.22078
-0.13648,0.21012
-0.13648,0.20346
-0.13648,0.19814
-0.13648,0.19414
-0.13648,0.19281
-0.13648,0.19014
-0.13796,0.17816
-0.13796,0.17016
-0.13796,0.16084
-0.13796,0.15285
-0.13796,0.14219
-0.13796,0.13953
-0.15277,0.13953
-0.16610,0.13953
-0.18240,0.13820
-0.19277,0.13686
-0.20462,0.13553
-0.22388,0.13154
-0.23573,0.13020
-0.24758,0.12754
-0.25795,0.12488
-0.26388,0.12354
-0.26684,0.12354
-0.27129,0.12354
-0.27277,0.12354
-0.27277,0.15152
-0.27277,0.17416
-0.26980,0.23543
-0.26240,0.26607
-0.25795,0.30336
-0.25499,0.33400
-0.25647,0.36996
-0.26092,0.38861
-0.26536,0.41658
-0.26832,0.43923
-0.26980,0.45521
-0.26980,0.47786
-0.26980,0.53114
-0.26980,0.55778
-0.26980,0.58308
-0.27129,0.59774
-0.27129,0.60972
-0.27129,0.61905
-0.27129,0.62171
-0.27129,0.62304
-0.27129,0.62970
-0.26684,0.68165
-0.26684,0.69497
-0.26684,0.71229
-0.26684,0.72028
-0.26684,0.72428
-0.26684,0.72827
-0.26684,0.73227
-0.26684,0.73760
-0.26684,0.74692
-0.26832,0.76024
-0.26980,0.77489
-0.26980,0.79088
-0.27129,0.80819
-0.27277,0.82151
-0.27277,0.82950
-0.27277,0.83616
-0.27277,0.84149
-0.27277,0.84549
-0.27277,0.85082
-0.27277,0.85748
-0.27277,0.86547
-0.27277,0.87346
-0.27277,0.87746
-0.27277,0.88012
-0.27277,0.88278
-0.27277,0.88412
-0.27277,0.88545
-0.27573,0.88944
-0.28017,0.89344
-0.28462,0.89610
-0.29203,0.89877
-0.29795,0.90143
-0.30388,0.90276
-0.31128,0.90410
-0.31869,0.90410
-0.32462,0.90410
-0.33054,0.90410
-0.33647,0.90410
-0.34091,0.90276
-0.34536,0.90143
-0.34980,0.90010
-0.35573,0.89877
-0.36165,0.89744
-0.37054,0.89744
-0.37499,0.89744
-0.38239,0.89744
-0.38684,0.89744
-0.38832,0.89744
-0.39128,0.89744
-0.39424,0.89744
-0.39573,0.89744
-0.39721,0.89744
-0.39869,0.89744
-0.39869,0.89610
-0.40165,0.89344
-0.40461,0.86680
-0.40461,0.85082
-0.40461,0.83483
-0.40461,0.82418
-0.40461,0.80952
-0.40461,0.78688
-0.40461,0.77356
-0.40461,0.76290
-0.40461,0.75491
-0.40461,0.73760
-0.40461,0.72294
-0.40461,0.70829
-0.40461,0.70030
-0.40461,0.69098
-0.40461,0.68565
-0.40461,0.67632
-0.40461,0.66434
-0.40758,0.65102
-0.41054,0.64036
-0.41202,0.62704
-0.41350,0.61905
-0.41498,0.60839
-0.41647,0.59374
-0.41795,0.56044
-0.41647,0.55112
-0.41647,0.54446
-0.41498,0.53513
-0.41202,0.52847
-0.41054,0.52048
-0.40758,0.51249
-0.40461,0.50183
-0.40165,0.48984
-0.39869,0.48452
-0.39573,0.47786
-0.39276,0.46587
-0.38980,0.46054
-0.38684,0.44988
-0.38387,0.44189
-0.38239,0.43124
-0.38091,0.42458
-0.37943,0.41658
-0.37943,0.41259
-0.37795,0.40593
-0.37795,0.40193
-0.37795,0.39927
-0.37795,0.39261
-0.37795,0.38328
-0.37795,0.37263
-0.37795,0.36197
-0.37943,0.35265
-0.37943,0.34599
-0.37943,0.34199
-0.37943,0.33933
-0.37943,0.33533
-0.37943,0.33000
-0.38091,0.32468
-0.38091,0.31269
-0.38091,0.30203
-0.38239,0.28605
-0.38239,0.27939
-0.38239,0.26340
-0.38387,0.25541
-0.38387,0.24742
-0.38387,0.24209
-0.38387,0.23543
-0.38387,0.23144
-0.38387,0.22611
-0.38536,0.21812
-0.38684,0.21412
-0.38684,0.21012
-0.38684,0.20613
-0.38684,0.20213
-0.38684,0.20213
-0.38684,0.20080
-0.38684,0.19947
-0.38684,0.19814
-0.38980,0.19148
-0.39424,0.18082
-0.40017,0.17150
-0.40313,0.16750
-0.40461,0.16350
-0.40461,0.16217
-0.40461,0.16084
-0.40461,0.15951
-0.40461,0.15818
-0.40461,0.15684
-0.40610,0.15551
-0.40906,0.15152
-0.41054,0.14885
-0.41202,0.14619
-0.41202,0.14619
-0.41202,0.14486
-0.41202,0.14352
-0.41202,0.14219
-0.41498,0.14219
-0.42091,0.14086
-0.43276,0.14086
-0.44313,0.14352
-0.45350,0.14486
-0.46239,0.14752
-0.46980,0.14885
-0.47424,0.14885
-0.48017,0.15018
-0.48461,0.15018
-0.48757,0.15018
-0.48906,0.15152
-0.49054,0.15152
-0.49498,0.15285
-0.49943,0.15418
-0.50387,0.15418
-0.50831,0.15551
-0.51128,0.15551
-0.51276,0.15551
-0.51424,0.15551
-0.51572,0.15551
-0.50980,0.17416
-0.50683,0.19148
-0.50535,0.21678
-0.50387,0.24209
-0.50387,0.25808
-0.50387,0.29004
-0.50387,0.32601
-0.50387,0.34199
-0.50683,0.39527
-0.50683,0.40193
-0.50831,0.43390
-0.50980,0.45122
-0.51128,0.46587
-0.51276,0.47786
-0.51424,0.48851
-0.51572,0.50583
-0.51572,0.52314
-0.51720,0.53646
-0.51868,0.54978
-0.52017,0.56577
-0.52017,0.57909
-0.52017,0.59108
-0.52017,0.61638
-0.52017,0.62837
-0.52017,0.64569
-0.51868,0.65901
-0.51868,0.66966
-0.51868,0.67899
-0.51868,0.68831
-0.51868,0.70163
-0.51868,0.71229
-0.51868,0.72561
-0.52165,0.73760
-0.52461,0.75092
-0.52905,0.76690
-0.53350,0.77889
-0.53498,0.78688
-0.53646,0.79487
-0.53794,0.80020
-0.53794,0.80286
-0.53942,0.80819
-0.53942,0.81352
-0.54091,0.82018
-0.54091,0.82817
-0.54091,0.83217
-0.54091,0.83750
-0.54091,0.84282
-0.54091,0.84416
-0.54091,0.84549
-0.54239,0.85748
-0.54239,0.86547
-0.54239,0.86813
-0.54239,0.87080
-0.54239,0.87612
-0.54239,0.87612
-0.54239,0.87746
-0.54239,0.87879
-0.54239,0.88012
-0.54239,0.88145
-0.54239,0.88278
-0.54239,0.88412
-0.54239,0.88545
-0.54979,0.88944
-0.55720,0.89211
-0.56609,0.89211
-0.57794,0.89211
-0.58831,0.89211
-0.59720,0.89078
-0.60609,0.88944
-0.61794,0.88678
-0.62387,0.88545
-0.62979,0.88412
-0.63720,0.88145
-0.64609,0.87879
-0.65498,0.87612
-0.66090,0.87346
-0.66979,0.87213
-0.67423,0.87080
-0.67720,0.87080
-0.67868,0.87080
-0.68016,0.87080
-0.68164,0.87080
-0.68312,0.87080
-0.68460,0.86946
-0.68460,0.86813
-0.68757,0.86280
-0.68757,0.85215
-0.68460,0.83616
-0.67868,0.80153
-0.67572,0.78155
-0.67423,0.76690
-0.67423,0.71362
-0.67423,0.70030
-0.67423,0.68698
-0.67275,0.66034
-0.67127,0.64436
-0.67127,0.62970
-0.66979,0.61638
-0.66831,0.58575
-0.66683,0.56177
-0.66683,0.55112
-0.66683,0.52847
-0.66831,0.49650
-0.66831,0.48851
-0.66535,0.47120
-0.66238,0.44589
-0.65794,0.42724
-0.65498,0.41658
-0.65053,0.39128
-0.64905,0.36464
-0.64609,0.33800
-0.64461,0.32201
-0.64312,0.30603
-0.64164,0.29138
-0.64164,0.28205
-0.63868,0.26740
-0.63868,0.23144
-0.63868,0.21945
-0.63868,0.21412
-0.63868,0.20613
-0.63868,0.20213
-0.63868,0.19947
-0.63868,0.19680
-0.63868,0.19014
-0.63868,0.18748
-0.63868,0.18215
-0.63868,0.17949
-0.63868,0.17816
-0.64016,0.17150
-0.64164,0.16617
-0.64312,0.15818
-0.64312,0.15684
-0.64312,0.15418
-0.64312,0.15285
-0.64312,0.15152
-0.64312,0.15018
-0.64312,0.14885
-0.67127,0.14885
-0.68757,0.14619
-0.69497,0.14352
-0.71275,0.13953
-0.73794,0.13287
-0.75571,0.13020
-0.76608,0.12887
-0.77349,0.12754
-0.77942,0.12754
-0.78682,0.12754
-0.79127,0.12754
-0.79867,0.12754
-0.80608,0.12754
-0.81349,0.12754
-0.81645,0.12754
-0.81645,0.12754
-0.81793,0.12754
-0.81941,0.12754
-0.82090,0.12754
-0.82238,0.12754
-0.82682,0.13154
-0.83275,0.15551
-0.83275,0.16617
-0.82978,0.19547
-0.82534,0.22078
-0.82238,0.23543
-0.81941,0.27006
-0.81941,0.29271
-0.81941,0.30869
-0.81941,0.33400
-0.81941,0.35132
-0.81941,0.36464
-0.82090,0.37796
-0.82090,0.39927
-0.82238,0.42191
-0.82238,0.43790
-0.82534,0.46454
-0.82534,0.48052
-0.82534,0.48718
-0.82534,0.48984
-0.82534,0.49118
-0.82534,0.50982
-0.82534,0.52448
-0.82534,0.59774
-0.82386,0.61905
-0.82238,0.63503
-0.82238,0.65235
-0.82238,0.66700
-0.82238,0.67766
-0.82238,0.69098
-0.82238,0.69897
-0.82238,0.71495
-0.82238,0.73227
-0.82238,0.74692
-0.82238,0.76024
-0.82386,0.78022
-0.82386,0.78422
-0.82534,0.79620
-0.82682,0.80153
-0.82830,0.80952
-0.82978,0.81618
-0.83127,0.82284
-0.83127,0.82817
-0.83127,0.82950
-0.83127,0.83084
-0.83127,0.83217
-0.83127,0.83350
-0.83423,0.83750
-0.83719,0.84149
-0.83867,0.84282
-0.84015,0.84416
-0.84164,0.84549
-0.84312,0.84682
-0.84756,0.84815
-0.85497,0.84815
-0.86089,0.84815
-0.86386,0.84948
-0.86978,0.84815
-0.87719,0.84815
-0.88312,0.84815
-0.89200,0.84815
-0.89645,0.84815
-0.89793,0.84815
-0.89941,0.84815
-0.90089,0.84815
-0.90237,0.84815
-0.90386,0.84815
-0.90534,0.84682
-0.90682,0.84682
-0.90830,0.84682
-0.90978,0.84682
-0.91126,0.84549
-0.91274,0.84549
-0.91571,0.84016
-0.91867,0.83084
-0.92163,0.81885
-0.92311,0.80819
-0.92311,0.79088
-0.92311,0.75624
-0.92311,0.73760
-0.92311,0.71895
-0.92756,0.65768
-0.92756,0.64302
-0.93052,0.61505
-0.93200,0.57776
-0.93200,0.56976
-0.93052,0.55511
-0.93052,0.53247
-0.92904,0.51249
-0.92756,0.50050
-0.92460,0.48452
-0.92460,0.45122
-0.92460,0.44056
-0.92460,0.43124
-0.92460,0.42058
-0.92460,0.40326
-0.92460,0.37396
-0.92460,0.36464
-0.92460,0.34998
-0.92460,0.32601
-0.92460,0.30869
-0.92460,0.29937
-0.92460,0.29404
-0.92311,0.28738
-0.92163,0.28472
-0.92163,0.28205
-0.92015,0.27672
-0.91423,0.24342
-0.91423,0.23277
-0.91571,0.21678
-0.92163,0.18348
-0.92311,0.17150
-0.92608,0.16217
-0.92608,0.15818
-0.92608,0.15551
-0.92608,0.15418
-0.92608,0.15418
-0.92608,0.15285
-0.92608,0.15018
-0.92608,0.14885
-0.92608,0.14752
-0.92608,0.14619
-0.92608,0.14619
diff --git a/examples/drawing/technical_report/drawing_from_input.tex b/examples/drawing/technical_report/drawing_from_input.tex
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/examples/drawing/utils.py b/examples/drawing/utils.py
index 407c0f1042a7c33b469370e7ef3dcb23ca3fbe14..5bc7bec6aa99be2f49dfd44fac70520f5aac5734 100644
--- a/examples/drawing/utils.py
+++ b/examples/drawing/utils.py
@@ -2,31 +2,23 @@ from smc import getMinimalArgParser
 from smc.control.cartesian_space import getClikArgs
 from smc.control.dmp.dmp import getDMPArgs
 from smc.util.calib_board_hacks import getBoardCalibrationArgs
+
 import argparse
 
 
-def getArgs() -> argparse.Namespace:
+def getArgsForDrawing() -> argparse.Namespace:
     parser = getMinimalArgParser()
     parser = getClikArgs(parser)
     parser = getDMPArgs(parser)
     parser = getBoardCalibrationArgs(parser)
     parser.description = "Make a drawing on screen,\
             watch the robot do it on the whiteboard."
-    parser.add_argument(
-        "--kp",
-        type=float,
-        help="proportial control constant for position errors",
-        default=1.0,
-    )
     parser.add_argument(
         "--mm-into-board",
         type=float,
         help="number of milimiters the path is into the board",
         default=3.0,
     )
-    parser.add_argument(
-        "--kv", type=float, help="damping in impedance control", default=0.001
-    )
     parser.add_argument(
         "--draw-new",
         action=argparse.BooleanOptionalAction,
diff --git a/examples/impedance/point_impedance_control.py b/examples/impedance/point_impedance_control.py
index a21977556931b67cd410ca8ce19c9257d74ddc25..7d15a642bec474b559283e0df0ef5ce18e5215b9 100644
--- a/examples/impedance/point_impedance_control.py
+++ b/examples/impedance/point_impedance_control.py
@@ -1,48 +1,32 @@
+from smc.robots.interfaces.force_torque_sensor_interface import (
+    ForceTorqueOnSingleArmWrist,
+)
+from smc import getMinimalArgParser, getRobotFromArgs
+from smc.control import ControlLoopManager
+from smc.control.cartesian_space import (
+    getClikArgs,
+    getIKSolver,
+)
+
 import pinocchio as pin
 import numpy as np
 import copy
 import argparse
 from functools import partial
-from smc.clik.clik import (
-    getClikArgs,
-    getClikController,
-    moveL,
-    moveUntilContact,
-)
-from smc.managers import (
-    getMinimalArgParser,
-    ControlLoopManager,
-    RobotManager,
-)
-from smc.basics.basics import moveJPI
+from typing import Any, Callable
+from collections import deque
 
 
 def getArgs():
     parser = getMinimalArgParser()
     parser = getClikArgs(parser)
-    parser.add_argument(
-        "--kp",
-        type=float,
-        help="proportial control constant for position errors",
-        default=1.0,
-    )
-    parser.add_argument(
-        "--kv", type=float, help="damping in impedance control", default=0.001
-    )
     parser.add_argument(
         "--cartesian-space-impedance",
         action=argparse.BooleanOptionalAction,
         help="is the impedance computed and added in cartesian or in joint space",
         default=False,
     )
-    parser.add_argument(
-        "--z-only",
-        action=argparse.BooleanOptionalAction,
-        help="whether you have general impedance or just ee z axis",
-        default=False,
-    )
 
-    #    argcomplete.autocomplete(parser)
     args = parser.parse_args()
     return args
 
@@ -53,16 +37,20 @@ def controller():
 
 
 # control loop to be passed to ControlLoopManager
-def controlLoopPointImpedance(
-    args, q_init, controller, robot: RobotManager, i, past_data
-):
+def JointSpacePointImpedanceControlLoop(
+    controller: Any,
+    q_init: np.ndarray,
+    args: argparse.Namespace,
+    robot: ForceTorqueOnSingleArmWrist,
+    i: int,
+    past_data: dict[str, deque[np.ndarray]],
+) -> tuple[bool, dict[str, np.ndarray], dict[str, np.ndarray]]:
     breakFlag = False
     # TODO rename this into something less confusing
     save_past_dict = {}
     log_item = {}
-    q = robot.getQ()
-    Mtool = robot.getT_w_e()
-    wrench = robot.getWrench()
+    q = robot.q
+    wrench = robot.wrench
     log_item["wrench_raw"] = wrench.reshape((6,))
     # deepcopy for good coding practise (and correctness here)
     save_past_dict["wrench"] = copy.deepcopy(wrench)
@@ -75,104 +63,87 @@ def controlLoopPointImpedance(
         np.array(past_data["wrench"]), axis=0
     )
     if not args.z_only:
-        Z = np.diag(np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0]))
+        Z = np.diag(np.array([1.0, 1.0, 1.0, 5.0, 5.0, 5.0]))
     else:
         Z = np.diag(np.array([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]))
-    # Z = np.diag(np.ones(6))
 
     wrench = Z @ wrench
 
-    # this jacobian might be wrong
-    J = pin.computeFrameJacobian(robot.model, robot.data, q, robot.ee_frame_id)
-    dq = robot.getQd()[:6].reshape((6, 1))
-    # get joint
+    J = robot.getJacobian()
     tau = J.T @ wrench
-    #    if i % 25 == 0:
-    #        print(*tau.round(1))
     tau = tau[:6].reshape((6, 1))
     # compute control law:
     # - feedback the position
     # kv is not needed if we're running velocity control
-    vel_cmd = (
-        args.kp * (q_init[:6].reshape((6, 1)) - q[:6].reshape((6, 1)))
-        + args.alpha * tau
-    )
-    # vel_cmd = np.zeros(6)
-    robot.sendQd(vel_cmd)
-
+    v_cmd = args.kp * (q_init.reshape((-1, 1)) - q.reshape((-1, 1))) + args.alpha * tau
     # immediatelly stop if something weird happened (some non-convergence)
-    if np.isnan(vel_cmd[0]):
+    if np.isnan(v_cmd[0]):
         breakFlag = True
+        v_cmd = np.zeros(robot.nv)
+    robot.sendVelocityCommand(v_cmd)
 
-    # log what you said you'd log
-    # TODO fix the q6 situation (hide this)
-    log_item["qs"] = q[:6].reshape((6,))
-    log_item["dqs"] = dq.reshape((6,))
+    log_item["qs"] = robot.q
+    log_item["dqs"] = robot.v
     log_item["wrench_used"] = wrench.reshape((6,))
-
     return breakFlag, save_past_dict, log_item
 
 
 def controlLoopCartesianPointImpedance(
-    args, Mtool_init, clik_controller, robot: RobotManager, i, past_data
-):
+    ik_solver: Callable[[np.ndarray, np.ndarray], np.ndarray],
+    Mtool_init: pin.SE3,
+    args: argparse.Namespace,
+    robot: ForceTorqueOnSingleArmWrist,
+    i: int,
+    past_data: dict[str, deque[np.ndarray]],
+) -> tuple[bool, dict[str, np.ndarray], dict[str, np.ndarray]]:
+
     breakFlag = False
-    # TODO rename this into something less confusing
     save_past_dict = {}
     log_item = {}
-    q = robot.getQ()
-    Mtool = robot.getT_w_e()
-    wrench = robot.getWrench()
+    T_w_e = robot.T_w_e
+    wrench = robot.wrench
     log_item["wrench_raw"] = wrench.reshape((6,))
     save_past_dict["wrench"] = copy.deepcopy(wrench)
-    # wrench = args.beta * wrench + (1 - args.beta) * past_data['wrench'][-1]
+
     wrench = args.beta * wrench + (1 - args.beta) * np.average(
         np.array(past_data["wrench"]), axis=0
     )
     # good generic values
-    # Z = np.diag(np.array([1.0, 1.0, 2.0, 1.0, 1.0, 1.0]))
-    # but let's stick to the default for now
     if not args.z_only:
         Z = np.diag(np.array([1.0, 1.0, 1.0, 10.0, 10.0, 10.0]))
     else:
         Z = np.diag(np.array([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]))
-    # Z = np.diag(np.array([1.0, 1.0, 1.0, 10.0, 10.0, 10.0]))
-    # Z = np.diag(np.array([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]))
-
     wrench = Z @ wrench
 
-    J = pin.computeFrameJacobian(robot.model, robot.data, q, robot.ee_frame_id)
-
-    SEerror = Mtool.actInv(Mtool_init)
+    SEerror = T_w_e.actInv(Mtool_init)
     err_vector = pin.log6(SEerror).vector
     v_cartesian_body_cmd = args.kp * err_vector + args.alpha * wrench
 
-    vel_cmd = clik_controller(J, v_cartesian_body_cmd)
-    robot.sendQd(vel_cmd)
+    J = robot.getJacobian()
+    v_cmd = ik_solver(J, v_cartesian_body_cmd)
 
     # immediatelly stop if something weird happened (some non-convergence)
-    if np.isnan(vel_cmd[0]):
+    if np.isnan(v_cmd[0]):
         breakFlag = True
+        v_cmd = np.zeros(robot.nv)
+    robot.sendVelocityCommand(v_cmd)
 
-    dq = robot.getQd()[:6].reshape((6, 1))
-    # log what you said you'd log
-    # TODO fix the q6 situation (hide this)
-    log_item["qs"] = q[:6].reshape((6,))
-    log_item["dqs"] = dq.reshape((6,))
+    log_item["qs"] = robot.q
+    log_item["dqs"] = robot.v
     log_item["wrench_used"] = wrench.reshape((6,))
-
     return breakFlag, save_past_dict, log_item
 
 
 if __name__ == "__main__":
     args = getArgs()
-    robot = RobotManager(args)
-    clikController = getClikController(args, robot)
+    robot = getRobotFromArgs(args)
+    assert issubclass(robot.__class__, ForceTorqueOnSingleArmWrist)
+    ik_solver = getIKSolver(args, robot)
 
-    # TODO and NOTE the weight, TCP and inertial matrix needs to be set on the robot
+    # NOTE: the weight, TCP and inertial matrix needs to be set on the robot
     # you already found an API in rtde_control for this, just put it in initialization
     # under using/not-using gripper parameters
-    # ALSO NOTE: to use this you need to change the version inclusions in
+    # NOTE: to use this you need to change the version inclusions in
     # ur_rtde due to a bug there in the current ur_rtde + robot firmware version
     # (the bug is it works with the firmware verion, but ur_rtde thinks it doesn't)
     # here you give what you're saving in the rolling past window
@@ -183,21 +154,21 @@ if __name__ == "__main__":
     }
     # here you give it it's initial value
     log_item = {
-        "qs": np.zeros(robot.n_arm_joints),
-        "dqs": np.zeros(robot.n_arm_joints),
+        "qs": np.zeros(robot.nq),
+        "dqs": np.zeros(robot.nv),
         "wrench_raw": np.zeros(6),
         "wrench_used": np.zeros(6),
     }
-    q_init = robot.getQ()
-    Mtool_init = robot.getT_w_e()
+    q_init = robot.q
+    T_w_einit = robot.T_w_e
 
     if not args.cartesian_space_impedance:
         controlLoop = partial(
-            controlLoopPointImpedance, args, q_init, controller, robot
+            JointSpacePointImpedanceControlLoop, controller, q_init, args, robot
         )
     else:
         controlLoop = partial(
-            controlLoopCartesianPointImpedance, args, Mtool_init, clikController, robot
+            controlLoopCartesianPointImpedance, ik_solver, T_w_einit, args, robot
         )
 
     loop_manager = ControlLoopManager(
@@ -209,8 +180,8 @@ if __name__ == "__main__":
         robot.stopRobot()
 
     if args.save_log:
-        robot.log_manager.saveLog()
-        robot.log_manager.plotAllControlLoops()
+        robot._log_manager.saveLog()
+        robot._log_manager.plotAllControlLoops()
 
     if args.visualize_manipulator:
         robot.killManipulatorVisualizer()
diff --git a/python/smc/control/cartesian_space/cartesian_space_point_to_point.py b/python/smc/control/cartesian_space/cartesian_space_point_to_point.py
index f50ae8319133e851335646956676177938370a2d..402f6b28e63fd5cdca86da896f897bb665d38dfd 100644
--- a/python/smc/control/cartesian_space/cartesian_space_point_to_point.py
+++ b/python/smc/control/cartesian_space/cartesian_space_point_to_point.py
@@ -63,7 +63,7 @@ def moveL(
     send a SE3 object as goal point.
     if you don't care about rotation, make it np.zeros((3,3))
     """
-    # assert type(goal_point) == pin.pinocchio_pywrap.SE3
+    assert type(T_w_goal) == pin.SE3
     ik_solver = getIKSolver(args, robot)
     controlLoop = partial(
         EEP2PCtrlLoopTemplate, ik_solver, T_w_goal, controlLoopClik, args, robot
@@ -139,8 +139,8 @@ def moveUntilContactControlLoop(
     if np.linalg.norm(wrench[mask]) > args.contact_detecting_force:
         print("hit with", np.linalg.norm(wrench[mask]))
         breakFlag = True
-        robot.sendVelocityCommand(np.zeros(robot.nq))
-    if (args.pinocchio_only) and (i > 500):
+        robot.sendVelocityCommand(np.zeros(robot.nv))
+    if (not args.real) and (i > 500):
         print("let's say you hit something lule")
         breakFlag = True
     # pin.computeJointJacobian is much different than the C++ version lel
diff --git a/python/smc/control/cartesian_space/utils.py b/python/smc/control/cartesian_space/utils.py
index 4bdbccb15677c19c8ba856d3d3edaa6bb50d34f5..685ed51b98af585f76964fdc5efebb92ea28c5c9 100644
--- a/python/smc/control/cartesian_space/utils.py
+++ b/python/smc/control/cartesian_space/utils.py
@@ -1,7 +1,7 @@
 import argparse
 
 
-def getClikArgs(parser: argparse.ArgumentParser):
+def getClikArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
     """
     getClikArgs
     ------------
@@ -61,6 +61,22 @@ def getClikArgs(parser: argparse.ArgumentParser):
     parser.add_argument(
         "--beta", type=float, help="low-pass filter beta parameter", default=0.01
     )
+    parser.add_argument(
+        "--kp",
+        type=float,
+        help="proportial control constant for position errors",
+        default=1.0,
+    )
+    # NOTE: unnecessary on velocity controlled robots
+    parser.add_argument(
+        "--kv", type=float, help="damping in impedance control", default=0.001
+    )
+    parser.add_argument(
+        "--z-only",
+        action=argparse.BooleanOptionalAction,
+        help="whether you have general impedance or just ee z axis",
+        default=False,
+    )
 
     ###############################
     #  path following parameters  #
diff --git a/python/smc/control/dmp/dmp.py b/python/smc/control/dmp/dmp.py
index 0737daa6ce0f29413531d4efb53471f9b17d3e9a..62cdc20bdd922cd6886520e460679f7ba3e046db 100644
--- a/python/smc/control/dmp/dmp.py
+++ b/python/smc/control/dmp/dmp.py
@@ -62,7 +62,7 @@ def getDMPArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
 
 
 class DMP:
-    def __init__(self, trajectory: str | np.ndarray, k=100, d=20, a_s=1, n_bfs=100):
+    def __init__(self, trajectory: str | np.ndarray, k=100, d=20, a_s:float=1.0, n_bfs=100):
         # TODO load the trajectory here
         # and then allocate all these with zeros of appopriate length
         # as this way they're basically declared twice
diff --git a/python/smc/control/freedrive.py b/python/smc/control/freedrive.py
index 5628e7a91a1c66e530ffaa97905a895c95716f8b..25c8194d5840945a1a10bf931034e4691110d455 100644
--- a/python/smc/control/freedrive.py
+++ b/python/smc/control/freedrive.py
@@ -61,7 +61,7 @@ def freedriveUntilKeyboard(args, robot: SingleArmInterface):
     you can save the log from this function and use that,
     or type on the keyboard to save specific points (q + T_w_e)
     """
-    if args.pinocchio_only:
+    if not args.real:
         print(
             """
     ideally now you would use some sliders or something, 
diff --git a/python/smc/path_generation/cartesian_to_joint_space_path_mapper.py b/python/smc/path_generation/cartesian_to_joint_space_path_mapper.py
index 219ba53d96008caccf098f70c265037c31f1451d..4d82d54cafae1933afeb6bbe6ece21fcb4117503 100644
--- a/python/smc/path_generation/cartesian_to_joint_space_path_mapper.py
+++ b/python/smc/path_generation/cartesian_to_joint_space_path_mapper.py
@@ -1,11 +1,22 @@
-from smc.robots.robotmanager_abstract import AbstractRobotManager
-import pinocchio as pin
+from smc.robots.abstract_robotmanager import AbstractRobotManager
+from smc import getRobotFromArgs
+from smc.control.cartesian_space import moveL
+
+from argparse import Namespace
 import numpy as np
+from pinocchio import SE3
+import copy
+from functools import partial
 
 
 def clikCartesianPathIntoJointPath(
-    args, robot, path, clikController, q_init, plane_pose
-):
+    ik_solver,
+    path: list[SE3],
+    q_init: np.ndarray,
+    t_final: float,
+    args: Namespace,
+    robot: AbstractRobotManager,
+) -> np.ndarray:
     """
     clikCartesianPathIntoJointPath
     ------------------------------
@@ -20,10 +31,6 @@ def clikCartesianPathIntoJointPath(
     return
     ------
     - joint_space_trajectory to follow the given path.
-
-    arguments
-    ----------
-    - path: cartesian path given in task frame
     """
     # we don't know how many there will be, so a linked list is
     # clearly the best data structure here (instert is o(1) still,
@@ -40,20 +47,21 @@ def clikCartesianPathIntoJointPath(
     sim_args.visualizer = False
     sim_args.save_log = False  # we're not using sim robot outside of this
     sim_args.max_iterations = 10000  # more than enough
-    sim_robot = AbstractRobotManager(sim_args)
-    sim_robot.q = q_init.copy()
+    if type(ik_solver) is partial:
+        sim_args.ik_solver = ik_solver.func.__name__
+    else:
+        sim_args.ik_solver = ik_solver.__name__
+    sim_robot = getRobotFromArgs(sim_args)
+    sim_robot._q = q_init.copy()
     sim_robot._step()
     for pose in path:
         moveL(sim_args, sim_robot, pose)
-        # loop logs is a dict, dict keys list preserves input order
-        new_q = sim_robot.q.copy()
         if args.viz_test_path:
-            # look into visualize.py for details on what's available
-            T_w_e = sim_robot.getT_w_e()
-            robot.updateViz({"q": new_q, "T_w_e": T_w_e, "point": T_w_e.copy()})
+            robot.updateViz(
+                {"q": sim_robot.q, "T_w_e": sim_robot.T_w_e, "point": sim_robot.T_w_e}
+            )
             # time.sleep(0.005)
-        qs.append(new_q[:6])
-        # plot this on the real robot
+        qs.append(sim_robot.q)
 
     ##############################################
     #  save the obtained joint-space trajectory  #
@@ -62,7 +70,7 @@ def clikCartesianPathIntoJointPath(
     # we're putting a dmp over this so we already have the timing ready
     # TODO: make this general, you don't want to depend on other random
     # arguments (make this one traj_time, then put tau0 = traj_time there
-    t = np.linspace(0, args.tau0, len(qs)).reshape((len(qs), 1))
+    t = np.linspace(0, t_final, len(qs)).reshape((len(qs), 1))
     joint_trajectory = np.hstack((t, qs))
     # TODO handle saving more consistently/intentionally
     # (although this definitely works right now and isn't bad, just mid)
@@ -70,5 +78,7 @@ def clikCartesianPathIntoJointPath(
     # TODO: check if we actually need this and if not remove the saving
     # whatever code uses this is responsible to log it if it wants it,
     # let's not have random files around.
-    np.savetxt("./joint_trajectory.csv", joint_trajectory, delimiter=",", fmt="%.5f")
+    np.savetxt(
+        "./parameters/joint_trajectory.csv", joint_trajectory, delimiter=",", fmt="%.5f"
+    )
     return joint_trajectory
diff --git a/python/smc/robots/abstract_robotmanager.py b/python/smc/robots/abstract_robotmanager.py
index c42b1420c140efb9581c6add0ccade6572335cca..dea74f558e82f7d3ae62cfa1d095dba5f38eab92 100644
--- a/python/smc/robots/abstract_robotmanager.py
+++ b/python/smc/robots/abstract_robotmanager.py
@@ -319,7 +319,7 @@ class AbstractRobotManager(abc.ABC):
         NOTE: this function does not change internal variables!
         because it shouldn't - it should only update the visualizer
         """
-        if self.args.visualize_manipulator:
+        if self.args.visualizer:
             self.visualizer_manager.sendCommand(viz_dict)
         else:
             if self.args.debug_prints:
diff --git a/python/smc/util/calib_board_hacks.py b/python/smc/util/calib_board_hacks.py
index e214412b5b9d3c51105fbb702585d37538ef70dd..a8e2bde5b014f734a7d845612d5cdc1976f56ff1 100644
--- a/python/smc/util/calib_board_hacks.py
+++ b/python/smc/util/calib_board_hacks.py
@@ -1,5 +1,10 @@
-from smc.robots.interfaces.force_torque_sensor_interface import ForceTorqueOnSingleArmWrist
-from smc.control.cartesian_space.cartesian_space_point_to_point import moveL, moveUntilContact
+from smc.robots.interfaces.force_torque_sensor_interface import (
+    ForceTorqueOnSingleArmWrist,
+)
+from smc.control.cartesian_space.cartesian_space_point_to_point import (
+    moveL,
+    moveUntilContact,
+)
 from smc.control.freedrive import freedriveUntilKeyboard
 
 import argparse
@@ -21,28 +26,43 @@ i.e. the normal vector to the plane.
 Returns R because that's what's needed to construct the hom transf. mat.
 """
 
-def getBoardCalibrationArgs(parser: argparse.ArgumentParser)-> argparse.ArgumentParser:
-    parser.add_argument('--board-width', type=float, \
-            help="width of the board (in meters) the robot will write on", \
-            default=0.30)
-    parser.add_argument('--board-height', type=float, \
-            help="height of the board (in meters) the robot will write on", \
-            default=0.30)
-    parser.add_argument('--calibration', action=argparse.BooleanOptionalAction, \
-            help="whether you want to do calibration", default=False)
-    parser.add_argument('--n-calibration-tests', type=int, \
-            help="number of calibration tests you want to run", default=10)
+
+def getBoardCalibrationArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
+    parser.add_argument(
+        "--board-width",
+        type=float,
+        help="width of the board (in meters) the robot will write on",
+        default=0.30,
+    )
+    parser.add_argument(
+        "--board-height",
+        type=float,
+        help="height of the board (in meters) the robot will write on",
+        default=0.30,
+    )
+    parser.add_argument(
+        "--calibrate-plane",
+        action=argparse.BooleanOptionalAction,
+        help="whether you want to do calibration",
+        default=False,
+    )
+    parser.add_argument(
+        "--n-calibration-tests",
+        type=int,
+        help="number of calibration tests you want to run",
+        default=10,
+    )
     return parser
 
 
-def fitNormalVector(positions:list[np.ndarray]) -> np.ndarray:
+def fitNormalVector(positions: list[np.ndarray]) -> np.ndarray:
     """
     fitNormalVector
     ----------------
     classic least squares fit.
     there's also weighting to make new measurements more important,
-    beucase we change the orientation of the end-effector as we go. 
-    the change in orientation is done so that the end-effector hits the 
+    beucase we change the orientation of the end-effector as we go.
+    the change in orientation is done so that the end-effector hits the
     board at the angle of the board, and thus have consistent measurements.
     """
     positions = np.array(positions)
@@ -55,7 +75,12 @@ def fitNormalVector(positions:list[np.ndarray]) -> np.ndarray:
     try:
         # strong
         W = np.diag(np.arange(1, len(positions) + 1))
-        n_linearly_weighted = np.linalg.inv(positions.T @ W @ positions) @ positions.T @ W @ np.ones(len(positions))
+        n_linearly_weighted = (
+            np.linalg.inv(positions.T @ W @ positions)
+            @ positions.T
+            @ W
+            @ np.ones(len(positions))
+        )
         n_linearly_weighted = n_linearly_weighted / np.linalg.norm(n_linearly_weighted)
         print("n_linearly_weighed", n_linearly_weighted)
         print("if the following give you roughly the same number, it worked")
@@ -66,7 +91,10 @@ def fitNormalVector(positions:list[np.ndarray]) -> np.ndarray:
         print("n_linearly_weighted is singular bruh")
     return n_non_weighted
 
-def constructFrameFromNormalVector(R_initial_estimate:np.ndarray, n:np.ndarray) -> np.ndarray:
+
+def constructFrameFromNormalVector(
+    R_initial_estimate: np.ndarray, n: np.ndarray
+) -> np.ndarray:
     """
     constructFrameFromNormalVector
     ----------------------------------
@@ -80,19 +108,22 @@ def constructFrameFromNormalVector(R_initial_estimate:np.ndarray, n:np.ndarray)
     x_new = np.array([1.0, 0.0, 0.0])
     y_new = np.cross(x_new, z_new)
     # reshaping so that hstack works as expected
-    R = np.hstack((x_new.reshape((3,1)), y_new.reshape((3,1))))
-    R = np.hstack((R, z_new.reshape((3,1))))
+    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)
     # NOTE this is potentially just an artifact of the previous solution which relied
     # on UR TCP readings which used rpy angles. but it ain't hurting nobody
     # so i'm leaving it.
     R = np.abs(R) * np.sign(R_initial_estimate)
-    print('rot mat to new frame:')
-    print(*R, sep=',\n')
+    print("rot mat to new frame:")
+    print(*R, sep=",\n")
     return R
 
-def handleUserToHandleTCPPose(args:argparse.Namespace, robot:ForceTorqueOnSingleArmWrist)-> None:
+
+def handleUserToHandleTCPPose(
+    args: argparse.Namespace, robot: ForceTorqueOnSingleArmWrist
+) -> None:
     """
     handleUserToHandleTCPPose
     -----------------------------
@@ -103,7 +134,8 @@ def handleUserToHandleTCPPose(args:argparse.Namespace, robot:ForceTorqueOnSingle
     4. release the freedrive and then start doing the calibration process
     5. MAKE SURE THE END-EFFECTOR FRAME IS ALIGNED  BY LOOKING AT THE MANIPULATOR VISUALIZER
     """
-    print("""
+    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 SOMEWHAT ABOVE of the plane 
     where you'll be doing said things. \n
@@ -119,50 +151,72 @@ def handleUserToHandleTCPPose(args:argparse.Namespace, robot:ForceTorqueOnSingle
     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 no (no means exit program)? [Y/n]")
-        if answer == 'n' or answer == 'N':
-            print("""
+        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("""
+        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!")
+            print(
+                "Whatever you typed in is neither 'Y' nor 'n'. Give it to me straight cheif!"
+            )
             continue
-    print("""
+    print(
+        """
     Entering freedrive. 
     Put the end-effector to the top left corner of your plane and mind the orientation.
     Press Enter to stop freedrive.
-    """)
+    """
+    )
     time.sleep(2)
 
-    freedriveUntilKeyboard(args, robot) 
+    freedriveUntilKeyboard(args, robot)
 
     while True:
-        answer = input("""
+        answer = input(
+            """
     I am assuming you got the end-effector in the correct pose. \n
     Are you ready to start calibrating or not (no means exit)? [Y/n]
-    """)
-        if answer == 'n' or answer == '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!")
+        elif answer == "y" or answer == "Y":
+            print(
+                "Calibration about to start. Have your hand on the big red stop button!"
+            )
             time.sleep(2)
             break
         else:
-            print("Whatever you typed in is neither 'Y' nor 'n'. Give it to me straight cheif!")
+            print(
+                "Whatever you typed in is neither 'Y' nor 'n'. Give it to me straight cheif!"
+            )
             continue
 
-def calibratePlane(args:argparse.Namespace, robot : ForceTorqueOnSingleArmWrist, plane_width:float, plane_height:float, n_tests:int) -> pin.SE3, np.ndarray:
+
+def calibratePlane(
+    args: argparse.Namespace,
+    robot: ForceTorqueOnSingleArmWrist,
+    plane_width: float,
+    plane_height: float,
+    n_tests: int,
+) -> tuple[pin.SE3, np.ndarray]:
     """
     calibratePlane
     --------------
@@ -171,7 +225,7 @@ def calibratePlane(args:argparse.Namespace, robot : ForceTorqueOnSingleArmWrist,
     we sam
     """
     handleUserToHandleTCPPose(args, robot)
-    if args.pinocchio_only:
+    if not args.real:
         robot._step()
     q_init = robot.q
     T_w_e = robot.T_w_e
@@ -188,7 +242,7 @@ def calibratePlane(args:argparse.Namespace, robot : ForceTorqueOnSingleArmWrist,
     # used to define going to above new sample point on the board
     go_above_new_sample_transf = pin.SE3.Identity()
 
-    # go in the end-effector's frame z direction 
+    # go in the end-effector's frame z direction
     # our goal is to align that with board z
     speed = np.zeros(6)
     speed[2] = 0.02
@@ -198,20 +252,23 @@ def calibratePlane(args:argparse.Namespace, robot : ForceTorqueOnSingleArmWrist,
         print("========================================")
         time.sleep(0.01)
         print("iteration number:", i)
-        #robot.rtde_control.moveUntilContact(speed)
+        # robot.rtde_control.moveUntilContact(speed)
         moveUntilContact(args, robot, speed)
-        # no step because this isn't wrapped by controlLoopManager 
+        # no step because this isn't wrapped by controlLoopManager
         robot._step()
         q = robot.q
         T_w_e = robot.T_w_e
-        print("pin:", *T_w_e.translation.round(4), \
-                *pin.rpy.matrixToRpy(T_w_e.rotation).round(4))
-#        print("ur5:", *np.array(robot.rtde_receive.getActualTCPPose()).round(4))
+        print(
+            "pin:",
+            *T_w_e.translation.round(4),
+            *pin.rpy.matrixToRpy(T_w_e.rotation).round(4)
+        )
+        #        print("ur5:", *np.array(robot.rtde_receive.getActualTCPPose()).round(4))
 
         positions.append(copy.deepcopy(T_w_e.translation))
-        if i < n_tests -1:
+        if i < n_tests - 1:
             current_pose = robot.T_w_e
-            # go back up 
+            # go back up
             new_pose = current_pose.act(go_away_from_plane_transf)
             moveL(args, robot, new_pose)
 
@@ -221,7 +278,9 @@ def calibratePlane(args:argparse.Namespace, robot : ForceTorqueOnSingleArmWrist,
             print("going to new pose for detection", new_pose)
             new_pose = init_pose.copy()
             go_above_new_sample_transf.translation[0] = np.random.random() * plane_width
-            go_above_new_sample_transf.translation[1] = np.random.random() * plane_height
+            go_above_new_sample_transf.translation[1] = (
+                np.random.random() * plane_height
+            )
             new_pose = new_pose.act(go_above_new_sample_transf)
             moveL(args, robot, new_pose)
             # fix orientation
@@ -250,9 +309,11 @@ def calibratePlane(args:argparse.Namespace, robot : ForceTorqueOnSingleArmWrist,
     new_pose.rotation = R
     print("going back to initial position with fitted R")
     moveL(args, robot, new_pose)
-    
-    print("i'll estimate the translation vector to board beginning now \
-           that we know we're going straight down")
+
+    print(
+        "i'll estimate the translation vector to board beginning now \
+           that we know we're going straight down"
+    )
     speed = np.zeros(6)
     speed[2] = 0.02
 
@@ -264,32 +325,32 @@ def calibratePlane(args:argparse.Namespace, robot : ForceTorqueOnSingleArmWrist,
     translation = T_w_e.translation.copy()
     print("got translation vector, it's:", translation)
 
-
     moveL(args, robot, new_pose)
     q = robot.q
     init_q = copy.deepcopy(q)
     print("went back up, saved this q as initial q")
-    
+
     # put the speed slider back to its previous value
-#    robot.setSpeedSlider(old_speed_slider)
-    print('also, the translation vector is:', translation)
-    if not args.pinocchio_only:
-        file_path = './plane_pose.pickle'
+    #    robot.setSpeedSlider(old_speed_slider)
+    print("also, the translation vector is:", translation)
+    if args.real:
+        file_path = "./parameters/plane_pose.pickle"
     else:
-        file_path = './plane_pose_sim.pickle'
-    log_file = open(file_path, 'wb')
+        file_path = "./parameters/plane_pose_sim.pickle"
+    log_file = open(file_path, "wb")
     plane_pose = pin.SE3(R, translation)
-    log_item = {'plane_top_left_pose': plane_pose, 'q_init': q_init.copy()}
+    log_item = {"plane_top_left_pose": plane_pose, "q_init": q_init.copy()}
     pickle.dump(log_item, log_file)
     log_file.close()
     return plane_pose, q_init
 
+
 # TODO: update for the current year
-#if __name__ == "__main__":
+# if __name__ == "__main__":
 #    robot = RobotManager()
 #    # TODO make this an argument
 #    n_tests = 10
-#    # TODO: 
+#    # 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