Import upstream snapshot 80e710700aac9573a2230f74f7ce9e094833a0bc
Upstream: https://github.com/Rhoban/onshape-to-robot Upstream-Commit: 80e710700aac9573a2230f74f7ce9e094833a0bc Upstream-Branch: master
This commit is contained in:
@@ -0,0 +1,541 @@
|
||||
from transforms3d.quaternions import mat2quat, quat2mat
|
||||
import math
|
||||
import sys
|
||||
import time
|
||||
import numpy as np
|
||||
import pybullet as p
|
||||
from time import sleep
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
class Simulation:
|
||||
"""
|
||||
A Bullet simulation involving Onshape to robot model
|
||||
"""
|
||||
|
||||
def __init__(self, robotPath, floor=True, fixed=False, transparent=False, gui=True, ignore_self_collisions=False,
|
||||
realTime=True, panels=False, useUrdfInertia=True, dt=0.002, physicsClient = None):
|
||||
"""Creates an instance of humanoid simulation
|
||||
|
||||
Keyword Arguments:
|
||||
field {bool} -- enable the display of the field (default: {False})
|
||||
fixed {bool} -- makes the base of the robot floating/fixed (default: {False})
|
||||
transparent {bool} -- makes the robot transparent (default: {False})
|
||||
gui {bool} -- enables the gui visualizer, if False it will runs headless (default {True})
|
||||
realTime {bool} -- try to have simulation in real time (default {True})
|
||||
panels {bool} -- show/hide the user interaction pyBullet panels (default {False})
|
||||
useUrdfInertia {bool} -- use URDF from URDF file (default {True})
|
||||
dt {float} -- time step (default {0.002})
|
||||
"""
|
||||
|
||||
self.dir = os.path.dirname(os.path.abspath(__file__))
|
||||
self.gui = gui
|
||||
self.realTime = realTime
|
||||
self.t = 0
|
||||
self.start = time.time()
|
||||
self.dt = dt
|
||||
self.mass = None
|
||||
|
||||
# Debug lines drawing
|
||||
self.lines = []
|
||||
self.currentLine = 0
|
||||
self.lastLinesDraw = 0
|
||||
self.lineColors = [[1, 0, 0], [0, 1, 0], [
|
||||
0, 0, 1], [1, 1, 0], [1, 0, 1], [0, 1, 1]]
|
||||
|
||||
# Instanciating bullet
|
||||
if physicsClient is None:
|
||||
if gui:
|
||||
physicsClient = p.connect(p.GUI)
|
||||
else:
|
||||
physicsClient = p.connect(p.DIRECT)
|
||||
p.setGravity(0, 0, -9.81)
|
||||
|
||||
# Light GUI
|
||||
if not panels:
|
||||
p.configureDebugVisualizer(p.COV_ENABLE_GUI, 0)
|
||||
p.configureDebugVisualizer(
|
||||
p.COV_ENABLE_SEGMENTATION_MARK_PREVIEW, 0)
|
||||
p.configureDebugVisualizer(p.COV_ENABLE_DEPTH_BUFFER_PREVIEW, 0)
|
||||
p.configureDebugVisualizer(p.COV_ENABLE_RGB_BUFFER_PREVIEW, 0)
|
||||
|
||||
p.configureDebugVisualizer(p.COV_ENABLE_MOUSE_PICKING, 1)
|
||||
|
||||
# Loading floor and/or plane ground
|
||||
if floor:
|
||||
self.floor = p.loadURDF(self.dir+'/bullet/plane.urdf')
|
||||
else:
|
||||
self.floor = None
|
||||
|
||||
# Loading robot
|
||||
startPos = [0, 0, 0]
|
||||
if not fixed:
|
||||
startPos[2] = 1
|
||||
startOrientation = p.getQuaternionFromEuler([0, 0, 0])
|
||||
flags = 0 if ignore_self_collisions else p.URDF_USE_SELF_COLLISION
|
||||
if useUrdfInertia:
|
||||
flags += p.URDF_USE_INERTIA_FROM_FILE
|
||||
self.robot = p.loadURDF(robotPath,
|
||||
startPos, startOrientation,
|
||||
flags=flags, useFixedBase=fixed)
|
||||
|
||||
# Setting frictions parameters to default ones
|
||||
self.setFloorFrictions()
|
||||
|
||||
# Engine parameters
|
||||
p.setPhysicsEngineParameter(fixedTimeStep=self.dt, maxNumCmdPer1ms=0)
|
||||
# p.setRealTimeSimulation(0)
|
||||
# p.setPhysicsEngineParameter(numSubSteps=1)
|
||||
|
||||
# Retrieving joints and frames
|
||||
self.joints = {}
|
||||
self.passive_joints = {}
|
||||
self.jointsInfos = {}
|
||||
self.jointsIndexes = {}
|
||||
self.frames = {}
|
||||
self.maxTorques = {}
|
||||
|
||||
# Collecting the available joints
|
||||
n = 0
|
||||
for k in range(p.getNumJoints(self.robot)):
|
||||
jointInfo = p.getJointInfo(self.robot, k)
|
||||
name = jointInfo[1].decode('utf-8')
|
||||
|
||||
if 'passive' in name:
|
||||
self.passive_joints[name] = k
|
||||
elif not name.endswith('_fixed'):
|
||||
if '_frame' in name:
|
||||
self.frames[name] = k
|
||||
else:
|
||||
self.jointsIndexes[name] = n
|
||||
n += 1
|
||||
self.joints[name] = k
|
||||
self.jointsInfos[name] = {
|
||||
'type': jointInfo[2]
|
||||
}
|
||||
if jointInfo[8] < jointInfo[9]:
|
||||
self.jointsInfos[name]['lowerLimit'] = jointInfo[8]
|
||||
self.jointsInfos[name]['upperLimit'] = jointInfo[9]
|
||||
|
||||
# Changing robot opacity if transparent set to true
|
||||
if transparent:
|
||||
for k in range(p.getNumJoints(self.robot)):
|
||||
p.changeVisualShape(self.robot, k, rgbaColor=[
|
||||
0.3, 0.3, 0.3, 0.3])
|
||||
|
||||
print('* Found '+str(len(self.joints))+' DOFs')
|
||||
print('* Found '+str(len(self.frames))+' frames')
|
||||
|
||||
def setFloorFrictions(self, lateral=1, spinning=-1, rolling=-1):
|
||||
"""Sets the frictions with the plane object
|
||||
|
||||
Keyword Arguments:
|
||||
lateral {float} -- lateral friction (default: {1.0})
|
||||
spinning {float} -- spinning friction (default: {-1.0})
|
||||
rolling {float} -- rolling friction (default: {-1.0})
|
||||
"""
|
||||
if self.floor is not None:
|
||||
p.changeDynamics(self.floor, -1, lateralFriction=lateral,
|
||||
spinningFriction=spinning, rollingFriction=rolling)
|
||||
|
||||
def lookAt(self, target):
|
||||
"""Control the look of the visualizer camera
|
||||
|
||||
Arguments:
|
||||
target {tuple} -- target as (x,y,z) tuple
|
||||
"""
|
||||
if self.gui:
|
||||
params = p.getDebugVisualizerCamera()
|
||||
p.resetDebugVisualizerCamera(
|
||||
params[10], params[8], params[9], target)
|
||||
|
||||
def getRobotPose(self):
|
||||
"""Gets the robot (origin) position
|
||||
|
||||
Returns:
|
||||
(tuple(3), tuple(3)) -- (x,y,z), (roll, pitch, yaw)
|
||||
"""
|
||||
pose = p.getBasePositionAndOrientation(self.robot)
|
||||
return (pose[0], p.getEulerFromQuaternion(pose[1]))
|
||||
|
||||
def frameToWorldMatrix(self, frame):
|
||||
"""Gets the given frame to world matrix transformation. can be a frame name
|
||||
from URDF/SDF or "origin" for the part origin
|
||||
|
||||
Arguments:
|
||||
frame {str} -- frame name
|
||||
|
||||
Returns:
|
||||
np.matrix -- a 4x4 matrix
|
||||
"""
|
||||
|
||||
if frame == 'origin':
|
||||
frameToWorldPose = p.getBasePositionAndOrientation(self.robot)
|
||||
else:
|
||||
frameToWorldPose = p.getLinkState(self.robot, self.frames[frame])
|
||||
|
||||
return self.poseToMatrix(frameToWorldPose)
|
||||
|
||||
def transformation(self, frameA, frameB):
|
||||
"""Transformation matrix AtoB
|
||||
|
||||
Arguments:
|
||||
frameA {str} -- frame A name
|
||||
frameB {str} -- frame B name
|
||||
|
||||
Returns:
|
||||
np.matrix -- A 4x4 matrix
|
||||
"""
|
||||
AtoWorld = self.frameToWorldMatrix(frameA)
|
||||
BtoWorld = self.frameToWorldMatrix(frameB)
|
||||
|
||||
return np.linalg.inv(BtoWorld) * AtoWorld
|
||||
|
||||
def poseToMatrix(self, pose):
|
||||
"""Converts a pyBullet pose to a transformation matrix"""
|
||||
translation = pose[0]
|
||||
quaternion = pose[1]
|
||||
|
||||
# NOTE: PyBullet quaternions are x, y, z, w
|
||||
rotation = quat2mat([quaternion[3], quaternion[0],
|
||||
quaternion[1], quaternion[2]])
|
||||
|
||||
m = np.identity(4)
|
||||
m[0:3, 0:3] = rotation
|
||||
m.T[3, 0:3] = translation
|
||||
|
||||
return np.matrix(m)
|
||||
|
||||
def matrixToPose(self, matrix):
|
||||
"""Converts a transformation matrix to a pyBullet pose"""
|
||||
arr = np.array(matrix)
|
||||
translation = list(arr.T[3, 0:3])
|
||||
quaternion = mat2quat(arr[0:3, 0:3])
|
||||
|
||||
# NOTE: PyBullet quaternions are x, y, z, w
|
||||
quaternion = [quaternion[1], quaternion[2],
|
||||
quaternion[3], quaternion[0]]
|
||||
|
||||
return translation, quaternion
|
||||
|
||||
def setRobotPose(self, pos, orn):
|
||||
"""Sets the robot (origin) pose
|
||||
|
||||
Arguments:
|
||||
pos {tuple} -- (x,y,z) position
|
||||
orn {tuple} -- (x,y,z,w) quaternions
|
||||
"""
|
||||
p.resetBasePositionAndOrientation(self.robot, pos, orn)
|
||||
|
||||
def reset(self, height=0.5, orientation='straight'):
|
||||
"""Resets the robot for experiment (joints, robot position, simulator time)
|
||||
|
||||
Keyword Arguments:
|
||||
height {float} -- height of the reset (m) (default: {0.55})
|
||||
orientation {str} -- orientation (straight, front or back) of the robot (default: {'straight'})
|
||||
"""
|
||||
self.lines = []
|
||||
self.t = 0
|
||||
self.start = time.time()
|
||||
|
||||
# Resets the robot position
|
||||
orn = [0, 0, 0]
|
||||
if orientation == 'front':
|
||||
orn = [0, math.pi/2, 0]
|
||||
elif orientation == 'back':
|
||||
orn = [0, -math.pi/2, 0]
|
||||
self.resetPose([0, 0, height], p.getQuaternionFromEuler(orn))
|
||||
|
||||
# Reset the joints to 0
|
||||
for entry in self.joints.values():
|
||||
p.resetJointState(self.robot, entry, 0)
|
||||
|
||||
def resetPose(self, pos, orn):
|
||||
"""Called by reset() with the robot pose
|
||||
|
||||
Arguments:
|
||||
pos {tuple} -- (x,y,z) position
|
||||
orn {tuple} -- (x,y,z,w) quaternions
|
||||
"""
|
||||
self.setRobotPose(pos, orn)
|
||||
|
||||
def getFrame(self, frame):
|
||||
"""Gets the given frame
|
||||
|
||||
Arguments:
|
||||
frame {str} -- frame name
|
||||
|
||||
Returns:
|
||||
tuple -- (pos, orn), where pos is (x, y, z) and orn is quaternions (x, y, z, w)
|
||||
"""
|
||||
jointState = p.getLinkState(self.robot, self.frames[frame])
|
||||
return (jointState[0], jointState[1])
|
||||
|
||||
def getFrames(self):
|
||||
"""Gets the available frames in the current robot model
|
||||
|
||||
Returns:
|
||||
dict -- dict of str -> (pos, orientation)
|
||||
"""
|
||||
frames = {}
|
||||
|
||||
for name in self.frames.keys():
|
||||
jointState = p.getLinkState(self.robot, self.frames[name])
|
||||
pos = jointState[0]
|
||||
orientation = p.getEulerFromQuaternion(jointState[1])
|
||||
frames[name] = [pos, orientation]
|
||||
|
||||
return frames
|
||||
|
||||
def getVelocity(self, frame):
|
||||
"""Gets the velocity of the given frame
|
||||
|
||||
Arguments:
|
||||
frame {str} -- frame name
|
||||
|
||||
Returns:
|
||||
tuple -- (linear, angular)
|
||||
"""
|
||||
jointState = p.getLinkState(self.robot, self.frames[frame], computeLinkVelocity=True)
|
||||
return (jointState[6], jointState[7])
|
||||
|
||||
def resetJoints(self, joints):
|
||||
"""Reset all the joints to a given position
|
||||
|
||||
Arguments:
|
||||
joints {dict} -- dict of joint name -> angle (float, radian)
|
||||
"""
|
||||
for name in joints:
|
||||
p.resetJointState(self.robot, self.joints[name], joints[name])
|
||||
|
||||
def setJoints(self, joints):
|
||||
"""Set joint targets for motor control in simulation
|
||||
|
||||
Arguments:
|
||||
joints {dict} -- dict of joint name -> angle (float, radian)
|
||||
|
||||
Raises:
|
||||
Exception: if a joint is not found, exception is raised
|
||||
|
||||
Returns:
|
||||
applied {dict} -- dict of joint states (position, velocity, reaction forces, applied torque)
|
||||
"""
|
||||
applied = {}
|
||||
|
||||
for name in self.passive_joints:
|
||||
p.setJointMotorControl2(self.robot, self.passive_joints[name], controlMode=p.VELOCITY_CONTROL, force=0)
|
||||
|
||||
for name in joints.keys():
|
||||
if name in self.joints:
|
||||
if name.endswith('_speed'):
|
||||
p.setJointMotorControl2(
|
||||
self.robot, self.joints[name], p.VELOCITY_CONTROL, targetVelocity=joints[name])
|
||||
else:
|
||||
if name in self.maxTorques:
|
||||
maxTorque = self.maxTorques[name]
|
||||
p.setJointMotorControl2(
|
||||
self.robot, self.joints[name], p.POSITION_CONTROL, joints[name], force=maxTorque)
|
||||
else:
|
||||
p.setJointMotorControl2(
|
||||
self.robot, self.joints[name], p.POSITION_CONTROL, joints[name])
|
||||
|
||||
applied[name] = p.getJointState(self.robot, self.joints[name])
|
||||
else:
|
||||
raise Exception("Can't find joint %s" % name)
|
||||
|
||||
return applied
|
||||
|
||||
def getJoints(self):
|
||||
"""Get all the joints names
|
||||
|
||||
Returns:
|
||||
list -- list of str, with joint names
|
||||
"""
|
||||
return self.joints.keys()
|
||||
|
||||
def getJointsInfos(self, name):
|
||||
"""Get informations about a joint
|
||||
|
||||
Return:
|
||||
list -- a list with key type, lowerLimit & upperLimit (if defined)
|
||||
"""
|
||||
|
||||
return self.jointsInfos[name]
|
||||
|
||||
def getRobotMass(self):
|
||||
"""Returns the robot mass
|
||||
|
||||
Returns:
|
||||
float -- the robot mass (kg)
|
||||
"""
|
||||
if self.mass is None:
|
||||
k = -1
|
||||
self.mass = 0
|
||||
while True:
|
||||
if k == -1 or p.getLinkState(self.robot, k) is not None:
|
||||
d = p.getDynamicsInfo(self.robot, k)
|
||||
self.mass += d[0]
|
||||
else:
|
||||
break
|
||||
k += 1
|
||||
|
||||
return self.mass
|
||||
|
||||
def getCenterOfMassPosition(self):
|
||||
"""Returns center of mass of the robot
|
||||
|
||||
Returns:
|
||||
pos -- (x, y, z) robot center of mass
|
||||
"""
|
||||
|
||||
k = -1
|
||||
mass = 0
|
||||
com = np.array([0., 0., 0.])
|
||||
while True:
|
||||
if k == -1:
|
||||
pos, _ = p.getBasePositionAndOrientation(self.robot)
|
||||
else:
|
||||
res = p.getLinkState(self.robot, k)
|
||||
if res is None:
|
||||
break
|
||||
pos = res[0]
|
||||
|
||||
d = p.getDynamicsInfo(self.robot, k)
|
||||
m = d[0]
|
||||
com += np.array(pos) * m
|
||||
mass += m
|
||||
|
||||
k += 1
|
||||
|
||||
return com / mass
|
||||
|
||||
def addDebugPosition(self, position, color=None, duration=30):
|
||||
"""Adds a debug position to be drawn as a line
|
||||
|
||||
Arguments:
|
||||
position {tuple} -- (x,y,z) (m)
|
||||
|
||||
Keyword Arguments:
|
||||
color {tuple} -- (r,g,b) (0->1) (default: {None})
|
||||
duration {float} -- line duration on screen before disapearing (default: {30})
|
||||
"""
|
||||
if color is None:
|
||||
color = self.lineColors[self.currentLine % len(self.lineColors)]
|
||||
|
||||
if self.currentLine >= len(self.lines):
|
||||
self.lines.append({})
|
||||
|
||||
self.lines[self.currentLine]['update'] = True
|
||||
self.lines[self.currentLine]['to'] = position
|
||||
self.lines[self.currentLine]['color'] = color
|
||||
self.lines[self.currentLine]['duration'] = duration
|
||||
|
||||
self.currentLine += 1
|
||||
|
||||
def drawDebugLines(self):
|
||||
"""Updates the drawing of debug lines"""
|
||||
self.currentLine = 0
|
||||
if time.time() - self.lastLinesDraw > 0.05:
|
||||
for line in self.lines:
|
||||
if 'from' in line:
|
||||
if line['update'] == True:
|
||||
p.addUserDebugLine(
|
||||
line['from'], line['to'], line['color'], 2, line['duration'])
|
||||
line['update'] = False
|
||||
else:
|
||||
del line['from']
|
||||
line['from'] = line['to']
|
||||
|
||||
self.lastLinesDraw = time.time()
|
||||
|
||||
def contactPoints(self):
|
||||
"""Gets all contact points and forces
|
||||
|
||||
Returns:
|
||||
list -- list of entries (link_name, position in m, normal force vector, force in N)
|
||||
"""
|
||||
result = []
|
||||
contacts = p.getContactPoints(bodyA=self.floor, bodyB=self.robot)
|
||||
for contact in contacts:
|
||||
link_index = contact[4]
|
||||
if link_index >= 0:
|
||||
link_name = (p.getJointInfo(
|
||||
self.robot, link_index)[12]).decode()
|
||||
else:
|
||||
link_name = 'base'
|
||||
result.append((link_name, contact[6], contact[7], contact[9]))
|
||||
|
||||
return result
|
||||
|
||||
def autoCollisions(self):
|
||||
"""Returns the total amount of N in autocollisions (not with ground)
|
||||
|
||||
Returns:
|
||||
float -- Newtons of collisions not with ground
|
||||
"""
|
||||
total = 0
|
||||
for k in range(1, p.getNumJoints(self.robot)):
|
||||
contacts = p.getContactPoints(bodyA=k)
|
||||
for contact in contacts:
|
||||
if contact[2] != self.floor:
|
||||
total += contact[9]
|
||||
return total
|
||||
|
||||
def addConstraint(self, frameA, frameB, constraint = p.JOINT_POINT2POINT):
|
||||
"""Adds a constraint between two given frames
|
||||
|
||||
Args:
|
||||
frameA (str): frame A name
|
||||
frameB (str): frame A name
|
||||
constraint (int, optional): pyBullet joint type. Defaults to p.JOINT_POINT2POINT.
|
||||
|
||||
Returns:
|
||||
int: returns from pybullet createConstraint
|
||||
"""
|
||||
infosA = p.getJointInfo(self.robot, self.frames[frameA])
|
||||
infosB = p.getJointInfo(self.robot, self.frames[frameB])
|
||||
|
||||
st = p.getLinkState(self.robot, infosA[16])
|
||||
T_world_parentA = self.poseToMatrix(st[:2])
|
||||
T_world_childA = self.poseToMatrix(self.getFrame(frameA))
|
||||
T_parentA_childA = np.linalg.inv(T_world_parentA) * T_world_childA
|
||||
childApose = self.matrixToPose(T_parentA_childA)
|
||||
|
||||
st = p.getLinkState(self.robot, infosB[16])
|
||||
T_world_parentB = self.poseToMatrix(st[:2])
|
||||
T_world_childB = self.poseToMatrix(self.getFrame(frameB))
|
||||
T_parentB_childB = np.linalg.inv(T_world_parentB) * T_world_childB
|
||||
childBpose = self.matrixToPose(T_parentB_childB)
|
||||
|
||||
c = p.createConstraint(
|
||||
self.robot,
|
||||
infosA[16],
|
||||
self.robot,
|
||||
infosB[16],
|
||||
constraint,
|
||||
[0.0, 0.0, 0.0],
|
||||
childApose[0],
|
||||
childBpose[0],
|
||||
childApose[1],
|
||||
childBpose[1],
|
||||
)
|
||||
|
||||
p.changeConstraint(c, maxForce=1e3)
|
||||
|
||||
return c
|
||||
|
||||
def execute(self):
|
||||
"""Executes the simulaiton infinitely (blocks)"""
|
||||
while True:
|
||||
self.tick()
|
||||
|
||||
def tick(self):
|
||||
"""Ticks one step of simulation. If realTime is True, sleeps to compensate real time"""
|
||||
self.t += self.dt
|
||||
self.drawDebugLines()
|
||||
|
||||
p.stepSimulation()
|
||||
delay = self.t - (time.time() - self.start)
|
||||
if delay > 0 and self.realTime:
|
||||
time.sleep(delay)
|
||||
Reference in New Issue
Block a user