diff --git a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py index 0876a250f66824132c4e5fab1903325e3c285f2d..1cabaa0e29c10c4b58049881bc1ae2c6f2c829ff 100644 --- a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py +++ b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling.py @@ -11,6 +11,7 @@ from smc.control.joint_space.joint_space_point_to_point import moveJP from smc.control.cartesian_space.pink_p2p import ( DualArmIKSelfAvoidanceViaEndEffectorSpheres, ) +from smc.control.cartesian_space.cartesian_space_point_to_point import moveL from utils import get_args, constructInitialT_w_abs from dual_arm_cart_pulling_control_loop import DualArmCartPulling @@ -89,25 +90,39 @@ if __name__ == "__main__": ################################################### print("getting base to start of path") p_basegoal = path_base[0] - robot.mode = AbstractRobotManager.control_mode.base_only - CrocoBaseP2PMPC(args, robot, p_basegoal) + # NOTE: requires passing a reduced model to work because crocoddyl needs the model to formulate itself + # robot.mode = AbstractRobotManager.control_mode.base_only + robot.mode = robot.control_mode.whole_body + # CrocoBaseP2PMPC(args, robot, p_basegoal) # NOTE: alternative: navigate to start point and position arms simulatenously # NOTE: does not work well :( (would need finessing which is too time-consuming for what it's worth) # CrocoDualEEAndBaseP2PMPC(args, robot, pathSE3[0], T_absgoal_l, T_absgoal_r, p_base) + ########################################## + # orient base to look at the goal (yes this should've be handled with basep2p) + ########################################## + robot.mode = robot.control_mode.base_only + # NOTE: T_w_absgoal's x-axis points toward the path. + # z-axis points down by an undocumented choice + rotation_init_rpy = pin.rpy.matrixToRpy(T_w_absgoal.rotation) + rotation_init = pin.rpy.rpyToMatrix(0.0, 0.0, rotation_init_rpy[2] + np.pi / 2) + T_w_bgoal = pin.SE3(rotation_init, p_basegoal) + moveL(args, robot, T_w_bgoal) + ################################################### - # DualArmMoveL 10 cm above handlebar + # grasp handlebar ################################################### + # DualArmMoveL 10 cm above handlebar robot.mode = AbstractRobotManager.control_mode.upper_body - T_w_absgoal.translation[2] -= 0.1 + T_w_absgoal.translation[2] += 0.1 + print("going above handlebar") DualArmIKSelfAvoidanceViaEndEffectorSpheres( T_w_absgoal, T_absgoal_l, T_absgoal_r, args, robot ) - ################################################### # DualArmMoveL down to handlebar - ################################################### - T_w_absgoal.translation[2] += 0.1 + print("going down to handlebar") + T_w_absgoal.translation[2] -= 0.1 DualArmIKSelfAvoidanceViaEndEffectorSpheres( T_w_absgoal, T_absgoal_l, T_absgoal_r, args, robot ) @@ -116,6 +131,8 @@ if __name__ == "__main__": ################################################### # time.sleep(5) + # TODO: for final demo replace this with disjoint controller + robot._mode = robot.control_mode.whole_body DualArmCartPulling(args, robot, path_planner, T_absgoal_l, T_absgoal_r) if args.real: diff --git a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py index 28f4a868f4ad722e54106b81aa1635e052077d12..3c0b05cd1047b010d6e258d516331e76ac7fc4a9 100644 --- a/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py +++ b/examples/cart_pulling/control_sub_problems/dual_arm_cart_pulling_control_loop.py @@ -1,4 +1,4 @@ -from smc.robots.interfaces.whole_body_interface import ( +from smc.robots.interfaces.whole_body_dual_arm_interface import ( DualArmWholeBodyInterface, ) from smc.control.control_loop_manager import ControlLoopManager @@ -23,18 +23,7 @@ import types from argparse import Namespace from pinocchio import SE3, log6 from collections import deque - - -def initializePastData( - args: Namespace, T_w_e: SE3, p_base: np.ndarray, max_base_v: float -) -> np.ndarray: - # prepopulate past data to make base and cart be on the same path in the past - # (which didn't actually happen because this just started) - p_ee = T_w_e.translation[:2] - straight_line_path = np.linspace(p_ee, p_base, args.past_window_size) - # straight_line_path_timed = path2D_timed(args, straight_line_path, max_base_v) - # return straight_line_path_timed # this one is shortened to args.n_knots! and we want the whole buffer - return straight_line_path +from utils import initializePastData def DualArmCartPullingControlLoop( @@ -87,7 +76,9 @@ def DualArmCartPullingControlLoop( (last_point_only, np.zeros((len(trajectory_base), 1))) ) last_point_only = last_point_only * trajectory_base[-1] - ocp.warmstartAndReSolve(x0, data=(last_point_only)) + ocp.warmstartAndReSolve( + x0, data=(last_point_only, trajectorySE3_l, trajectorySE3_r) + ) xs = np.array(ocp.solver.xs) v_cmd = xs[1, robot.model.nq :] @@ -122,7 +113,7 @@ def DualArmCartPulling( a dynamics level, and velocities we command are actually extracted from the state x(q,dq). """ - robot._mode = DualArmWholeBodyInterface.control_mode.whole_body + robot._mode = robot.control_mode.whole_body # NOTE: i'm shoving these into the class here - bad style, # but i don't T_w_abs = robot.T_w_abs diff --git a/examples/cart_pulling/control_sub_problems/single_arm_cart_pulling_control_loop.py b/examples/cart_pulling/control_sub_problems/single_arm_cart_pulling_control_loop.py index c395830be43ca44c49c6fd633c2dc24db5633ad7..652e704c3bef0053b35ddd3357b25f3236e9a393 100644 --- a/examples/cart_pulling/control_sub_problems/single_arm_cart_pulling_control_loop.py +++ b/examples/cart_pulling/control_sub_problems/single_arm_cart_pulling_control_loop.py @@ -1,4 +1,4 @@ -from smc.robots.interfaces.whole_body_interface import ( +from smc.robots.interfaces.whole_body_single_arm_interface import ( SingleArmWholeBodyInterface, ) from smc.control.control_loop_manager import ControlLoopManager diff --git a/examples/cart_pulling/control_sub_problems/utils.py b/examples/cart_pulling/control_sub_problems/utils.py index 88c5c23d59196d4339c655ffdf3b367b0abf51e9..1f5e999e68d35700a51ea01b116591160f21f8c8 100644 --- a/examples/cart_pulling/control_sub_problems/utils.py +++ b/examples/cart_pulling/control_sub_problems/utils.py @@ -60,4 +60,18 @@ def constructInitialT_w_abs( handlebar_direction = -1 * direction handlebar_direction = handlebar_direction / np.linalg.norm(handlebar_direction) offset = args.base_to_handlebar_preferred_distance * handlebar_direction - return SE3(rotation, path_base[0] + offset) + translation = path_base[0] + offset + translation[2] = args.handlebar_height + return SE3(rotation, translation) + + +def initializePastData( + args: argparse.Namespace, T_w_e: SE3, p_base: np.ndarray, max_base_v: float +) -> np.ndarray: + # prepopulate past data to make base and cart be on the same path in the past + # (which didn't actually happen because this just started) + p_ee = T_w_e.translation[:2] + straight_line_path = np.linspace(p_ee, p_base, args.past_window_size) + # straight_line_path_timed = path2D_timed(args, straight_line_path, max_base_v) + # return straight_line_path_timed # this one is shortened to args.n_knots! and we want the whole buffer + return straight_line_path diff --git a/examples/cart_pulling/disjoint_control/mpc_base_clik_single_arm.py b/examples/cart_pulling/disjoint_control/mpc_base_clik_single_arm.py index 4852ebc06d027ef37ba9b49c2d1aa6e8e8d6de3e..41a1f7c909d53b905a2fdd695ad8dd2355431779 100644 --- a/examples/cart_pulling/disjoint_control/mpc_base_clik_single_arm.py +++ b/examples/cart_pulling/disjoint_control/mpc_base_clik_single_arm.py @@ -5,7 +5,9 @@ from smc.path_generation.path_math.path2d_to_6d import path2D_to_SE3 from smc.control.optimal_control.util import get_OCP_args from smc.control.cartesian_space import getClikArgs from smc.path_generation.planner import starPlanner, getPlanningArgs -from smc.control.optimal_control.croco_mpc_point_to_point import CrocoEEAndBaseP2PMPC +from smc.control.optimal_control.croco_point_to_point.mpc.base_and_single_arm_reference_mpc import ( + CrocoEEAndBaseP2PMPC, +) from smc.multiprocessing import ProcessManager from mpc_base_clik_single_arm_control_loop import BaseMPCANDEECLIKCartPulling diff --git a/examples/cart_pulling/disjoint_control/mpc_base_clik_single_arm_control_loop.py b/examples/cart_pulling/disjoint_control/mpc_base_clik_single_arm_control_loop.py index 2e5d09dc454bc3390f7afe47c30cbb1072f0c504..1a3a2b74d144bb54de551fff26347b89d0dffee4 100644 --- a/examples/cart_pulling/disjoint_control/mpc_base_clik_single_arm_control_loop.py +++ b/examples/cart_pulling/disjoint_control/mpc_base_clik_single_arm_control_loop.py @@ -1,13 +1,12 @@ -from smc.robots.interfaces.whole_body_interface import SingleArmWholeBodyInterface +from smc.robots.interfaces.whole_body_single_arm_interface import ( + SingleArmWholeBodyInterface, +) from smc.control.control_loop_manager import ControlLoopManager from smc.multiprocessing.process_manager import ProcessManager from smc.control.optimal_control.abstract_croco_ocp import CrocoOCP -from smc.control.optimal_control.path_following_croco_ocp import ( +from smc.control.optimal_control.croco_path_following.mpc.base_reference_mpc import ( BasePathFollowingOCP, ) -from smc.path_generation.path_math.path2d_to_6d import ( - path2D_to_SE3, -) from smc.path_generation.path_math.cart_pulling_path_math import ( construct_EE_path, ) @@ -16,7 +15,8 @@ from smc.control.controller_templates.path_following_template import ( PathFollowingFromPlannerCtrllLoopTemplate, ) from smc.control.cartesian_space.ik_solvers import dampedPseudoinverse -from smc.control.optimal_control.croco_mpc_path_following import initializePastData + +from utils import initializePastData import numpy as np from functools import partial diff --git a/examples/cart_pulling/disjoint_control/utils.py b/examples/cart_pulling/disjoint_control/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..1f5e999e68d35700a51ea01b116591160f21f8c8 --- /dev/null +++ b/examples/cart_pulling/disjoint_control/utils.py @@ -0,0 +1,77 @@ +from smc.control.optimal_control.util import get_OCP_args +from smc.path_generation.planner import getPlanningArgs +from smc.control.cartesian_space import getClikArgs +from smc import getMinimalArgParser + +import argparse +from pinocchio import SE3 +import numpy as np + + +def get_args() -> argparse.Namespace: + parser = getMinimalArgParser() + parser = get_OCP_args(parser) + parser = getClikArgs(parser) # literally just for goal error + parser = getPlanningArgs(parser) + parser.add_argument( + "--handlebar-height", + type=float, + default=0.5, + help="heigh of handlebar of the cart to be pulled", + ) + parser.add_argument( + "--base-to-handlebar-preferred-distance", + type=float, + default=0.5, + help="prefered path arclength from mobile base position to handlebar", + ) + parser.add_argument( + "--planner", + action=argparse.BooleanOptionalAction, + help="if on, you're in a pre-set map and a planner produce a plan to navigate. if off, you draw the path to be followed", + default=True, + ) + parser.add_argument( + "--draw-new", + action=argparse.BooleanOptionalAction, + help="are you drawing a new path or reusing the previous one", + default=False, + ) + parser.add_argument( + "--map-width", + type=float, + help="width of the map in meters (x-axis) - only used for drawing of the path", + default=3.0, + ) + parser.add_argument( + "--map-height", + type=float, + help="height of the map in meters (y-axis) - only used for drawing of the path", + default=3.0, + ) + args = parser.parse_args() + return args + + +def constructInitialT_w_abs( + args: argparse.Namespace, path_base: np.ndarray, rotation: np.ndarray +) -> SE3: + direction = path_base[1] - path_base[0] + handlebar_direction = -1 * direction + handlebar_direction = handlebar_direction / np.linalg.norm(handlebar_direction) + offset = args.base_to_handlebar_preferred_distance * handlebar_direction + translation = path_base[0] + offset + translation[2] = args.handlebar_height + return SE3(rotation, translation) + + +def initializePastData( + args: argparse.Namespace, T_w_e: SE3, p_base: np.ndarray, max_base_v: float +) -> np.ndarray: + # prepopulate past data to make base and cart be on the same path in the past + # (which didn't actually happen because this just started) + p_ee = T_w_e.translation[:2] + straight_line_path = np.linspace(p_ee, p_base, args.past_window_size) + # straight_line_path_timed = path2D_timed(args, straight_line_path, max_base_v) + # return straight_line_path_timed # this one is shortened to args.n_knots! and we want the whole buffer + return straight_line_path diff --git a/examples/cartesian_space/clik_point_to_point.py b/examples/cartesian_space/clik_point_to_point.py index db7ef64e84e2f09caa1d2d2c13505b0695a1f460..4d1f33cca7ffc4aeaee0c862d0bb839b5c0b5565 100644 --- a/examples/cartesian_space/clik_point_to_point.py +++ b/examples/cartesian_space/clik_point_to_point.py @@ -5,6 +5,7 @@ from smc.robots.utils import defineGoalPointCLI from smc.control.cartesian_space.cartesian_space_point_to_point import moveL import argparse +import numpy as np def get_args() -> argparse.Namespace: @@ -34,6 +35,7 @@ if __name__ == "__main__": print("Ain't no way you're going to a random goal on the real robot!") print("Look at the current pose, define something appropriate manually") T_w_goal = defineGoalPointCLI(robot) + T_w_goal.rotation = np.eye(3) # compliantMoveL(args, robot, Mgoal) print(robot.mode) moveL(args, robot, T_w_goal) diff --git a/examples/math_understanding/rotate_around_z.py b/examples/math_understanding/rotate_around_z.py new file mode 100644 index 0000000000000000000000000000000000000000..0c8b1e8d44d0eb4829184cd2838df71d24c34506 --- /dev/null +++ b/examples/math_understanding/rotate_around_z.py @@ -0,0 +1,30 @@ +from smc.visualization.meshcat_viewer_wrapper.visualizer import MeshcatVisualizer + +import time +import pinocchio as pin +import numpy as np +import meshcat_shapes + +A = pin.SE3.Identity() +B = pin.SE3.Identity() +B.rotation = pin.rpy.rpyToMatrix(0.0, 0.0, np.random.random()) +B.translation += np.random.random(3) + +interpolated = [] +t_line = np.linspace(0, 1, 20) +for t in t_line: + interpolated.append(pin.SE3.Interpolate(A, B, t)) + +visualizer = MeshcatVisualizer() +meshcat_shapes.frame(visualizer.viewer["A"], opacity=0.5) +meshcat_shapes.frame(visualizer.viewer["B"], opacity=0.5) +# meshcat_shapes.frame(visualizer.viewer["C"], opacity=0.5) +visualizer.viewer["A"].set_transform(A.homogeneous) +visualizer.viewer["B"].set_transform(B.homogeneous) +# visualizer.viewer["C"].set_transform(C.homogeneous) + +visualizer.addFramePath("", interpolated) +visualizer.addFramePath("", interpolated) + + +time.sleep(100) diff --git a/python/smc/control/cartesian_space/pink_p2p.py b/python/smc/control/cartesian_space/pink_p2p.py index 3322b726f4b385c841d01bbca8ef69e1d73e6720..7f91b62c28cd22b443f00e6c2f6ac29278f72785 100644 --- a/python/smc/control/cartesian_space/pink_p2p.py +++ b/python/smc/control/cartesian_space/pink_p2p.py @@ -30,7 +30,11 @@ def DualArmIKSelfAvoidanceViaEndEffectorSpheresCtrlLoop( t: int, past_data: dict[str, deque[np.ndarray]], ) -> tuple[np.ndarray, dict[str, np.ndarray], dict[str, np.ndarray]]: - configuration = pink.Configuration(robot.model, robot.data, robot.q) + + # NOTE: for control modes to work, passing a reduced model is required. + # this model takes in full q, not the reduced one + # TODO: re-write stuff to accomodate new model + configuration = pink.Configuration(robot.model, robot.data, robot._q) # NOTE: there are limits in pink, but they are a class where G matrix is constucted # combining lb and ub and forming Gx \leq h # this makes it possible to combine other stuff into this inequality constraint @@ -57,7 +61,8 @@ def DualArmIKSelfAvoidanceViaEndEffectorSpheresCtrlLoop( # NOTE: this is a temporary solution to deal with the fact that model isn't a property depending on control mode yet # TODO: make model a property depending on control mode to avoid this shitty issue if robot.mode == AbstractRobotManager.control_mode.upper_body: - v_cmd[:3] = 0.0 + # v_cmd[:3] = 0.0 + v_cmd = v_cmd[3:] dist_ee = np.linalg.norm(robot.T_w_l.translation - robot.T_w_r.translation) log_item = {"dist_ees": dist_ee.reshape((1,))} return v_cmd, {}, log_item @@ -101,7 +106,10 @@ def DualArmIKSelfAvoidanceViaEndEffectorSpheres( # NOTE: model and data are shared pointers between configuration and robot. # this is redundant as hell, but I don't have the time butcher pink right now - configuration = pink.Configuration(robot.model, robot.data, robot.q) + # NOTE: for control modes to work, passing a reduced model is required. + # this model takes in full q, not the reduced one + # TODO: re-write stuff to accomodate new model + configuration = pink.Configuration(robot.model, robot.data, robot._q) posture_task.set_target_from_configuration(configuration) left_end_effector_task.set_target(T_absgoal_l.act(T_w_absgoal)) right_end_effector_task.set_target(T_absgoal_r.act(T_w_absgoal)) diff --git a/python/smc/control/controller_templates/point_to_point.py b/python/smc/control/controller_templates/point_to_point.py index 04d57137387267325676ff10ec4a0acaa7967b40..1cc63d15d3778382a1feb48f98606210d6d4f9d1 100644 --- a/python/smc/control/controller_templates/point_to_point.py +++ b/python/smc/control/controller_templates/point_to_point.py @@ -8,11 +8,12 @@ from typing import Any, Callable import numpy as np from collections import deque -from smc.robots.interfaces.whole_body_interface import ( - SingleArmWholeBodyInterface, +from smc.robots.interfaces.whole_body_dual_arm_interface import ( DualArmWholeBodyInterface, ) - +from smc.robots.interfaces.whole_body_single_arm_interface import ( + SingleArmWholeBodyInterface, +) global control_loop_return control_loop_return = tuple[bool, dict[str, np.ndarray], dict[str, np.ndarray]] diff --git a/python/smc/control/joint_space/joint_space_point_to_point.py b/python/smc/control/joint_space/joint_space_point_to_point.py index 93e30cfe0f0a02b246b76eb17de0a9459f71da2f..06bc77d001d1f683a152955de5b50d1c9c0d29ad 100644 --- a/python/smc/control/joint_space/joint_space_point_to_point.py +++ b/python/smc/control/joint_space/joint_space_point_to_point.py @@ -7,6 +7,8 @@ from collections import deque from argparse import Namespace import pinocchio as pin +from smc.robots.interfaces.mobile_base_interface import MobileBaseInterface + def moveJControlLoop( q_desired: np.ndarray, @@ -22,7 +24,18 @@ def moveJControlLoop( """ breakFlag = False q = robot.q - q_error = pin.difference(robot.model, q, q_desired) + # TODO: make a robot output a different model depending on control mode + # to avoid having this issue + if (robot.control_mode == AbstractRobotManager.control_mode.whole_body) or ( + robot.control_mode == AbstractRobotManager.control_mode.base_only + ): + q_error = pin.difference(robot.model, q, q_desired) + else: + if issubclass(robot.__class__, MobileBaseInterface): + q_desired = q_desired[4:] + q_error = q_desired - q + # q_error = pin.difference(robot.model, q, q_desired) + err_norm = np.linalg.norm(q_error) if err_norm < 1e-3: breakFlag = True diff --git a/python/smc/control/optimal_control/croco_path_following/mpc/base_and_dual_arm_reference_mpc.py b/python/smc/control/optimal_control/croco_path_following/mpc/base_and_dual_arm_reference_mpc.py index 88c465bdfb3527be77acf32bb17f76205227288e..0686f9b7a3f07fdbc37ea65823adf6dde5d71280 100644 --- a/python/smc/control/optimal_control/croco_path_following/mpc/base_and_dual_arm_reference_mpc.py +++ b/python/smc/control/optimal_control/croco_path_following/mpc/base_and_dual_arm_reference_mpc.py @@ -1,4 +1,4 @@ -from smc.robots.interfaces.whole_body_interface import ( +from smc.robots.interfaces.whole_body_dual_arm_interface import ( DualArmWholeBodyInterface, ) from smc.control.control_loop_manager import ControlLoopManager diff --git a/python/smc/control/optimal_control/croco_path_following/mpc/base_and_single_arm_reference_mpc.py b/python/smc/control/optimal_control/croco_path_following/mpc/base_and_single_arm_reference_mpc.py index 7124b094212f08b981d972f96c5370374c0d83de..8d3fae0cfdd47358f08eb096011e77f59c3ce52d 100644 --- a/python/smc/control/optimal_control/croco_path_following/mpc/base_and_single_arm_reference_mpc.py +++ b/python/smc/control/optimal_control/croco_path_following/mpc/base_and_single_arm_reference_mpc.py @@ -1,4 +1,4 @@ -from smc.robots.interfaces.whole_body_interface import ( +from smc.robots.interfaces.whole_body_single_arm_interface import ( SingleArmWholeBodyInterface, ) from smc.control.control_loop_manager import ControlLoopManager diff --git a/python/smc/control/optimal_control/croco_path_following/ocp/base_and_dual_arm_reference_ocp.py b/python/smc/control/optimal_control/croco_path_following/ocp/base_and_dual_arm_reference_ocp.py index fd7784023032e67d2cf90f31dac7f57b9fcc50d1..0a887add5dcbc696f232a85a8d80c38bb09802c9 100644 --- a/python/smc/control/optimal_control/croco_path_following/ocp/base_and_dual_arm_reference_ocp.py +++ b/python/smc/control/optimal_control/croco_path_following/ocp/base_and_dual_arm_reference_ocp.py @@ -1,7 +1,7 @@ from smc.control.optimal_control.croco_point_to_point.ocp.base_and_dual_arm_reference_ocp import ( BaseAndDualArmIKOCP, ) -from smc.robots.interfaces.whole_body_interface import ( +from smc.robots.interfaces.whole_body_dual_arm_interface import ( DualArmWholeBodyInterface, ) diff --git a/python/smc/control/optimal_control/croco_path_following/ocp/base_and_single_arm_reference_ocp.py b/python/smc/control/optimal_control/croco_path_following/ocp/base_and_single_arm_reference_ocp.py index ef4072c25e1e47259cab993752734d63db9869b5..906d7b4754b7b00696469c68232540962f38696d 100644 --- a/python/smc/control/optimal_control/croco_path_following/ocp/base_and_single_arm_reference_ocp.py +++ b/python/smc/control/optimal_control/croco_path_following/ocp/base_and_single_arm_reference_ocp.py @@ -1,7 +1,7 @@ from smc.control.optimal_control.croco_point_to_point.ocp.base_and_single_arm_reference_ocp import ( BaseAndSingleArmIKOCP, ) -from smc.robots.interfaces.whole_body_interface import ( +from smc.robots.interfaces.whole_body_single_arm_interface import ( SingleArmWholeBodyInterface, ) diff --git a/python/smc/control/optimal_control/croco_point_to_point/mpc/base_and_dual_arm_reference_mpc.py b/python/smc/control/optimal_control/croco_point_to_point/mpc/base_and_dual_arm_reference_mpc.py index 9dd11edf7d61e6663d2efc93809a49595d08e631..05671ac70557dcdbdfb4df2554668720ee623ac1 100644 --- a/python/smc/control/optimal_control/croco_point_to_point/mpc/base_and_dual_arm_reference_mpc.py +++ b/python/smc/control/optimal_control/croco_point_to_point/mpc/base_and_dual_arm_reference_mpc.py @@ -1,10 +1,9 @@ from smc.control.controller_templates.point_to_point import ( DualEEAndBaseP2PCtrlLoopTemplate, ) -from smc.robots.interfaces.whole_body_interface import ( +from smc.robots.interfaces.whole_body_dual_arm_interface import ( DualArmWholeBodyInterface, ) -from smc.control.optimal_control.abstract_croco_ocp import CrocoOCP from smc.control.optimal_control.croco_point_to_point.ocp.base_and_dual_arm_reference_ocp import ( BaseAndDualArmIKOCP, ) @@ -18,7 +17,7 @@ from argparse import Namespace def CrocoP2PDualEEAndBaseMPCControlLoop( - ocp, + ocp: BaseAndDualArmIKOCP, T_w_absgoal: pin.SE3, T_absgoal_l: pin.SE3, T_absgoal_r: pin.SE3, diff --git a/python/smc/control/optimal_control/croco_point_to_point/mpc/base_and_single_arm_reference_mpc.py b/python/smc/control/optimal_control/croco_point_to_point/mpc/base_and_single_arm_reference_mpc.py index 2c6a411c70858c8649601a2a57f0d3605a8dd906..8ccbc3fdc5a68e27d014f4661bea2a5859523ae8 100644 --- a/python/smc/control/optimal_control/croco_point_to_point/mpc/base_and_single_arm_reference_mpc.py +++ b/python/smc/control/optimal_control/croco_point_to_point/mpc/base_and_single_arm_reference_mpc.py @@ -1,7 +1,7 @@ from smc.control.controller_templates.point_to_point import ( EEAndBaseP2PCtrlLoopTemplate, ) -from smc.robots.interfaces.whole_body_interface import ( +from smc.robots.interfaces.whole_body_single_arm_interface import ( SingleArmWholeBodyInterface, ) from smc.control.optimal_control.abstract_croco_ocp import CrocoOCP diff --git a/python/smc/control/optimal_control/croco_point_to_point/ocp/base_and_dual_arm_reference_ocp.py b/python/smc/control/optimal_control/croco_point_to_point/ocp/base_and_dual_arm_reference_ocp.py index 44c342f6daa0f6500e13b4c38022e763c1a9bc50..d85fefd4298e436dc5b1174337e973b8535488be 100644 --- a/python/smc/control/optimal_control/croco_point_to_point/ocp/base_and_dual_arm_reference_ocp.py +++ b/python/smc/control/optimal_control/croco_point_to_point/ocp/base_and_dual_arm_reference_ocp.py @@ -1,4 +1,4 @@ -from smc.robots.interfaces.whole_body_interface import ( +from smc.robots.interfaces.whole_body_dual_arm_interface import ( DualArmWholeBodyInterface, ) from smc.control.optimal_control.croco_point_to_point.ocp.base_reference_ocp import ( diff --git a/python/smc/control/optimal_control/croco_point_to_point/ocp/base_and_single_arm_reference_ocp.py b/python/smc/control/optimal_control/croco_point_to_point/ocp/base_and_single_arm_reference_ocp.py index 75f4d1c0609628d5369c285ba09248af6b6f35bf..1ef96450927e85e695b196d8522dfa2fc5b3b397 100644 --- a/python/smc/control/optimal_control/croco_point_to_point/ocp/base_and_single_arm_reference_ocp.py +++ b/python/smc/control/optimal_control/croco_point_to_point/ocp/base_and_single_arm_reference_ocp.py @@ -1,4 +1,4 @@ -from smc.robots.interfaces.whole_body_interface import ( +from smc.robots.interfaces.whole_body_single_arm_interface import ( SingleArmWholeBodyInterface, ) from smc.control.optimal_control.croco_point_to_point.ocp.base_reference_ocp import ( diff --git a/python/smc/robots/implementations/heron.py b/python/smc/robots/implementations/heron.py index d322e996bdac355da570fcae6c0bbcf3e3fe11e7..a409401f54292ccd83c760969696c748cf06ef92 100644 --- a/python/smc/robots/implementations/heron.py +++ b/python/smc/robots/implementations/heron.py @@ -6,7 +6,7 @@ from smc.robots.interfaces.force_torque_sensor_interface import ( from smc.robots.interfaces.mobile_base_interface import ( get_mobile_base_model, ) -from smc.robots.interfaces.whole_body_interface import SingleArmWholeBodyInterface +from smc.robots.interfaces.whole_body_single_arm_interface import SingleArmWholeBodyInterface from smc.robots.implementations.ur5e import get_model from smc.robots.grippers.robotiq.robotiq_gripper import RobotiqGripper from smc.robots.grippers.rs485_robotiq.rs485_robotiq import RobotiqHand diff --git a/python/smc/robots/implementations/mobile_yumi.py b/python/smc/robots/implementations/mobile_yumi.py index 8d142e13887393f15f53fdeca0461d39b8db0bab..8c62ce22fd990ee51375a7ecfe25ce9f9ca91f1d 100644 --- a/python/smc/robots/implementations/mobile_yumi.py +++ b/python/smc/robots/implementations/mobile_yumi.py @@ -1,9 +1,12 @@ from smc.robots.abstract_simulated_robotmanager import AbstractSimulatedRobotManager -from smc.robots.interfaces.whole_body_interface import DualArmWholeBodyInterface +from smc.robots.interfaces.whole_body_dual_arm_interface import ( + DualArmWholeBodyInterface, +) from smc.robots.interfaces.mobile_base_interface import ( get_mobile_base_model, ) from smc.robots.implementations.yumi import get_yumi_model +from smc.robots.abstract_robotmanager import AbstractRobotManager import pinocchio as pin from argparse import Namespace @@ -52,8 +55,26 @@ class AbstractMobileYuMiRobotManager(DualArmWholeBodyInterface): 1.400, ] ) + # NOTE: there's a shitton of stuff to re-write for this to work, and i'm not doing it now + # self._base_only_model, _ = get_mobile_base_model(underactuated=False) + # self._upper_body_model, _, _, _ = get_yumi_model() + # print(self._upper_body_model) + super().__init__(args) + # NOTE: there's a shitton of stuff to re-write for this to work, and i'm not doing it now + + +# @property +# def model(self) -> pin.Model: +# if self._mode == AbstractRobotManager.control_mode.whole_body: +# return self._model +# if self._mode == AbstractRobotManager.control_mode.base_only: +# return self._base_only_model +# if self._mode == AbstractRobotManager.control_mode.upper_body: +# return self._upper_body_model +# return self._model + class SimulatedMobileYuMiRobotManager( AbstractSimulatedRobotManager, AbstractMobileYuMiRobotManager diff --git a/python/smc/robots/interfaces/__init__.py b/python/smc/robots/interfaces/__init__.py index bc7ff830cf8a0b67c96dce94e013d1ba55628aa3..db32966c5fc9b79640822b5025bd9e418498f946 100644 --- a/python/smc/robots/interfaces/__init__.py +++ b/python/smc/robots/interfaces/__init__.py @@ -2,4 +2,5 @@ from .dual_arm_interface import * from .force_torque_sensor_interface import * from .mobile_base_interface import * from .single_arm_interface import * -from .whole_body_interface import * +from .whole_body_dual_arm_interface import * +from .whole_body_single_arm_interface import * diff --git a/python/smc/robots/interfaces/mobile_base_interface.py b/python/smc/robots/interfaces/mobile_base_interface.py index 6a266da4a887624c6720ee2b812406beabbeb502..2bb2a9170a4e3b19b662866dcf99f01cd718b801 100644 --- a/python/smc/robots/interfaces/mobile_base_interface.py +++ b/python/smc/robots/interfaces/mobile_base_interface.py @@ -83,10 +83,14 @@ class MobileBaseInterface(AbstractRobotManager): self.forwardKinematics() def getJacobian(self) -> np.ndarray: - J = pin.computeFrameJacobian( - self.model, self.data, self._q, self._base_frame_id - ) - return J + # J = pin.computeFrameJacobian( + # self.model, self.data, self._q, self._base_frame_id + # ) + # return J + J_base = np.zeros((6, 3)) + J_base[:2, :2] = self.T_w_b.rotation[:2, :2] + J_base[5, 2] = 1 + return J_base def get_mobile_base_model(underactuated: bool) -> tuple[pin.Model, pin.GeometryModel]: diff --git a/python/smc/robots/interfaces/whole_body_interface.py b/python/smc/robots/interfaces/whole_body_dual_arm_interface.py similarity index 57% rename from python/smc/robots/interfaces/whole_body_interface.py rename to python/smc/robots/interfaces/whole_body_dual_arm_interface.py index da3800866567254e26900d7f489a1409fe879de9..9ab517bf074d6fb56ae5fc550ffdaca47bad654c 100644 --- a/python/smc/robots/interfaces/whole_body_interface.py +++ b/python/smc/robots/interfaces/whole_body_dual_arm_interface.py @@ -1,152 +1,17 @@ from smc.robots.abstract_robotmanager import AbstractRobotManager from smc.robots.interfaces.mobile_base_interface import MobileBaseInterface -from smc.robots.interfaces.single_arm_interface import SingleArmInterface from smc.robots.interfaces.dual_arm_interface import DualArmInterface import pinocchio as pin from argparse import Namespace import numpy as np -from copy import deepcopy + +# from copy import deepcopy # TODO: put back in when python3.12 will be safe to use # from typing import override -class SingleArmWholeBodyInterface(SingleArmInterface, MobileBaseInterface): - """ - SingleArmWholeBodyInterface - --------------------------- - exists to provide either: - 1) whole body values - 2) base only value - 3) arm only value - - what you get depends on the mode you set - they're enumerate as above - """ - - def __init__(self, args: Namespace): - if args.debug_prints: - print("SingleArmWholeBodyInterface init") - self._mode: AbstractRobotManager.control_mode - self._available_modes: list[AbstractRobotManager.control_mode] = [ - AbstractRobotManager.control_mode.whole_body, - AbstractRobotManager.control_mode.base_only, - AbstractRobotManager.control_mode.upper_body, - ] - super().__init__(args) - - # TODO: override model property to produce the reduced version - # depending on the control mode. - # you might want to instantiate all of them in advance for easy switching later - # NOTE: that this is currently only important for ocp construction, - # even though it's obviously the correct move either way - - # @AbstractRobotManager.mode.setter - # def mode(self, mode: AbstractRobotManager.control_mode) -> None: - # assert type(mode) in self._available_modes - # self._mode = mode - - # TODO: put back in when python3.12 will be safe to use - # @override - @property - def q(self) -> np.ndarray: - if self._mode == self.control_mode.base_only: - return self._q[:4] - - if self._mode == self.control_mode.upper_body: - return self._q[4:] - - return self._q.copy() - - @property - def nq(self): - if self._mode == self.control_mode.base_only: - return 4 - - if self._mode == self.control_mode.upper_body: - return self.model.nq - 4 - - return self.model.nq - - @property - def v(self) -> np.ndarray: - if self._mode == self.control_mode.base_only: - return self._v[:3] - - if self._mode == self.control_mode.upper_body: - return self._v[3:] - - return self._v.copy() - - @property - def nv(self): - if self._mode == self.control_mode.base_only: - return 3 - - if self._mode == self.control_mode.upper_body: - return self.model.nv - 3 - - return self.model.nv - - # TODO: put back in when python3.12 will be safe to use - # @override - @property - def max_v(self) -> np.ndarray: - if self._mode == self.control_mode.base_only: - return self._max_v[:3] - if self._mode == self.control_mode.upper_body: - return self._max_v[3:] - return self._max_v.copy() - - # TODO: put back in when python3.12 will be safe to use - # @override - def forwardKinematics(self) -> None: - pin.forwardKinematics( - self.model, - self.data, - self._q, - ) - pin.updateFramePlacement(self.model, self.data, self._ee_frame_id) - pin.updateFramePlacement(self.model, self.data, self._base_frame_id) - self._T_w_e = self.data.oMf[self._ee_frame_id].copy() - self._T_w_b = self.data.oMf[self._base_frame_id].copy() - - # TODO: put back in when python3.12 will be safe to use - # @override - def getJacobian(self) -> np.ndarray: - J_full = pin.computeFrameJacobian( - self.model, self.data, self._q, self._ee_frame_id - ) - if self._mode == self.control_mode.base_only: - return J_full[:, :3] - - if self._mode == self.control_mode.upper_body: - return J_full[:, 3:] - - return J_full - - # TODO: put back in when python3.12 will be safe to use - # @override - def sendVelocityCommand(self, v) -> None: - """ - sendVelocityCommand - ------------------- - 1) saturate the command to comply with hardware limits or smaller limits you've set - 2) send it via the particular robot's particular communication interface - """ - assert type(v) == np.ndarray - - if self._mode == self.control_mode.base_only: - v = np.hstack((v, np.zeros(self.model.nv - 3))) - - if self._mode == self.control_mode.upper_body: - v = np.hstack((np.zeros(3), v)) - - assert len(v) == self.model.nv - v = np.clip(v, -1 * self._max_v, self._max_v) - self.sendVelocityCommandToReal(v) - - class DualArmWholeBodyInterface(DualArmInterface, MobileBaseInterface): def __init__(self, args: Namespace): if args.debug_prints: @@ -158,20 +23,23 @@ class DualArmWholeBodyInterface(DualArmInterface, MobileBaseInterface): AbstractRobotManager.control_mode.left_arm_only, AbstractRobotManager.control_mode.right_arm_only, ] -# self._full_model = deepcopy(self._model) -# self._base_only_model = pin.buildReducedModel(self._model, [i for i in range(1, self._model.njoints + 1) if i > 1], np.zeros(self._model.nq)) -# self._upper_body_model = pin.buildReducedModel(self._model, [i for i in range(1, self._model.njoints + 1) if i < 2], np.zeros(self._model.nq)) + # self._full_model = deepcopy(self._model) + # self._base_only_model = pin.buildReducedModel(self._model, [i for i in range(1, self._model.njoints + 1) if i > 1], np.zeros(self._model.nq)) + # NOTE: if you try to take out the mobile base joint, i.e. the first joint, i.e. a planarJoint, you get a segmentation fault :( + # meaning this needs to be done on a case-by-case basis + # also there's a shitton of stuff to re-write and i'm not doing it + # self._upper_body_model = pin.buildReducedModel(self._model, [i for i in range(1, self._model.njoints + 1) if i < 2], np.zeros(self._model.nq)) super().__init__(args) - -# @property -# def model(self) -> pin.Model: -# if self.control_mode == AbstractRobotManager.control_mode.whole_body: -# return self._full_model -# if self.control_mode == AbstractRobotManager.control_mode.base_only: -# return self._base_only_model -# if self.control_mode == AbstractRobotManager.control_mode.upper_body: -# return self._upper_body_model -# return self._full_model + + # @property + # def model(self) -> pin.Model: + # if self.control_mode == AbstractRobotManager.control_mode.whole_body: + # return self._full_model + # if self.control_mode == AbstractRobotManager.control_mode.base_only: + # return self._base_only_model + # if self.control_mode == AbstractRobotManager.control_mode.upper_body: + # return self._upper_body_model + # return self._full_model # TODO: override model property to produce the reduced version # depending on the control mode. @@ -260,6 +128,19 @@ class DualArmWholeBodyInterface(DualArmInterface, MobileBaseInterface): return self._max_v[3 + (self.model.nv - 3) // 2 :] return self._max_v.copy() + # NOTE: lil' bit of evil to access cartesian controllers for single arm without changing the controller + @property + def T_w_e(self): + if self.mode == self.control_mode.left_arm_only: + return self._T_w_l.copy() + if self.mode == self.control_mode.right_arm_only: + return self._T_w_r.copy() + if self.mode == self.control_mode.upper_body: + return self._T_w_abs.copy() + if self.mode == self.control_mode.base_only: + return self._T_w_b.copy() + return self._T_w_abs.copy() + # TODO: put back in when python3.12 will be safe to use # @override def forwardKinematics(self) -> None: @@ -290,7 +171,11 @@ class DualArmWholeBodyInterface(DualArmInterface, MobileBaseInterface): # for efficiency of course it would be best to construct it in place, # but who cares if it runs on time either way. if self._mode == self.control_mode.base_only: - return J_left_with_base[:, :3] + J_base = np.zeros((6, 3)) + J_base[:2, :2] = self.T_w_b.rotation[:2, :2] + J_base[5, 2] = 1 + return J_base + # return J_left_with_base[:, :3] J_right = pin.computeFrameJacobian( self.model, self.data, self._q, self._r_ee_frame_id @@ -333,5 +218,4 @@ class DualArmWholeBodyInterface(DualArmInterface, MobileBaseInterface): v_cmd_to_real[3 + (self.model.nv - 3) // 2 :] = v_cmd v_cmd_to_real = np.clip(v_cmd_to_real, -1 * self._max_v, self._max_v) - self.sendVelocityCommandToReal(v_cmd_to_real) diff --git a/python/smc/robots/interfaces/whole_body_single_arm_interface.py b/python/smc/robots/interfaces/whole_body_single_arm_interface.py new file mode 100644 index 0000000000000000000000000000000000000000..a9c5fd4d6e633ccb8ac10dd4ad929b8b367cbca3 --- /dev/null +++ b/python/smc/robots/interfaces/whole_body_single_arm_interface.py @@ -0,0 +1,156 @@ +from smc.robots.abstract_robotmanager import AbstractRobotManager +from smc.robots.interfaces.mobile_base_interface import MobileBaseInterface +from smc.robots.interfaces.single_arm_interface import SingleArmInterface + +import pinocchio as pin +from argparse import Namespace +import numpy as np + +# from copy import deepcopy + +# TODO: put back in when python3.12 will be safe to use +# from typing import override + + +class SingleArmWholeBodyInterface(SingleArmInterface, MobileBaseInterface): + """ + SingleArmWholeBodyInterface + --------------------------- + exists to provide either: + 1) whole body values + 2) base only value + 3) arm only value + + what you get depends on the mode you set - they're enumerate as above + """ + + def __init__(self, args: Namespace): + if args.debug_prints: + print("SingleArmWholeBodyInterface init") + self._mode: AbstractRobotManager.control_mode + self._available_modes: list[AbstractRobotManager.control_mode] = [ + AbstractRobotManager.control_mode.whole_body, + AbstractRobotManager.control_mode.base_only, + AbstractRobotManager.control_mode.upper_body, + ] + super().__init__(args) + + # TODO: override model property to produce the reduced version + # depending on the control mode. + # you might want to instantiate all of them in advance for easy switching later + # NOTE: that this is currently only important for ocp construction, + # even though it's obviously the correct move either way + + # @AbstractRobotManager.mode.setter + # def mode(self, mode: AbstractRobotManager.control_mode) -> None: + # assert type(mode) in self._available_modes + # self._mode = mode + + # TODO: put back in when python3.12 will be safe to use + # @override + @property + def q(self) -> np.ndarray: + if self._mode == self.control_mode.base_only: + return self._q[:4] + + if self._mode == self.control_mode.upper_body: + return self._q[4:] + + return self._q.copy() + + @property + def nq(self): + if self._mode == self.control_mode.base_only: + return 4 + + if self._mode == self.control_mode.upper_body: + return self.model.nq - 4 + + return self.model.nq + + @property + def v(self) -> np.ndarray: + if self._mode == self.control_mode.base_only: + return self._v[:3] + + if self._mode == self.control_mode.upper_body: + return self._v[3:] + + return self._v.copy() + + @property + def nv(self): + if self._mode == self.control_mode.base_only: + return 3 + + if self._mode == self.control_mode.upper_body: + return self.model.nv - 3 + + return self.model.nv + + # TODO: put back in when python3.12 will be safe to use + # @override + @property + def max_v(self) -> np.ndarray: + if self._mode == self.control_mode.base_only: + return self._max_v[:3] + if self._mode == self.control_mode.upper_body: + return self._max_v[3:] + return self._max_v.copy() + + # NOTE: lil' bit of evil to access cartesian controllers just for the base without changing the controller + @property + def T_w_e(self): + if self.mode == self.control_mode.upper_body: + return self._T_w_e.copy() + if self.mode == self.control_mode.base_only: + return self._T_w_b.copy() + return self._T_w_e.copy() + + # TODO: put back in when python3.12 will be safe to use + # @override + def forwardKinematics(self) -> None: + pin.forwardKinematics( + self.model, + self.data, + self._q, + ) + pin.updateFramePlacement(self.model, self.data, self._ee_frame_id) + pin.updateFramePlacement(self.model, self.data, self._base_frame_id) + self._T_w_e = self.data.oMf[self._ee_frame_id].copy() + self._T_w_b = self.data.oMf[self._base_frame_id].copy() + + # TODO: put back in when python3.12 will be safe to use + # @override + def getJacobian(self) -> np.ndarray: + J_full = pin.computeFrameJacobian( + self.model, self.data, self._q, self._ee_frame_id + ) + if self._mode == self.control_mode.base_only: + return J_full[:, :3] + + if self._mode == self.control_mode.upper_body: + return J_full[:, 3:] + + return J_full + + # TODO: put back in when python3.12 will be safe to use + # @override + def sendVelocityCommand(self, v) -> None: + """ + sendVelocityCommand + ------------------- + 1) saturate the command to comply with hardware limits or smaller limits you've set + 2) send it via the particular robot's particular communication interface + """ + assert type(v) == np.ndarray + + if self._mode == self.control_mode.base_only: + v = np.hstack((v, np.zeros(self.model.nv - 3))) + + if self._mode == self.control_mode.upper_body: + v = np.hstack((np.zeros(3), v)) + + assert len(v) == self.model.nv + v = np.clip(v, -1 * self._max_v, self._max_v) + self.sendVelocityCommandToReal(v)