Tarea 11

Trayectorias de robots

Regresar a Teoría

Introducción

Se utiliza la librería de roboticstoolbox-python para el análisis.

Descargar teoría PDF
...

Código de trayectorias para calcular el espacio de trabajo

    import numpy as np
    import matplotlib.pyplot as plt
    
    # Función para calcular el espacio de trabajo (esfera con cono invertido)
    def calcular_espacio_trabajo(radio_max, apertura_cono, puntos=10000):
        # Generar puntos aleatorios dentro de una esfera
        puntos_3d = []
        for _ in range(puntos):
            # Generar coordenadas esféricas aleatorias
            r = radio_max * np.cbrt(np.random.rand())  # Para que la distribución sea uniforme
            theta = np.random.uniform(0, 2 * np.pi)
            phi = np.random.uniform(0, np.pi)
            
            # Convertir a coordenadas cartesianas
            x = r * np.sin(phi) * np.cos(theta)
            y = r * np.sin(phi) * np.sin(theta)
            z = r * np.cos(phi)
    
            # Comprobar si el punto está fuera del cono invertido
            angulo_cono = np.arctan2(np.sqrt(x**2 + y**2), -z)  # Ahora comprobamos el ángulo con respecto al eje Z negativo
            if angulo_cono > apertura_cono:
                puntos_3d.append((x, y, z))
        
        return np.array(puntos_3d)
    
    # Función para dibujar el cubo dentro de la esfera
    def dibujar_cubo(radio_max):
        # El cubo debe estar dentro de la esfera, y su diagonal debe ser <= al diámetro de la esfera
        lado_cubo = 2 * radio_max / np.sqrt(3)  # Calcular el lado del cubo
        puntos_cubo = []
    
        for x in np.linspace(-lado_cubo/2, lado_cubo/2, num=15):  # Añadir puntos intermedios para una mejor visualización
            for y in np.linspace(-lado_cubo/2, lado_cubo/2, num=15):
                for z in np.linspace(-lado_cubo/2, lado_cubo/2, num=15):
                    puntos_cubo.append((x, y, z))
    
        return np.array(puntos_cubo)
    
    # Función para filtrar los puntos dentro del cubo que son afectados por el cono
    def filtrar_puntos_por_cono(puntos_cubo, apertura_cono):
        puntos_filtrados = []
        for punto in puntos_cubo:
            x, y, z = punto
            angulo_cono = np.arctan2(np.sqrt(x**2 + y**2), -z)  # Ángulo con respecto al eje Z negativo
            if angulo_cono > apertura_cono:  # Solo conservar puntos fuera del cono
                puntos_filtrados.append((x, y, z))
        return np.array(puntos_filtrados)
    
    
    ########################################################################################
    #                           FUNCIONES DE PRUEBA
    ########################################################################################
    
    # Función para verificar si un punto es verde o azul
    def es_punto_lineal_o_articulado(x, y, z, radio_max, apertura_cono):
        # El cubo debe estar dentro de la esfera, y su diagonal debe ser <= al diámetro de la esfera
        lado_cubo = 2 * radio_max / np.sqrt(3)  # Calcular el lado del cubo
        
        # Comprobación del cubo: El punto debe estar dentro de los límites del cubo
        dentro_del_cubo = abs(x) <= lado_cubo/2 and abs(y) <= lado_cubo/2 and abs(z) <= lado_cubo/2
        
        # Comprobación del cono: El punto debe estar fuera del cono (se calcula el ángulo)
        angulo_cono = np.arctan2(np.sqrt(x**2 + y**2), -z)  # Ángulo con respecto al eje Z negativo
        fuera_del_cono = angulo_cono > apertura_cono
    
        # Verificar si el punto es verde
        es_lineal = dentro_del_cubo and fuera_del_cono
        
        # Verificar si el punto es azul
        # El punto debe estar dentro de la esfera y fuera del cono
        distancia_al_origen = np.sqrt(x**2 + y**2 + z**2)
        es_articular = distancia_al_origen <= radio_max and fuera_del_cono
    
        return es_lineal, es_articular
    
    # Ejemplo de uso:
    x, y, z = 0.005, 0.004, 0.15  # Punto de ejemplo
    radio_max = 18/100  # Radio máximo de la esfera
    apertura_cono = np.deg2rad(60)  # Ángulo de apertura del cono
    
    print(radio_max, apertura_cono)
    
    # Verificar si el punto es verde o azul
    es_lineal, es_articular = es_punto_lineal_o_articulado(x, y, z, radio_max, apertura_cono)
    print(f"¿Es el punto verde? {es_lineal}")
    print(f"¿Es el punto azul? {es_articular}")
    #Revisando WP10... (0.005, 0.004806308479691594, 0.151378186779085)
    
    ########################################################################################
    #                           PLOT DE ESPACIO DE TRABAJO
    ########################################################################################
    
    # Calcular puntos del espacio de trabajo
    puntos_trabajo = calcular_espacio_trabajo(radio_max, apertura_cono)
    
    # Dibujar el cubo dentro de la esfera
    puntos_cubo = dibujar_cubo(radio_max)
    
    # Filtrar puntos dentro del cubo que no sean afectados por el cono
    puntos_cubo_filtrados = filtrar_puntos_por_cono(puntos_cubo, apertura_cono)
    
    # Graficar el espacio de trabajo
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    
    # Graficar los puntos dentro del área de trabajo (esfera)
    ax.scatter(puntos_trabajo[:, 0], puntos_trabajo[:, 1], puntos_trabajo[:, 2], c='b', s=0.5, label="Espacio de trabajo")
    
    # Graficar los puntos del cubo (rojos)
    #ax.scatter(puntos_cubo[:, 0], puntos_cubo[:, 1], puntos_cubo[:, 2], c='r', s=50, label="Puntos del cubo")
    
    # Graficar los puntos verdes dentro del cubo, que están fuera del cono
    ax.scatter(puntos_cubo_filtrados[:, 0], puntos_cubo_filtrados[:, 1], puntos_cubo_filtrados[:, 2], c='g', s=20, label="Puntos afectados por el cono (dentro del cubo)")
    
    # Graficar punto de prueba como morado
    ax.scatter(x, y, z, c='m', s=50, label="Punto de prueba")
    
    # Configuración de la visualización
    ax.set_xlabel('X')
    ax.set_ylabel('Y')
    ax.set_zlabel('Z')
    ax.set_title("Espacio de trabajo con cubo y puntos afectados por el cono")
    
    # Mostrar gráfico
    ax.legend()
    plt.show()
    
    
Espacio de trabajo

Se presenta el video de la simulación del espacio de trabajo.

