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 Lua scripting.

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

World File

 1#VRML_SIM R2022a utf8
 2WorldInfo {
 3}
 4Viewpoint {
 5  orientation 0.12859871179887852 -0.023940037157748277 -0.9914077092420426 3.8572616925142666
 6  position 4.0633189701268275 -3.320537956126432 1.983958187570635
 7  followType "None"
 8}
 9Background {
10  skyColor [
11    0.1 0.1 0.1
12  ]
13}
14DirectionalLight {
15  direction -0.4 -0.5 -1
16  intensity 3
17  castShadows TRUE
18}
19RectangleArena {
20  floorSize 2 2
21}
22curtain {
23}