| | 1 | = Tutorial 2: Using the 3D simulator = |
| | 2 | |
| | 3 | ''Files for this tutorial are available in [https://framagit.org/fma38/Py4bot/tree/master/py4bot/examples/tutorial_2 py4bot/examples/tutorial_2/]'' |
| | 4 | |
| | 5 | Here, we will see how to use the 3D simulation stuff. |
| | 6 | |
| | 7 | As before, we create a new empty dir in our home dir: |
| | 8 | |
| | 9 | {{{ |
| | 10 | $ mkdir tutorial_2 |
| | 11 | $ cd tutorial_2 |
| | 12 | }}} |
| | 13 | |
| | 14 | {{{ |
| | 15 | #!python |
| | 16 | |
| | 17 | #!/usr/bin/env python |
| | 18 | # -*- coding: utf-8 -*- |
| | 19 | |
| | 20 | import math |
| | 21 | |
| | 22 | import visual |
| | 23 | |
| | 24 | from py4bot.api import * |
| | 25 | |
| | 26 | import settings |
| | 27 | }}} |
| | 28 | |
| | 29 | Nothing fancy, here. |
| | 30 | |
| | 31 | {{{ |
| | 32 | #!python |
| | 33 | |
| | 34 | class Hexapod3D(Robot3D): |
| | 35 | def _createBody(self): |
| | 36 | return Body(settings.LEGS_ORIGIN) |
| | 37 | |
| | 38 | def _createLegs(self): |
| | 39 | legs = {} |
| | 40 | legIk = {} |
| | 41 | for legIndex in settings.LEGS_INDEX: |
| | 42 | legs[legIndex] = Leg3Dof(legIndex, {'coxa': Coxa(), 'femur': Femur(), 'tibia': Tibia()}, settings.FEET_NEUTRAL[legIndex]) |
| | 43 | legIk[legIndex] = Leg3DofIk(settings.LEGS_GEOMETRY[legIndex]) |
| | 44 | |
| | 45 | return legs, legIk |
| | 46 | }}} |
| | 47 | |
| | 48 | Same as before, except that we inherits from {{{Robot3D}}}, instead of {{{Robot}}}. |
| | 49 | |
| | 50 | {{{ |
| | 51 | #!python |
| | 52 | |
| | 53 | def _createActuatorPool(self): |
| | 54 | driver = Driver3D() |
| | 55 | pool = ActuatorPool(driver) |
| | 56 | |
| | 57 | num = 0 |
| | 58 | for leg in self._legs.values(): |
| | 59 | |
| | 60 | # Create joints actuators |
| | 61 | coxa3D = Coxa3D(leg.coxa, num, settings.LEGS_GEOMETRY[leg.index]['coxa']) |
| | 62 | pool.add(coxa3D) |
| | 63 | num += 1 |
| | 64 | |
| | 65 | femur3D = Femur3D(leg.femur, num, settings.LEGS_GEOMETRY[leg.index]['femur']) |
| | 66 | pool.add(femur3D) |
| | 67 | num += 1 |
| | 68 | |
| | 69 | tibia3D = Tibia3D(leg.tibia, num, settings.LEGS_GEOMETRY[leg.index]['tibia']) |
| | 70 | pool.add(tibia3D) |
| | 71 | num += 1 |
| | 72 | |
| | 73 | # Init 3D view |
| | 74 | coxa3D.frame = self._body3D |
| | 75 | femur3D.frame = coxa3D |
| | 76 | tibia3D.frame = femur3D |
| | 77 | |
| | 78 | coxa3D.pos = (settings.LEGS_ORIGIN[leg.index]['x'], 0, -settings.LEGS_ORIGIN[leg.index]['y']) |
| | 79 | femur3D.pos = (settings.LEGS_GEOMETRY[leg.index]['coxa'], 0, 0) |
| | 80 | tibia3D.pos = (settings.LEGS_GEOMETRY[leg.index]['femur'], 0, 0) |
| | 81 | |
| | 82 | coxa3D.rotate(angle=math.radians(settings.LEGS_ORIGIN[leg.index]['gamma0']), axis=(0, 1, 0)) |
| | 83 | |
| | 84 | return pool |
| | 85 | }}} |
| | 86 | |
| | 87 | Here, we use specific 3D actuators: {{{Coxa3D}}}, {{{Femur3D}}}, {{{Tibia3D}}}; they all use the [http://vpython.org VPython] library to represent themselves on the screen. |
| | 88 | |
| | 89 | Note that we don't use a custom actuator mapping; nums are automatically incremented. |
| | 90 | |
| | 91 | We also need to configure the hierarchy, and the relative positions of the actuators. |
| | 92 | |
| | 93 | ''Note: this may change in the future; I'm working on a simpler way to do all this''. |
| | 94 | |
| | 95 | {{{ |
| | 96 | #!python |
| | 97 | |
| | 98 | class Gamepad(RemoteControl): |
| | 99 | def _createFrontend(self): |
| | 100 | return Thrustmaster(settings.THRUSTMASTER_PATH) |
| | 101 | |
| | 102 | def _buildComponents(self): |
| | 103 | self._addConfig("walk") |
| | 104 | self._addConfig("body") |
| | 105 | |
| | 106 | self._addComponent(Button, command=self.selectNextConfig, key="button_008", trigger="hold") |
| | 107 | self._addComponent(Button, command=self.selectPreviousConfig, key="button_009", trigger="hold") |
| | 108 | |
| | 109 | self._addComponent(Button, command=self.robot.incBodyPosition, key="button_004", mapper=MapperSetValue(dz=+5)) |
| | 110 | self._addComponent(Button, command=self.robot.incBodyPosition, key="button_005", mapper=MapperSetValue(dz=-5)) |
| | 111 | self._addComponent(Joystick, configs="walk", command=GaitSequencer().walk, keys=("analog_02", "analog_03", "analog_00"), mapper=MapperWalk()) |
| | 112 | |
| | 113 | self._addComponent(Analog, configs="body", command=self.robot.setBodyExtraPosition, key="analog_00", mapper=MapperSetMultiply('yaw', coef=-15)) |
| | 114 | self._addComponent(Analog, configs="body", command=self.robot.setBodyExtraPosition, key="analog_03", mapper=MapperSetMultiply('pitch', coef=-15)) |
| | 115 | self._addComponent(Analog, configs="body", command=self.robot.setBodyExtraPosition, key="analog_02", mapper=MapperSetMultiply('roll', coef=15)) |
| | 116 | }}} |
| | 117 | |
| | 118 | Same as before... |
| | 119 | |
| | 120 | {{{ |
| | 121 | #!python |
| | 122 | |
| | 123 | def main(): |
| | 124 | def addGait(gaitClass, gaitName): |
| | 125 | group = settings.GAIT_LEGS_GROUPS[gaitName] |
| | 126 | params = settings.GAIT_PARAMS['default'] |
| | 127 | for key1, value1 in settings.GAIT_PARAMS[gaitName].items(): |
| | 128 | for key2, value2 in value1.items(): |
| | 129 | params[key1][key2] = value2 |
| | 130 | gait = gaitClass(gaitName, group, params) |
| | 131 | GaitManager().add(gait) |
| | 132 | |
| | 133 | scene = visual.display(width=settings.SCENE_WIDTH, height=settings.SCENE_HEIGHT) |
| | 134 | ground = visual.box(axis=(0, 1, 0), pos=(0, 0, 0), length=0.1, height=500, width=500, color=visual.color.gray(0.75), opacity=0.5) |
| | 135 | |
| | 136 | addGait(GaitTripod, "tripod") |
| | 137 | addGait(GaitTetrapod, "tetrapod") |
| | 138 | addGait(GaitRiple, "riple") |
| | 139 | addGait(GaitWave, "metachronal") |
| | 140 | addGait(GaitWave, "wave") |
| | 141 | GaitManager().select("riple") |
| | 142 | |
| | 143 | robot = Hexapod3D() |
| | 144 | |
| | 145 | remote = Gamepad(robot) |
| | 146 | |
| | 147 | GaitSequencer().start() |
| | 148 | remote.start() |
| | 149 | |
| | 150 | robot.setBodyPosition(z=30) |
| | 151 | |
| | 152 | robot.mainLoop() |
| | 153 | |
| | 154 | remote.stop() |
| | 155 | remote.join() |
| | 156 | GaitSequencer().stop() |
| | 157 | GaitSequencer().join() |
| | 158 | |
| | 159 | if __name__ == "__main__": |
| | 160 | main() |
| | 161 | }}} |
| | 162 | |
| | 163 | The only difference, here, is the scene size init, and the ground addition (optional). |
| | 164 | |
| | 165 | == Settings == |
| | 166 | |
| | 167 | {{{ |
| | 168 | #!python |
| | 169 | |
| | 170 | # -*- coding: utf-8 -*- |
| | 171 | |
| | 172 | from py4bot.common import config |
| | 173 | |
| | 174 | |
| | 175 | # Legs index |
| | 176 | LEGS_INDEX = ('RF', 'RM', 'RR', 'LR', 'LM', 'LF') |
| | 177 | |
| | 178 | # Legs geometry |
| | 179 | LEGS_GEOMETRY = { |
| | 180 | 'RM': {'coxa': 25, 'femur': 45, 'tibia': 65}, |
| | 181 | 'RF': {'coxa': 25, 'femur': 45, 'tibia': 65}, |
| | 182 | 'LF': {'coxa': 25, 'femur': 45, 'tibia': 65}, |
| | 183 | 'LM': {'coxa': 25, 'femur': 45, 'tibia': 65}, |
| | 184 | 'LR': {'coxa': 25, 'femur': 45, 'tibia': 65}, |
| | 185 | 'RR': {'coxa': 25, 'femur': 45, 'tibia': 65} |
| | 186 | } |
| | 187 | |
| | 188 | # Legs origin |
| | 189 | LEGS_ORIGIN = { |
| | 190 | 'RM': {'x': 35., 'y': 0., 'gamma0' : 0.}, |
| | 191 | 'RF': {'x': 35., 'y': 65., 'gamma0' : 30.}, |
| | 192 | 'LF': {'x': -35., 'y': 65., 'gamma0' : 150.}, |
| | 193 | 'LM': {'x': -35., 'y': 0., 'gamma0' : 180.}, |
| | 194 | 'LR': {'x': -35., 'y': -65., 'gamma0' : 210.}, |
| | 195 | 'RR': {'x': 35., 'y': -65., 'gamma0' : 330.}, |
| | 196 | } |
| | 197 | |
| | 198 | # Legs feet neutral position |
| | 199 | FEET_NEUTRAL = { |
| | 200 | 'RM': LEGS_GEOMETRY['RM']['coxa'] + LEGS_GEOMETRY['RM']['femur'], |
| | 201 | 'RF': LEGS_GEOMETRY['RF']['coxa'] + LEGS_GEOMETRY['RF']['femur'], |
| | 202 | 'LF': LEGS_GEOMETRY['LF']['coxa'] + LEGS_GEOMETRY['LF']['femur'], |
| | 203 | 'LM': LEGS_GEOMETRY['LM']['coxa'] + LEGS_GEOMETRY['LM']['femur'], |
| | 204 | 'LR': LEGS_GEOMETRY['LR']['coxa'] + LEGS_GEOMETRY['LR']['femur'], |
| | 205 | 'RR': LEGS_GEOMETRY['RR']['coxa'] + LEGS_GEOMETRY['RR']['femur'], |
| | 206 | } |
| | 207 | |
| | 208 | # Gaits |
| | 209 | GAIT_LEGS_GROUPS = { |
| | 210 | 'tripod': (('RM', 'LF', 'LR'), ('RF', 'LM', 'RR')), |
| | 211 | 'tetrapod': (('RR', 'LM'), ('RF', 'LR'), ('RM', 'LF')), |
| | 212 | 'riple': (('RR',), ('LM',), ('RF',), ('LR',), ('RM',), ('LF',)), |
| | 213 | 'metachronal': (('RR',), ('LM',), ('RF',), ('LR',), ('RM',), ('LF',)), |
| | 214 | 'wave': (('RR',), ('RM',), ('RF',), ('LR',), ('LM',), ('LF',)) |
| | 215 | } |
| | 216 | |
| | 217 | # Gaits parameters |
| | 218 | GAIT_PARAMS = { |
| | 219 | 'default': { |
| | 220 | 'length': { |
| | 221 | 'min': 5., |
| | 222 | 'max': 40. |
| | 223 | }, |
| | 224 | 'angle': { |
| | 225 | 'min': 0.5, |
| | 226 | 'max': 5. |
| | 227 | }, |
| | 228 | 'height': { |
| | 229 | 'min': 20., |
| | 230 | 'max': 40. |
| | 231 | }, |
| | 232 | 'speed': { |
| | 233 | 'min': 25., |
| | 234 | 'max': 250. |
| | 235 | } |
| | 236 | }, |
| | 237 | 'tripod': {}, |
| | 238 | 'tetrapod': {}, |
| | 239 | 'riple': {}, |
| | 240 | 'metachronal': {}, |
| | 241 | 'wave': {} |
| | 242 | } |
| | 243 | |
| | 244 | # Gamepad path |
| | 245 | THRUSTMASTER_PATH = "/dev/input/by-id/usb-Mega_World_Thrustmaster_dual_analog_3.2-event-joystick" |
| | 246 | |
| | 247 | # VPython scene default size |
| | 248 | SCENE_WIDTH = 640 |
| | 249 | SCENE_HEIGHT = 480 |
| | 250 | |
| | 251 | }}} |
| | 252 | |
| | 253 | We removed the servos mapping/calibration, as we don't need them anymore, and we just added the scene default size. |