Código de trayectoria circular

 #Para trayectoria de un círculo
    import numpy as np
    import time
    import matplotlib.pyplot as plt
    from spatialmath import SE3
    import roboticstoolbox as rtb
    from roboticstoolbox import jtraj, ctraj
    
    #---------------------------
    #    1. Definicion del robot
    #---------------------------
    a1 = 0.02
    a2 = 0.10
    a3 = 0.08
    
    name = "Robot_3ejes"
    robot = rtb.DHRobot(
        [
            rtb.RevoluteDH(alpha=-np.pi/2,  a=0,    d=a1,  offset=0,  qlim=(-np.deg2rad(360), np.deg2rad(360))),
            rtb.RevoluteDH(alpha=0,         a=a2,   d=0,   offset=0,  qlim=(-np.deg2rad(210), np.deg2rad(30))),
            rtb.RevoluteDH(alpha=0,         a=a3,   d=0,   offset=0,  qlim=(-np.deg2rad(170), np.deg2rad(170)))
        ],
        name=name
    )
    print("Robot details:")
    print(robot)
    
    '''
    ---------------------------
        2. Creacion de matriz de coordenadas para trayectoria
    ---------------------------
    '''
    # Inicializa el primer waypoint
    P = np.array([[0.001],                              # X
                  [0.001 * np.cos(np.deg2rad(0))],        # Y
                  [0.001 * np.sin(np.deg2rad(0)) + 0.10]])  # Z
    
    # Genera waypoints a lo largo de una circunferencia (179 puntos adicionales cada 2 grados)
    for i in range(179):
        new_col = np.array([0.001, 
                            0.001 * np.cos(np.deg2rad(i*2)), 
                            0.001 * np.sin(np.deg2rad(i*2)) + 0.10]).reshape(3, 1)
        P = np.concatenate((P, new_col), axis=1)
    
    #########################################
    #        Revisar espacio de trabajo
    #########################################
    # Función para verificar si un punto es verde o azul
    def es_punto_lineal_o_articulado(x, y, z, radio_max, apertura_cono):
        # El cubo debe estar dentro de la esfera, y su diagonal debe ser <= al diámetro de la esfera
        lado_cubo = 2 * radio_max / np.sqrt(3)  # Calcular el lado del cubo
        
        # Comprobación del cubo: El punto debe estar dentro de los límites del cubo
        dentro_del_cubo = abs(x) <= lado_cubo/2 and abs(y) <= lado_cubo/2 and abs(z) <= lado_cubo/2
        
        # Comprobación del cono: El punto debe estar fuera del cono (se calcula el ángulo)
        angulo_cono = np.arctan2(np.sqrt(x**2 + y**2), -z)  # Ángulo con respecto al eje Z negativo
        fuera_del_cono = angulo_cono > apertura_cono
    
        # Verificar si el punto es verde
        es_lineal = dentro_del_cubo and fuera_del_cono
        
        # Verificar si el punto es azul
        # El punto debe estar dentro de la esfera y fuera del cono
        distancia_al_origen = np.sqrt(x**2 + y**2 + z**2)
        es_articular = distancia_al_origen <= radio_max and fuera_del_cono
    
        return es_lineal, es_articular
    
    # Revisar si los puntos de la trayectoria son lineales o articulados
    radio_max = a2+a3  # Radio máximo del robot
    apertura_cono = np.deg2rad(60)  # Ángulo de apertura del cono
    
    # Verificar si es lineal o articulado
    for i in range(P.shape[1]):
        x, y, z = P[:, i].flatten()  # Aplanar la columna para obtener x, y, z
        es_lineal, es_articular = es_punto_lineal_o_articulado(x, y, z, radio_max, apertura_cono)
        print(f"WP{i+1}:lineal {es_lineal},articulado {es_articular}")
    
    
    # Matriz de rotacion para la orientacion
    R = np.array([[1, 0, 0],
                  [0, 1, 0],
                  [0, 0, 1]])
    
    # Se crea una lista de objetos SE3 para cada waypoint
    coordenadas = [SE3.Rt(R, [x, y, z]) for x, y, z in zip(P[0], P[1], P[2])]
    
    # print("\nCoordenadas en el espacio de trabajo:")
    # for i, pos in enumerate(coordenadas):
    #     print(f"WP{i+1}: {pos}")
    
    '''
    ---------------------------
        3. Resolver la Cinematica inversa de cada coordenada
    ---------------------------
    '''
    #region
    q_sol = []                           
    q_actual = np.zeros(robot.n)         
    ik_init = time.time()
    
    for i, pos in enumerate(coordenadas):
        sol = robot.ikine_LM(pos, q0=q_actual, mask=(1,1,1,0,0,0))
        if sol.success:
            q_sol.append(sol.q)
            q_actual = sol.q
            print(f"IK para coord{i+1} resuelta: {sol.q}")
        else:
            print(f"IK fallo para coord: {i+1}")
    
    ik_end = time.time()
    print("\nTiempo total de calculo de IK: {:.4f} segs".format(ik_end - ik_init))
    #endregion
    '''
    ---------------------------
        4. Generar la trayectoria para movimiento por articulacion
    ---------------------------
    '''
    #region
    tray_segments = []  # Lista de posiciones
    vel_segments = []   # Lista de velocidades
    acc_segments = []   # Lista de aceleraciones
    T_segment = 1       # Duracion de cada segmento en segs
    n_points = 5       # Numero de puntos por segmento
    
    # Generar trayectorias entre cada par de configuraciones
    for i in range(len(q_sol) - 1):
        t_segment = np.linspace(0, T_segment, n_points)
        traj_seg = jtraj(q_sol[i], q_sol[i+1], t_segment)
        tray_segments.append(traj_seg.q)
        vel_segments.append(traj_seg.qd)
        acc_segments.append(traj_seg.qdd)
    
    # Trayectoria de regreso al punto inicial
    t_segment = np.linspace(0, T_segment, n_points)
    traj_return = jtraj(q_sol[-1], q_sol[0], t_segment)
    tray_segments.append(traj_return.q)
    vel_segments.append(traj_return.qd)
    acc_segments.append(traj_return.qdd)
    
    # Apilar los segmentos en una trayectoria completa
    q_traj  = np.vstack(tray_segments)
    qd_traj = np.vstack(vel_segments)
    qdd_traj = np.vstack(acc_segments)
    #endregion
    '''
    ---------------------------
        5. Evaluamos la trayectoria generada usando FK
    ---------------------------
    '''
    #region
    task_traj = []
    for q in q_traj:
        T_fk = robot.fkine(q)
        task_traj.append(T_fk.t)
    task_traj = np.array(task_traj)
    P_closed = np.hstack((P, P[:, 0:1]))
    #endregion
    '''
    ---------------------------
        6. Graficar Trayectorias y Angulos de Articulacion
    ---------------------------
    '''
    #region
    # Plot de la trayectoria en el espacio
    traj_fig = plt.figure("Trayectorias")
    ax = traj_fig.add_subplot(111, projection='3d')
    ax.plot(task_traj[:, 0], task_traj[:, 1], task_traj[:, 2],
            'b-', label='Trayectoria J')
    ax.plot(P_closed[0], P_closed[1], P_closed[2],
            'r:', linewidth=2, label='Trayectoria Ideal')
    ax.scatter(P[0], P[1], P[2],
               color='red', marker='o', s=50, label='Coordenadas')
    # for i in range(P.shape[1]):
    #     ax.text(P[0, i], P[1, i], P[2, i], f' WP{i+1}', color='black', fontsize=12)
    ax.set_title('Trayectoria Espacio Articular')
    ax.set_xlabel('X [m]')
    ax.set_ylabel('Y [m]')
    ax.set_zlabel('Z [m]')
    ax.legend()
    #endregion
    '''
    ---------------------------
        7. Graficar Posicione, Velocidades y Aceleraciones
    ---------------------------
    '''
    #region
    q_traj  = np.vstack(tray_segments)  # Posiciones
    qd_traj = np.vstack(vel_segments)    # Velocidad
    qdd_traj = np.vstack(acc_segments)   # Aceleraciones
    
    tiempo_total = len(tray_segments) * T_segment  
    eje_t = np.linspace(0, tiempo_total, q_traj.shape[0]) 
    
    fig_joint = plt.figure("Ángulo vs Tiempo")
    for i in range(q_traj.shape[1]):
        plt.plot(eje_t, q_traj[:, i], label=f'J {i+1}')
    plt.xlabel("Tiempo (s)")
    plt.ylabel("Ángulo (rad)")
    plt.title("Ángulo vs Tiempo")
    plt.legend()
    plt.grid(True)
    
    
    fig_vel = plt.figure("Velocidades vs Tiempo")
    for i in range(qd_traj.shape[1]):
        plt.plot(eje_t, qd_traj[:, i], label=f'J {i+1}')
    plt.xlabel("Tiempo (s)")
    plt.ylabel("Velocidad (rad/s)")
    plt.title("Velocidades de Articulaciones vs Tiempo")
    plt.legend()
    plt.grid(True)
    
    fig_acc = plt.figure("Aceleraciones vs Tiempo")
    for i in range(qdd_traj.shape[1]):
        plt.plot(eje_t, qdd_traj[:, i], label=f'J {i+1}')
    plt.xlabel("Tiempo (s)")
    plt.ylabel("Aceleración (rad/s²)")
    plt.title("Aceleraciones de Articulaciones vs Tiempo")
    plt.legend()
    plt.grid(True)
    #endregion
    robot.plot(q_traj, block=False, loop=True)
    plt.show()    
        
