Menagerie World¶
This sample world includes numerous performance robots and a fixed vision system. It is intended for exploring interactive and ensemble performance within the HL A11 space.
The scene includes the following:
networked arm: ZYY Robot Arm Model, menagerie_arm.py
wheel rolling in circle on passive boom: boom-wheel.proto, boom_wheel.py
actuated storage chest: chest.proto, chest.py
actuated ramp: actuated-ramp.proto, chest.py
controlled spotlight: spotlight.proto, spotlight.py
fixed vision camera: fixed-camera.proto, camera.py
moving vision target: cartesian-target.proto, cartesian_target.py
disembodied director script: director.py
obstacle ramp: ramp.proto
The vision system script uses the OpenCV Python module for image processing.
This can be installed in your Python system using pip3 install opencv-python
.
The sample code makes extensive use of the Webots simulated radio system for inter-agent messaging on a common channel. The message string format is inspired by OSC: each message begins with an ‘address’ string identifying an endpoint or data channel, optionally followed by space-delimited arguments. E.g., “/center_light/color 255 0 0” tells the spotlight named center_light to emit a pure red color. More messaging examples can be found in director.py.
The world file is menagerie.wbt
, which can be found within the Webots.zip archive. The necessary components are also isolated
in the menagerie.zip archive.
World File¶
The world file specifies all the elements of the scene: camera position, background scenery, lighting, fixed objects, active robots, and the disembodied ‘director’.
1#VRML_SIM R2023b utf8
2
3EXTERNPROTO "../protos/HL-A11-room.proto"
4EXTERNPROTO "../protos/ramp.proto"
5EXTERNPROTO "../protos/zyy.proto"
6EXTERNPROTO "../protos/chest.proto"
7EXTERNPROTO "../protos/spotlight.proto"
8EXTERNPROTO "../protos/fixed-camera.proto"
9EXTERNPROTO "../protos/cartesian-target.proto"
10EXTERNPROTO "../protos/boom-wheel.proto"
11EXTERNPROTO "../protos/actuated-ramp.proto"
12
13WorldInfo {
14}
15Viewpoint {
16 # orientation 0 0 -1 3.14159
17 # position 7.5 0 1.3
18 orientation 0.108 -0.0825566 -0.990721 4.38712
19 position 2.87298 -9.19051 3.15944
20 followType "None"
21}
22Background {
23 skyColor [
24 0.1 0.1 0.1
25 ]
26}
27HL-A11-room {
28 transparency 0.6
29}
30ramp {
31 translation 0.8 -2 0
32 width 0.2
33 length 0.4
34}
35
36spotlight {
37 location 1.5 -2 0.1
38 rotation 0.68 0.68 -0.28 -1.1
39 name "left_light"
40 customData "color_cycle"
41}
42
43spotlight {
44 location 1.5 2 0.1
45 rotation -0.73 0.42 0.55 -1.63
46 name "right_light"
47 customData "color_cycle"
48}
49
50spotlight {
51 location -1.5 0 2
52 rotation 0 1 0 2.6
53 name "center_light"
54}
55
56fixed-camera {
57 location -1.5 0 1.5
58 fieldOfView 60
59}
60
61zyy {
62 translation 0 1.0 0.0
63 name "right_arm"
64 controller "menagerie_arm"
65}
66zyy {
67 translation 0 -1.0 0.0
68 name "left_arm"
69 controller "menagerie_arm"
70}
71chest {
72 width 0.5
73 depth 0.5
74 baseHeight 0.5
75}
76
77cartesian-target {
78 translation 2.3 0 0
79 radius 0.5
80}
81boom-wheel {
82 translation 0.8 1.4 0
83 name "right_wheel"
84}
85boom-wheel {
86 translation 0.8 -1.4 0
87 name "left_wheel"
88}
89actuated-ramp {
90 translation 1.4 1.4 -0.025
91 rotation 0 0 1 1.57
92 width 0.3
93 length 0.5
94}
95
96Robot {
97 name "director"
98 controller "director"
99 children [
100 Receiver {
101 channel 1
102 }
103 Emitter {
104 channel 1
105 }
106 ]
107}
Director Script¶
The overall scene is coordinated by the director script attached to a Robot node with no associated physical form. This script monitors status messages on the radio network, user keyboard input, and potentially other data sources, then transmits instructions over the radio network to the actors. This script would be a good location for full-scene state machines or event sequencing.
1# director.py
2#
3# Sample Webots controller file for a disembodied robot which coordinates
4# activity between sensory robots and actor robots.
5
6# No copyright, 2024, Garth Zeglin. This file is explicitly placed in the
7# public domain.
8
9print("loading director.py...")
10
11################################################################
12# standard Python libraries
13import math
14
15# Import the Webots API.
16from controller import Robot
17from controller import Keyboard
18
19# Define the time step in milliseconds between controller updates.
20EVENT_LOOP_DT = 200
21
22################################################################
23class Director(Robot):
24
25 def __init__(self):
26 super(Director, self).__init__()
27
28 self.robot_name = self.getName()
29 print("%s: controller connected." % (self.robot_name))
30
31 # Connect to the radio emitter and receiver. The radio runs at the same
32 # rate as the event loop as it is the main function of this robot.
33 self.receiver = self.getDevice('receiver')
34 self.emitter = self.getDevice('emitter')
35 self.receiver.enable(EVENT_LOOP_DT)
36
37 # Connect to keyboard input from the GUI.
38 self.keyboard = Keyboard()
39 self.keyboard.enable(EVENT_LOOP_DT)
40
41 # Initialize top-level script state machines.
42 self.state_timer = 2*EVENT_LOOP_DT
43 self.state_index = 0
44 self.target_timer = 0
45
46 return
47
48 #================================================================
49 def poll_receiver(self):
50 """Process all available radio messages."""
51 while self.receiver.getQueueLength() > 0:
52 packet = self.receiver.getString()
53 print("%s received: %s" % (self.robot_name, packet))
54 tokens = packet.split()
55 if len(tokens) > 0:
56 if tokens[0] == '/camera/target/dropped':
57 self.emitter.send(b"/left_arm/mode path")
58 self.emitter.send(b"/right_arm/mode path")
59 self.emitter.send(b"/left_wheel/speed -5")
60 self.target_timer = 1000
61
62 elif tokens[0] == '/camera/target':
63 if self.target_timer <= 0:
64 self.emitter.send(b"/left_arm/mode pose")
65 self.emitter.send(b"/right_arm/mode pose")
66 self.emitter.send(b"/left_wheel/speed -15")
67 self.target_timer = 1000
68
69 # done with packet processing, advance to the next packet
70 self.receiver.nextPacket()
71 # no more data
72 return
73
74 #================================================================
75 def poll_keyboard(self):
76 """Process all available pressed keyboard keys. Note that the keyboard
77 is treated as a state, not events; holding a key will continue to
78 generate a reported key, and briefly pressing a key might be lost
79 between samples.
80 """
81 while True:
82 key = self.keyboard.getKey()
83 if key == -1:
84 return
85 else:
86 code = key & Keyboard.KEY
87 modifiers = "shift" if key & Keyboard.SHIFT else ""
88 modifiers += "ctrl" if key & Keyboard.CONTROL else ""
89 modifiers += "alt" if key & Keyboard.ALT else ""
90
91 # convert the integer key number to a lowercase single-character string
92 letter = chr(code).lower()
93 print("%s: user pressed key %d (%c, %s)" % (self.robot_name, key, letter, modifiers))
94
95 # demonstrate translating a keypress to a network message
96 if letter == 'p':
97 self.emitter.send(b"/left_arm/mode pose")
98
99 elif letter == 's':
100 self.emitter.send(b"/left_arm/mode path")
101
102 elif letter == 'r':
103 self.emitter.send(b"/center_light/color 255 0 0")
104 print("%s: sending lighting command" % (self.robot_name))
105
106 elif letter == 'b':
107 self.emitter.send(b"/center_light/color 0 0 0")
108 print("%s: sending lighting command" % (self.robot_name))
109
110 elif letter == 'f':
111 self.emitter.send(b"/left_wheel/speed -20")
112 print("%s: sending speed command" % (self.robot_name))
113
114 elif letter == 'c':
115 self.emitter.send(b"/chest/period 5")
116 print("%s: sending period command" % (self.robot_name))
117
118 #================================================================
119 def poll_scene_sequence(self):
120 """State machine update function to walk through a series of scene phases."""
121
122 # Update the state timers
123 self.state_timer -= EVENT_LOOP_DT
124 self.target_timer -= EVENT_LOOP_DT
125
126 # If the timer has elapsed, reset the timer and update the state.
127 if self.state_timer < 0:
128 self.state_timer += 5000
129
130 # here is where scene changes could be programmed
131
132 #================================================================
133 def run(self):
134
135 # Run loop to execute a periodic script until the simulation quits.
136 # If the controller returns -1, the simulator is quitting.
137 while self.step(EVENT_LOOP_DT) != -1:
138
139 # Poll the simulated radio receiver.
140 self.poll_receiver()
141
142 # Poll user input.
143 self.poll_keyboard()
144
145 # Read simulator clock time.
146 t = self.getTime()
147
148 # Update scene state machine.
149 self.poll_scene_sequence()
150
151################################################################
152
153controller = Director()
154controller.run()