diff --git a/python/examples/drawing_from_input_drawing.py b/python/examples/drawing_from_input_drawing.py index 35e0e69578300d20e21368dae3ffce306c5acb26..a0b8d642c6528d79754ac34332f0e72a11473710 100644 --- a/python/examples/drawing_from_input_drawing.py +++ b/python/examples/drawing_from_input_drawing.py @@ -15,7 +15,6 @@ import copy import argparse import time from functools import partial -from ur_simple_control.util.get_model import get_model from ur_simple_control.visualize.visualize import plotFromDict from ur_simple_control.util.draw_path import drawPath from ur_simple_control.dmp.dmp import DMP, NoTC,TCVelAccConstrained @@ -295,7 +294,7 @@ def controller(): # to find option 3) # control loop to be passed to ControlLoopManager -def controlLoopWriting(wrench_offset, dmp, tc, controller, robot, i, past_data): +def controlLoopWriting(dmp, tc, controller, robot, i, past_data): breakFlag = False # TODO rename this into something less confusing save_past_dict = {} @@ -325,7 +324,6 @@ def controlLoopWriting(wrench_offset, dmp, tc, controller, robot, i, past_data): #Z = np.diag(np.array([1.0, 0.6, 1.0, 0.5, 0.5, 0.5])) wrench = robot.getWrench() - wrench = wrench - wrench_offset # deepcopy for good coding practise (and correctness here) save_past_dict['wrench'] = copy.deepcopy(wrench) # rolling average @@ -387,8 +385,8 @@ if __name__ == "__main__": robot = RobotManager(args) # calibrate FT first - wrench_offset = robot.calibrateFT() - robot.wrench_offset = wrench_offset + # it's done by default now because it's basically always necessary + ####################################################################### # drawing a path, making a joint trajectory for it # ####################################################################### @@ -396,7 +394,7 @@ if __name__ == "__main__": # draw the path on the screen if args.draw_new: - pixel_path = drawPath() + pixel_path = drawPath(args) # make it 3D else: pixel_path_file_path = './path_in_pixels.csv' @@ -523,7 +521,7 @@ if __name__ == "__main__": 'dqs' : np.zeros((args.max_iterations, 6)), 'dmp_vels' : np.zeros((args.max_iterations, 6)), } - controlLoop = partial(controlLoopWriting, wrench_offset, dmp, tc, controller, robot) + controlLoop = partial(controlLoopWriting, dmp, tc, controller, robot) loop_manager = ControlLoopManager(robot, controlLoop, args, save_past_dict, log_dict) ####################################################################### # physical setup # diff --git a/python/examples/planar_dragging_via_top_contact_force.py b/python/examples/planar_dragging_via_top_contact_force.py index 653485e12bab015a78ed1aa23026942f106f4259..171b4442cbc4c188dda526cbb45aa8e33105325a 100644 --- a/python/examples/planar_dragging_via_top_contact_force.py +++ b/python/examples/planar_dragging_via_top_contact_force.py @@ -5,7 +5,6 @@ import copy import argparse import time from functools import partial -from ur_simple_control.util.get_model import get_model from ur_simple_control.visualize.visualize import plotFromDict from ur_simple_control.util.draw_path import drawPath from ur_simple_control.dmp.dmp import DMP, NoTC,TCVelAccConstrained @@ -153,7 +152,7 @@ def controller(): # control loop to be passed to ControlLoopManager -def controlLoopPlanarDragging(wrench_offset, dmp, tc, controller, robot, i, past_data): +def controlLoopPlanarDragging(dmp, tc, controller, robot, i, past_data): breakFlag = False # TODO rename this into something less confusing save_past_dict = {} @@ -166,7 +165,6 @@ def controlLoopPlanarDragging(wrench_offset, dmp, tc, controller, robot, i, past Z = np.diag(np.ones(6)) wrench = robot.getWrench() - wrench = wrench - wrench_offset wrench = np.average(np.array(past_data['wrench']), axis=0) # first-order low pass filtering @@ -175,7 +173,7 @@ def controlLoopPlanarDragging(wrench_offset, dmp, tc, controller, robot, i, past #wrench = beta * wrench + (1 - beta) * past_data['wrench'][-1] wrench = robot.getMtool().toDualActionMatrix().T @ wrench - wrench = Z @ robot.getWrench() + wrench = Z @ wrench # deepcopy for good coding practise (and correctness here) save_past_dict['wrench'] = copy.deepcopy(wrench) # rolling average @@ -214,8 +212,6 @@ if __name__ == "__main__": robot = RobotManager(args) # calibrate FT first - wrench_offset = robot.calibrateFT() - speed_down = np.array([0,0,-1,0,0,0]) moveUntilContact(args, robot, speed_down) m_goal_up = robot.getMtool() @@ -230,7 +226,7 @@ if __name__ == "__main__": # log_dict = { # 'wrench': np.zeros((args.max_iterations, 6)), # } -# controlLoop = partial(controlLoopPlanarDragging, wrench_offset, dmp, tc, controller, robot) +# controlLoop = partial(controlLoopPlanarDragging, dmp, tc, controller, robot) # loop_manager = ControlLoopManager(robot, controlLoop, args, save_past_dict, log_dict) # ####################################################################### # # physical setup # diff --git a/python/examples/point_impedance_control.py b/python/examples/point_impedance_control.py index 78ad110f6da290dcc60383b431910c6b4154b84a..d1d06e4946e840cbb37fae117cdd8eee7bb73b74 100644 --- a/python/examples/point_impedance_control.py +++ b/python/examples/point_impedance_control.py @@ -5,7 +5,6 @@ import copy import argparse import time from functools import partial -from ur_simple_control.util.get_model import get_model from ur_simple_control.visualize.visualize import plotFromDict from ur_simple_control.util.draw_path import drawPath from ur_simple_control.dmp.dmp import DMP, NoTC,TCVelAccConstrained diff --git a/python/examples/test_gripper.py b/python/examples/test_gripper.py index 366a6a583b562b14ff7c0dd4ac137ea6ee860e98..6613fdde2a794b2bbf04560270ccfbb8cfc4986d 100644 --- a/python/examples/test_gripper.py +++ b/python/examples/test_gripper.py @@ -6,7 +6,6 @@ import copy import argparse import time from functools import partial -from ur_simple_control.util.get_model import get_model from ur_simple_control.visualize.visualize import plotFromDict from ur_simple_control.util.draw_path import drawPath from ur_simple_control.managers import ControlLoopManager, RobotManager diff --git a/python/ur_simple_control/__pycache__/__init__.cpython-311.pyc b/python/ur_simple_control/__pycache__/__init__.cpython-311.pyc index 6730f1e95d99f25500f6b1680857af2968134e4f..e6536f859f51b8b8a17c0032e2d11854161023ad 100644 Binary files a/python/ur_simple_control/__pycache__/__init__.cpython-311.pyc and b/python/ur_simple_control/__pycache__/__init__.cpython-311.pyc differ diff --git a/python/ur_simple_control/__pycache__/managers.cpython-311.pyc b/python/ur_simple_control/__pycache__/managers.cpython-311.pyc index 26120b0cff823e6d4bc7f91a37548db23576a5ab..89732ed96ef8a84e5f69a8c8ee92ef71e3c71bee 100644 Binary files a/python/ur_simple_control/__pycache__/managers.cpython-311.pyc and b/python/ur_simple_control/__pycache__/managers.cpython-311.pyc differ diff --git a/python/ur_simple_control/clik/__pycache__/__init__.cpython-311.pyc b/python/ur_simple_control/clik/__pycache__/__init__.cpython-311.pyc index 46c82e11fbe0ff3bb421e7f1e4c8aec6e35c9363..47dfd580761c955ca39175dd03a865d137c0bcb2 100644 Binary files a/python/ur_simple_control/clik/__pycache__/__init__.cpython-311.pyc and b/python/ur_simple_control/clik/__pycache__/__init__.cpython-311.pyc differ diff --git a/python/ur_simple_control/clik/clik_point_to_point.py b/python/ur_simple_control/clik/clik_point_to_point.py index c101839febbabbe17622dd0e81741568b74af6cd..b20bdc645d0677edc48f5c1d976df050f8be3c54 100644 --- a/python/ur_simple_control/clik/clik_point_to_point.py +++ b/python/ur_simple_control/clik/clik_point_to_point.py @@ -182,10 +182,9 @@ def moveUntilContactControlLoop(speed, robot, clik_controller, i, past_data): pin.forwardKinematics(robot.model, robot.data, q) # break if wrench is nonzero basically wrench = robot.getWrench() - # TODO: move this to the robot class - wrench = wrench - robot.wrench_offset - # TODO: remove magick number - # it is empirical bla bla, but make it an argument at least + # NOTE: this 4.2 is a magic number + # it is a 100% empirical, with the goal being that it's just above noise. + # so far it's worked fine, and it's pretty soft too. if np.linalg.norm(wrench) > 4.2: print("hit with", np.linalg.norm(wrench)) breakFlag = True diff --git a/python/ur_simple_control/dmp/__pycache__/__init__.cpython-311.pyc b/python/ur_simple_control/dmp/__pycache__/__init__.cpython-311.pyc index 6d20d4dc491caffb2381d4d29f6e5fa47875ecf5..94aa9adee31a8c24a5d1149f612b7a8a46762036 100644 Binary files a/python/ur_simple_control/dmp/__pycache__/__init__.cpython-311.pyc and b/python/ur_simple_control/dmp/__pycache__/__init__.cpython-311.pyc differ diff --git a/python/ur_simple_control/managers.py b/python/ur_simple_control/managers.py index 4fba1193aab8f83f39530ea0c331f346b4b91d08..689d5b20162781539f8c9e8e0c36199d7961f5cd 100644 --- a/python/ur_simple_control/managers.py +++ b/python/ur_simple_control/managers.py @@ -1,14 +1,13 @@ -# TODO clear out unnecessary imports # TODO rename all private variables to start with '_' # TODO: just read the q and update everything every timestep, don't deepcopy, +# TODO: rewrite all getSomething functions to updateSomething functions, +# and then make the getSomething function just be return self.something.copy() # --> just create a RobotManager.step() function, update everything there # don't do forwardKinematics 2 extra times for no good reason. make that the libraries # responsibility, not the users import pinocchio as pin import numpy as np import time -from pinocchio.visualize import GepettoVisualizer -#import gepetto.corbaserver from rtde_control import RTDEControlInterface from rtde_receive import RTDEReceiveInterface from rtde_io import RTDEIOInterface @@ -102,8 +101,6 @@ class ControlLoopManager: def __init__(self, robot_manager, controlLoop, args, save_past_dict, log_dict): signal.signal(signal.SIGINT, self.stopHandler) self.max_iterations = args.max_iterations - # NOTE the robot manager is all over the place, - # there might be a better design approach to that self.robot_manager = robot_manager self.controlLoop = controlLoop self.args = args @@ -115,8 +112,8 @@ class ControlLoopManager: self.past_data[key] = deque() # immediatelly populate every deque with initial values for i in range(args.past_window_size): - # deepcopy just in case, better safe than sorry. - # and we can be slow with initialization. + # deepcopy just in case, better safe than sorry plus it's during initialization, + # not real time self.past_data[key].append(copy.deepcopy(save_past_dict[key])) # similar story for log_dict as for past_data, @@ -145,7 +142,7 @@ class ControlLoopManager: # TODO: write an assert assuring the keys are what's been promised # (ideally this is done only once, not every time, so think whether/how that can be avoided) for key in latest_to_save_dict: - # remove oldes entry + # remove oldest entry self.past_data[key].popleft() # add new entry self.past_data[key].append(latest_to_save_dict[key]) @@ -159,9 +156,6 @@ class ControlLoopManager: #self.robot_manager.stopHandler(None, None) self.log_dict[key][i] = log_entry_dict[key] - #for key in log_entry_dict: - # self.log_dict[key][i] = log_entry_dict[key] - # break if done if breakFlag: break @@ -174,27 +168,24 @@ class ControlLoopManager: else: time.sleep(self.robot_manager.dt - diff) self.final_iteration = i -# TODO: provide a debug flag for this -# if i < self.max_iterations -1: -# print("success in", i, "iterations!") -# else: -# print("FAIL: did not succed in", max_iterations, "iterations") -# TODO: reintroduce maybe -# if not self.args.pinocchio_only: -# self.robot_manager.stopHandler(None, None) + if args.debug_prints: + if i < self.max_iterations -1: + print("success in", i, "iterations!") + else: + print("FAIL: did not succed in", max_iterations, "iterations") return self.log_dict, self.final_iteration """ stopHandler ----------- - TODO: make ifs for simulation too - can have self as first argument apparently. upon receiving SIGINT it sends zeros for speed commands to - stop the robot + stop the robot. + NOTE: apparently this isn't enough, + nor does stopJ do anything, so it goes to freedriveMode + and exists, which actually stops it. """ def stopHandler(self, signum, frame): - #plotFromDict(signum, frame) print('sending 300 speedjs full of zeros and exiting') for i in range(300): vel_cmd = np.zeros(6) @@ -207,11 +198,12 @@ class ControlLoopManager: self.robot_manager.rtde_control.freedriveMode() plotFromDict(self.log_dict, self.final_iteration, self.args) self.robot_manager.rtde_control.endFreedriveMode() - #exit() """ robotmanager: --------------- +- design goal: rely on pinocchio as much as possible while + concealing obvious bookkeeping - right now it is assumed you're running this on UR5e so some magic numbers are just put to it. this will be extended once there's a need for it. @@ -225,25 +217,14 @@ robotmanager: actually run at 500Hz and not more. - this is probably not the most new-user friendly solution, but it's designed for fastest idea to implementation rate. -- NOTE and TODO: rely on pinocchio as much as possible. - do NOT copy data that's already present in pin.model and pin.data, - it's just confusing. this lib already heavily relies on pinocchio, - let's just lean into that all the way and not invent hot water. -- TODO: write out default arguments needed +- if this was a real programming language, all of these would be private variables. +- TODO: write out default arguments needed here as well """ 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): - # if you just stop it the program with Ctrl-C, it will continue running - # the last speedj lmao. - # there's also an actual stop command, but sending 0s works so i'm not changing it - #signal.signal(signal.SIGINT, self.stopHandler) - self.args = args self.pinocchio_only = args.pinocchio_only self.simulation = args.simulation @@ -253,58 +234,61 @@ class RobotManager: self.model, self.collision_model, self.visual_model, self.data = \ get_model(args.visualize) # TODO: fix this + # ---> TODO: write your own vedo 3D visualzier, + # connect it with the real-time matplotlib line plots, + # and add some basic gui control (show/don't show stuff, + # define a goal point, choose controller etc) if args.visualize: raise NotImplementedError('Paths in the urdf or something else need to \ be fixed to use this first. Sorry. Coming soon.') pass - # run visualizer from code (not working atm) - #gepetto.corbaserver.start_server() - #time.sleep(3) - #viz = GepettoVisualizer(model, collision_model, visual_model) - #viz.initViewer() - #viz.loadViewerModel() - #viz.display(q0) + # TODO: make general if self.gripper_flag and not self.pinocchio_only: self.gripper = RobotiqGripper() self.gripper.connect("192.168.1.102", 63352) self.gripper.activate() - # TODO: this is obviously at least a partial redundancy w.r.t. the pinocchio data object - # however i have no time to investigate what i get or don't get there - # so i'll just leave this be. but fix this!!!! we don't want to duplicate information!! + # also TODO: figure out how to best solve the gripper_velocity problem - #self.q = np.zeros(6) - #self.qd = np.zeros(6) - #self.qdd = np.zeros(6) - #self.gripper_pos = 0.0 - #self.gripper_past_pos = 0.0 - # set q,qd,qdd,griper_pos, gripper_past_pos, gripper_vel to dummy values + # NOTE: you need to initialize differently for other types of joints + self.q = np.zeros(self.model.nq) + # v_q is the generalization of qd for every type of joint. + # for revolute joints it's qd, but for ex. the planar joint it's the body velocity. + self.v_q = np.zeros(self.model.nv) + # same note as v_q, but it's a_q. + self.a_q = np.zeros(self.model.nv) # initialize and connect the interfaces self.simulation = args.simulation - if self.pinocchio_only: - self.q = pin.neutral(self.model) - elif not args.simulation: - # NOTE: you can't connect twice, dw 'bout that + if (not args.simulation) and (not args.pinocchio_only) : + # NOTE: you can't connect twice, so you can't have more than one RobotManager. + # if this produces errors like "already in use", and it's not already in use, + # try just running your new program again. it could be that the socket wasn't given + # back to the os even though you've shut off the previous program. print("CONNECTING TO UR5e!") self.rtde_control = RTDEControlInterface("192.168.1.102") self.rtde_receive = RTDEReceiveInterface("192.168.1.102") self.rtde_io = RTDEIOInterface("192.168.1.102") - if args.gripper: + # NOTE: the force/torque sensor just has large offsets for no reason, + # and you need to minus them to have usable readings. + # we provide this with calibrateFT + self.wrench_offset = self.calibrateFT() + if self.gripper_flag: self.gripper.connect("192.168.1.102", 63352) # this is a blocking call # this isn't actually needed and it's annoying # TODO: test after rebooting the robot # self.gripper.activate() - else: + elif not args.pinocchio_only: self.rtde_control = RTDEControlInterface("127.0.0.1") self.rtde_receive = RTDEReceiveInterface("127.0.0.1") self.rtde_io = RTDEIOInterface("127.0.0.1") # ur specific magic numbers + # NOTE: all of this is ur-specific, and needs to be if-ed if other robots are added. # TODO: this is 8 in pinocchio and that's what you actually use # if we're being real lmao # the TODO here is make this consistent obviously - self.n_joints = 6 + self.n_arm_joints = 6 # last joint because pinocchio adds base frame as 0th joint. # and since this is unintuitive, we add the other variable too # so that the control designer doesn't need to think about such bs @@ -314,28 +298,23 @@ class RobotManager: # you better not give me crazy stuff # and i'm not clipping it, you're fixing it assert args.acceleration <= 1.7 and args.acceleration > 0.0 - # we're not saying it's qdd because it isn't directly (apparently) - # TODO: check that again + # this is the number passed to speedj self.acceleration = args.acceleration + # NOTE: this is evil and everything only works if it's set to 1 + # you really should control the acceleration via the acceleration argument. assert args.speed_slider <= 1.0 and args.acceleration > 0.0 self.speed_slider = args.speed_slider if not args.pinocchio_only: self.rtde_io.setSpeedSlider(args.speed_slider) - self.max_iterations = args.max_iterations # TODO: these are almost certainly higher # NOTE and TODO: speed slider is evil, put it to 1, handle the rest yourself. - # maybe you want to have them high and cap the rest with speed slider? - # idk really, but it's better to have this low and burried for safety and robot longevity reasons - self.max_qdd = 1.7 * args.speed_slider - # NOTE: i had this thing at 2 in other code + # NOTE: i have no idea what's the relationship between max_qdd and speed slider + #self.max_qdd = 1.7 * args.speed_slider + # NOTE: this is an additional kinda evil speed limitation (by this code, not UR). + # we're clipping joint velocities with this. + # if your controllers are not what you expect, you might be commanding a very high velocity, + # which is clipped, resulting in unexpected movement. self.max_qd = 0.5 * args.speed_slider - # error limit - # TODO this is clik specific, put it in a better place - self.goal_error = args.goal_error - # this is probably the right place to manage this - self.wrench_offset = np.zeros(6) - - """ calibrateFT @@ -354,10 +333,7 @@ class RobotManager: print("Use this as offset.") for i in range(2000): start = time.time() - q = self.rtde_receive.getActualQ() ft = self.rtde_receive.getActualTCPForce() - tau = self.rtde_control.getJointTorques() - current = self.rtde_receive.getActualCurrent() ft_readings.append(ft) end = time.time() diff = end - start @@ -365,10 +341,9 @@ class RobotManager: time.sleep(self.dt - diff) ft_readings = np.array(ft_readings) - avg = np.average(ft_readings, axis=0) - self.wrench_offset = avg - print("average ft time", avg) - return avg + self.wrench_offset = np.average(ft_readings, axis=0) + print(self.wrench_offset) + return self.wrench_offset.copy() """ step @@ -395,9 +370,14 @@ class RobotManager: def step(self): self.getQ() self.getQd() - self.wrench = np.array(self.rtde_receive.getActualTCPForce()) - pin.forwardKinematics(self.model, self.data, self.q) - self.Mtool = self.data.oMi[self.JOINT_ID] + self.getWrench() + # certainly not necessary btw + # but if it runs on time, does it matter? it makes everything available... + pin.computeAllTerms(self.model, self.data, self.q, self.v_q) + self.T_w_e = self.data.oMi[self.JOINT_ID].copy() + # this isn't real because we're on a velocity-controlled robot, + # so this is actually None (no tau, no a_q, as expected) + self.a_q = self.data.ddq """ setSpeedSlider @@ -488,8 +468,8 @@ class RobotManager: self.q = q pin.forwardKinematics(self.model, self.data, q) # TODO probably remove deepcopy - self.Mtool = self.data.oMi[self.JOINT_ID] - return copy.deepcopy(self.Mtool) + self.T_w_e = self.data.oMi[self.JOINT_ID] + return self.T_w_e.copy() """ getQd @@ -522,11 +502,8 @@ class RobotManager: # let's just have both options for getting q, it's just a 8d float list # readability is a somewhat subjective quality after all qd = np.array(qd) - self.qd = qd - # TODO make sure this makes sense - else: - return self.qd - return qd + self.v_q = qd + return self.v_q.copy() """ getWrench @@ -536,14 +513,17 @@ class RobotManager: here because this things depend on the arguments which are manager here (hence the class name RobotManager) """ - def getWrench(self): + def getWrenchRaw(self): if not self.pinocchio_only: wrench = np.array(self.rtde_receive.getActualTCPForce()) else: raise NotImplementedError("Don't have time to implement this right now.") - return wrench + def getWrench(self): + self.wrench = np.array(self.rtde_receive.getActualTCPForce()) - self.wrench_offset + return self.wrench.copy() + """ sendQd ----- @@ -584,7 +564,7 @@ class RobotManager: q = self.getQ() # define goal pin.forwardKinematics(self.model, self.data, np.array(q)) - Mtool = self.data.oMi[self.JOINT_ID] + T_w_e = self.data.oMi[self.JOINT_ID] if not self.args.visualize: print("You can only specify the translation right now.") if not self.pinocchio_only: @@ -596,7 +576,7 @@ class RobotManager: # remain with the current orientation # TODO: add something, probably rpy for orientation because it's the least number # of numbers you need to type in - Mgoal = copy.deepcopy(Mtool) + Mgoal = T_w_e.copy() # this is a reasonable way to do it too, maybe implement it later #Mgoal.translation = Mgoal.translation + np.array([0.0, 0.0, -0.1]) # do a while loop until this is parsed correctly diff --git a/python/ur_simple_control/robot_descriptions/__pycache__/__init__.cpython-311.pyc b/python/ur_simple_control/robot_descriptions/__pycache__/__init__.cpython-311.pyc index 0b438269c81061782619270835f25ca18b5845e3..310d0f3ccd9af60dab2a7927182652fe8b3decea 100644 Binary files a/python/ur_simple_control/robot_descriptions/__pycache__/__init__.cpython-311.pyc and b/python/ur_simple_control/robot_descriptions/__pycache__/__init__.cpython-311.pyc differ diff --git a/python/ur_simple_control/robot_descriptions/urdf/__pycache__/__init__.cpython-311.pyc b/python/ur_simple_control/robot_descriptions/urdf/__pycache__/__init__.cpython-311.pyc index d70d22745d75183a65543fa1b8c7a0c8298cd164..87cf9af8fa0a9f776b63d90be581eb2a06f08dde 100644 Binary files a/python/ur_simple_control/robot_descriptions/urdf/__pycache__/__init__.cpython-311.pyc and b/python/ur_simple_control/robot_descriptions/urdf/__pycache__/__init__.cpython-311.pyc differ diff --git a/python/ur_simple_control/util/__pycache__/__init__.cpython-311.pyc b/python/ur_simple_control/util/__pycache__/__init__.cpython-311.pyc index cc7e2ef70b43104b84abbcf2d7964c31e18b8618..73ad86e2073753e1f862d0c21f332c78f73adcef 100644 Binary files a/python/ur_simple_control/util/__pycache__/__init__.cpython-311.pyc and b/python/ur_simple_control/util/__pycache__/__init__.cpython-311.pyc differ diff --git a/python/ur_simple_control/util/__pycache__/get_model.cpython-311.pyc b/python/ur_simple_control/util/__pycache__/get_model.cpython-311.pyc index 4f5e2990e6644b46ae0420a9cac8ef5da6c2e1ed..386fce4af890b63f03d87eb47a3d28d507c0012d 100644 Binary files a/python/ur_simple_control/util/__pycache__/get_model.cpython-311.pyc and b/python/ur_simple_control/util/__pycache__/get_model.cpython-311.pyc differ diff --git a/python/ur_simple_control/util/__pycache__/robotiq_gripper.cpython-311.pyc b/python/ur_simple_control/util/__pycache__/robotiq_gripper.cpython-311.pyc index fd1cbb042ac4e18c03f4548f12b5ef3c475a0fd8..480d94bcf9e5d9e5d77bb06fefa9fe15d9971f79 100644 Binary files a/python/ur_simple_control/util/__pycache__/robotiq_gripper.cpython-311.pyc and b/python/ur_simple_control/util/__pycache__/robotiq_gripper.cpython-311.pyc differ diff --git a/python/ur_simple_control/util/default_args.py b/python/ur_simple_control/util/default_args.py index aea31eada73c866974221c443644217a068a2cb4..218de8545f1b2c8b13ef45653f48418ac1633911 100644 --- a/python/ur_simple_control/util/default_args.py +++ b/python/ur_simple_control/util/default_args.py @@ -4,130 +4,11 @@ import argparse """ -preferably you copy-paste these parameters and add/remove -arguments to suit your specific arguments. -the generic arguments are pretty much always used. -TODO: think about setting the generic arguments somewhere else, -not parsing there, but returning the parses and then only adding -stuff you want. -that way it's a bit cleanear, and you also can't delete the obligatory ones +TODO: have 2 versions: + 1. only the minimal set of argumets (those needed to load robot + and run anything) + 2. every single one you have os far """ def getDefaultArgs(): - ####################################################################### - # generic arguments # - ####################################################################### - parser = argparse.ArgumentParser(description='Make a drawing on screen,\ - watch the robot do it on the whiteboard.', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - # TODO this one won't really work but let's leave it here for the future - parser.add_argument('--simulation', action=argparse.BooleanOptionalAction, - help="whether you are running the UR simulator. \ - NOTE: doesn't actually work because it's not a physics simulator", \ - default=False) - parser.add_argument('--pinocchio-only', action=argparse.BooleanOptionalAction, - help="whether you want to just integrate with pinocchio.\ - NOTE: doesn't actually work because it's not a physics simulator", \ - default=False) - parser.add_argument('--visualize', action=argparse.BooleanOptionalAction, - help="whether you want to visualize with gepetto, but \ - NOTE: not implemented yet", default=False) - parser.add_argument('--gripper', action=argparse.BooleanOptionalAction, \ - help="whether you're using the gripper", default=False) - parser.add_argument('--acceleration', type=float, \ - help="robot's joints acceleration. scalar positive constant, \ - max 1.7, and default 0.4. \ - BE CAREFUL WITH THIS. the urscript doc says this is 'lead axis acceleration'.\ - TODO: check what this means", default=0.3) - parser.add_argument('--speed-slider', type=float,\ - help="cap robot's speed with the speed slider \ - to something between 0 and 1, 1.0 by default because for dmp. \ - BE CAREFUL WITH THIS.", default=1.0) - parser.add_argument('--max-iterations', type=int, \ - help="maximum allowable iteration number (it runs at 500Hz)", default=50000) - ####################################################################### - # your controller specific arguments # - ####################################################################### - # not applicable here, but leaving it in the case it becomes applicable - # it's also in the robot manager even though it shouldn't be - parser.add_argument('--past-window-size', type=int, \ - help="how many timesteps of past data you want to save", default=5) - parser.add_argument('--goal-error', type=float, \ - help="the final position error you are happy with. NOTE: not used here", \ - default=1e-3) - # TODO: test the interaction of this and the overall demo - parser.add_argument('--tikhonov-damp', type=float, \ - help="damping scalar in tiknohov regularization.\ - This is used when generating the joint trajectory from the drawing.", \ - default=1e-2) - # TODO add the rest - parser.add_argument('--clik-controller', type=str, \ - help="select which click algorithm you want", \ - default='dampedPseudoinverse', \ - choices=['dampedPseudoinverse', 'jacobianTranspose']) - # maybe you want to scale the control signal - parser.add_argument('--controller-speed-scaling', type=float, \ - default='1.0', help='not actually_used atm') - ############################# - # dmp specific arguments # - ############################# - parser.add_argument('--temporal-coupling', action=argparse.BooleanOptionalAction, \ - help="whether you want to use temporal coupling", default=True) - parser.add_argument('--kp', type=float, \ - help="proportial control constant for position errors", \ - default=1.0) - parser.add_argument('--tau0', type=float, \ - help="total time needed for trajectory. if you use temporal coupling,\ - you can still follow the path even if it's too fast", \ - default=5) - parser.add_argument('--gamma-nominal', type=float, \ - help="positive constant for tuning temporal coupling: the higher,\ - the fast the return rate to nominal tau", \ - default=1.0) - parser.add_argument('--gamma-a', type=float, \ - help="positive constant for tuning temporal coupling, potential term", \ - default=0.5) - parser.add_argument('--eps-tc', type=float, \ - help="temporal coupling term, should be small", \ - default=0.001) - parser.add_argument('--alpha', type=float, \ - help="force feedback proportional coefficient", \ - default=0.007) - # TODO add low pass filtering and make it's parameters arguments too - ####################################################################### - # task specific arguments # - ####################################################################### - # TODO measure this for the new board - parser.add_argument('--board-width', type=float, \ - help="width of the board (in meters) the robot will write on", \ - default=0.5) - parser.add_argument('--board-height', type=float, \ - help="height of the board (in meters) the robot will write on", \ - default=0.35) - parser.add_argument('--calibration', action=argparse.BooleanOptionalAction, \ - help="whether you want to do calibration", default=False) - parser.add_argument('--draw-new', action=argparse.BooleanOptionalAction, \ - help="whether draw a new picture, or use the saved path path_in_pixels.csv", default=True) - parser.add_argument('--pick_up_marker', action=argparse.BooleanOptionalAction, \ - help=""" - whether the robot should pick up the marker. - NOTE: THIS IS FROM A PREDEFINED LOCATION. - """, default=True) - parser.add_argument('--find-marker-offset', action=argparse.BooleanOptionalAction, \ - help=""" - whether you want to do find marker offset (recalculate TCP - based on the marker""", default=False) - parser.add_argument('--n-calibration-tests', type=int, \ - help="number of calibration tests you want to run", default=10) - parser.add_argument('--clik-goal-error', type=float, \ - help="the clik error you are happy with", default=1e-2) - parser.add_argument('--max-init-clik-iterations', type=int, \ - help="number of max clik iterations to get to the first point", default=10000) - parser.add_argument('--max-running-clik-iterations', type=int, \ - help="number of max clik iterations between path points", default=1000) - - args = parser.parse_args() - if args.gripper and args.simulation: - raise NotImplementedError('Did not figure out how to put the gripper in \ - the simulation yet, sorry :/ . You can have only 1 these flags right now') - return args + raise NotImplementedError("sorry, this hasn't been implemented yet") diff --git a/python/ur_simple_control/util/draw_path.py b/python/ur_simple_control/util/draw_path.py index 0fa950d844b57782611bfcf0dcb3b19552cb1311..4a54dca26f391b847aa8811f09a76e632748e387 100644 --- a/python/ur_simple_control/util/draw_path.py +++ b/python/ur_simple_control/util/draw_path.py @@ -1,10 +1,14 @@ -# TODO: possible improvement: -# - draw multiple lines -# - then you would just generate multiple dmps for each trajectory -# and do movel's + movej's to provide the connections -# TODO: possible improvement: make it all bezier curves -# https://matplotlib.org/stable/users/explain/artists/paths.html -# look at the example for path handling if that's what you'll need +""" +possible improvement: +- draw multiple lines +- then you would just generate multiple dmps for each trajectory + and do movel's + movej's to provide the connections +possible improvement: make it all bezier curves + https://matplotlib.org/stable/users/explain/artists/paths.html + look at the example for path handling if that's what you'll need + - not really needed, especially because we want hard edges to test our controllers + (but if that was a parameter that would be ok i guess) +""" import numpy as np import matplotlib.pyplot as plt @@ -21,9 +25,10 @@ from matplotlib.widgets import LassoSelector #from matplotlib.path import Path class DrawPathManager: - def __init__(self, ax): + def __init__(self, args, ax): self.canvas = ax.figure.canvas self.lasso = LassoSelector(ax, onselect=self.onselect) + self.args = args def onselect(self, verts): # verts is a list of tuples @@ -40,16 +45,15 @@ class DrawPathManager: # made to save and exit def accept(self, event): if event.key == "enter": - print("pixel path:") - print(self.path) + if args.debug_prints: + print("pixel path:") + print(self.path) self.disconnect() - # TODO: ensure this runs fine after packaging np.savetxt("./path_in_pixels.csv", self.path, delimiter=',', fmt='%.5f') - print("TODO: run clik on the pixel path to make it a trajectory") # plt.close over exit so that we can call this from elsewhere and not kill the program plt.close() -def drawPath(): +def drawPath(args): # normalize both x and y to 0-1 range # we can multiply however we want later # idk about the number of points, but it's large enough to draw @@ -59,7 +63,7 @@ def drawPath(): subplot_kw = dict(xlim=(0, 1), ylim=(0, 1), autoscale_on=False) fig, ax = plt.subplots(subplot_kw=subplot_kw) - selector = DrawPathManager(ax) + selector = DrawPathManager(args, ax) # map key press to our function # thankfully it didn't mind have self as the first argument @@ -70,4 +74,4 @@ def drawPath(): if __name__ == '__main__': - drawPath() + drawPath(args) diff --git a/python/ur_simple_control/util/get_model.py b/python/ur_simple_control/util/get_model.py index 4a32e98a4303921a0fc9e50ea8ce51f2105ea85f..3059057a447fc0a92c6d2b5b509cc12f88b0eeb2 100644 --- a/python/ur_simple_control/util/get_model.py +++ b/python/ur_simple_control/util/get_model.py @@ -1,3 +1,9 @@ +""" +possible improvement: + get the calibration file of the robot without ros + and then put it here. + these magic numbers are not a good look. +""" import pinocchio as pin import numpy as np import sys @@ -6,7 +12,6 @@ from importlib.resources import files # can't get the urdf reading with these functions to save my life, idk what or why -# TODO fix paths via python packaging def get_model(visualize): #urdf_path_relative = "../robot_descriptions/urdf/ur5e_with_robotiq_hande.urdf" diff --git a/python/ur_simple_control/util/logging_utils.py b/python/ur_simple_control/util/logging_utils.py index 730ff2c23de6775015e64007f0de2156f621bd57..c8f0c5d516ae35675b8624b16abef47998bc452b 100644 --- a/python/ur_simple_control/util/logging_utils.py +++ b/python/ur_simple_control/util/logging_utils.py @@ -6,8 +6,10 @@ def saveLog(log_dict, final_iteration, args): # shave off the zeros, noone needs 'em for key in log_dict: log_dict[key] = log_dict[key][:final_iteration] - # TODO make generic - # preferably name files after arguments + # TODO make generic: + # - generate name based on args + add index + # - you need to run shell ls to get the index, + # there's at least one chalmers project with code for that run_file_path = "./data/clik_run_001.pickle" args_file_path = "./data/clik_run_001_args.pickle" # save the logged data diff --git a/python/ur_simple_control/visualize/__pycache__/__init__.cpython-311.pyc b/python/ur_simple_control/visualize/__pycache__/__init__.cpython-311.pyc index aba675302559b05bdf8aa8c563b486bbf526fd1f..ac4eb3273e2b46597521f26cb6adac319d37d244 100644 Binary files a/python/ur_simple_control/visualize/__pycache__/__init__.cpython-311.pyc and b/python/ur_simple_control/visualize/__pycache__/__init__.cpython-311.pyc differ diff --git a/python/ur_simple_control/visualize/__pycache__/pin_to_vedo_plot_util.cpython-311.pyc b/python/ur_simple_control/visualize/__pycache__/pin_to_vedo_plot_util.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3f8454db4cce95335a702b67ae9e76fa717ce46 Binary files /dev/null and b/python/ur_simple_control/visualize/__pycache__/pin_to_vedo_plot_util.cpython-311.pyc differ diff --git a/python/ur_simple_control/visualize/__pycache__/visualize.cpython-311.pyc b/python/ur_simple_control/visualize/__pycache__/visualize.cpython-311.pyc index 1b8fab368fc8d0086e4066c6ee5f220f2bd77329..44f507b6f1ebdeda62574ea70174ab2afce3bc34 100644 Binary files a/python/ur_simple_control/visualize/__pycache__/visualize.cpython-311.pyc and b/python/ur_simple_control/visualize/__pycache__/visualize.cpython-311.pyc differ diff --git a/python/ur_simple_control/visualize/pin_to_vedo_plot_util.py b/python/ur_simple_control/visualize/pin_to_vedo_plot_util.py index d400cf7f74ecd176a77ab8ecf0dfca5e27a8a500..a89138777224afd73d6ea4369234e6cbe43d0034 100644 --- a/python/ur_simple_control/visualize/pin_to_vedo_plot_util.py +++ b/python/ur_simple_control/visualize/pin_to_vedo_plot_util.py @@ -36,13 +36,12 @@ def updateFrameArrowsFromSE3(arrows : list[vedo.Arrow], frame : pin.SE3): arrows[i].top = np.array([x,y,z]) def drawSE3AsFrame(frame : pin.SE3, - text : str) -> vedo.Arrows: + text : str, scaling=0.1) -> vedo.Arrows: #print(text) #print(frame) colors = ['r', 'g', 'b'] arrows = [] #print(frame.rotation) - scaling = 0.02 for i in range(0,3): x = frame.translation[0] + frame.rotation[0,i] * scaling y = frame.translation[1] + frame.rotation[1,i] * scaling diff --git a/python/ur_simple_control/visualize/vedo_manipulator.py b/python/ur_simple_control/visualize/vedo_manipulator.py index 5c3972b701ee4da0621447c0d9847a10755f727c..cf03cc89f9d885bae5db99efafd66c8bcb833367 100644 --- a/python/ur_simple_control/visualize/vedo_manipulator.py +++ b/python/ur_simple_control/visualize/vedo_manipulator.py @@ -15,222 +15,95 @@ import vedo import pinocchio as pin import time from ur_simple_control.visualize.pin_to_vedo_plot_util import * -from friction import ReducedFrictionModel -from multiprocessing import Process -from scipy.integrate import solve_ivp - - +from ur_simple_control.managers import RobotManager +import argparse + + +def get_args(): + parser = argparse.ArgumentParser(description='Run closed loop inverse kinematics \ + of various kinds. Make sure you know what the goal is before you run!', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--simulation', action=argparse.BooleanOptionalAction, + help="whether you are running the UR simulator", default=False) + parser.add_argument('--pinocchio-only', action=argparse.BooleanOptionalAction, + help="whether you want to just integrate with pinocchio", default=True) + parser.add_argument('--visualize', action=argparse.BooleanOptionalAction, + help="whether you want to visualize with gepetto, but NOTE: not implemented yet", default=False) + parser.add_argument('--gripper', action=argparse.BooleanOptionalAction, \ + help="whether you're using the gripper", default=False) + parser.add_argument('--goal-error', type=float, \ + help="the final position error you are happy with", default=1e-2) + parser.add_argument('--max-iterations', type=int, \ + help="maximum allowable iteration number (it runs at 500Hz)", default=100000) + parser.add_argument('--acceleration', type=float, \ + help="robot's joints acceleration. scalar positive constant, max 1.7, and default 0.4. \ + BE CAREFUL WITH THIS. the urscript doc says this is 'lead axis acceleration'.\ + TODO: check what this means", default=0.3) + parser.add_argument('--speed-slider', type=float,\ + help="cap robot's speed with the speed slider \ + to something between 0 and 1, 0.5 by default \ + BE CAREFUL WITH THIS.", default=0.5) + parser.add_argument('--tikhonov-damp', type=float, \ + help="damping scalar in tiknohov regularization", default=1e-3) + # TODO add the rest + parser.add_argument('--clik-controller', type=str, \ + help="select which click algorithm you want", \ + default='dampedPseudoinverse', choices=['dampedPseudoinverse', 'jacobianTranspose']) + # maybe you want to scale the control signal + parser.add_argument('--controller-speed-scaling', type=float, \ + default='1.0', help='not actually_used atm') + parser.add_argument('--debug-prints', action=argparse.BooleanOptionalAction, \ + help="print some debug info", default=False) + + args = parser.parse_args() + if args.gripper and args.simulation: + raise NotImplementedError('Did not figure out how to put the gripper in \ + the simulation yet, sorry :/ . You can have only 1 these flags right now') + return args ####################################################################### # IMPLEMENT THIS ACTUALLY # ####################################################################### -if __name__ == "__main__": - args = getArgs() - # change this after you add the manipulator - box_dimensions = [args.box_length, args.box_width, args.box_height] - - ########################################### - # vedo instantiation, frame definitions # - ########################################### - vedo.settings.use_depth_peeling = True - - # need to manually seed lmao - #pin.seed(int(time.time())) - T_w_J = pin.SE3() - # TODO: check how friction behaves when we're perpendicular to gravity - T_w_J.setIdentity() - T_w_J.translation = np.array([1,2,3]) - T_w_J.setRandom() - #T_w_J.rotation = pin.rpy.rpyToMatrix(0,np.pi/2,0) - - - model, data, OBJECT_JOINT_ID = getPlanarJointModel(args, T_w_J, box_dimensions) - q_init = pin.randomConfiguration(model) - #theta = np.random.random() * 2 * np.pi - theta = 0.345321 - q_init[0] = 0.1 - q_init[1] = 0.1 - q_init[2] = np.cos(theta) - q_init[3] = np.sin(theta) - - v_b_se2 = np.array([0.1, 0.0, 0.5]) - v_b_se3 = pin.Motion(np.array([v_b_se2[0], v_b_se2[1], 0]), np.array([0,0,v_b_se2[2]])) - print("v_b_se2", v_b_se2) - #print("pin.exp3(v)", pin.exp3(v)) - #exit() - #tau = np.ones((model.nv)) * 0.1 - #a = pin.aba(model, data, q, v, tau) - # tau_q_control does not exist in reality, those are the actuators - # of the planar joint - #tau_q_control = np.zeros((model.nv)) - - pin.forwardKinematics(model, data, q_init) - T_w_b_init = data.oMi[1].copy() - - q_pin = pin.integrate(model, q_init, v_b_se2) - print("q_pin:", q_pin) - pin.forwardKinematics(model, data, q_pin) - pin.updateFramePlacements(model, data) -# for vvv in data.v: -# print(vvv) -# for fr in data.oMf: -# print(fr) - T_w_b_pin = data.oMi[1].copy() - - q_3 = np.zeros(3) - q_3[0] = q_init[0] - q_3[1] = q_init[1] - q_3[2] = theta - print("q_3", q_3) - - in_SE3 = False - -######################################################## -## JUST USE THE EXP FOR THE LOVE OF GOD -#############################################3333333 -# omega = v[2] -# cv = np.cos(omega) -# sv = np.sin(omega) -# R_omega = np.array([[cv, -1*sv], [sv, cv]]) -# vcross = np.array([-1*v[1], v[0]]) -# vcross -= -v[:2]*R_omega[:,0] + v[:2]*R_omega[:,1] -# vcross /= omega -# t = vcross.copy() -# T_next = np.hstack((R_omega, t.reshape((2,1)))) -# T_next = np.vstack((T_next, np.array([0,0,1]).reshape((1,3)))) -# # let's try inverse -# #T_next = np.hstack((R_omega.T, -1*R_omega.T @ t.reshape((2,1)))) -# #T_next = np.vstack((T_next, np.array([0,0,1]).reshape((1,3)))) -# print("T_next", T_next) - -# c_theta = np.cos(theta) -# s_theta = np.sin(theta) -# R_theta = np.array([[c_theta, -1*s_theta], [s_theta, c_theta]]) -# T_J_b_se2 = np.hstack((R_theta, np.array([q_3[0],q_3[1]]).reshape((2,1)))) -# T_J_b_se2 = np.vstack((T_J_b_se2, np.array([0,0,1]).reshape((1,3)))) - -# print("T_J_b_se2", T_J_b_se2) -# T_J_b_se2 = T_next @ T_J_b_se2 -# print("T_J_b_se2", T_J_b_se2) - -# T_next_SE3 = pin.exp(v_b_se3) -# T_next_SE2 = np.hstack((T_next_SE3.rotation[:2,:2], T_next_SE3.translation[:2].reshape((2,1)))) -# T_next_SE2 = np.vstack((T_next_SE2, np.array([0,0,1]).reshape((1,3)))) - - # wrogn notation last term lel -# T_J_b_SE2 = T_next_SE2 @ T_J_b_se2 -# q_3[0] = T_J_b_SE2[0,2] -# q_3[1] = T_J_b_SE2[1,2] -# # not good but w/3 -# q_3[2] = np.arccos(T_J_b_SE2[0,0]) - - # this changes the no rotation problem - #q_3[2] = omega +def drawStateAnim(self): + toBase = [np.array([[1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1]])] + drawOrientation(self.ax, toBase[-1][0:3,0:3], np.zeros((3,)), self.avg_link_lenth * 2) + for j in range(len(self.joints)): + toBase.append(toBase[-1] @ self.joints[j].HomMat) + drawOrientationAnim(self.ax, toBase[-1][0:3,0:3], self.lines[j][:-1], toBase[-1][0:3,3], self.avg_link_lenth) + drawVectorAnim(self.ax, -1* ( toBase[-2][0:3,3] - toBase[-1][0:3,3] ), self.lines[j][-1], toBase[-2][0:3,3],self.color_link) + + +if __name__ == "__main__": + pin.seed(int(time.time())) + args = get_args() + robot = RobotManager(args) + q = pin.randomConfiguration(robot.model) + q = np.zeros(8) + pin.forwardKinematics(robot.model, robot.data, q) + print(type(robot.data)) + #pin.computeAllTerms(robot.model, robot.data, q, np.zeros(8)) + # no need to use AnimationPlayer, we can update stuff on Plotter as we please. + # so the comm logic + updating will be implemented here (ideal circumstance) + plt = vedo.Plotter(axes=1) + plt.camera = vedo.utils.oriented_camera(center=(0, 0, 0), + up_vector=(0, 1, 0), + backoff_vector=(0, 1, 0), + backoff=1.0) - #print("q_3 + v_b_se2", q_3) -################# -# q_euler = np.zeros(4) -# q_euler[0] = q_3[0] #+ np.cos(v[2]) -# q_euler[1] = q_3[1] #+ np.exp(v[2]) -# q_euler[2] = np.cos(q_3[2]) -# q_euler[3] = np.sin(q_3[2]) -# print("q_euler:", q_euler) -# pin.forwardKinematics(model, data, q_euler) - #T_w_b_euler = data.oMi[1].copy() - #T_w_b_euler = pin.exp6(v_b_se3).act(T_w_J) - #T_w_b_euler = pin.exp6(v_twist).act(T_w_b_init) -# T_J_b = T_w_J.actInv(T_w_b_init) - #print(T_J_b) - #T_w_b_euler = pin.exp6(T_J_b.act(v_twist)).act(T_w_b_init) - # THIS IS THE ONE!!!!!!!!!!!!!!!!!!! - if in_SE3: - print("SE3 version") - v_w_se3 = T_w_b_init.act(v_b_se3) - print(v_w_se3) - T_w_next_SE3 = pin.exp6(v_w_se3) - print(T_w_next_SE3) - T_w_b_euler = T_w_next_SE3.act(T_w_b_init) - #T_w_b_euler = pin.exp6(v_twist).act(T_w_b_init) - #T_J_b_next_euler = pin.exp6(v_twist).act(T_J_b) - #T_w_b_euler = T_w_J.act(T_J_b_next_euler) - # let's try to get this within J and SE(2) - # get v_b to v_J - else: - print("SE2 version") - c_theta = np.cos(theta) - s_theta = np.sin(theta) - R_theta = np.array([[c_theta, -1*s_theta], [s_theta, c_theta]]) - # needed later - T_J_b_SE2 = np.zeros((3,3)) - T_J_b_SE2[:2,:2] = R_theta - T_J_b_SE2[0,2] = q_3[0] - T_J_b_SE2[1,2] = q_3[1] - T_J_b_SE2[2,2] = 1 - Ad_T_J_b_SE2 = np.zeros((3,3)) - Ad_T_J_b_SE2[:2,:2] = R_theta - Ad_T_J_b_SE2[0,2] = q_init[1] - Ad_T_J_b_SE2[1,2] = -q_init[0] - Ad_T_J_b_SE2[2,2] = 1 - v_J_se2 = Ad_T_J_b_SE2 @ v_b_se2 - print("v_J_se2", v_J_se2) - # get v_J_se2 to T_next_SE2 - omega = v_J_se2[2] - c_omega = np.cos(omega) - s_omega = np.sin(omega) - # note that this is the same as exp(omega) - R_omega = np.array([[c_omega, -1*s_omega], [s_omega, c_omega]]) - omega_abs = np.abs(omega) - t = np.zeros(2) - if omega_abs > 1e-14: - V = np.array([[s_omega, -1*(1 -c_omega)], [1- c_omega, s_omega]]) - V /= omega - t = V @ np.array([v_J_se2[0], v_J_se2[1]])#.reshape((2,1)) - else: - t = np.zeros(2) - t[0] = v_J_se2[0] - t[1] = v_J_se2[1] - T_next_SE2 = np.hstack((R_omega, t.reshape((2,1)))) - T_next_SE2 = np.vstack((T_next_SE2, np.array([0,0,1]).reshape((1,3)))) - print("T_next_SE2", T_next_SE2) - T_J_b_SE2 = T_next_SE2 @ T_J_b_SE2 - q_euler = np.zeros(4) - q_euler[0] = T_J_b_SE2[0,2] - q_euler[1] = T_J_b_SE2[1,2] - q_euler[2] = T_J_b_SE2[0,0] - q_euler[3] = T_J_b_SE2[1,0] - #q_euler[0] = q_3[0] - #q_euler[1] = q_3[1] - #q_euler[2] = np.cos(q_3[2]) - #q_euler[3] = np.sin(q_3[2]) - print("q3 + v_J", q_3 + v_J_se2) - print("q_euler:", q_euler) - pin.forwardKinematics(model, data, q_euler) - T_w_b_euler = data.oMi[OBJECT_JOINT_ID] + for i in range(robot.model.nq): + plt += drawSE3AsFrame(robot.data.oMi[i], "",scaling=0.02) + if i+1 < robot.model.nq: + # plt += vedo.Arrow(robot.data.oMi[i].translation, + # robot.data.oMi[i+1].translation - robot.data.oMi[i].translation) + #plt += vedo.Arrow(robot.data.oMi[i].translation, + # -1 * (robot.data.oMi[i].translation - robot.data.oMi[i+1].translation)) + plt += vedo.Arrow(robot.data.oMi[i].translation, + robot.data.oMi[i].act(robot.data.liMi[i+1]).translation) - joint_arrows, joint_text = drawSE3AsFrame(T_w_J, "J") - b_init_arrows, b_init_text = drawSE3AsFrame(T_w_b_init, "b_init") - # pin - objectt = drawBoxFromSE3(T_w_b_pin, box_dimensions) - b_arrows_pin, b_text_pin = drawSE3AsFrame(T_w_b_pin, "b_pin") - # euler - objectt = drawBoxFromSE3(T_w_b_pin, box_dimensions) - b_arrows_euler, b_text_euler = drawSE3AsFrame(T_w_b_euler, "b_euler") - plt = vedo.Plotter(axes=1) - plt.camera = vedo.utils.oriented_camera(center=(0, 0, 0), - up_vector=(0, 1, 0), - backoff_vector=(0, 1, 0), - backoff=1.0) - - plt.add(objectt) - plt.add(b_init_arrows, b_init_text) - plt.add(b_arrows_pin, b_text_pin) - plt.add(b_arrows_euler, b_text_euler) - plt.add(joint_arrows, joint_text) plt.render() plt.show() - plt.close()