Trayectoria circular

Se presenta el video de la simulación de trayectoria circular.

Código de trayectorias para trayectoria de un cubo

 import numpy as np
    import time
    import matplotlib.pyplot as plt
    from spatialmath import SE3
    import roboticstoolbox as rtb
    from roboticstoolbox import ctraj
    
    #---------------------------
    #    1. Definicion del robot
    #---------------------------
    a1 = 0.02
    a2 = 0.10
    a3 = 0.08
    
    name = "Robot_3ejes"
    robot = rtb.DHRobot(
        [
            rtb.RevoluteDH(alpha=-np.pi/2,  a=0,    d=a1,  offset=0,  qlim=(-np.deg2rad(360), np.deg2rad(360))),
            rtb.RevoluteDH(alpha=0,         a=a2,   d=0,   offset=0,  qlim=(-np.deg2rad(210), np.deg2rad(30))),
            rtb.RevoluteDH(alpha=0,         a=a3,   d=0,   offset=0,  qlim=(-np.deg2rad(170), np.deg2rad(170)))
        ],
        name=name
    )
    print("Robot details:")
    print(robot)
    
    '''
    ---------------------------
        2. Creacion de matriz de coordenadas para trayectoria
    ---------------------------
    '''
    # Definir las dimensiones del cubo
    lado_cubo = 2 * 0.1 / np.sqrt(3)  # Usando el radio maximo
    
    # Definir los vértices del cubo
    vertices_cubo = [
        [-lado_cubo / 2, -lado_cubo / 2, -lado_cubo / 2], # 1
        [lado_cubo / 2, -lado_cubo / 2, -lado_cubo / 2],  # 2
        [lado_cubo / 2, lado_cubo / 2, -lado_cubo / 2],   # 3
        [-lado_cubo / 2, lado_cubo / 2, -lado_cubo / 2],  # 4
        [-lado_cubo / 2, lado_cubo / 2, lado_cubo / 2],   # 5
        [lado_cubo / 2, lado_cubo / 2, lado_cubo / 2],    # 6
        [lado_cubo / 2, -lado_cubo / 2, lado_cubo / 2],   # 7
        [-lado_cubo / 2, -lado_cubo / 2, lado_cubo / 2],  # 8
        # Puntos para completar aristas del cubo
        [-lado_cubo / 2, lado_cubo / 2, lado_cubo / 2],   # 5
        [lado_cubo / 2, lado_cubo / 2, lado_cubo / 2],    # 6
        [lado_cubo / 2, lado_cubo / 2, -lado_cubo / 2],   # 3
        [-lado_cubo / 2, lado_cubo / 2, -lado_cubo / 2],  # 4
        [-lado_cubo / 2, -lado_cubo / 2, -lado_cubo / 2], # 1
        [-lado_cubo / 2, -lado_cubo / 2, lado_cubo / 2],  # 8
        [lado_cubo / 2, -lado_cubo / 2, lado_cubo / 2],   # 7
        [lado_cubo / 2, -lado_cubo / 2, -lado_cubo / 2]   # 2
    ]
    
    # Inicializar P con el primer vértice del cubo
    P = np.array(vertices_cubo[0]).reshape(3, 1)  # Primer punto del cubo
    
    # Concatenar los vértices restantes
    for v in vertices_cubo[1:]:  # Comienza desde el segundo vértice
        new_col = np.array(v).reshape(3, 1)  # Convertir a columna 3x1
        P = np.concatenate((P, new_col), axis=1)  # Concatenar en el eje 1 (columnas)
    
    #########################################
    #        Revisar espacio de trabajo
    #########################################
    # Función para verificar si un punto es verde o azul
    def es_punto_lineal_o_articulado(x, y, z, radio_max, apertura_cono):
        # El cubo debe estar dentro de la esfera, y su diagonal debe ser <= al diámetro de la esfera
        lado_cubo = 2 * radio_max / np.sqrt(3)  # Calcular el lado del cubo
        
        # Comprobación del cubo: El punto debe estar dentro de los límites del cubo
        dentro_del_cubo = abs(x) <= lado_cubo/2 and abs(y) <= lado_cubo/2 and abs(z) <= lado_cubo/2
        
        # Comprobación del cono: El punto debe estar fuera del cono (se calcula el ángulo)
        angulo_cono = np.arctan2(np.sqrt(x**2 + y**2), -z)  # Ángulo con respecto al eje Z negativo
        fuera_del_cono = angulo_cono > apertura_cono
    
        # Verificar si el punto es verde
        es_lineal = dentro_del_cubo and fuera_del_cono
        
        # Verificar si el punto es azul
        # El punto debe estar dentro de la esfera y fuera del cono
        distancia_al_origen = np.sqrt(x**2 + y**2 + z**2)
        es_articular = distancia_al_origen <= radio_max and fuera_del_cono
    
        return es_lineal, es_articular
    
    # Revisar si los puntos de la trayectoria son lineales o articulados
    radio_max = a2+a3  # Radio máximo del robot
    apertura_cono = np.deg2rad(60)  # Ángulo de apertura del cono
    
    # Verificar si es lineal o articulado
    for i in range(P.shape[1]):
        x, y, z = P[:, i].flatten()  # Aplanar la columna para obtener x, y, z
        es_lineal, es_articular = es_punto_lineal_o_articulado(x, y, z, radio_max, apertura_cono)
        print(f"WP{i+1}:lineal - {es_lineal}, articulado - {es_articular}")
    
    # Matriz de rotacion para la orientacion
    R = np.array([[1, 0, 0],
                  [0, 1, 0],
                  [0, 0, 1]])
    
    # Se crea una lista de objetos SE3 para cada waypoint
    coordenadas = [SE3.Rt(R, [x, y, z]) for x, y, z in zip(P[0], P[1], P[2])]
    
    print("\nCoordenadas en el espacio de trabajo:")
    for i, pos in enumerate(coordenadas):
        print(f"WP{i+1}: {pos}")
    
    '''
    ---------------------------
        3. Generar la trayectoria usando ctraj (trayectoria cartesiana)
    ---------------------------
    '''
    T_segment = 5      # Duracion conceptual de cada segmento en segundos
    n_points = 5       # Numero de puntos por segmento (cartesiano)
    q_traj_segments = []  # Lista para almacenar trayectorias en espacio articular
    q_current = np.zeros(robot.n)  # Condicion inicial para IK
    
    ik_start = time.time()
    
    # Para cada segmento entre waypoints consecutivos
    for i in range(len(coordenadas) - 1):
        cart_traj = ctraj(coordenadas[i], coordenadas[i+1], n_points)
        q_traj_seg = []  # Trayectoria articular para este segmento
        for pose in cart_traj:
            sol = robot.ikine_LM(pose, q0=q_current, mask=(1,1,1,0,0,0))
            if sol.success:
                q_traj_seg.append(sol.q)
                q_current = sol.q  # Actualiza q_current para la siguiente pose
            else:
                print(f"IK fallo en el segmento {i+1} para una pose intermedia")
        q_traj_segments.append(np.array(q_traj_seg))
    
    # Trayectoria de retorno: de la ultima pose al primer waypoint
    cart_traj = ctraj(coordenadas[-1], coordenadas[0], n_points)
    q_traj_seg = []
    for pose in cart_traj:
        sol = robot.ikine_LM(pose, q0=q_current, mask=(1,1,1,0,0,0))
        if sol.success:
            q_traj_seg.append(sol.q)
            q_current = sol.q
        else:
            print("IK fallo en el segmento de retorno para una pose intermedia")
    q_traj_segments.append(np.array(q_traj_seg))
    
    ik_end = time.time()
    print("\nTiempo total de calculo de IK: {:.4f} segs".format(ik_end - ik_start))
    
    # Concatena todas las trayectorias en una sola secuencia de configuraciones articulares
    q_traj = np.vstack(q_traj_segments)
    
    '''
    ---------------------------
        4. Evaluamos la trayectoria generada usando FK
    ---------------------------
    '''
    task_traj = []
    for q in q_traj:
        T_fk = robot.fkine(q)
        task_traj.append(T_fk.t)  # Extrae la traslacion de la pose
    task_traj = np.array(task_traj)
    
    # Para comparacion, se cierra la trayectoria ideal agregando el primer waypoint al final
    P_closed = np.hstack((P, P[:, 0:1]))
    
    '''
    ---------------------------
        5. Graficar Trayectorias, Ángulos, Velocidades y Aceleraciones
    ---------------------------
    '''
    # Plot de la trayectoria en el espacio de trabajo
    traj_fig = plt.figure("Trayectorias")
    ax = traj_fig.add_subplot(111, projection='3d')
    ax.plot(task_traj[:, 0], task_traj[:, 1], task_traj[:, 2],
            'b-', label='Trayectoria generada (IK)')
    ax.plot(P_closed[0], P_closed[1], P_closed[2],
            'r:', linewidth=2, label='Trayectoria Ideal')
    ax.scatter(P[0], P[1], P[2],
               color='red', marker='o', s=50, label='Coordenadas')
    # for i in range(P.shape[1]):
        # ax.text(P[0, i], P[1, i], P[2, i], f' WP{i+1}', color='black', fontsize=12)
    ax.set_title('Trayectoria en el Espacio de Trabajo')
    ax.set_xlabel('X [m]')
    ax.set_ylabel('Y [m]')
    ax.set_zlabel('Z [m]')
    ax.legend()
    ax.set_box_aspect([1, 1, 1])
    
    # Construir el vector de tiempo para la trayectoria completa.
    # Cada segmento se asume que dura T_segment segundos.
    num_segments = len(q_traj_segments)
    time_total = T_segment * num_segments
    t_vec = np.linspace(0, time_total, q_traj.shape[0])
    
    # Calcular velocidades y aceleraciones de las articulaciones usando diferencias finitas.
    qd_traj = np.gradient(q_traj, t_vec[1]-t_vec[0], axis=0)#Derivada de posi
    qdd_traj = np.gradient(qd_traj, t_vec[1]-t_vec[0], axis=0)#Derivada de vel
    
    # Plot de Ángulo de Articulaciones vs Tiempo
    fig_joint = plt.figure("Ángulo vs Tiempo")
    for i in range(q_traj.shape[1]):
        plt.plot(t_vec, q_traj[:, i], label=f'J {i+1}')
    plt.xlabel("Tiempo (s)")
    plt.ylabel("Ángulo (rad)")
    plt.title("Ángulo de Articulaciones vs Tiempo")
    plt.legend()
    plt.grid(True)
    
    # Plot de Velocidades de Articulaciones vs Tiempo
    fig_vel = plt.figure("Velocidades vs Tiempo")
    for i in range(qd_traj.shape[1]):
        plt.plot(t_vec, qd_traj[:, i], label=f'J {i+1}')
    plt.xlabel("Tiempo (s)")
    plt.ylabel("Velocidad (rad/s)")
    plt.title("Velocidades de Articulaciones vs Tiempo")
    plt.legend()
    plt.grid(True)
    
    # Plot de Aceleraciones de Articulaciones vs Tiempo
    fig_acc = plt.figure("Aceleraciones vs Tiempo")
    for i in range(qdd_traj.shape[1]):
        plt.plot(t_vec, qdd_traj[:, i], label=f'J {i+1}')
    plt.xlabel("Tiempo (s)")
    plt.ylabel("Aceleración (rad/s²)")
    plt.title("Aceleraciones de Articulaciones vs Tiempo")
    plt.legend()
    plt.grid(True)
    
    # Animacion del robot utilizando la trayectoria en espacio articular
    robot.plot(q_traj, block=False, loop=True)
    plt.show()
    
