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.

../_images/curtain.png

Screenshot of Webots model of the curtain robot.

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 Javascript.

  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-2026 Garth Zeglin.  This file is explicitly placed in the public domain.
  5# template language: javascript
  6
  7EXTERNPROTO "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/appearances/protos/PaintedWood.proto"
  8EXTERNPROTO "https://raw.githubusercontent.com/cyberbotics/webots/R2023b/projects/appearances/protos/GlossyPaint.proto"
  9
 10PROTO curtain [
 11  field SFVec3f    translation  0 0 0
 12  field SFRotation rotation     0 1 0 0
 13  field SFString   controller   "curtain"
 14  field SFString   name         "curtain"
 15  field SFInt32    numchains    6
 16  field SFString   customData   ""
 17]
 18{
 19  Robot {
 20    # connect properties to user-visible data fields
 21    translation IS translation
 22    rotation IS rotation
 23    controller IS controller
 24    name IS name
 25    customData IS customData
 26
 27    # Calculate derived parameters
 28    %<
 29      let chain_y_spacing = 0.25;
 30      let link_y_width    = 0.2;
 31      let basewidth = (fields.numchains.value - 1) * chain_y_spacing + link_y_width;
 32      let chain1_y = (-0.5 * basewidth) + (0.5 * link_y_width);
 33    >%
 34    children [
 35      # define the non-moving hanging support
 36      Transform {
 37        translation 0 0 1.6
 38        children [
 39          Shape {
 40            appearance DEF baseColor PaintedWood {
 41              colorOverride 0.21529 0.543008 0.99855
 42            }
 43            geometry Box {
 44              size 0.02 %<= basewidth >% 0.18
 45            }
 46          }
 47        ]
 48      }
 49
 50      # loop to create each chain
 51      %< for (let c = 1; c <= fields.numchains.value; c++) { >%
 52      %< let motor_name = "\"motor" + c + "\""; >%
 53      %< let sensor_name = "\"joint" + c + "\""; >%
 54
 55      # template defining an individual chain
 56      HingeJoint {
 57        jointParameters HingeJointParameters {
 58          axis 0 1 0
 59          anchor 0 0 1.5
 60        }
 61        device [
 62          PositionSensor {
 63            name %<= sensor_name >%
 64          }
 65          RotationalMotor {
 66            name %<= motor_name >%
 67            controlPID 10 0 0
 68            maxVelocity 3.14
 69            minPosition -10
 70            maxPosition 10
 71            maxTorque 2
 72          }
 73        ]
 74        endPoint Solid {
 75          translation 0 %<= chain1_y + (c-1) * chain_y_spacing >% 1.5
 76          rotation 0 1 0 0
 77          children [
 78            Transform {
 79              translation 0 0 -0.15
 80              children [
 81                Shape {
 82                  appearance DEF linkColor GlossyPaint {
 83                    baseColor 1 0.975219 0.328771
 84                  }
 85                  geometry Box {
 86                    size 0.02 0.2 0.28
 87                  }
 88                }
 89              ]
 90            }
 91            HingeJoint {
 92              jointParameters HingeJointParameters {
 93                axis 0 1 0
 94                anchor 0 0 -0.3
 95                dampingConstant 0.1
 96              }
 97              device [
 98                # PositionSensor { name "joint1B" }
 99              ]
100              endPoint Solid {
101                translation 5.816463393668452e-06 0 -0.30015172239714644
102                rotation 0 -1 0 0.0016977587231221368
103                children [
104                  Transform {
105                    translation 0 0 -0.15
106                    children [
107                      Shape {
108                        appearance USE linkColor
109                        geometry Box {
110                          size 0.02 0.2 0.28
111                        }
112                      }
113                    ]
114                  }
115                  HingeJoint {
116                    jointParameters HingeJointParameters {
117                      axis 0 1 0
118                      anchor 0 0 -0.3
119                      dampingConstant 0.1
120                    }
121                    device [
122                      # PositionSensor { name "joint1C" }
123                    ]
124                    endPoint Solid {
125                      translation 0 0 -0.3
126                      rotation 0 1 0 0
127                      children [
128                        Transform {
129                          translation 0 0 -0.15
130                          children [
131                            Shape {
132                              appearance USE linkColor
133                              geometry Box {
134                                size 0.02 0.2 0.28
135                              }
136                            }
137                          ]
138                        }
139                        HingeJoint {
140                          jointParameters HingeJointParameters {
141                            axis 0 1 0
142                            anchor 0 0 -0.3
143                            dampingConstant 0.1
144                          }
145                          device [
146                            # PositionSensor { name "joint1D" }
147                          ]
148                          endPoint Solid {
149                            translation 0 0 -0.3
150                            rotation 0 1 0 0
151                            children [
152                              Transform {
153                                translation 0 0 -0.15
154                                children [
155                                  Shape {
156                                    appearance USE linkColor
157                                    geometry Box {
158                                      size 0.02 0.2 0.28
159                                    }
160                                  }
161                                ]
162                              }
163                            ]
164                            name %<= "\"link" + c + "_4\"" >%
165                            physics Physics {
166                              density -1
167                              mass 0.5
168                              centerOfMass [
169                                0 0 -0.15
170                              ]
171                              inertiaMatrix [
172                                0.006 0.004 0.002
173                                0 0 0
174                              ]
175                            }
176                          }
177                        }
178                      ]
179                      name %<= "\"link" + c + "_3\"" >%
180                      physics Physics {
181                        density -1
182                        mass 0.5
183                        centerOfMass [
184                          0 0 -0.15
185                        ]
186                        inertiaMatrix [
187                          0.006 0.004 0.002
188                          0 0 0
189                        ]
190                      }
191                    }
192                  }
193                ]
194                name %<= "\"link" + c + "_2\"" >%
195                physics Physics {
196                  density -1
197                  mass 0.5
198                  centerOfMass [
199                    0 0 -0.15
200                  ]
201                  inertiaMatrix [
202                    0.006 0.004 0.002
203                    0 0 0
204                  ]
205                }
206              }
207            }
208          ]
209          name %<= "\"link" + c + "_1\"" >%
210          physics Physics {
211            density -1
212            mass 0.5
213            centerOfMass [
214              0 0 -0.15
215            ]
216            inertiaMatrix [
217              0.006 0.004 0.002
218              0 0 0
219            ]
220          }
221        }
222      } # end definition of individual chain
223      %< } >% # end loop to create each chain
224    ] # end children of Robot
225  } # end Robot
226}

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}