diff --git a/python/examples/clik.py b/python/examples/clik.py index f066ec8870934cd454dd81387fa071b18f95aa1f..3a32ec245b32caf0e7f670ec1aec9a516bfdd575 100644 --- a/python/examples/clik.py +++ b/python/examples/clik.py @@ -58,6 +58,8 @@ def get_args(): default='1.0', help='not actually_used atm') parser.add_argument('--debug-prints', action=argparse.BooleanOptionalAction, \ help="print some debug info", default=False) + parser.add_argument('--save-log', action=argparse.BooleanOptionalAction, \ + help="save log data folder in whatever directory you called this in. if it doesn't exists there, it's saved to /tmp/data", default=False) args = parser.parse_args() if args.gripper and args.simulation: @@ -68,7 +70,7 @@ def get_args(): if __name__ == "__main__": args = get_args() robot = RobotManager(args) - Mgoal = robot.defineGoalPoint() + Mgoal = robot.defineGoalPointCLI() clik_controller = getClikController(args) controlLoop = partial(controlLoopClik, robot, clik_controller) log_dict = { @@ -81,5 +83,6 @@ if __name__ == "__main__": # something that generates a file name from something in args. # also save args there log_dict, final_iteration = loop_manager.run() - saveLog(log_dict, final_iteration, args) + if args.save_log: + saveLog(log_dict, final_iteration, args) diff --git a/python/ur_simple_control/__pycache__/managers.cpython-311.pyc b/python/ur_simple_control/__pycache__/managers.cpython-311.pyc index 053fed9e52e9806ecfd57867b690a7f41b92295f..5455df7279181101a5bbc6a30cb325d201481eab 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__/clik_point_to_point.cpython-311.pyc b/python/ur_simple_control/clik/__pycache__/clik_point_to_point.cpython-311.pyc index 6b95c52eca9803e3be3409010974b2933bc51953..9b3a270c6e1c04ea59a98227f6d366babc3201fd 100644 Binary files a/python/ur_simple_control/clik/__pycache__/clik_point_to_point.cpython-311.pyc and b/python/ur_simple_control/clik/__pycache__/clik_point_to_point.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 b20bdc645d0677edc48f5c1d976df050f8be3c54..ba8399c1675a5522e05d691cb9155eefdb023440 100644 --- a/python/ur_simple_control/clik/clik_point_to_point.py +++ b/python/ur_simple_control/clik/clik_point_to_point.py @@ -139,7 +139,7 @@ def controlLoopClik(robot, clik_controller, i, past_data): # first check whether we're at the goal SEerror = robot.data.oMi[robot.JOINT_ID].actInv(robot.Mgoal) err_vector = pin.log6(SEerror).vector - if np.linalg.norm(err_vector) < robot.goal_error: + if np.linalg.norm(err_vector) < robot.args.goal_error: # print("Convergence achieved, reached destionation!") breakFlag = True # pin.computeJointJacobian is much different than the C++ version lel @@ -235,7 +235,7 @@ def moveL(args, robot, goal_point): if __name__ == "__main__": args = get_args() robot = RobotManager(args) - Mgoal = robot.defineGoalPoint() + Mgoal = robot.defineGoalPointCLI() clik_controller = getClikController(args) controlLoop = partial(controlLoopClik, robot, clik_controller) # we're not using any past data or logging, hence the empty arguments diff --git a/python/ur_simple_control/managers.py b/python/ur_simple_control/managers.py index 605a788f90a4898c9725d2995fd0bc66d85d143d..716a238d6d44df1c272c3c5decf47050f656e220 100644 --- a/python/ur_simple_control/managers.py +++ b/python/ur_simple_control/managers.py @@ -17,6 +17,7 @@ import signal from ur_simple_control.util.get_model import get_model from collections import deque from ur_simple_control.visualize.visualize import plotFromDict +from pinocchio.visualize import MeshcatVisualizer """ general notes @@ -156,6 +157,12 @@ class ControlLoopManager: #self.robot_manager.stopHandler(None, None) self.log_dict[key][i] = log_entry_dict[key] + # TODO: offload this to a different + # process, it's hindering real-time performance (not by a lot, + # but it does) + if self.args.visualize: + if i % 20 == 0: + self.robot_manager.viz.display(self.robot_manager.q) # break if done if breakFlag: break @@ -168,7 +175,7 @@ class ControlLoopManager: else: time.sleep(self.robot_manager.dt - diff) self.final_iteration = i - if args.debug_prints: + if self.args.debug_prints: if i < self.max_iterations -1: print("success in", i, "iterations!") else: @@ -232,13 +239,24 @@ class RobotManager: # load model # collision and visual models are none if args.visualize == False self.model, self.collision_model, self.visual_model, self.data = \ - get_model(args.visualize) + get_model() + # we're using meshcat exclusively. + # there are no good options, + # but this does work and isn't a dead project if args.visualize: - # ---> 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) - pass + # for whatever reason the hand-e files don't have/ + # meshcat can't read scaling information. + # so we scale manually + for geom in self.visual_model.geometryObjects: + if "hand" in geom.name: + s = geom.meshScale + # this looks exactly correct lmao + s *= 0.001 + geom.meshScale = s + self.viz = MeshcatVisualizer(self.model, self.collision_model, self.visual_model) + self.viz.initViewer(open=True) + self.viz.loadViewerModel() + # TODO: make general if self.gripper_flag and not self.pinocchio_only: @@ -545,7 +563,7 @@ class RobotManager: """ - defineGoalPoint + defineGoalPointCLI ------------------ --> best way to handle the goal is to tell the user where the gripper is in both UR tcp frame and with pinocchio and have them @@ -557,45 +575,41 @@ class RobotManager: but also you do want to have both options. obviously you go for the sliders in the case you're visualizing, makes no sense otherwise. """ - def defineGoalPoint(self): + def defineGoalPointCLI(self): q = self.getQ() # define goal pin.forwardKinematics(self.model, self.data, np.array(q)) 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: - print("In the following, first 3 numbers are x,y,z position, and second 3 are r,p,y angles") - print("Here's where the robot is currently. Ensure you know what the base frame is first.") - print("base frame end-effector pose from pinocchio:\n", \ - *self.data.oMi[6].translation.round(4), *pin.rpy.matrixToRpy(self.data.oMi[6].rotation).round(4)) - print("UR5e TCP:", *np.array(self.rtde_receive.getActualTCPPose()).round(4)) - # 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 = 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 - while True: - goal = input("Please enter the target end-effector position in the x.x,y.y,z.z format: ") - try: - e = "ok" - goal_list = goal.split(',') - for i in range(len(goal_list)): - goal_list[i] = float(goal_list[i]) - except: - e = sys.exc_info() - print("The input is not in the expected format. Try again.") - print(e) - if e == "ok": - Mgoal.translation = np.array(goal_list) - break - print("this is goal pose you defined:\n", Mgoal) - else: - raise NotImplementedError('Paths in the urdf or something else need to \ - be fixed to use this first. Also need to program in the sliders \ - and visualizing the goal frame. Sorry. Coming soon.') + print("You can only specify the translation right now.") + if not self.pinocchio_only: + print("In the following, first 3 numbers are x,y,z position, and second 3 are r,p,y angles") + print("Here's where the robot is currently. Ensure you know what the base frame is first.") + print("base frame end-effector pose from pinocchio:\n", \ + *self.data.oMi[6].translation.round(4), *pin.rpy.matrixToRpy(self.data.oMi[6].rotation).round(4)) + print("UR5e TCP:", *np.array(self.rtde_receive.getActualTCPPose()).round(4)) + # 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 = 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 + while True: + goal = input("Please enter the target end-effector position in the x.x,y.y,z.z format: ") + try: + e = "ok" + goal_list = goal.split(',') + for i in range(len(goal_list)): + goal_list[i] = float(goal_list[i]) + except: + e = sys.exc_info() + print("The input is not in the expected format. Try again.") + print(e) + if e == "ok": + Mgoal.translation = np.array(goal_list) + break + print("this is goal pose you defined:\n", Mgoal) + # NOTE i'm not deepcopying this on purpose # but that might be the preferred thing, we'll see self.Mgoal = Mgoal 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 177620f1965e84d10ab4a264f60aa2384ae543f1..625e9afb9c9bf6ff072333dee008f431cf9c1eb8 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__/logging_utils.cpython-311.pyc b/python/ur_simple_control/util/__pycache__/logging_utils.cpython-311.pyc index 759cafe5cf649d89b5731ceaa655e38fdaa3a52f..11e5dcc03d67ce58afde5d3306cdc033b3d7633e 100644 Binary files a/python/ur_simple_control/util/__pycache__/logging_utils.cpython-311.pyc and b/python/ur_simple_control/util/__pycache__/logging_utils.cpython-311.pyc differ diff --git a/python/ur_simple_control/util/get_model.py b/python/ur_simple_control/util/get_model.py index 452c0d301cdc5af0139a788ad73d13c18894a4dd..2d624babbace54bee57892bd25692f37c833fd5f 100644 --- a/python/ur_simple_control/util/get_model.py +++ b/python/ur_simple_control/util/get_model.py @@ -13,24 +13,29 @@ from importlib.resources import files # can't get the urdf reading with these functions to save my life, idk what or why ############################################################# -# PACKAGE_DIR IS THE WHOLE GIT REPO -# PACKAGE:// IS WHAT'S BEING REPLACED WITH THE PACKAGE_DIR ARGUMENT -# TODO: REWRITE URDFS TO THAT THEY FOLLOW THIS -# YOU GIVE ABSOLUTE PATH TO URDF THO. +# PACKAGE_DIR IS THE WHOLE UR_SIMPLE_CONTROL FOLDER (cos that's accessible from anywhere it's installed) +# PACKAGE:// IS WHAT'S BEING REPLACED WITH THE PACKAGE_DIR ARGUMENT IN THE URDF. +# YOU GIVE ABSOLUTE PATH TO THE URDF THO. ############################################################# - -def get_model(visualize): +""" +loads what needs to be loaded. +calibration for the particular robot was extracted from the yml +obtained from ros and appears here as magic numbers. +i have no idea how to extract calibration data without ros +and i have no plans to do so. +aligning what UR thinks is the world frame +and what we think is the world frame is not really necessary, +but it does aleviate some brain capacity while debugging. +having that said, supposedly there is a big missalignment (few cm) +between the actual robot and the non-calibrated model. +NOTE: this should be fixed for a proper release +""" +def get_model(): - #urdf_path_relative = "../robot_descriptions/urdf/ur5e_with_robotiq_hande.urdf" - # NOTE THIS ONE WORKS IF YOU'RE NOT LOADING VISUAL STUFF - #urdf_path_relative = files('ur_simple_control.robot_descriptions.urdf').joinpath('ur5e_with_robotiq_hande.urdf') - #urdf_path_relative = files('ur_simple_control.robot_descriptions').joinpath('ur5e_with_robotiq_hande.urdf') urdf_path_relative = files('ur_simple_control.robot_descriptions.urdf').joinpath('ur5e_with_robotiq_hande_FIXED_PATHS.urdf') urdf_path_absolute = os.path.abspath(urdf_path_relative) - #mesh_dir = "../robot_descriptions/" mesh_dir = files('ur_simple_control') - #mesh_dir_absolute = os.path.abspath(mesh_dir) mesh_dir_absolute = os.path.abspath(mesh_dir) shoulder_trans = np.array([0, 0, 0.1625134425523304]) @@ -57,33 +62,17 @@ def get_model(visualize): wrist_3_rpy = np.array([1.572215514545703, 3.141592653589793, 3.141592633687631]) wrist_3_se3 = pin.SE3(pin.rpy.rpyToMatrix(wrist_3_rpy),wrist_3_trans) - # TODO fix paths via python packaging, get this to work - if visualize: - #raise NotImplementedError('Paths in the urdf or something else need to be fixed to use this first. Sorry. Coming soon.') - model = None - collision_model = None - visual_model = None - print("urdf_path_absolute", type(urdf_path_absolute), urdf_path_absolute) - print("mesh_dir", type(mesh_dir_absolute), mesh_dir_absolute) - #model, collision_model, visual_model = pin.buildModelsFromUrdf(urdf_path_absolute, mesh_dir_absolute) - #print(urdf_path_absolute) - model = pin.buildModelFromUrdf(urdf_path_absolute) - visual_model = pin.buildGeomFromUrdf(model, urdf_path_absolute, pin.GeometryType.VISUAL, None, mesh_dir_absolute) - collision_model = pin.buildGeomFromUrdf(model, urdf_path_absolute, pin.GeometryType.COLLISION, None, mesh_dir_absolute) - #visual_model = pin.buildGeomFromUrdf(model, urdf_path_absolute, pin.GeometryType.VISUAL, None, mesh_dir_absolute) - #collision_model = pin.buildGeomFromUrdf(model, urdf_path_absolute, pin.GeometryType.COLLISION) - #geom_model = None - #package_dir = None - #mesh_loader = None - #visual_model = pin.buildGeomFromUrdf(model, urdf_path_absolute, pin.GeometryType.VISUAL, geom_model, package_dirs=mesh_dir_absolute) - #visual_model = pin.buildGeomFromUrdf(model, urdf_path_absolute, pin.GeometryType.VISUAL, package_dirs=mesh_dir_absolute) - #collision_model = pin.buildGeomFromUrdf(model, urdf_path_absolute, pin.GeometryType.COLLISION) - #data = pin.Data(model) - else: - model = pin.buildModelFromUrdf(urdf_path_absolute) - collision_model = None - visual_model = None + model = None + collision_model = None + visual_model = None + # this command just calls the ones below it. both are kept here + # in case pinocchio people decide to change their api. + #model, collision_model, visual_model = pin.buildModelsFromUrdf(urdf_path_absolute, mesh_dir_absolute) + model = pin.buildModelFromUrdf(urdf_path_absolute) + visual_model = pin.buildGeomFromUrdf(model, urdf_path_absolute, pin.GeometryType.VISUAL, None, mesh_dir_absolute) + collision_model = pin.buildGeomFromUrdf(model, urdf_path_absolute, pin.GeometryType.COLLISION, None, mesh_dir_absolute) + # updating joint placements. model.jointPlacements[1] = shoulder_se3 model.jointPlacements[2] = upper_arm_se3 model.jointPlacements[3] = forearm_se3 diff --git a/python/ur_simple_control/util/logging_utils.py b/python/ur_simple_control/util/logging_utils.py index c8f0c5d516ae35675b8624b16abef47998bc452b..99d4b3e8878c1095d2ea9d7677c870e510130991 100644 --- a/python/ur_simple_control/util/logging_utils.py +++ b/python/ur_simple_control/util/logging_utils.py @@ -1,7 +1,7 @@ import pickle import numpy as np +import os -# we're not def saveLog(log_dict, final_iteration, args): # shave off the zeros, noone needs 'em for key in log_dict: @@ -10,8 +10,13 @@ def saveLog(log_dict, final_iteration, args): # - 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" + if os.path.exists('./data'): + run_file_path = "./data/clik_run_001.pickle" + args_file_path = "./data/clik_run_001_args.pickle" + else: + os.makedirs('/tmp/data', exist_ok=True) + run_file_path = "/tmp/data/clik_run_001.pickle" + args_file_path = "/tmp/data/clik_run_001_args.pickle" # save the logged data # you need to open it binary for pickling log_file = open(run_file_path, 'wb') diff --git a/python/ur_simple_control/visualize/manipulator_visual_motion_analyzer.py b/python/ur_simple_control/visualize/manipulator_visual_motion_analyzer.py index b4b9380a88a1498e52b9345abd85ba72d4b23ce0..59952dc1d02ab557f1ff176a436e9bb7495d3802 100644 --- a/python/ur_simple_control/visualize/manipulator_visual_motion_analyzer.py +++ b/python/ur_simple_control/visualize/manipulator_visual_motion_analyzer.py @@ -151,8 +151,8 @@ class ManipulatorVisualMotionAnalyzer: # dicts not because they have to be, but just so that # they are named because that might be useful # and because names are the only thing keeping horrendous this code together - # TODO: current evil sharing of pointers (thanks python lmao) needs to be deleted - # ofc delete from ik_env, retain here (they're in both spots rn) + # TODO: current evil sharing of pointers (thanks python lmao) needs to be deleted + # ofc delete from ik_env, retain here (they're in both spots rn) # NOTE: you need to do ax.draw(artist) for blitting. so each artist needs to be connected # to its particular artist. so they can't all be in one dict. # we'll just increase the dict depth by 1, and add a named tuple to layer 1 (layer of axis) diff --git a/python/ur_simple_control/visualize/vedo_manipulator.py b/python/ur_simple_control/visualize/vedo_manipulator.py index ec5c249280683eb21b97b25a60de2a5b4d0996b8..d4a2a38726554c3231b7299b71d7f1253b06cd33 100644 --- a/python/ur_simple_control/visualize/vedo_manipulator.py +++ b/python/ur_simple_control/visualize/vedo_manipulator.py @@ -65,15 +65,6 @@ def get_args(): ####################################################################### -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() diff --git a/python/ur_simple_control/visualize/visualize.py b/python/ur_simple_control/visualize/visualize.py index 9e8c4677057aac406452ba5de2875c71a717da25..4c7f71e165100d56a01d20fcd83d075e6847334e 100644 --- a/python/ur_simple_control/visualize/visualize.py +++ b/python/ur_simple_control/visualize/visualize.py @@ -1,10 +1,13 @@ import numpy as np import matplotlib.pyplot as plt -# TODO let's try to make this general -# make plot_data a dictionary. -# every key is one subplot, and it's value -# is what you plot +""" +plotFromDict +------------ +plots logs stored in a dictionary +- every key is one subplot, and it's value + is what you plot +""" def plotFromDict(plot_data, final_iteration, args): # TODO cut zeros from data # TODO: replace with actual time @@ -25,3 +28,11 @@ def plotFromDict(plot_data, final_iteration, args): ax_dict[data_key].plot(t, plot_data[data_key][:final_iteration,j], color=colors[j], label=data_key + "_" + str(j)) ax_dict[data_key].legend() plt.show() + + +# you need to write out specifications for this. +# only then implement. +# this is too fucked to try to do it from your head +#class RealTimePlotter: +# def __init__(self, TODO): +