Trayectoria de un cubo

Se presenta el video de la simulación para una trayectoria de un cubo.

Código de trayectorias para una M

 #Para trayectoria M
"""
Código para generar la trayectoria de la letra M y un retorno circular
en el espacio articular (de la última pose al primer waypoint).
"""

import numpy as np
import time
import matplotlib.pyplot as plt
from spatialmath import SE3
import roboticstoolbox as rtb
from roboticstoolbox import ctraj, jtraj

# ---------------------------
#    1. Definición del robot
# ---------------------------
a1 = 0.02
a2 = 0.10
a3 = 0.08

name = "Robot_3ejes"
robot = rtb.DHRobot(
    [
        rtb.RevoluteDH(alpha=-np.pi/2, a=0,    d=a1, offset=0, qlim=(-np.deg2rad(360), np.deg2rad(360))),
        rtb.RevoluteDH(alpha=0,        a=a2,   d=0,  offset=0, qlim=(-np.deg2rad(210), np.deg2rad(30))),
        rtb.RevoluteDH(alpha=0,        a=a3,   d=0,  offset=0, qlim=(-np.deg2rad(170), np.deg2rad(170)))
    ],
    name=name
)
print("Robot details:")
print(robot)

# ---------------------------
#    2. Creación de matriz de coordenadas para trayectoria
# ---------------------------
# Para este ejemplo se genera la letra M en el plano XZ, con offset en Y
offset_y = 0.05  # Desplazamiento en Y

