Curtain¶
This sample project includes a robot which consists of a row of actuators driving suspended passive chains. This model demonstrates several key concepts: underactuated systems, generative movement, and scripted modeling.
This model is demonstrated in the curtain.wbt
world. The base link for all
actuators has a NULL Physics object so it does not move, simulating a rigid
connection to the ground. Each link has a NULL boundingObject so it does not
incur collision detection calculations; as a result each body needs specified
mass properties in the Physics node.
Sample Robot Control Code¶
The controller implements keyboard input to trigger generative poses and movements. The structures uses the position controllers implemented in Webots for driven modes, and may also invoke a zero-torque model for free dynamics.
1# curtain.py
2#
3# Sample Webots controller file for driving a
4# 'curtain' of actuated hanging chains.
5#
6# No copyright, 2020-2022, Garth Zeglin. This file is
7# explicitly placed in the public domain.
8
9print("loading curtain.py...")
10
11# Import the Webots simulator API.
12from controller import Robot
13from controller import Keyboard
14
15# Import the standard Python math library.
16import math
17
18# Define the time step in milliseconds between
19# controller updates.
20EVENT_LOOP_DT = 20
21
22################################################################
23
24# Request a proxy object representing the robot to control.
25robot = Robot()
26name = robot.getName()
27print(f"curtain.py waking up for {name}...")
28
29# Query the number of devices. The curtain.proto model has one joint actuator
30# and one sensor per chain.
31num_devices = robot.getNumberOfDevices()
32chains = num_devices // 2
33print(f"Found {num_devices} devices, assuming {chains} hanging chains.")
34
35# Enable computer keyboard input for user control.
36keyboard = Keyboard()
37keyboard.enable(EVENT_LOOP_DT)
38
39# Fetch handles for the joint sensors. The names are generated by the curtain.proto scripting.
40joints = [robot.getDevice('joint%d' % (jnum+1)) for jnum in range(chains)]
41
42# Specify the sampling rate for the joint sensors.
43for j in joints:
44 j.enable(EVENT_LOOP_DT)
45
46# Fetch handles for the position actuator at the top of each chain.
47motors = [robot.getDevice('motor%d' % (jnum+1)) for jnum in range(chains)]
48for m in motors:
49 m.setPosition(0.0)
50
51################################################################
52# Run an event loop until the simulation quits,
53# indicated by the step function returning -1.
54
55while robot.step(EVENT_LOOP_DT) != -1:
56
57 # Read simulator clock time.
58 t = robot.getTime()
59
60 # Read the new joint positions.
61 q = [j.getValue() for j in joints]
62
63 # Read any computer keyboard keypresses. Returns -1 or an integer keycode while a key is held down.
64 key = keyboard.getKey()
65 if key != -1:
66 # convert the integer key number to a lowercase single-character string
67 letter = chr(key).lower()
68
69 # special case: 'p' will enter a passive zero-torque mode
70 if letter == 'p':
71 for m in motors:
72 m.setTorque(0.0)
73
74 # drive to downward reference position
75 elif letter == 'd':
76 for m in motors:
77 m.setPosition(0.0)
78
79 # drive all to front
80 elif letter == 'f':
81 for m in motors:
82 m.setPosition(-0.5)
83
84 # drive all to back
85 elif letter == 'b':
86 for m in motors:
87 m.setPosition(0.5)
88
89 # drive to alternating positions
90 elif letter == 'l':
91 for i, m in enumerate(motors):
92 p = 0.5 if (i&1) == 0 else -0.5
93 m.setPosition(p)
94
95 # drive to opposite alternating positions
96 elif letter == 'r':
97 for i, m in enumerate(motors):
98 p = 0.5 if (i&1) == 1 else -0.5
99 m.setPosition(p)
100
101 # generate a traveling wave (while 'w' is held down)
102 elif letter == 'w':
103 for i, m in enumerate(motors):
104 p = 0.5 * math.sin(1.5 * t + 0.75 * i)
105 m.setPosition(p)
Proto File¶
The robot is modeled in a proto file to allow scripted generation of the chains. The number of chains can be varied after creation and the robot model will be regenerated. The proto file is VRML with embedded Lua scripting.
1#VRML_SIM R2023b utf8
2# documentation url: https://courses.ideate.cmu.edu/16-375
3# Curtain. A variable number of hanging chains with a single position actuator at top.
4# license: No copyright, 2020-2022 Garth Zeglin. This file is explicitly placed in the public domain.
5EXTERNPROTO "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/appearances/protos/PaintedWood.proto"
6EXTERNPROTO "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/appearances/protos/GlossyPaint.proto"
7
8PROTO curtain [
9 field SFVec3f translation 0 0 0
10 field SFRotation rotation 0 1 0 0
11 field SFString controller "curtain"
12 field SFString name "curtain"
13 field SFInt32 numchains 6
14 field SFString customData ""
15]
16{
17 Robot {
18 # connect properties to user-visible data fields
19 translation IS translation
20 rotation IS rotation
21 controller IS controller
22 name IS name
23 customData IS customData
24
25 # Calculate derived parameters
26 %{
27 local chain_y_spacing = 0.25
28 local link_y_width = 0.2
29 local basewidth = (fields.numchains.value - 1) * chain_y_spacing + link_y_width
30 local chain1_y = (-0.5 * basewidth) + (0.5 * link_y_width)
31 }%
32 children [
33 # define the non-moving hanging support
34 Transform {
35 translation 0 0 1.6
36 children [
37 Shape {
38 appearance DEF baseColor PaintedWood {
39 colorOverride 0.21529 0.543008 0.99855
40 }
41 geometry Box {
42 size 0.02 %{= basewidth }% 0.18
43 }
44 }
45 ]
46 }
47
48 # loop to create each chain
49 %{ for c = 1, fields.numchains.value do }%
50 %{ local motor_name = "\"motor" .. c .. "\"" }%
51 %{ local sensor_name = "\"joint" .. c .. "\"" }%
52
53 # template defining an individual chain
54 HingeJoint {
55 jointParameters HingeJointParameters {
56 axis 0 1 0
57 anchor 0 0 1.5
58 }
59 device [
60 PositionSensor {
61 name %{= sensor_name }%
62 }
63 RotationalMotor {
64 name %{= motor_name }%
65 controlPID 10 0 0
66 maxVelocity 3.14
67 minPosition -10
68 maxPosition 10
69 maxTorque 2
70 }
71 ]
72 endPoint Solid {
73 translation 0 %{= chain1_y + (c-1) * chain_y_spacing }% 1.5
74 rotation 0 1 0 0
75 children [
76 Transform {
77 translation 0 0 -0.15
78 children [
79 Shape {
80 appearance DEF linkColor GlossyPaint {
81 baseColor 1 0.975219 0.328771
82 }
83 geometry Box {
84 size 0.02 0.2 0.28
85 }
86 }
87 ]
88 }
89 HingeJoint {
90 jointParameters HingeJointParameters {
91 axis 0 1 0
92 anchor 0 0 -0.3
93 dampingConstant 0.1
94 }
95 device [
96 # PositionSensor { name "joint1B" }
97 ]
98 endPoint Solid {
99 translation 5.816463393668452e-06 0 -0.30015172239714644
100 rotation 0 -1 0 0.0016977587231221368
101 children [
102 Transform {
103 translation 0 0 -0.15
104 children [
105 Shape {
106 appearance USE linkColor
107 geometry Box {
108 size 0.02 0.2 0.28
109 }
110 }
111 ]
112 }
113 HingeJoint {
114 jointParameters HingeJointParameters {
115 axis 0 1 0
116 anchor 0 0 -0.3
117 dampingConstant 0.1
118 }
119 device [
120 # PositionSensor { name "joint1C" }
121 ]
122 endPoint Solid {
123 translation 0 0 -0.3
124 rotation 0 1 0 0
125 children [
126 Transform {
127 translation 0 0 -0.15
128 children [
129 Shape {
130 appearance USE linkColor
131 geometry Box {
132 size 0.02 0.2 0.28
133 }
134 }
135 ]
136 }
137 HingeJoint {
138 jointParameters HingeJointParameters {
139 axis 0 1 0
140 anchor 0 0 -0.3
141 dampingConstant 0.1
142 }
143 device [
144 # PositionSensor { name "joint1D" }
145 ]
146 endPoint Solid {
147 translation 0 0 -0.3
148 rotation 0 1 0 0
149 children [
150 Transform {
151 translation 0 0 -0.15
152 children [
153 Shape {
154 appearance USE linkColor
155 geometry Box {
156 size 0.02 0.2 0.28
157 }
158 }
159 ]
160 }
161 ]
162 name %{= "\"link" .. c .. "_4\"" }%
163 physics Physics {
164 density -1
165 mass 0.5
166 centerOfMass [
167 0 0 -0.15
168 ]
169 inertiaMatrix [
170 0.006 0.004 0.002
171 0 0 0
172 ]
173 }
174 }
175 }
176 ]
177 name %{= "\"link" .. c .. "_3\"" }%
178 physics Physics {
179 density -1
180 mass 0.5
181 centerOfMass [
182 0 0 -0.15
183 ]
184 inertiaMatrix [
185 0.006 0.004 0.002
186 0 0 0
187 ]
188 }
189 }
190 }
191 ]
192 name %{= "\"link" .. c .. "_2\"" }%
193 physics Physics {
194 density -1
195 mass 0.5
196 centerOfMass [
197 0 0 -0.15
198 ]
199 inertiaMatrix [
200 0.006 0.004 0.002
201 0 0 0
202 ]
203 }
204 }
205 }
206 ]
207 name %{= "\"link" .. c .. "_1\"" }%
208 physics Physics {
209 density -1
210 mass 0.5
211 centerOfMass [
212 0 0 -0.15
213 ]
214 inertiaMatrix [
215 0.006 0.004 0.002
216 0 0 0
217 ]
218 }
219 }
220 } # end definition of individual chain
221 %{ end }% # end loop to create each chain
222 ] # end children of Robot
223 } # end Robot
224}
World File¶
1#VRML_SIM R2023b utf8
2
3EXTERNPROTO "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/objects/floors/protos/RectangleArena.proto"
4EXTERNPROTO "../protos/curtain.proto"
5
6WorldInfo {
7}
8Viewpoint {
9 orientation 0.12859871179887852 -0.023940037157748277 -0.9914077092420426 3.8572616925142666
10 position 4.0633189701268275 -3.320537956126432 1.983958187570635
11 followType "None"
12}
13Background {
14 skyColor [
15 0.1 0.1 0.1
16 ]
17}
18DirectionalLight {
19 direction -0.4 -0.5 -1
20 intensity 3
21 castShadows TRUE
22}
23RectangleArena {
24 floorSize 2 2
25}
26curtain {
27}