# Definir los vértices 3D (en este caso en el plano XZ, Y constante)
vertices_3d = [
    (-0.10, 0, offset_y),       # punto inferior izquierdo
    (-0.09, 0.075, offset_y),   # punto superior izquierdo
    (-0.06, 0.05, offset_y),    # punto medio izquierdo (ajustado)
    (-0.03, 0.075, offset_y),   # punto medio derecho (ajustado)
    (-0.02, 0, offset_y),       # punto inferior derecho
]

# Inicializar P con el primer vértice
P = np.array(vertices_3d[0]).reshape(3, 1)
# Concatenar el resto de los vértices
for v in vertices_3d[1:]:
    new_col = np.array(v).reshape(3, 1)
    P = np.concatenate((P, new_col), axis=1)

#########################################
#        Revisar espacio de trabajo
#########################################
def es_punto_lineal_o_articulado(x, y, z, radio_max, apertura_cono):
    # Calcula el lado del cubo a partir de la esfera
    lado_cubo = 2 * radio_max / np.sqrt(3)
    dentro_del_cubo = abs(x) <= lado_cubo/2 and abs(y) <= lado_cubo/2 and abs(z) <= lado_cubo/2
    angulo_cono = np.arctan2(np.sqrt(x**2 + y**2), -z)
    fuera_del_cono = angulo_cono > apertura_cono
    es_lineal = dentro_del_cubo and fuera_del_cono
    distancia_al_origen = np.sqrt(x**2 + y**2 + z**2)
    es_articular = distancia_al_origen <= radio_max and fuera_del_cono
    return es_lineal, es_articular

radio_max = a2 + a3
apertura_cono = np.deg2rad(60)

# Verificar cada waypoint
for i in range(P.shape[1]):
    x, y, z = P[:, i].flatten()
    es_lineal, es_articular = es_punto_lineal_o_articulado(x, y, z, radio_max, apertura_cono)
    print(f"WP{i+1}: lineal - {es_lineal}, articulado - {es_articular}")

# Matriz de rotación (identidad en este caso)
R = np.array([[1, 0, 0],
              [0, 1, 0],
              [0, 0, 1]])

# Crear una lista de poses SE3 para cada waypoint
coordenadas = [SE3.Rt(R, [x, y, z]) for x, y, z in zip(P[0], P[1], P[2])]
print("\nCoordenadas en el espacio de trabajo:")
for i, pos in enumerate(coordenadas):
    print(f"WP{i+1}: {pos}")

# ---------------------------
#    3. Generar la trayectoria usando ctraj (trayectoria cartesiana)
# ---------------------------
T_segment = 10      # Duración conceptual de cada segmento en segundos
n_points = 50       # Número de puntos por segmento
q_traj_segments = []  # Lista para almacenar trayectorias en espacio articular
q_current = np.zeros(robot.n)  # Condición inicial para IK

ik_start = time.time()

# Para cada segmento entre waypoints consecutivos (trayectoria cartesiana)
for i in range(len(coordenadas) - 1):
    cart_traj = ctraj(coordenadas[i], coordenadas[i+1], n_points)
    q_traj_seg = []  # Trayectoria articular para este segmento
    for pose in cart_traj:
        sol = robot.ikine_LM(pose, q0=q_current, mask=(1,1,1,0,0,0))
        if sol.success:
            q_traj_seg.append(sol.q)
            q_current = sol.q  # Actualizar q_current para la siguiente iteración
        else:
            print(f"IK falló en el segmento {i+1} para una pose intermedia")
    q_traj_segments.append(np.array(q_traj_seg))

ik_end = time.time()
print("\nTiempo total de cálculo de IK en segmentos: {:.4f} segs".format(ik_end - ik_start))

# ---------------------------
#    4. Generar trayectoria de retorno circular en el espacio articular
#    (de la última pose al primer waypoint)
# ---------------------------
# Obtener las configuraciones articulares para el primer y último waypoint
sol_first = robot.ikine_LM(coordenadas[0], q0=q_current, mask=(1,1,1,0,0,0))
if sol_first.success:
    q_first = sol_first.q
    q_current = sol_first.q
    print(f"IK para primer waypoint resuelta: {q_first}")
else:
    print("IK falló para primer waypoint")

sol_last = robot.ikine_LM(coordenadas[-1], q0=q_current, mask=(1,1,1,0,0,0))
if sol_last.success:
    q_last = sol_last.q
    q_current = sol_last.q
    print(f"IK para último waypoint resuelta: {q_last}")
else:
    print("IK falló para último waypoint")

# Definir la parametrización del círculo en el espacio articular
# Queremos pasar de q_last (inicio del retorno) a q_first (final del retorno)
m = (q_last + q_first) / 2           # Centro del arco
d = q_first - q_last                 # Vector de la cuerda
r = np.linalg.norm(d) / 2            # Radio del círculo

# Elegir un vector arbitrario para definir el plano del círculo
v = np.array([0, 0, 1])
if np.allclose(np.cross(d, v), 0):
    v = np.array([0, 1, 0])
u = np.cross(d, v)
u = u / np.linalg.norm(u)

n_points_circular = 50
theta = np.linspace(0, np.pi, n_points_circular)
q_circular = []
for t in theta:
    # Parametrización del círculo:
    # Para t=0 se obtiene q_last y para t=pi se obtiene q_first
    q_t = m + (q_last - m) * np.cos(t) + u * r * np.sin(t)
    q_circular.append(q_t)
q_circular = np.array(q_circular)

# Agregar la trayectoria circular a la lista de segmentos de trayectoria
q_traj_segments.append(q_circular)
print("Trayectoria de retorno circular generada exitosamente.")

# Concatenar todas las trayectorias en una secuencia completa de configuraciones articulares
q_traj = np.vstack(q_traj_segments)

# ---------------------------
#    5. Evaluar la trayectoria generada usando FK
# ---------------------------
task_traj = []
for q in q_traj:
    T_fk = robot.fkine(q)
    task_traj.append(T_fk.t)  # Extraer la traslación de la pose
task_traj = np.array(task_traj)

# Para comparar, se cierra la trayectoria ideal agregando el primer waypoint al final
P_closed = np.hstack((P, P[:, 0:1]))

# ---------------------------
#    6. Graficar Trayectorias, Ángulos, Velocidades y Aceleraciones
# ---------------------------
# Plot de la trayectoria en el espacio de trabajo
traj_fig = plt.figure("Trayectorias")
ax = traj_fig.add_subplot(111, projection='3d')
ax.plot(task_traj[:, 0], task_traj[:, 1], task_traj[:, 2],
        'b-', label='Trayectoria generada (IK)')
ax.plot(P_closed[0], P_closed[1], P_closed[2],
        'r:', linewidth=2, label='Trayectoria Ideal')
ax.scatter(P[0], P[1], P[2],
           color='red', marker='o', s=50, label='Coordenadas')
ax.set_title('Trayectoria en el Espacio de Trabajo')
ax.set_xlabel('X [m]')
ax.set_ylabel('Y [m]')
ax.set_zlabel('Z [m]')
ax.legend()
ax.set_box_aspect([1, 1, 1])

# Vector de tiempo para la trayectoria completa.
num_segments = len(q_traj_segments)
time_total = T_segment * num_segments
t_vec = np.linspace(0, time_total, q_traj.shape[0])

# Calcular velocidades y aceleraciones usando diferencias finitas
qd_traj = np.gradient(q_traj, t_vec[1]-t_vec[0], axis=0)
qdd_traj = np.gradient(qd_traj, t_vec[1]-t_vec[0], axis=0)

# Plot de Ángulo de Articulaciones vs Tiempo
fig_joint = plt.figure("Ángulo vs Tiempo")
for i in range(q_traj.shape[1]):
    plt.plot(t_vec, q_traj[:, i], label=f'J{i+1}')
plt.xlabel("Tiempo (s)")
plt.ylabel("Ángulo (rad)")
plt.title("Ángulo de Articulaciones vs Tiempo")
plt.legend()
plt.grid(True)

# Plot de Velocidades de Articulaciones vs Tiempo
fig_vel = plt.figure("Velocidades vs Tiempo")
for i in range(qd_traj.shape[1]):
    plt.plot(t_vec, qd_traj[:, i], label=f'J{i+1}')
plt.xlabel("Tiempo (s)")
plt.ylabel("Velocidad (rad/s)")
plt.title("Velocidades de Articulaciones vs Tiempo")
plt.legend()
plt.grid(True)

# Plot de Aceleraciones de Articulaciones vs Tiempo
fig_acc = plt.figure("Aceleraciones vs Tiempo")
for i in range(qdd_traj.shape[1]):
    plt.plot(t_vec, qdd_traj[:, i], label=f'J{i+1}')
plt.xlabel("Tiempo (s)")
plt.ylabel("Aceleración (rad/s²)")
plt.title("Aceleraciones de Articulaciones vs Tiempo")
plt.legend()
plt.grid(True)

# Animacion del robot utilizando la trayectoria en espacio articular
robot.plot(q_traj, block=False, loop=True)
plt.show()
Trayectoria M

Se presenta el video de la simulación para una trayectoria de M.

Código de trayectorias para una M segundo intento

 """
    Código combinado para generar la trayectoria de la letra M con movimientos lineales
    (en los segmentos de la M) y un retorno articulado (medio círculo) para cerrar la M.
    
    Los segmentos de la M se ejecutan con movimientos lineales en el espacio cartesiano,
    mientras que el retorno del último vértice al primero se hace mediante un movimiento
    articulado en el espacio de articulaciones.
    """
    
    import numpy as np
    import time
    import matplotlib.pyplot as plt
    from spatialmath import SE3
    import roboticstoolbox as rtb
    from roboticstoolbox import ctraj, jtraj
    
    # ---------------------------
    #    1. Definición del robot
    # ---------------------------
    a1 = 0.02
    a2 = 0.10
    a3 = 0.08
    
    name = "Robot_3ejes"
    robot = rtb.DHRobot(
        [
            rtb.RevoluteDH(alpha=-np.pi/2, a=0,    d=a1, offset=0, qlim=(-np.deg2rad(360), np.deg2rad(360))),
            rtb.RevoluteDH(alpha=0,        a=a2,   d=0,  offset=0, qlim=(-np.deg2rad(210), np.deg2rad(30))),
            rtb.RevoluteDH(alpha=0,        a=a3,   d=0,  offset=0, qlim=(-np.deg2rad(170), np.deg2rad(170)))
        ],
        name=name
    )
    print("Robot details:")
    print(robot)
    
    # ---------------------------
    #    2. Creación de matriz de coordenadas para trayectoria
    # ---------------------------
    # Para este ejemplo se genera la letra M en el plano XZ, con offset en Y
    offset_y = 0.05  # Desplazamiento en Y
    
    # Definir los vértices 3D (en este caso en el plano XZ, Y constante) para definir M
    vertices_3d = [
        (-0.10, offset_y, 0     ),      # punto inferior izquierdo
        (-0.09, offset_y, 0.075 ),      # punto superior izquierdo
        (-0.06, offset_y, 0.05  ),      # punto medio izquierdo (ajustado)
        (-0.03, offset_y, 0.075 ),      # punto medio derecho (ajustado)
        (-0.02, offset_y, 0     ),      # punto inferior derecho
    ]
    
    # Inicializar P con el primer vértice
    P = np.array(vertices_3d[0]).reshape(3, 1)
    # Concatenar el resto de los vértices
    for v in vertices_3d[1:]:
        new_col = np.array(v).reshape(3, 1)
        P = np.concatenate((P, new_col), axis=1)
    
    #########################################
    #        Revisar espacio de trabajo
    #########################################
    def es_punto_lineal_o_articulado(x, y, z, radio_max, apertura_cono):
        # Calcula el lado del cubo a partir de la esfera
        lado_cubo = 2 * radio_max / np.sqrt(3)
        dentro_del_cubo = abs(x) <= lado_cubo/2 and abs(y) <= lado_cubo/2 and abs(z) <= lado_cubo/2
        angulo_cono = np.arctan2(np.sqrt(x**2 + y**2), -z)
        fuera_del_cono = angulo_cono > apertura_cono
        es_lineal = dentro_del_cubo and fuera_del_cono
        distancia_al_origen = np.sqrt(x**2 + y**2 + z**2)
        es_articular = distancia_al_origen <= radio_max and fuera_del_cono
        return es_lineal, es_articular
    
    radio_max = a2 + a3
    apertura_cono = np.deg2rad(60)
    
    # Verificar cada waypoint
    for i in range(P.shape[1]):
        x, y, z = P[:, i].flatten()
        es_lineal, es_articular = es_punto_lineal_o_articulado(x, y, z, radio_max, apertura_cono)
        print(f"WP{i+1}: lineal - {es_lineal}, articulado - {es_articular}")
    
    # Matriz de rotación (identidad en este caso)
    R = np.array([[1, 0, 0],
                  [0, 1, 0],
                  [0, 0, 1]])
    
    # Crear una lista de poses SE3 para cada waypoint
    coordenadas = [SE3.Rt(R, [x, y, z]) for x, y, z in zip(P[0], P[1], P[2])]
    print("\nCoordenadas en el espacio de trabajo:")
    for i, pos in enumerate(coordenadas):
        print(f"WP{i+1}: {pos}")
    
    # ---------------------------
    #    3. Generar la trayectoria lineal para los segmentos de la M
    # ---------------------------
    T_segment = 5      # Duración conceptual de cada segmento en segundos
    n_points = 50       # Número de puntos por segmento
    q_traj_segments = []  # Lista para almacenar trayectorias en espacio articular
    q_current = np.zeros(robot.n)  # Condición inicial para IK
    
    print("\nGenerando trayectorias lineales para los segmentos de la M:")
    ik_start = time.time()
    
    # Para cada segmento entre waypoints consecutivos (trayectoria cartesiana LINEAL)
    for i in range(len(coordenadas) - 1):
        print(f"Generando trayectoria lineal para el segmento {i+1}/{len(coordenadas)-1}")
        # ctraj genera una trayectoria LINEAL en el espacio cartesiano
        cart_traj = ctraj(coordenadas[i], coordenadas[i+1], n_points)
        q_traj_seg = []  # Trayectoria articular para este segmento
        for pose in cart_traj:
            sol = robot.ikine_LM(pose, q0=q_current, mask=(1,1,1,0,0,0))
            if sol.success:
                q_traj_seg.append(sol.q)
                q_current = sol.q  # Actualizar q_current para la siguiente iteración
            else:
                print(f"IK falló en el segmento {i+1} para una pose intermedia")
        q_traj_segments.append(np.array(q_traj_seg))
    
    ik_end = time.time()
    print("\nTiempo total de cálculo de IK en segmentos lineales: {:.4f} segs".format(ik_end - ik_start))
    
    # ---------------------------
    #    4. Generar trayectoria de retorno circular en el espacio articular
    #    (de la última pose al primer waypoint)
    # ---------------------------
    print("\nGenerando trayectoria articulada para el medio círculo de retorno:")
    
    # Generar puntos 3D para el medio círculo
    n_points_arc = 50
    P_inferior_derecho = np.array(vertices_3d[-1]).reshape(3,1)  # (-0.02, offset_y, 0)
    P_inferior_izquierdo = np.array(vertices_3d[0]).reshape(3,1)  # (-0.10, offset_y, 0)
    
    # Calcular el centro (m) y el radio (r) del arco
    m = (P_inferior_derecho + P_inferior_izquierdo) / 2  # centro del arco
    r = np.linalg.norm(P_inferior_derecho - m)           # radio del arco
    
    # Generar puntos del medio círculo en el plano XZ (Y constante)
    theta = np.linspace(0, np.pi, n_points_arc)
    P_arc = np.zeros((3, n_points_arc))
    for i, t in enumerate(theta):
        # Parametrización: P(t) = m + r*[cos(t), 0, sin(t)]
        # NOTA: Ajustamos para que en theta=0 estemos en P_inferior_derecho
        x = m[0,0] + r * np.cos(t)
        y = offset_y
        z = m[2,0] + r * np.sin(t)
        P_arc[:, i] = [x, y, z]
    
    # Crear poses SE3 para cada punto del arco
    arc_poses = []
    for i in range(n_points_arc):
        arc_poses.append(SE3.Rt(R, P_arc[:, i]))
    
    # Resolver la cinemática inversa para cada pose del arco
    # Usar la última configuración articular como punto de partida
    q_arc = []
    q_current = q_traj_segments[-1][-1]  # Última configuración articular de los segmentos de la M
    
    ik_arc_start = time.time()
    print("\nResolviendo cinemática inversa para los puntos del arco:")
    
    for i, pose in enumerate(arc_poses):
        sol = robot.ikine_LM(pose, q0=q_current, mask=(1,1,1,0,0,0))
        if sol.success:
            q_arc.append(sol.q)
            q_current = sol.q  # Actualizar para la siguiente iteración
            if i % 10 == 0:  # Mostrar progreso cada 10 puntos
                print(f"IK resuelta para punto {i+1}/{n_points_arc} del arco")
        else:
            print(f"IK falló para el punto {i+1} del arco")
            # Si falla, intentar mantener la última configuración válida
            if len(q_arc) > 0:
                q_arc.append(q_arc[-1])
            else:
                # Si no hay configuración previa, usar la última de los segmentos de la M
                q_arc.append(q_traj_segments[-1][-1])
    
    ik_arc_end = time.time()
    print(f"Tiempo total de cálculo de IK para el arco: {ik_arc_end - ik_arc_start:.4f} segs")
    
    # Convertir a array numpy y añadir a los segmentos
    q_arc = np.array(q_arc)
    q_traj_segments.append(q_arc)
    print("Trayectoria del arco generada exitosamente.")
    
    # Concatenar todas las trayectorias en una secuencia completa de configuraciones articulares
    q_traj = np.vstack(q_traj_segments)
    
    # ---------------------------
    #    5. Evaluar la trayectoria generada usando FK
    # ---------------------------
    print("\nEvaluando la trayectoria generada usando cinemática directa (FK)...")
    task_traj = []
    for q in q_traj:
        T_fk = robot.fkine(q)
        task_traj.append(T_fk.t)  # Extraer la traslación de la pose
    task_traj = np.array(task_traj)
    
    # Para comparar, crear una trayectoria ideal completa (la M lineal + el medio círculo)
    P_ideal = np.hstack((P, P_arc))  # Concatenar la M y el arco
    P_ideal_closed = np.hstack((P_ideal, P_ideal[:, 0:1]))  # Cerrar la trayectoria ideal
    
    # ---------------------------
    #    6. Graficar Trayectorias, Ángulos, Velocidades y Aceleraciones
    # ---------------------------
    print("\nGenerando gráficas...")
    
    # Plot de la trayectoria en el espacio de trabajo
    traj_fig = plt.figure("Trayectorias")
    ax = traj_fig.add_subplot(111, projection='3d')
    ax.plot(task_traj[:, 0], task_traj[:, 1], task_traj[:, 2],
            'b-', label='Trayectoria generada (FK)')
    ax.plot(P_ideal_closed[0], P_ideal_closed[1], P_ideal_closed[2],
            'r:', linewidth=2, label='Trayectoria Ideal')
    ax.scatter(P[0], P[1], P[2],
               color='red', marker='o', s=50, label='Waypoints M')
    ax.scatter(P_arc[0], P_arc[1], P_arc[2],
               color='green', marker='.', s=20, label='Puntos del arco')
    ax.set_title('Trayectoria en el Espacio de Trabajo')
    ax.set_xlabel('X [m]')
    ax.set_ylabel('Y [m]')
    ax.set_zlabel('Z [m]')
    ax.legend()
    ax.set_box_aspect([1, 1, 1])
    
    # Calcular un vector de tiempo apropiado para toda la trayectoria
    num_segments_linear = len(q_traj_segments) - 1  # Excluyendo el arco
    T_arc = 5  # Duración del arco
    
    # Vector de tiempo total
    t_vec = np.zeros(q_traj.shape[0])
    idx = 0
    
    # Asignar tiempos para los segmentos lineales
    for i in range(num_segments_linear):
        n_seg = q_traj_segments[i].shape[0]
        t_seg = np.linspace(i*T_segment, (i+1)*T_segment, n_seg)
        t_vec[idx:idx+n_seg] = t_seg
        idx += n_seg
    
    # Asignar tiempos para el arco
    n_arc = q_traj_segments[-1].shape[0]
    t_arc = np.linspace(num_segments_linear*T_segment, num_segments_linear*T_segment + T_arc, n_arc)
    t_vec[idx:idx+n_arc] = t_arc
    
    # Calcular velocidades y aceleraciones usando diferencias finitas
    qd_traj = np.gradient(q_traj, t_vec[1]-t_vec[0], axis=0)
    qdd_traj = np.gradient(qd_traj, t_vec[1]-t_vec[0], axis=0)
    
    # Plot de Ángulo de Articulaciones vs Tiempo
    fig_joint = plt.figure("Ángulo vs Tiempo")
    for i in range(q_traj.shape[1]):
        plt.plot(t_vec, q_traj[:, i], label=f'J{i+1}')
    plt.xlabel("Tiempo (s)")
    plt.ylabel("Ángulo (rad)")
    plt.title("Ángulo de Articulaciones vs Tiempo")
    plt.legend()
    plt.grid(True)
    
    # Plot de Velocidades de Articulaciones vs Tiempo
    fig_vel = plt.figure("Velocidades vs Tiempo")
    for i in range(qd_traj.shape[1]):
        plt.plot(t_vec, qd_traj[:, i], label=f'J{i+1}')
    plt.xlabel("Tiempo (s)")
    plt.ylabel("Velocidad (rad/s)")
    plt.title("Velocidades de Articulaciones vs Tiempo")
    plt.legend()
    plt.grid(True)
    
    # Plot de Aceleraciones de Articulaciones vs Tiempo
    fig_acc = plt.figure("Aceleraciones vs Tiempo")
    for i in range(qdd_traj.shape[1]):
        plt.plot(t_vec, qdd_traj[:, i], label=f'J{i+1}')
    plt.xlabel("Tiempo (s)")
    plt.ylabel("Aceleración (rad/s²)")
    plt.title("Aceleraciones de Articulaciones vs Tiempo")
    plt.legend()
    plt.grid(True)
    
    # Añadir líneas verticales para indicar dónde comienza la trayectoria del arco
    tiempo_inicio_arco = num_segments_linear*T_segment
    plt.figure("Ángulo vs Tiempo")
    plt.axvline(x=tiempo_inicio_arco, color='k', linestyle='--', label='Inicio Arco')
    plt.figure("Velocidades vs Tiempo")
    plt.axvline(x=tiempo_inicio_arco, color='k', linestyle='--', label='Inicio Arco')
    plt.figure("Aceleraciones vs Tiempo")
    plt.axvline(x=tiempo_inicio_arco, color='k', linestyle='--', label='Inicio Arco')
    
    print("Mostrando animación del robot...")
    # Animacion del robot utilizando la trayectoria en espacio articular
    robot.plot(q_traj, block=False, loop=True)
    plt.show()
Trayectoria M 2

Se presenta el video de la simulación para una trayectoria de M.

Referencias Bibliográficas